/* TM paketi — kaynak sirasi korunur. Uretici: scripts/build_assets.py */

/* ===== css/ds/ds-tokens.css ===== */
/* =================================================================
   TRENDMETRIK DESIGN SYSTEM — DS SEMANTIC TOKEN LAYER (Faz 1, 2026-09-12;
   values re-derived from the measured reference spec 2026-09-13 —
   docs/ui-reference-spec.md, Apex primary)

   Primitive source of truth stays static/css/v2/tokens.css (untouched; the
   ~200 legacy pages keep resolving every legacy token). This file is the
   NARROWED semantic set the design-system scope (.tm-ds) is allowed to use:

     spacing  7 : --tm-space-1/2/3/4/6/8/12   (4/8/12/16/24/32/48px)
                  card inset 24 · column gap 16 · row gap 24 (reference: 24/16/24)
     radius   3 : --tm-radius-control (6) · --tm-radius-card (14) · --tm-radius-overlay (14)
     shadow   3 : --tm-shadow-card (resting surface, one hairline-soft shadow)
                  · --tm-shadow-overlay · --tm-shadow-modal
     colour     : semantic names only (bg / border / text / accent / status -fg -bg)
     type     6 : display 24/32 (+ display-sm 20/28, KPI-only) · title 18/28 · heading 16/24 · body 14/20 · caption 12/16 · micro 11/14
                  (reference scale 11/12/14/16/18/24, ratio ≈1.14; no fractional sizes)
     weight   4 : 400 body · 500 label · 600 card title · 700 page title + KPI number

   Rules (enforced by tests/test_ui_design_system_guard.py):
   - nothing in static/css/ds/** references a token outside this set;
   - no colour literal outside this file;
   - no box-shadow other than the three tokens; only --tm-shadow-card on resting surfaces.
   Everything below .tm-ds is scoped → inert on pages that have not opted in.
   ================================================================= */

:root {
  /* Elevation. Additive names; nothing legacy references them.
     Cila V1 (2026-09-23, onaylı mockup): light-mode kart gölgesi iki katmanlı
     (hairline + geniş/yumuşak) → "katmanlı derinlik" hissi. Koyu tema DEĞİŞMEDİ
     (bkz. [data-theme="dark"] bloğu aşağıda, aynı kalır). */
  /* 2026-10-11: hairline 0.04 → 0.08 — koyulaşan zeminde kartın kenarı
     yumuşak gölgede kayboluyordu ("içerik pasif"). */
  --tm-shadow-card:    0 1px 2px rgba(15, 23, 42, 0.08), 0 10px 28px -14px rgba(15, 23, 42, 0.22);
  --tm-shadow-overlay: 0 4px 12px rgb(0 0 0 / 0.08);   /* dropdown, popover, tooltip */
  --tm-shadow-modal:   0 16px 48px rgb(0 0 0 / 0.16);  /* modal, drawer */

  /* Site-wide kart gölge/radius aliası (Cila V1) — .tm-ds dışındaki bileşenler
     (.kpi-card, account_pages_base_compatible.css, assistant_widget.css, admin
     panelleri…) ZATEN `var(--tm-card-shadow, <fallback>)` / `var(--tm-radius-card,
     12px)` zinciriyle yazılmış (35+ dosya) ama token hiçbir yerde bare :root'ta
     tanımlı değildi → hepsi kendi yerel fallback'ine düşüyordu. Burada tanımlamak
     base.html'i extend eden TÜM sayfalara (satıcı paneli + admin kabuğu) aynı
     kartı tek kaynaktan verir — kapsam kasıtlı geniş (bkz. görev notu "Kapsam").
     .tm-ds içindeki `--tm-radius-card` (aşağıda .tm-ds bloğu) aynı 16px'e
     hizalandı; kontrol(6)/overlay(14) DEĞİŞMEDİ. */
  --tm-card-shadow:       var(--tm-shadow-card);
  --tm-card-shadow-hover: var(--tm-shadow-overlay);
  --tm-radius-card: 16px;
}

/* ---------- Promo paleti (2026-09-15, kullanıcı kararı: katalog banner'ı
   "renkli, canlı, kayan" olacak) ----------------------------------------
   "Düz yüzey" kuralı PAZARLAMA yüzeyleri için bilerek esnetildi; ama renk
   JS'te inline YAZILMAZ — token'dan gelir. Sebep: (a) koyu modda tema-dışı
   renk = okunmaz metin (bugün üç kez yaşandı), (b) inline renk guard'a
   görünmez → kilitlenemez, (c) review edilemez. Kullanım: kart/şerit
   `data-promo="1..6"` alır, rengi CSS seçer. Metin daima --tm-promo-fg
   (her ton için kontrast ölçülür, ≥4.5:1). */
:root {
  --tm-promo-fg:   #ffffff;
  --tm-promo-1:      #b4232b;   /* base = gradient başlangıcı */
  --tm-promo-1-from: var(--tm-promo-1);
  --tm-promo-1-to:   #82191f;
  --tm-promo-2:      #b35309;   /* base = gradient başlangıcı */
  --tm-promo-2-from: var(--tm-promo-2);
  --tm-promo-2-to:   #813c06;
  --tm-promo-3:      #6d28d9;   /* base = gradient başlangıcı */
  --tm-promo-3-from: var(--tm-promo-3);
  --tm-promo-3-to:   #4e1d9c;
  --tm-promo-4:      #0369a1;   /* base = gradient başlangıcı */
  --tm-promo-4-from: var(--tm-promo-4);
  --tm-promo-4-to:   #024c74;
  --tm-promo-5:      #15803d;   /* base = gradient başlangıcı */
  --tm-promo-5-from: var(--tm-promo-5);
  --tm-promo-5-to:   #0f5c2c;
  --tm-promo-6:      #9d174d;   /* base = gradient başlangıcı */
  --tm-promo-6-from: var(--tm-promo-6);
  --tm-promo-6-to:   #711137;
  /* Ambiyans bandı: aktif kartın tonundan şeffafa. Kullanım:
     `background: linear-gradient(90deg, var(--tm-promo-grad-from), var(--tm-promo-grad-to));`
     `--tm-promo-grad-from` kart tonuna `data-promo` ile bağlanır.
     Kart gradienti: `linear-gradient(135deg, var(--tm-promo-N-from), var(--tm-promo-N-to))`.
     `-to` daima aynı hue'nun KOYU ucu → beyaz metin gradientin HER İKİ ucunda
     da >=4.5:1 kalır (ölçüldü; açık mod 5.02-11.43, koyu mod 7.37-13.70). */
  --tm-promo-grad-from: var(--tm-promo-1);
  --tm-promo-grad-to:   transparent;
}

[data-theme="dark"],
html.dark-mode {
  /* Koyu modda aynı hue, daha düşük parlaklık (yüzey ışığı bastırmasın);
     metin yine --tm-promo-fg (beyaz) — kontrastlar ölçüldü. */
  --tm-promo-1:      #8f1b22;
  --tm-promo-1-to:   #6a1419;
  --tm-promo-2:      #8a4007;
  --tm-promo-2-to:   #662f05;
  --tm-promo-3:      #55209f;
  --tm-promo-3-to:   #3f1876;
  --tm-promo-4:      #02537c;
  --tm-promo-4-to:   #013d5c;
  --tm-promo-5:      #10632f;
  --tm-promo-5-to:   #0c4923;
  --tm-promo-6:      #7a1240;
  --tm-promo-6-to:   #5a0d2f;
}

/* ---------- Motion grammar (PR-1, 2026-10-10; referans video kare kare ölçüldü) ----------
   İlkel süre/eğriler static/css/v2/tokens.css'te kalır (--tm-dur-fast 150 /
   --tm-dur-base 200 / --tm-dur-slow 300, --tm-ease*, reduced-motion'da 0'lanır).
   Burada yalnız koreografinin ROLLERİ eklenir; tüketici: ds-motion.css,
   tm-kpi.js (sayım), tm-charts.js (morf, PR-3). :root'ta — kabuk (sidebar/ticker)
   ve body'ye portallanan modal da aynı dili konuşsun. */
:root {
  --tm-dur-enter: var(--tm-dur-slow);   /* giriş: opaklık + 6px kayma, tek sefer      (referans ~300 ms) */
  --tm-dur-morph: 450ms;                /* veri değişimi: sayı/çizgi/çubuk mevcut→hedef (referans ~450 ms) */
  /* İLK SAYIM — 2026-10-11'de KAPATILMIŞTI, 2026-10-15'te KAPIYLA geri açıldı.
     🔴 2026-10-11 gerekçesi (kullanıcı: *"sayfa yüklemeleri kopuk, kasıyor"*): KPI'lar
     0,00'dan sayarken ~0,9 sn boyunca GERÇEK OLMAYAN değerler gösteriyordu ve kartlar
     zaten ~2,1 sn'de geliyordu. O gerekçe KARTIN GEÇ GELMESİNDEN kaynaklıydı.
     🔴 2026-10-15 (kullanıcı: *"evet, kartlar hızlanınca"*): kartlar artık ızgaranın
     ardında senkron yükleniyor ve DCL beklemeden çiziliyor (ölçüldü: Genel Bakış
     556 → 306 ms medyan, −%45). Sayım geri açıldı AMA `--tm-sayim-esik` kapısıyla:
     kart bu eşikten GEÇ geldiyse tm-kpi.js sayımı KENDİLİĞİNDEN atlar → yavaş
     sayfada/yavaş cihazda sahte "0,00" penceresi oluşamaz. Kapı koddadır, insan
     kararına bağlı değildir. Canlı yenilemedeki mevcut→yeni geçişi (--tm-dur-morph)
     DEĞİŞMEDİ. */
  --tm-dur-count: 600ms;                /* ilk sayım 0→değer (eşik altında)            (referans ~900 ms) */
  --tm-sayim-esik: 1200ms;              /* kart bundan geç geldiyse sayım HİÇ oynamaz  */
  --tm-stagger:   50ms;                 /* okuma sırasında öğe başına gecikme          (referans ~50 ms) */
  --tm-ease-std:  var(--tm-ease-standard);
  /* Sıralı açılış (ds-motion.css "SIRALI AÇILIŞ", static/js/ds/tm-sira.js) */
  /* 🔴 2026-10-15: 50/30 ms İNSAN GÖZÜYLE EŞZAMANLIYDI (5 kartlık satır 120 ms'de biterdi).
     Ölçüldü (motion-timeline, canlı): kademe aralıkları 0 ms. Üst sınır ARALIK ≤ 120 ms —
     tests/test_ui_sirali_acilis_2026_10_11.py ② `v["kpi"] < 300` kilidi (100 ms'de ≈206 ms). */
  --tm-sira-aralik: 100ms;              /* bir satırın kademesi bittikten sonra alttaki için ara */
  --tm-sira-kademe: 80ms;               /* satır içinde soldan sağa panel başına gecikme   */
  --tm-sira-bekle:  900ms;              /* hazır olmayan satırı en çok bu kadar bekle      */
  --tm-sira-tavan:  2200ms;             /* sayfa açılışından sonra hepsini aç              */
  --tm-sira-kurtar: 3000ms;             /* JS yoksa CSS kendi açar (emniyet)            */
}
@media (prefers-reduced-motion: reduce) {
  :root { --tm-dur-enter: 0ms; --tm-dur-morph: 0ms; --tm-dur-count: 0ms; --tm-stagger: 0ms; }
}

[data-theme="dark"],
html.dark-mode {
  --tm-shadow-card:    0 1px 2px rgb(0 0 0 / 0.40);
  --tm-shadow-overlay: 0 4px 12px rgb(0 0 0 / 0.45);
  --tm-shadow-modal:   0 16px 48px rgb(0 0 0 / 0.60);
}

.tm-ds {
  /* ---------- Radius (3) ---------- */
  --tm-radius-control: var(--tm-radius-md);   /* 6px  — input, badge, button */
  --tm-radius-card:    16px;                  /* Cila V1: eski var(--tm-radius-xl)=14px; yalnız kart token'ı büyüdü */
  --tm-radius-overlay: var(--tm-radius-xl);   /* 14px — modal, popover (DEĞİŞMEDİ) */

  /* ---------- Resting surfaces: one soft shadow, no hover lift ---------- */
  --tm-card-shadow:       var(--tm-shadow-card);
  --tm-card-shadow-hover: var(--tm-shadow-card);

  /* ---------- Surfaces ---------- */
  --tm-bg-canvas:  var(--tm-surface-sunken);  /* page background */
  --tm-bg-surface: var(--tm-surface);         /* card — the ONE card tone per mode */
  --tm-bg-subtle:  var(--tm-ink-100);         /* table head, hover row */
  /* --tm-border / --tm-border-strong / --tm-text-primary / -secondary / -muted:
     inherited from tokens.css as-is (already semantic, already dark-aware). */

  /* ---------- Meaning colours: one job each ---------- */
  --tm-accent:      var(--tm-accent-500);
  --tm-accent-fg:   var(--tm-accent-600);
  --tm-accent-bg:   var(--tm-accent-50);
  --tm-accent-solid: #0175e4;                 /* solid accent SURFACE (brand blue, both modes; dark-mode.css lightens --tm-accent-500 for text/borders, not for a filled card) */
  --tm-on-accent:   var(--tm-text-on-brand);  /* text on a solid accent surface */
  --tm-success-fg:  var(--tm-ok);
  --tm-success-bg:  var(--tm-ok-bg);
  --tm-warning-fg:  var(--tm-warn);
  --tm-warning-bg:  var(--tm-warn-bg);
  --tm-danger-fg:   var(--tm-bad);
  --tm-danger-bg:   var(--tm-bad-bg);
  --tm-info-fg:     var(--tm-info);
  /* --tm-info-bg: NOT re-declared here. tokens.css already owns that exact name for both
     modes (#e0f2fe light / rgba(2,132,199,.15) dark) and it inherits into .tm-ds, exactly
     like --tm-border / --tm-text-* above. Until 2026-09-16 this line read
     `--tm-info-bg: var(--tm-info-bg)` — a self-reference. Per CSS spec a cyclic custom
     property is invalid at computed-value time, so it did not fall back to the tokens.css
     value: it OVERRODE it with nothing and every .tm-badge--info/--picking rendered on a
     TRANSPARENT background (measured in a real browser: rgba(0,0,0,0)). The badge then read
     as plain blue text, which looks like a link — a broken token that passed for a design
     choice. Siblings are safe because they alias a DIFFERENT primitive name
     (--tm-success-bg: var(--tm-ok-bg)); only info shared its own name. */

  /* ---------- Chart palette (value-preserving: identical to the literals
     hard-coded in static/js/tm-charts.js:21 before Faz 3 binds it here) ---------- */
  --tm-chart-1: var(--tm-brand-500);   /* #0175e4 */
  --tm-chart-2: var(--tm-orange-500);  /* #fd751f */
  --tm-chart-3: var(--tm-ok);          /* #16a34a */
  --tm-chart-4: var(--tm-brand-300);   /* #73b7fe */
  --tm-chart-5: #8b5cf6;               /* violet — no primitive exists yet */
  --tm-chart-6: #f59e0b;               /* amber  — no primitive exists yet */
  --tm-chart-7: var(--tm-bad);         /* #dc2626 */
  --tm-chart-axis:  var(--tm-text-muted);
  --tm-chart-track: var(--tm-ink-100);
  --tm-chart-grid:  var(--tm-border);

  /* ---------- Type scale (6) — size / line-height ---------- */
  --tm-type-display:    24px; --tm-type-display-lh: 32px;   /* page title, KPI number (≥1440) */
  --tm-type-display-sm: 20px; --tm-type-display-sm-lh: 28px; /* KPI number in the 5-up row under 1440 (24 ÷ 1.14).
     Do not raise this to the reference's 24/700: that figure was measured on a 4-up deck with wider cards.
     In our 5-up deck (user decision, ui-reference-spec.md line 47) the longest real value clips — measured
     2026-09-24, "264.254,40 TL" needs 150px against 144px of value box at 1280 and 148px at 1024. */
  --tm-type-title:      18px; --tm-type-title-lh:   28px;   /* section title */
  --tm-type-heading:    16px; --tm-type-heading-lh: 24px;   /* card title */
  --tm-type-body:       14px; --tm-type-body-lh:    20px;   /* table cell, subtitle */
  --tm-type-caption:    12px; --tm-type-caption-lh: 16px;   /* label, legend, delta */
  --tm-type-micro:      11px; --tm-type-micro-lh:   14px;   /* axis, badge */
  --tm-weight-regular:  400;
  --tm-weight-medium:   500;
  --tm-weight-semibold: 600;
  --tm-weight-bold:     700;

  /* ---------- KPI card: one fixed height for skeleton AND loaded card (the 14px grow when
     real cards replaced 148px placeholders was the whole CLS 0.014 on Genel Bakış) ---------- */
  --tm-dash-kpi-h: 160px;   /* reference 150 + 24px icon-disc row − tighter body; legacy value 148 stays outside .tm-ds */

  /* ---------- Density (Faz 3 DataTable / Faz 4 orders toggle) ---------- */
  --tm-row-h:       var(--tm-dash-table-row-h);    /* 52px comfortable */
  --tm-row-h-dense: var(--tm-dash-table-dense-h);  /* 40px */
}

/* Numeric columns / live counters: digits keep a fixed advance so values do
   not jitter when they change (Canlı Performans stability rule). */
.tm-ds .tm-num,
.tm-ds .kpi-value,
.tm-ds .tm-table td.tm-num,
.tm-ds [data-tm-num] {
  font-variant-numeric: tabular-nums;
}

/* =================================================================
   SHELL (A2, 2026-09-13) — sidebar / topbar / demo-banner / promo-FAB live
   OUTSIDE <main class="tm-ds">, so the `.tm-ds { … }` block above is invisible
   to them (custom properties only resolve for the scoping element + its
   descendants). This mirror is scoped to `body.tm-ds-shell` instead (set in
   templates/layouts/base.html exactly when `.tm-ds` is active AND ?legacy=1
   is absent — same pages, different ancestor). Consumed by
   static/css/ds/ds-shell.css. Kept HERE (not in ds-shell.css) because this
   is the one DS file the guard allows colour literals in
   (tests/test_ui_design_system_guard.py: TOKEN_FILE excluded from the
   colour-literal check) — the shell's permanently-dark sidebar surface has
   no existing semantic alias to reuse (unlike control-radius / shadow-card,
   which are already global in static/css/v2/tokens.css and used as-is). */
body.tm-ds-shell {
  /* Sidebar: ONE dark neutral surface in both app-themes (reference target,
     docs/ui-design-log.md § Kabuk). Text tones checked for ≥ 4.5:1 on --tm-shell-sidebar-bg. */
  /* --tm-shell-sidebar-* tokenları KALDIRILDI (2026-10-08): sol ray artık
     kendi tokenlarını v2/components/sidebar.css §0'da tanımlıyor. Bu küme
     yalnız ds-shell.css tarafından okunuyordu ve ray'e İKİNCİ bir renk
     kaynağı veriyordu (ds sayfalarında #12161f, diğerlerinde başka). Ölü
     token bırakmak, birinin yeniden bağlamasını davet eder. */
  /* Ticker band token'lari :root'a tasindi — asagiya bak (2026-09-16). */
  --tm-shell-accent:              #0175e4;   /* == --tm-accent-solid (.tm-ds-scoped, invisible here) */

  /* Control radius mirror (identical value to `.tm-ds`'s var(--tm-radius-md);
     no `.tm-ds` alias reaches the shell, so it is restated here, not copied
     as a literal). --tm-radius-full is already global (static/css/v2/tokens.css
     :root), no mirror needed. */
  --tm-radius-control: var(--tm-radius-md);
  /* Card/overlay radius mirror — portalled modals (catalog picker appends to
     body) are outside `.tm-ds`; without this they fall back to 6px. */
  --tm-radius-card:    var(--tm-radius-xl);
  --tm-radius-overlay: var(--tm-radius-xl);
}


/* ---------- Top announcement band tokens (ticker) — :root, KAPSAMSIZ -------
   templates/includes/_shell_ticker.html her sayfada render edilebilsin diye bu 7 token
   `body.tm-ds-shell` blogundan `:root`a tasindi (2026-09-16, kullanici istegi: "bu banner
   cok guzel ama bazi sayfalarda var bazilarinda yok, tum sayfalarda gozuksun").

   🔴 NEDEN ZORUNLUYDU: ticker kurallari bu token'lari 18 yerde kullaniyor ve HICBIRINDE
   `var(--x, yedek)` fallback'i yok. Kapsam disinda kalirlarsa CSS kuralinca deger `unset`
   olur → band zemini SEFFAF, metin miras — yani "kaynakta var, ekranda yok". Olculdu:
   7 token yalniz `body.tm-ds-shell`'de tanimliydi, digerleri (`--tm-space-*`, `--tm-radius-*`)
   zaten `v2/tokens.css`'te `:root`ta.

   ⚠️ Bu token'larin dark-mode varyanti YOK (tek tanim) — band iki modda da ayni renkte,
   bilerek: duyuru seridi marka rengi tasiyor, tema ile degismiyor. */
:root {
  /* 🔴 KONTRAST DUZELTMESI (2026-09-16, olculdu). Eski ton renkleri beyaz metinle AA'yi
     GECMIYORDU — serit metni 14/15px (buyuk-metin esigi degil), yani esik 4.5:
         eski  promo #0175e4 = 4.50 (tam sinir)  ·  success #16a34a = 3.30 ❌
               warning #d97706 = 3.19 ❌          ·  danger #dc2626 = 4.83
     Tonlar bir kademe koyulastirildi; ayni renk ailesi, okunur:
         yeni  promo 5.89 · success 5.02 · warning 5.02 · danger 5.75
     Bu, kullanicinin "gorsel iyilestirme" istegiyle AYNI turda cikti: banda parilti eklemek
     icin once zeminin okunur olmasi gerekiyordu — ACIK parilti kontrasti DUSURUYOR
     (olculdu: alpha .08 -> en dusuk 2.90), o yuzden parilti KOYU uctan yapiliyor. */
  --tm-shell-band-bg:      #0163c2;
  --tm-shell-band-bg-2:    #0b5cab;   /* gradyanin koyu ucu — kontrast tabandan YUKSEK */
  --tm-shell-band-fg:      #ffffff;
  --tm-shell-band-fg-18:   rgba(255, 255, 255, 0.18);
  --tm-shell-band-fg-45:   rgba(255, 255, 255, 0.45);
  --tm-shell-band-ok:      #15803d;
  --tm-shell-band-ok-2:    #166534;
  --tm-shell-band-warn:    #b45309;
  --tm-shell-band-warn-2:  #92400e;
  --tm-shell-band-danger:  #c62222;
  --tm-shell-band-danger-2:#b91c1c;
}

/* ---------- Cila V1 (2026-09-23, onaylı mockup docs/ui-audit/mockups/polish-v1.css,
   entegrasyon: static/css/ds/ds-polish.css) — 4 yeni token, yalnız burada mevcut
   bir semantic ada indirgenemeyen 4 durum için (gerisi mevcut token'lardan
   kuruluyor: --tm-brand-*, --tm-decor-*-soft, --tmv2-surface-2/3,
   --tmv2-form-focus-shadow, --tm-account-hero-gradient). Sidebar gradyanı
   :root'ta KAPSAMSIZ (sidebar her temada koyu — bkz. v2/components/sidebar.css
   §20 yorumu); header glass/gölge ve demo-banner yalnız açık temada kullanılır
   (ds-polish.css'te `html:not(.dark-mode):not([data-theme="dark"])` önekiyle). */
:root {
  --tm-polish-sidebar-grad: linear-gradient(180deg, #0f1622 0%, #0b1119 100%);
  --tm-polish-sidebar-active-grad: linear-gradient(90deg, rgba(1, 117, 228, 0.28), rgba(1, 117, 228, 0.10));
  --tm-polish-header-glass: rgba(255, 255, 255, 0.82);
  --tm-polish-header-shadow: 0 8px 24px -20px rgba(15, 23, 42, 0.35);
  --tm-polish-demo-bg-from: #fff4dc;
  --tm-polish-demo-bg-to: #fff8e8;
  --tm-polish-demo-border: #f3dfae;
}


/* ===== css/ds/ds-primitives.css ===== */
/* =================================================================
   TRENDMETRIK DESIGN SYSTEM — LAYOUT PRIMITIVES (Faz 2, 2026-09-12)

   Scope: every rule lives under .tm-ds (set on <main> by pages that opted in
   via {% block tmv2_ds_scope %}) → inert on the other ~200 pages.
   Padding owners: .tm-page and .tm-card ONLY. Everything else aligns; nothing
   else adds space. Sibling spacing is always the parent's gap, never margin.
   Resting surfaces: 1px border + the single --tm-shadow-card (reference-derived).

   Primitives:
     .tm-page            single outer container (max-width + --tm-space-6 padding)
     .tm-page-header     title · subtitle · actions — one per page
     .tm-grid            12-column grid, gap --tm-space-4, rows stretch
       .tm-col-{1..12}   column span (collapses: ≥6→12 under 1024, all→12 under 768)
     .tm-card            one content surface; .tm-card__header inside it
   ⚠ SCOPE: every DS rule is a DESCENDANT selector (`.tm-ds .tm-card`). Putting `tm-ds` and
     `tm-card` on the SAME node does NOT match — the element ends up completely unstyled
     (background rgba(0,0,0,0), border 0px) while the source looks correct. Use a wrapper:
     `<div class="tm-ds"><div class="tm-card">`. (Measured 2026-09-16, buybox banner.)
     This is the opposite of the modal-portal fix, where the scope class DOES go on the node
     itself — because there the node is the ANCESTOR of the DS-styled children.
     .tm-stack / .tm-row vertical / horizontal flow, gap only
     .tm-divider         1px separator inside a card (instead of a nested card)
   ================================================================= */

/* 🔴 Kapsam KÖKÜ font-family'i (2026-09-17 — kullanıcı: "yazıların stilleri
   uyumlu olsun", ⑥ madde). ÖLÇÜLDÜ (rendered DOM, 4 ds-scope'lu sayfa):
   `.tm-page-header__title` + `.tm-tab` (v2/components/tabs.css, `--tm-font-
   sans` KULLANIYOR, ds-DIŞI ama AYNI token) `"Inter var", Inter, ...` alıyor;
   `.tm-text-title`/`.tm-num`/`.tm-text-caption`/`.tm-badge` gibi ds
   utility'lerinin HİÇBİRİ font-family YAZMIYOR → Tailwind preflight'ın
   `ui-sans-serif, system-ui, ...` varsayılanına DÜŞÜYOR. Yani aykırı olan
   `.tm-tab` DEĞİLDİ (önceki varsayım YANLIŞTI) — aykırı olan ds utility'
   lerinin KENDİ token'ını hiç UYGULAMAMASIYDI.
   A/B ÖLÇÜMÜ (gerçek Chromium, aynı metin, iki stack yan yana): genişlik
   farkı 0px — repo'da HİÇBİR `@font-face` YOK, "Inter var"/"Inter" hiçbir
   makinede YÜKLENMİYOR, ikisi de aynı sistem-fontuna (`-apple-system`/
   `Segoe UI`/`ui-sans-serif`) düşüyor. Yani bu kural pratikte HİÇBİR sayfayı
   BOZMUYOR — computed-style DİZGİSİNİ tek token'a topluyor, görünen glif
   AYNI kalıyor (ve "Inter" ileride gerçekten yüklenirse tutarlılık ekliyor).
   Karar (koordinatörün ①/②/③ kuralı): tutarlılık yönünde + hiçbir sayfa
   bozulmuyor → DS'E TEK KURAL (dar `.tm-tab` müdahalesi değil). */
.tm-ds { font-family: var(--tm-font-sans); }

/* ---------- Page ---------- */
.tm-ds .tm-page {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--tm-space-6);
  width: 100%;
  max-width: var(--tm-container-max);
  min-width: 0;
  margin: 0 auto;
  padding: var(--tm-space-6) 0;   /* horizontal gutter comes from the shell (.tmv2-page-body, 32px) — identical on every page */
  background: transparent;
  overflow-x: clip;
}
.tm-ds .tm-page--constrained { max-width: var(--tm-content-max); }

/* The shell wrappers between <main> and .tm-page must not add space of their
   own inside DS scope (they fight over padding with !important elsewhere —
   audit §2). Faz 4 measures this per page; Faz 5 removes the legacy rules. */
.tm-ds .tmv2-page-inner,
.tm-ds .tmv2-page-shell,
.tm-ds .tmv2-page-body {
  padding: 0;
  max-width: none;
}

/* ---------- Page header ---------- */
.tm-ds .tm-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tm-space-4);
  min-height: 40px;
  padding: 0;
}
.tm-ds .tm-page-header__title-group {
  display: flex;
  flex-direction: column;
  gap: var(--tm-space-1);
  min-width: 0;
}
.tm-ds .tm-page-header__title {
  margin: 0;
  font-family: var(--tm-font-sans);
  font-size: var(--tm-type-display);
  line-height: var(--tm-type-display-lh);
  font-weight: var(--tm-weight-bold);
  color: var(--tm-text-primary);
  letter-spacing: var(--tm-tracking-tight);
}
.tm-ds .tm-page-header__subtitle,
.tm-ds .tm-page-header__eyebrow {
  margin: 0;
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  color: var(--tm-text-secondary);
}
.tm-ds .tm-page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--tm-space-2);
  flex-shrink: 0;
}

/* ---------- Grid ---------- */
.tm-ds .tm-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--tm-space-6) var(--tm-space-4);   /* rows 24 · columns 16 (reference) */
  align-items: stretch;
  width: 100%;
  min-width: 0;
}
.tm-ds .tm-col-1  { grid-column: span 1; }
.tm-ds .tm-col-2  { grid-column: span 2; }
.tm-ds .tm-col-3  { grid-column: span 3; }
.tm-ds .tm-col-4  { grid-column: span 4; }
.tm-ds .tm-col-5  { grid-column: span 5; }
.tm-ds .tm-col-6  { grid-column: span 6; }
.tm-ds .tm-col-7  { grid-column: span 7; }
.tm-ds .tm-col-8  { grid-column: span 8; }
.tm-ds .tm-col-9  { grid-column: span 9; }
.tm-ds .tm-col-10 { grid-column: span 10; }
.tm-ds .tm-col-11 { grid-column: span 11; }
.tm-ds .tm-col-12 { grid-column: span 12; }
.tm-ds .tm-grid > * { min-width: 0; }

@media (max-width: 1023.98px) {
  .tm-ds .tm-col-6, .tm-ds .tm-col-7, .tm-ds .tm-col-8, .tm-ds .tm-col-9,
  .tm-ds .tm-col-10, .tm-ds .tm-col-11 { grid-column: span 12; }
  .tm-ds .tm-col-3, .tm-ds .tm-col-4, .tm-ds .tm-col-5 { grid-column: span 6; }
}
@media (max-width: 767.98px) {
  .tm-ds .tm-page { padding: var(--tm-space-4) 0; gap: var(--tm-space-4); }
  .tm-ds .tm-grid { gap: var(--tm-space-3); }
  .tm-ds .tm-grid > [class*="tm-col-"] { grid-column: span 12; }
  .tm-ds .tm-page-header { flex-direction: column; align-items: flex-start; }
}

/* ---------- Card (the only content surface) ---------- */
.tm-ds .tm-card {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--tm-space-4);
  min-width: 0;
  padding: var(--tm-space-6);
  background: var(--tm-bg-surface);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-card);
  box-shadow: var(--tm-shadow-card);
  color: var(--tm-text-primary);
  transition: none;
}
/* 🔴 GENEL `:hover` kuralı KALDIRILDI (2026-09-17 — ÖLÇÜLDÜ, gerçek mouse
   hover + 6 hesaplanmış özellik: box-shadow/transform/background/border/
   cursor/outline, `diffKeys: []`). Kök: `box-shadow` REST'teki AYNI değeri
   tekrar yazıyordu + `transform: none` lift'i açıkça kapatıyordu + base
   kuralda `transition: none` olduğu için ZATEN geçişsizdi — üçü birlikte
   sıfır görsel etki üretiyordu ("fire ediyor ama görünmüyor"). Bırakmak
   okuyanı "hover var" sanmaya iterdi; kaldırmanın blast-radius'u SIFIR
   (hiçbir yerde bir şeyi DEĞİŞTİRMİYORDU). Satır-bazlı gerçek hover
   `.tm-card--band[data-bb3-row]:hover` altında, aşağıda. */
.tm-ds .tm-card--flush { padding: 0; }           /* table fills the card edge to edge */
.tm-ds .tm-card--flush > .tm-card__header { padding: var(--tm-space-6) var(--tm-space-6) 0; }

/* --band: TEK SATIRLIK bildirim şeridi (metin + tek aksiyon). Dolgu 24px → 12/16, satır-arası
   boşluk 16 → 8. Gerekçe (buybox oturumu, 2026-09-16 ölçümü): `.tm-card` ile kurulan şerit 90px
   oluyor; Genel Bakış'ta mevcut promo şeridiyle üst üste gelince ~134px üst-fold demek. Kullanıcı
   bu panel için üç turdur "çok karmaşık, dağınık" diyor — dikey bütçe burada gerçek bir maliyet.
   ⚠ Kart DEĞİLDİR: içine `__header`/kolon yığını koyulmaz. Tek satır taşamıyorsa `.tm-card` kullan.
   Yükseklik sabitlenmedi (`height`/`min-height` YOK): 360px'te metin iki satıra düşer ve şerit
   kendiliğinden uzar — sabit yükseklik orada metni keserdi. */
.tm-ds .tm-card--band { padding: var(--tm-space-3) var(--tm-space-4); gap: var(--tm-space-2); }
.tm-ds .tm-card--band > .tm-card__body { display: flex; flex-direction: row; align-items: center; gap: var(--tm-space-3); flex-wrap: wrap; }

/* 🔴 SATIR HOVER — GERÇEK geri bildirim (kullanıcı: "görsel olarak çok
   düz... görsel canlılık eklenmeli", 2026-09-17). ÖLÇÜLDÜ: genel
   `.tm-card:hover` (yukarıda kaldırıldı) sıfır görsel etki üretiyordu.
   Desen YENİ İCAT EDİLMEDİ — `ds-table.css:71`teki AYNI kalıp
   (`.tm-table tbody tr:hover > td { background: var(--tm-bg-subtle); }`)
   satır bağlamına taşındı, tutarlılık için.
   🔴 `prefers-reduced-motion` ALTINA KONMADI (koordinatör kararı,
   2026-09-17): bu bir DURUM DEĞİŞİMİ (renk), HAREKET değil — o ayar
   açık olan kullanıcı için bile çalışmaya devam etmeli; azaltılmış
   hareket tercihi, azaltılmış GERİ BİLDİRİM anlamına gelmemeli.
   `[data-bb3-row]` ile SADECE buybox v3 fırsat satırlarına daraltıldı —
   `.tm-card--band` `_campaign_hero.html`/`_rules_manager.html`/v2
   `_opportunity_banner.html`de de kullanılıyor, blast-radius onları
   ETKİLEMESİN diye. Boyut/dikey uzunluk DEĞİŞMEDİ — yalnız renk. */
.tm-ds .tm-card--band[data-bb3-row]:hover { background: var(--tm-bg-subtle); }

/* 🔴 SOL ŞERİT — fırsat büyüklüğüne göre renk (kullanıcı: "listeyi daha
   okunabilir renkli vs yapmak lazım", 2026-09-17).
   Amaç süs değil TARANABİLİRLİK: 100 satırlık listede göz, sayıyı okumadan
   önce şeridi görür ve nereye bakacağını seçer.
   ⚠️ TABAN ŞEFFAF: her satır AYNI geometriyi alır, yalnız RENK değişir —
   yoksa kademesi olan satır 3px kayar ve liste tırtıklı görünür (bu sayfanın
   üç turdur kovaladığı "kayma" şikâyetinin ta kendisi).
   ⚠️ Kademe adı `bb3-render.js::gapTone`'dan gelir; eşik BURADA TEKRAR
   YAZILMAZ — renk ile sayı aynı kaynaktan beslenir.
   ⛔ `unknown` (fark hesaplanamadı) BOYANMAZ: "bilmiyorum"u en soluk tonla
   göstermek onu "küçük fırsat" diye okutur.
   `[data-bb3-row]` ile yalnız v3 fırsat satırlarına daraltıldı — `.tm-card--band`
   başka yerlerde de kullanılıyor, blast-radius onları etkilemesin. */
.tm-ds .tm-card--band[data-bb3-row] { border-inline-start: 3px solid transparent; }
.tm-ds .tm-card--band[data-bb3-row][data-bb3-tier="high"] { border-inline-start-color: var(--tm-success-fg); }
.tm-ds .tm-card--band[data-bb3-row][data-bb3-tier="mid"]  { border-inline-start-color: color-mix(in srgb, var(--tm-success-fg) 75%, var(--tm-text-secondary) 25%); }
.tm-ds .tm-card--band[data-bb3-row][data-bb3-tier="low"]  { border-inline-start-color: color-mix(in srgb, var(--tm-success-fg) 45%, var(--tm-text-muted) 55%); }
/* `flex-direction: row` AÇIKÇA yazılı: `.tm-ds .tm-card__body` `column` diyor ve bu kural onu
   EZMEZ — yalnız yazmadığın özellik miras kalır. İlk yazımda unuttum, şerit 86px kaldı (hedef
   ~54); ölçünce çıktı. Aynı hatanın kardeşi bugün `.cpk-weekly__tabs`'ta yaşandı. */

.tm-ds .tm-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tm-space-3);
  min-height: 24px;
}
.tm-ds .tm-card__title {
  margin: 0;
  font-size: var(--tm-type-heading);
  line-height: var(--tm-type-heading-lh);
  font-weight: var(--tm-weight-semibold);
  color: var(--tm-text-primary);
}
.tm-ds .tm-card__subtitle {
  margin: 0;
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  color: var(--tm-text-muted);
}
.tm-ds .tm-card__actions {
  display: flex;
  align-items: center;
  gap: var(--tm-space-2);
  flex-shrink: 0;
}
.tm-ds .tm-card__body { display: flex; flex-direction: column; gap: var(--tm-space-3); min-width: 0; flex: 1 1 auto; }

/* ---------- Flow ---------- */
.tm-ds .tm-stack { display: flex; flex-direction: column; gap: var(--tm-space-4); min-width: 0; }
.tm-ds .tm-row   { display: flex; flex-direction: row; align-items: center; gap: var(--tm-space-3); min-width: 0; flex-wrap: wrap; }
.tm-ds .tm-stack--tight, .tm-ds .tm-row--tight { gap: var(--tm-space-2); }
.tm-ds .tm-stack--loose, .tm-ds .tm-row--loose { gap: var(--tm-space-6); }
.tm-ds .tm-row--between { justify-content: space-between; }
.tm-ds .tm-row--end { justify-content: flex-end; }
/* 🔴 `flex-basis: 0%` (Tailwind'in `flex-1`iyle AYNI), `auto` DEĞİL — bilerek.
   `min-width:0` yalnız SHRINK aşamasını serbest bırakır; SATIR KIRMA kararı
   `flex-basis`e (auto→max-content) bakar, shrink potansiyeline BAKMAZ.
   Ölçüldü (2026-09-16, buybox 390px, çizilmiş DOM): checkbox+thumb'ın
   yanındaki ad+barkod grubu `flex:1 1 auto` iken UZUN ADIN max-content
   genişliği (~301px) satıra sığmıyordu → grup THUMB'IN ALTINA
   kırılıyordu (ad.top 428 >= thumb.bottom 420), oysa `.tm-truncate`
   içeriği zaten kırpabiliyordu — kırpma devreye girmeden satır kırılması
   karar veriliyordu. `flex-basis:0%` bu grubun satır-kırma hesabına max-
   content'i SOKMAZ; grup sıfır tabanla satıra girer, `flex-grow:1` kalan
   alanı doldurur, `.tm-truncate` içeride kırpar. Sonuç: 390'da ad artık
   THUMB'IN YANINDA (aynı satır), satır kırılmıyor.

   ⚠️ GLOBAL PRİMİTİF — DEĞİŞİKLİKTEN ÖNCE PATLAMA YARIÇAPI ÖLÇÜLDÜ (Tailwind
   `flex-1`/`flex-auto`u BİLEREK ayırır; burada `.tm-grow` TEK sınıf olduğu
   için `auto→0%` HER tüketiciyi etkiler). Repo-genelinde `.tm-grow` YALNIZ
   2 şablonda, 13 kullanım (`buybox/v3/_opportunity_list.html`,
   `_rule_wizard.html`) — hepsi tek-satır (`.tm-row`/`.tm-stack`) içinde TEK
   büyüyen öğe ya da `.tm-truncate` ile eşleşiyor, sabit-genişlik beklentisi
   YOK. 5× boş iskelet `<span>` (içerik yok → `auto` da `0%` de 0, fark yok).
   Ölçüldü (gerçek Chromium, 390px, sihirbaz adım 1/3): `_rule_wizard.html`
   strateji etiketleri AYNI kökten AYNI hatayı taşıyordu — "Rakiple eşleş" /
   "Kademeli in" metni radyo düğmesinin YANINA sığmıyor, ALTINA kırılıyordu
   (`auto`: satır 85px/69px, metin kutusu düğmeyle aynı-satırda DEĞİL); bu
   fix'le düzeldi (satır 64px, metin düğmenin yanında). Adım 3 önizleme
   `dl`sindeki 3 istatistik sütunu içerik-oranlı (98/98/88px) → eşit
   (95/95/95px) genişliğe geçti — taşma/kırpma YOK, görsel iyileşme.
   12/12 sihirbaz+önizleme testi yeşil. Karar: GLOBAL KABUL (tek tüketici
   grubu bile bulunmadı, geri alınacak bir davranış YOK). */
.tm-ds .tm-grow { flex: 1 1 0%; min-width: 0; }

/* Sabit-taban KARŞILAŞTIRMA sütunu — flex satırında (TABLO DEĞİL; `.tm-table
   .tm-col-*` `width` kullanır ve `td`ye bağımlıdır, burada karşılığı yok).
   `min-width` (`width` DEĞİL): taşan içerik KIRPILMAZ. Fiyat metninin
   KENDİSİNİ hizalar (`.tm-col-info` aşağıda gerçek kökü çözer); bu ikisi
   BİRLİKTE çalışıyor. Tek tüketici bugün: `_opportunity_list.html`
   `[data-bb3-prices]`. */
.tm-ds .tm-col-value { display: inline-block; min-width: 26ch; }

/* 🔴 ASIL "KAYMA" KÖKÜ (kullanıcı geri bildirimi + ÖLÇÜM, 2026-09-17):
   fiyat metninin KENDİSİ değil, `.tm-card__body`'nin İKİ üst-seviye
   çocuğunun paylaşım biçimiydi. Ad grubu (`.tm-grow`, flex-basis 0%) satırda
   KALAN her şeyi yutar; bilgi/aksiyon grubu `auto` bazlıydı — yani ONUN
   genişliği (rozet metni + fark tutarı uzunluğuna göre) değiştikçe ad grubu
   ne kadar yer bırakacağını yeniden hesaplıyor ve bilgi grubunun BAŞLANGIÇ
   x'i satırdan satıra kayıyordu (Chromium'da ÖLÇÜLDÜ: TY1/TY4 satırlarında
   824,7px vs 848,9px — 24px fark, salt rozet metni uzunluğundan).
   Çözüm: bu gruba SABİT bir `flex-basis` ver (`auto` DEĞİL) — artık ad
   grubu HER satırda AYNI genişliği bırakıyor, bilgi grubu HER SATIRDA AYNI
   x'te başlıyor. `flex-shrink:1` + `min-width:0` KALDI: 390'da bu grup gene
   kendi satırına düşüp gerektiği kadar küçülebiliyor (mevcut wrap deseni
   BOZULMADI). Değer bugünkü en geniş içeriğe (~437px) değil, PAY BIRAKARAK
   seçildi (~480px) — `bbx-design`'ın bu bloğa ekleyeceği sapma göstergesi/
   durum işareti/denetim izi bugün taşmasın diye (koordinatör talebi).
   ⚠️ `.tm-grow` (ad grubu) BURADA DEĞİŞMEDİ — o ayrı, ölçülmüş, "GLOBAL
   KABUL" edilmiş bir primitif (yukarıdaki not); bu sınıf onun KARDEŞİNE
   uygulanıyor, ikisi çakışmıyor. */
.tm-ds .tm-col-info { flex: 0 1 480px; min-width: 0; }

/* ---------- Card density modifier (liste satırı) ---------- */
/* 🔴 ÖLÇÜLDÜ (2026-09-18, kullanıcı: "SATIRLAR HALA DİKEY OLARAK ÇOK GENİŞ"):
   Buy Box fırsat satırı 1440px'te 130px. Parçalar: kart dolgusu 12+12=24 ·
   gövde dolgusu 20+20=40 · içerik 64 (görsel). Yani satırın %49'u SAF BOŞLUK.
   ⛔ GÖRSEL KÜÇÜLTÜLMEDİ: kullanıcı 2026-09-17'de tam tersini istedi
   ("görseller çok küçük") ve 64px o istekle geldi. Bir şikâyeti çözerken
   önceki şikâyeti geri getirmek kabul edilmez — sıkıştırma YALNIZ boşluktan.
   Ölçüm: 130 → 106px (-%18), görsel boyutu DEĞİŞMEDEN. */
/* ⚠️ YALNIZ ≥992px — ÖLÇÜMLE SINIRLANDI. İlk hâli kapsamsızdı ve 390px'te
   TERS etki yapıyordu: dar ekranda gövde dolgusu zaten daha küçük, kural onu
   8px'e ÇIKARIYOR ve satır 146 → 162px oluyordu (mevcut sıkılık testi
   kırmızı yandı, bisect ile bu dosyaya indirgendi). Yani "daraltma" diye
   yazılan kural mobilde GENİŞLETİYORDU. Kazanım masaüstünde ölçüldü, kural
   da orada kalıyor. */
@media (min-width: 992px) {
  .tm-ds .tm-card--dense > .tm-card__body {
    padding-top: var(--tm-space-2);
    padding-bottom: var(--tm-space-2);
  }
}

/* ---------- Divider (sectioning inside a card — never a nested card) ---------- */
.tm-ds .tm-divider {
  height: 1px;
  width: 100%;
  background: var(--tm-border);
  border: 0;
  margin: 0;
}

/* ---------- Focus ring: visible everywhere, same everywhere ---------- */
.tm-ds :focus-visible {
  outline: 2px solid var(--tm-accent);
  outline-offset: 2px;
  box-shadow: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SİHİRBAZ BİLEŞENLERİ (2026-09-17, kullanıcı: "kural oluşturma modalinde
   görsel olarak daha iyi, checkbox yerine toggle, her adımı açıklayan
   animasyon veya ikonlar, daha okunaklı yazı renkleri ve büyüklükleri")

   ⚠️ Üçü de GLOBAL DS'te, sayfa CSS'i YOK (kullanıcının bağlayıcı kararı).
   ⚠️ Her kural `.tm-ds` altında — `test_every_primitive_rule_is_scoped_under_tm_ds`.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---------- 1) ADIM GÖSTERGESİ (.tm-stepper) ----------
   Kullanıcı "her adımı açıklayan ikonlar" istedi. Metinsel "Adım 3 / 4" tek
   başına NEREDE olduğunu söyler ama NE KALDIĞINI göstermez.
   🔴 TAMAMEN CSS-SÜRÜCÜLÜ: geçerli adım ataya yazılan `data-current` ile
   seçilir (`bb3-wizard.js` tek satır yazar). Adım başına JS/sınıf yönetimi
   YOK — `bb3-wizard.js` 496/500 satırda ve bu bileşen ona 1 satır ekliyor. */
.tm-ds .tm-stepper { display: flex; align-items: center; gap: var(--tm-space-2); list-style: none; margin: 0; padding: 0; }
.tm-ds .tm-stepper__item { display: flex; align-items: center; gap: var(--tm-space-2); flex: 1 1 0; min-width: 0; }
.tm-ds .tm-stepper__dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  border-radius: var(--tm-radius-full);
  border: 1px solid var(--tm-border);
  background: var(--tm-bg-subtle);
  color: var(--tm-text-muted);
  /* Geçiş RENK/ZEMİN üzerinde — konum değişmiyor, yani "kayma" üretmiyor.
     `prefers-reduced-motion` bloğu (aşağıda) süreyi zaten sıfırlıyor. */
  transition: background-color var(--tm-dur-base) var(--tm-ease), color var(--tm-dur-base) var(--tm-ease), border-color var(--tm-dur-base) var(--tm-ease);
}
.tm-ds .tm-stepper__dot svg { width: 15px; height: 15px; }
.tm-ds .tm-stepper__label { font-size: var(--tm-font-caption); color: var(--tm-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tm-ds .tm-stepper__line { flex: 1 1 auto; height: 1px; background: var(--tm-border); min-width: var(--tm-space-2); }

/* GEÇERLİ adım: dolgulu + vurgulu. */
.tm-ds .tm-stepper[data-current="1"] > .tm-stepper__item:nth-of-type(1) > .tm-stepper__dot,
.tm-ds .tm-stepper[data-current="2"] > .tm-stepper__item:nth-of-type(2) > .tm-stepper__dot,
.tm-ds .tm-stepper[data-current="3"] > .tm-stepper__item:nth-of-type(3) > .tm-stepper__dot,
.tm-ds .tm-stepper[data-current="4"] > .tm-stepper__item:nth-of-type(4) > .tm-stepper__dot {
  background: var(--tm-accent-solid); border-color: var(--tm-accent-solid); color: #fff;
}
.tm-ds .tm-stepper[data-current="1"] > .tm-stepper__item:nth-of-type(1) > .tm-stepper__label,
.tm-ds .tm-stepper[data-current="2"] > .tm-stepper__item:nth-of-type(2) > .tm-stepper__label,
.tm-ds .tm-stepper[data-current="3"] > .tm-stepper__item:nth-of-type(3) > .tm-stepper__label,
.tm-ds .tm-stepper[data-current="4"] > .tm-stepper__item:nth-of-type(4) > .tm-stepper__label {
  color: var(--tm-text-primary); font-weight: 600;
}
/* TAMAMLANAN adımlar: yeşil kenar + yeşil ikon (ilerleme görünür olsun). */
.tm-ds .tm-stepper[data-current="2"] > .tm-stepper__item:nth-of-type(1) > .tm-stepper__dot,
.tm-ds .tm-stepper[data-current="3"] > .tm-stepper__item:nth-of-type(-n+2) > .tm-stepper__dot,
.tm-ds .tm-stepper[data-current="4"] > .tm-stepper__item:nth-of-type(-n+3) > .tm-stepper__dot {
  border-color: var(--tm-success-fg); color: var(--tm-success-fg); background: var(--tm-success-bg);
}

/* ---------- 2) TOGGLE (.tm-switch) ----------
   Kullanıcı: "checkbox yerine toggle". Onay kutusu *"form alanı"* gibi
   görünüyor; bu iki onay ise **yetki veriyor** (fiyat yazma izni). Anahtar
   metaforu o kararı daha doğru anlatıyor.
   🔴 GERÇEK `<input type="checkbox">` KORUNUR (görsel olarak gizlenir, ama
   DOM'da durur): klavye, `:checked`, form davranışı ve mevcut JS
   (`$('bb3Consent').checked`) aynen çalışır. Salt-CSS bir div ile taklit
   etmek erişilebilirliği ve mevcut kodu kırardı. */
.tm-ds .tm-switch { position: relative; display: inline-flex; align-items: center; flex-shrink: 0; width: 38px; height: 22px; }
.tm-ds .tm-switch > input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.tm-ds .tm-switch > .tm-switch__track {
  pointer-events: none;
  width: 38px; height: 22px; border-radius: var(--tm-radius-full);
  background: var(--tm-bg-subtle); border: 1px solid var(--tm-border);
  transition: background-color var(--tm-dur-base) var(--tm-ease), border-color var(--tm-dur-base) var(--tm-ease);
}
.tm-ds .tm-switch > .tm-switch__track::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: var(--tm-radius-full);
  background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  transition: transform var(--tm-dur-base) var(--tm-ease);
}
.tm-ds .tm-switch > input:checked + .tm-switch__track { background: var(--tm-accent-solid); border-color: var(--tm-accent-solid); }
.tm-ds .tm-switch > input:checked + .tm-switch__track::after { transform: translateX(16px); }
/* Odak HALKASI input'ta değil RAY'da görünür (input şeffaf) — klavye
   kullanıcısı anahtarın seçili olduğunu görmek zorunda. */
.tm-ds .tm-switch > input:focus-visible + .tm-switch__track { outline: 2px solid var(--tm-accent); outline-offset: 2px; }

/* ---------- 3) SEÇİM KARTI (.tm-card--choice) ----------
   ⚠️ KART VARYANTI olarak yazıldı, yeni bir bileşen olarak DEĞİL: DS'in
   "dolguyu yalnız sayfa ve kart sahiplenir" sözleşmesi
   (`test_padding_owners_are_page_and_card_only`) bunu şart koşuyor. İlk
   yazımımda `.tm-choice` idi ve kilit haklı olarak kırmızı yandı — kuralı
   gevşetmek yerine bileşen doğru yere taşındı.
   Adım 1'in radyo listesi düz metindi. Kart, tıklama alanını büyütür ve
   seçili olanı ZEMİNLE gösterir (yalnız küçük bir radyo noktasıyla değil). */
.tm-ds .tm-card--choice {
  /* 🔴 `flex-direction: row` AÇIKÇA YAZILI — `.tm-card` tabanı `column`
     diyor ve `display:flex` onu EZMEZ; yalnız YAZMADIĞIN özellik miras
     kalır. İlk yazımımda unuttum ve kullanıcı sonucu gördü: radyo, ikon,
     başlık ve açıklama ALT ALTA dizildi, kart 140px'e şişti.
     ⚠️ Bu dosyanın `--band` yorumu (yukarıda) AYNI tuzağı zaten yazıyor:
     *"İlk yazımda unuttum, şerit 86px kaldı; ölçünce çıktı."* İkinci kez. */
  display: flex; flex-direction: row; align-items: flex-start; gap: var(--tm-space-3);
  padding: var(--tm-space-3);
  border: 1px solid var(--tm-border); border-radius: var(--tm-radius-control);
  cursor: pointer;
  transition: background-color var(--tm-dur-fast) var(--tm-ease), border-color var(--tm-dur-fast) var(--tm-ease);
}
.tm-ds .tm-card--choice:hover { background: var(--tm-bg-subtle); }
.tm-ds .tm-choice-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex-shrink: 0;
  border-radius: var(--tm-radius-control);
  background: var(--tm-accent-bg); color: var(--tm-accent-fg);
}
.tm-ds .tm-choice-ic svg { width: 17px; height: 17px; }
/* `:has()` desteklenmiyorsa kart yine tıklanabilir ve radyo görünür kalır —
   yalnız seçili ZEMİN olmaz. Bilinçli düşüş (graceful degradation). */
.tm-ds .tm-card--choice:has(> input:checked) { border-color: var(--tm-accent-solid); background: var(--tm-accent-bg); }
.tm-ds .tm-card--choice:has(> input:focus-visible) { outline: 2px solid var(--tm-accent); outline-offset: 2px; }

/* ---------- KAYDIRMA KUTUSU (.tm-scrollbox) ----------
   Kullanici (2026-09-17): "buybox listesini scroll edilebilir yapalim, liste
   uzun ve sayfa dikey olarak cok uzuyor." 100 satir x ~130px = ~13.000px
   sayfa demek; ustteki sekmeler/ozet ekrandan cikiyor.
   UYARI `min(...vh, ...px)`: kucuk ekranda oransal, buyuk ekranda sabit tavan —
   4K'da 68vh 1.400px olurdu ve "uzun sayfa" sorunu geri gelirdi.
   UYARI `overscroll-behavior: contain`: liste sonuna gelince kaydirma SAYFAYA
   zincirlenmesin. */
.tm-ds .tm-scrollbox {
  max-height: min(68vh, 760px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---------- Firsat satirini SIKILASTIR ----------
   Kullanici: "satirlari biraz daha daralt, cok bosluklar var."
   YASAK: DOLGU DEGISTIRILMEDI — DS sozlesmesi dolguyu yalniz `.tm-page`/
   `.tm-card` (+ varyantlari) sahiplendiriyor ve `[data-bb3-row]` oznitelikli
   bir secici oraya giremez (`test_padding_owners_are_page_and_card_only`).
   Sikisma GAP'lerden alindi — kural gevsetilmedi.
   UYARI `[data-bb3-row]` ile yalniz v3 firsat satirlarina daraltildi. */
.tm-ds .tm-card--band[data-bb3-row] { gap: var(--tm-space-1); }
.tm-ds .tm-card--band[data-bb3-row] > .tm-card__body { gap: var(--tm-space-2); row-gap: var(--tm-space-1); }

/* ---------- ADIM İKONU ÇİZİLME ANİMASYONU ----------
   Kullanıcı (2026-09-17) iki kez istedi: *"ben svg animasyonu olsun istedim."*
   Lucide ikonları inline `<svg>` ve `stroke` ile çiziliyor → `stroke-dasharray`
   + `stroke-dashoffset` ile GERÇEK bir çizilme animasyonu kurulabiliyor.
   ⚠️ Süs değil BİLGİ: yalnız GEÇERLİ adımın ikonu çizilir, yani animasyon
   "neredesin"i anlatır. Her adımda çalışsa anlamı kalmazdı.
   ⚠️ `data-current` değişince seçici eşleşmesi değişir → animasyon her adımda
   yeniden başlar (JS gerekmiyor).
   🟢 `prefers-reduced-motion` bloğu (hemen aşağıda) süreyi zaten sıfırlıyor:
   hareket istemeyen kullanıcı ikonu ANINDA çizilmiş görür, kaybetmez. */
@keyframes tm-stepper-draw {
  from { stroke-dashoffset: 56; }
  to   { stroke-dashoffset: 0; }
}
.tm-ds .tm-stepper[data-current="1"] > .tm-stepper__item:nth-of-type(1) > .tm-stepper__dot svg,
.tm-ds .tm-stepper[data-current="2"] > .tm-stepper__item:nth-of-type(2) > .tm-stepper__dot svg,
.tm-ds .tm-stepper[data-current="3"] > .tm-stepper__item:nth-of-type(3) > .tm-stepper__dot svg,
.tm-ds .tm-stepper[data-current="4"] > .tm-stepper__item:nth-of-type(4) > .tm-stepper__dot svg {
  stroke-dasharray: 56;
  animation: tm-stepper-draw var(--tm-dur-morph) var(--tm-ease-out) forwards;
}

/* ---------- Motion only on user action ---------- */
@media (prefers-reduced-motion: reduce) {
  .tm-ds *, .tm-ds *::before, .tm-ds *::after {
    animation-duration: 0.001ms;
    animation-iteration-count: 1;
    transition-duration: 0.001ms;
    scroll-behavior: auto;
  }
}


/* ===== css/ds/ds-motion.css ===== */
/* =================================================================
   DS — MOTION GRAMMAR (PR-1, 2026-10-10 — docs/ui-design-log.md
   "Hareket dilbilgisi"; referans video kare kare ölçüldü)

   Referansta TEK hareket dili var ve her yerde aynı:
     giriş      opaklık 0→1 + 6px yukarı kayma · ~300 ms · ease-out · öğe başına ~50 ms kademe
     durum      renk/zemin/pil 200 ms
     veri       sayı/çizgi/çubuk MEVCUT değerden hedefe 450 ms (yok et + sıfırdan çiz YOK)
     ilk sayım  0 → değer 900 ms
   Bizde 1.337 `transition` (çoğu `all 0.2s ease`), 367 `@keyframes` adı ve
   8 ayrı süre yazımı vardı. Bu dosya DS kapsamı için TEK giriş animasyonu ve
   kademe mekanizmasını verir; süreler/eğriler YALNIZ token'dan okunur
   (ds-tokens.css `--tm-dur-*`, `--tm-stagger`; static/css/v2/tokens.css
   `--tm-ease*`). Guard: tests/test_ui_design_system_guard.py —
   DS dosyalarında literal süre/eğri RATCHET, `transition: all` YASAK.

   Kullanım (PR-2 koreografisi): kart SARMALAYICISINA `data-tm-stage="N"`
   (N = okuma sırası, 0..11). Attribute yalnız statik şablon öğelerine konur
   (TMKpi.render / TMCharts'ın yeniden yazdığı iç düğümlere DEĞİL — aksi hâlde
   her yeniden render girişi tekrar oynatır; 2026-07-17 KPI flicker vakası).

   Kurallar:
   - yalnız `opacity` + `transform` (layout özelliği animasyonu yasak);
   - `animation-fill-mode: backwards` → gecikme boyunca başlangıç karesi,
     bitişte NORMAL hesaplanmış stil: hiçbir öğe opaklık 0'da KALAMAZ
     (2026-09-10 "boş ızgara" kuralı);
   - prefers-reduced-motion: ds-primitives.css'teki `.tm-ds *` kuralı süreyi
     0.001 ms'e indirir ve ds-tokens.css süre token'larını 0'lar → bu dosyada
     AYRI bir reduced-motion bloğu YOK (26 dağınık blok sorununu büyütmemek için).
   ================================================================= */

@keyframes tm-enter {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Tek giriş: kademe indeksi attribute'tan okunur (inline style yok — şablon
   `style=` ratchet'i tests/test_ui_design_system_guard.py). */
.tm-ds [data-tm-stage] {
  animation-name: tm-enter;
  animation-duration: var(--tm-dur-enter);
  animation-timing-function: var(--tm-ease-out);
  animation-fill-mode: backwards;
  animation-delay: calc(var(--tm-stage, 0) * var(--tm-stagger));
}
.tm-ds [data-tm-stage="0"]  { --tm-stage: 0; }
.tm-ds [data-tm-stage="1"]  { --tm-stage: 1; }
.tm-ds [data-tm-stage="2"]  { --tm-stage: 2; }
.tm-ds [data-tm-stage="3"]  { --tm-stage: 3; }
.tm-ds [data-tm-stage="4"]  { --tm-stage: 4; }
.tm-ds [data-tm-stage="5"]  { --tm-stage: 5; }
.tm-ds [data-tm-stage="6"]  { --tm-stage: 6; }
.tm-ds [data-tm-stage="7"]  { --tm-stage: 7; }
.tm-ds [data-tm-stage="8"]  { --tm-stage: 8; }
.tm-ds [data-tm-stage="9"]  { --tm-stage: 9; }
.tm-ds [data-tm-stage="10"] { --tm-stage: 10; }
.tm-ds [data-tm-stage="11"] { --tm-stage: 11; }

/* Grup kademesi: JS'in yeniden yazdığı çocuklar (TMKpi.render → .kpi-card) attribute
   taşıyamaz; ebeveyn `data-tm-stage-group="N"` taban indeksi verir, çocuk sırası
   (nth-child) eklenir → kart i, (N+i)×50 ms'de girer. SSR iskelet (.kpi-card--ph)
   animasyonsuz: yerini gerçek kart aldığında giriş bir kez oynar. Yeniden-render
   (2026-07-17 flicker vakası) grid.__tmRendered guard'ıyla nav başına 1×. */
.tm-ds [data-tm-stage-group="0"] { --tm-stage: 0; }
.tm-ds [data-tm-stage-group="1"] { --tm-stage: 1; }
.tm-ds [data-tm-stage-group="2"] { --tm-stage: 2; }
.tm-ds [data-tm-stage-group="3"] { --tm-stage: 3; }
.tm-ds [data-tm-stage-group="4"] { --tm-stage: 4; }
.tm-ds [data-tm-stage-group="5"] { --tm-stage: 5; }
.tm-ds [data-tm-stage-group="6"] { --tm-stage: 6; }
.tm-ds [data-tm-stage-group] > :not(.kpi-card--ph):not(.kpi-card):not(.tm-skeleton):not(.skeleton) {
  animation-name: tm-enter;
  animation-duration: var(--tm-dur-enter);
  animation-timing-function: var(--tm-ease-out);
  animation-fill-mode: backwards;
  animation-delay: calc((var(--tm-stage, 0) + var(--tm-i, 0)) * var(--tm-stagger));
}
/* 🔴 KPI KARTLARI GİRİŞ SOLMASI ALMAZ (2026-10-11, ölçüldü — Genel Bakış). Kartlar SSR
   iskeletinin (.kpi-card--ph) YERİNE geçiyor; `backwards` dolgu yeni kartı opaklık 0'da
   tutuyor ve animasyon ancak ana iş parçacığı boşalınca (render'dan ~250 ms sonra)
   başlıyordu → iskelet kayboluyor, satır ~200 ms BOŞ kalıyor, sonra kartlar beliriyordu
   (iskelet → boşluk → kart). İskelet → kart doğrudan geçiş; diğer kartların kademesi aynen
   → aşağıdaki grup kuralının `:not()` listesine `.kpi-card` eklendi. */
.tm-ds [data-tm-stage-group] > :nth-child(2) { --tm-i: 1; }
.tm-ds [data-tm-stage-group] > :nth-child(3) { --tm-i: 2; }
.tm-ds [data-tm-stage-group] > :nth-child(4) { --tm-i: 3; }
.tm-ds [data-tm-stage-group] > :nth-child(5) { --tm-i: 4; }
.tm-ds [data-tm-stage-group] > :nth-child(6) { --tm-i: 5; }
.tm-ds [data-tm-stage-group] > :nth-child(7) { --tm-i: 6; }
.tm-ds [data-tm-stage-group] > :nth-child(8) { --tm-i: 7; }

/* Kayan seçim göstergesi (segment kontrol / sekme pili): gösterge YER
   DEĞİŞTİRMEZ, kayar. Konteyner `data-tm-slide` alır; JS yalnız
   `--tm-slide-x` / `--tm-slide-w` (px) yazar, geri kalanı CSS. Referans:
   7/30/90 gün pili (1,7–2,5 s). Tüketici PR-3'te bağlanır; burada yalnız
   mekanizma. */
.tm-ds [data-tm-slide] { position: relative; isolation: isolate; }
.tm-ds [data-tm-slide]::before {
  content: "";
  position: absolute;
  top: var(--tm-space-1);
  bottom: var(--tm-space-1);
  left: 0;
  width: var(--tm-slide-w, 0px);
  transform: translateX(var(--tm-slide-x, 0px));
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-surface);
  box-shadow: var(--tm-shadow-card);
  /* yalnız transform geçer; `width` layout özelliğidir, animasyonu YASAK (docs/ui-reference-spec.md hareket bütçesi) — JS genişliği anında yazar */
  transition: transform var(--tm-dur-base) var(--tm-ease-out);
  opacity: 0;
  z-index: -1;
  pointer-events: none;
}
.tm-ds [data-tm-slide].is-slide-ready::before { opacity: 1; }

/* =================================================================
   SAYFA GEÇİŞİ — belge-arası View Transition (PR-7, 2026-10-11)
   Ölçüldü (nav-timeline.cjs): her sidebar tıklaması tam yeniden yükleme; ~1,1 s
   boyunca sidebar yok (boş sayfa) + 0,6–0,8 s "Yükleniyor" perdesi. Referans:
   kabuk sabit, içerik blur + solma ~200 ms. Tarayıcı destekliyorsa (Chromium 126+)
   eski sayfanın görüntüsü yeni sayfa çizilene kadar kalır, kök çapraz solar;
   sidebar/üst çubuk aynı adla eşleşir → yerinde durur. Desteklemeyen tarayıcıda
   kural yok sayılır (ilerici iyileştirme). Süre/eğri token'dan.
   NOT: aynı ad bir sayfada İKİ öğede olursa geçiş iptal olur — seçiciler tekil. */
@view-transition { navigation: auto; }
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--tm-dur-base);
  animation-timing-function: var(--tm-ease-out);
}
::view-transition-old(root) { animation-name: tm-vt-out; }
::view-transition-new(root) { animation-name: tm-vt-in; }
@keyframes tm-vt-out { to { opacity: 0; } }
@keyframes tm-vt-in  { from { opacity: 0; } }
#tmv2SidebarRoot { view-transition-name: tm-sidebar; }
#tmv2Header { view-transition-name: tm-topbar; }
::view-transition-group(tm-sidebar),
::view-transition-group(tm-topbar) { animation-duration: 0s; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* =================================================================
   SIRALI AÇILIŞ — yukarıdan aşağı (2026-10-11, kullanıcı: *"yükleme karmaşık,
   sıralı oluşmuyor; üsttekiler önce, alttakiler sonra oluşmalı"*)
   Ölçüldü (filmstrip, Genel Bakış): panellerin içeriği VERİNİN GELİŞ sırasıyla
   doluyordu — en alttaki "Son siparişler" görselleri KPI değerlerinden ÖNCE
   çiziliyordu. Kart kademesi (data-tm-stage) kabuğu sıralıyor ama içerik
   fetch'e bağlı olduğu için sırayı bozuyordu.
   Düzenek: kök `[data-tm-sira-kok]`, panel = `.kpi-card · .ch-panel · .tm-card ·
   [data-tm-sira]`. Sırası gelmemiş panel BÜTÜNÜYLE saklanır (yer kaplar → kayma yok);
   iskeletli panel de — ölçüldü, Canlı'da en alttaki sipariş tablosunun iskeleti
   ortadaki grafiklerden önce görünüyordu. Sırası gelen iskelet görünür, verisi
   gelince yerine geçen kart solmadan açılır.
   Solma panelin KENDİSİNDE, çocuklarında değil: ölçüldü, TMKpi/grafikler çocukları
   sonradan yeniden yazınca çocuk animasyonu baştan başlıyor ve alttaki satır
   üsttekinden önce beliriyordu. Sıra ve hazırlık static/js/ds/tm-sira.js.
   `tm-sira` sınıfını <head> koyar (reduced-motion'da konmaz); sıra bitince
   `tm-sira-bitti` kuralı kapatır (sonraki filtre/yenileme etkilenmez).
   Emniyet: JS hiç çalışmasa da `--tm-sira-kurtar` sonra içerik görünür
   (forwards dolgu → hiçbir öğe opaklık 0'da KALMAZ). */
/* Kök içindeki kart kademesi KALICI kapalı (yalnız sıra bitene kadar değil): `none` →
   `tm-enter` dönüşü animasyonu YENİDEN başlatır — ölçüldü, sıra bitince tüm paneller
   ikinci kez kaybolup solarak geliyordu. */
html.tm-sira [data-tm-sira-kok] [data-tm-stage],
html.tm-sira [data-tm-sira-kok] [data-tm-stage-group] > * {
  animation-name: none;
}
/* İçinde başka panel olan sarmalayıcıyı tm-sira.js ilk boyamadan önce `tm-sira-acik` ile
   işaretler (JS yalnız en içtekileri sıralar). `:has()` ile yapılmadı: ölçüldü, sipariş
   tablosu çizilirken her DOM değişikliği seçiciyi yeniden değerlendirtiyordu. */
html.tm-sira:not(.tm-sira-bitti) [data-tm-sira-kok] :is(.kpi-card, .ch-panel, .tm-card, [data-tm-sira]):not(.tm-sira-acik) {
  opacity: 0;
  animation-name: tm-enter;
  animation-duration: var(--tm-dur-enter);
  animation-timing-function: var(--tm-ease-out);
  animation-delay: var(--tm-sira-kurtar);
  animation-fill-mode: forwards;
}
html.tm-sira [data-tm-sira-kok] .tm-sira-gir {
  animation-name: tm-enter;
  animation-duration: var(--tm-dur-enter);
  animation-timing-function: var(--tm-ease-out);
  animation-delay: var(--tm-sira-d);
  animation-fill-mode: backwards;
}


/* ===== css/ds/ds-stat-card.css ===== */
/* =================================================================
   DS — STAT CARD (Faz 3; layout re-derived from the measured reference,
   docs/ui-reference-spec.md, 2026-09-13). Canonical StatCard = the DOM that
   static/js/tm-kpi.js buildCard() emits (also what macros/ds/tm_components.html
   tm_stat_card renders server-side). Class names are the JS contract
   (docs/ui-audit.md Ek B) and are NEVER renamed here; this file only re-skins
   them inside .tm-ds:

     .kpi-grid > .kpi-card[.accent][.kpi-card--nospark][.kpi-card--ph][.kpi-card--error]
       .kpi-glow (accent only — neutralised: no colour wash)
       .kpi-head  > .kpi-ic > i · .kpi-label · .kpi-head-tools > .kpi-livebadge > .live-dot · .kpi-info
       .kpi-body  > .kpi-value > .unit · .kpi-deltarow > .kpi-delta(.up|.down|.flat) > i · .kpi-delta-txt · .kpi-vs
       .kpi-chart > svg (.kpi-spark-area · .kpi-spark-line · .kpi-dot · .kpi-cross-*)
       .kpi-pop (info popover — floating layer → --tm-shadow-overlay)

   Reference proportions (Apex KPI card, 1280): height ≈150 · text inset 20–24 ·
   label 12/500 muted (sentence case) · number 24/700 · delta 12 coloured text +
   muted "vs" text · sparkline 40px bleeding to the card's bottom edge · a tinted
   icon disc at the top-right. Same inner layout for every card; skeleton
   (.kpi-card--ph) has the exact height of the loaded card (no jump).
   ================================================================= */

.tm-ds .kpi-grid {
  display: grid;
  grid-template-columns: repeat(var(--tm-dash-kpi-cols, 5), minmax(0, 1fr));
  gap: var(--tm-space-4);
  align-items: stretch;
  grid-auto-rows: auto;
  width: 100%;
  min-width: 0;
  margin: 0;              /* legacy tm-kpi.css margin-bottom made the KPI→chart gap 36 instead of the 24 page gap */
}
@media (max-width: 1023.98px) {
  .tm-ds .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tm-ds .kpi-grid > .kpi-card:nth-child(5):last-child { grid-column: span 2; }   /* 2 + 2 + 1 full-width, never a lone half card */
}
@media (max-width: 599.98px) {
  .tm-ds .kpi-grid { grid-template-columns: minmax(0, 1fr); }
  .tm-ds .kpi-grid > .kpi-card:nth-child(5):last-child { grid-column: auto; }
}
.tm-ds .kpi-card.accent { grid-column: auto; }   /* the accent card is one cell like the others at every width */

.tm-ds .kpi-card,
.tm-ds .kpi-card.accent,
.tm-ds .kpi-card.accent.orange,
.tm-ds .kpi-card--ph {
  --kc-tone: var(--tm-accent);
  --kc-ic-bg: var(--tm-accent-bg);
  --kc-ic-fg: var(--tm-accent-fg);
  --kc-fg: var(--tm-text-primary);
  --kc-fg-2: var(--tm-text-secondary);
  --kc-fg-3: var(--tm-text-muted);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0;
  box-sizing: border-box;
  height: var(--tm-dash-kpi-h);        /* fixed: skeleton and loaded card are the same box → no shift on data arrival */
  min-height: var(--tm-dash-kpi-h);
  min-width: 0;
  padding: var(--tm-space-4) var(--tm-space-4) 0;   /* KPI inset 16 (reference 20; nearest token) */
  background: var(--tm-bg-surface);
  background-image: none;
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-card);
  box-shadow: var(--tm-shadow-card);
  color: var(--kc-fg);
  overflow: hidden;
  transition: none;
  transform: none;
}
.tm-ds .kpi-card:hover { box-shadow: var(--tm-shadow-card); border-color: var(--tm-border); transform: none; }
.tm-ds .kpi-card::after,
.tm-ds .kpi-glow { display: none; }             /* no accent bar, no colour wash */

/* Legacy tm-kpi.css pins `#tmOverviewKpiGrid .kpi-card { min-height: 213px }` at ≥1101px (id
   specificity 1,1,0, not !important). Restated with matching id specificity so the DS height
   applies; the legacy rule is a Faz 5 deletion candidate. */
.tm-ds #tmOverviewKpiGrid .kpi-card { min-height: var(--tm-dash-kpi-h); }

/* head: label first, tools, then the icon disc at the far right (DOM order is
   icon · label · tools — reordered visually only) */
.tm-ds .kpi-head {
  display: flex;
  align-items: center;
  gap: var(--tm-space-2);
  min-height: 24px;                   /* = disc height: the value row starts below the disc */
  /* 48 = disc 24 + 4 + info 16 + 4: label neither runs under the "i" button nor the disc (2026-09-24) */
  padding-right: calc(var(--tm-space-6) + var(--tm-space-4) + 2 * var(--tm-space-1));
}
/* Containing block is .kpi-head (tm-kpi.css `.kpi-head { position: relative }`), NOT the card:
   the old `top/right: 16px` (written for the card) dropped the disc 16px into the value row and
   left it overlapping the "i" button (measured 2026-09-24: disc 413..437 × 324..348, info
   409..425 × 312..328 → 12×4 px overlap). Anchored to the head's own top-right. */
.tm-ds .kpi-ic {
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--tm-radius-full);
  background: var(--kc-ic-bg);
  color: var(--kc-ic-fg);
  flex-shrink: 0;
}
.tm-ds .kpi-ic i, .tm-ds .kpi-ic svg { width: 13px; height: 13px; }
.tm-ds .kpi-label {
  order: 1;
  flex: 0 1 auto;
  min-width: 0;
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  font-weight: var(--tm-weight-medium);
  color: var(--kc-fg-2);
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;               /* long labels ("Geçerli Sipariş") wrap to a second line instead of truncating */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* tools (info button, live badge) float left of the disc so the label keeps the whole head width
   (with them in flow, "Geçerli Sipariş" wrapped and the row grew from 154 to 170px) */
.tm-ds .kpi-head-tools { order: 2; position: absolute; top: 50%; transform: translateY(-50%); right: calc(var(--tm-space-6) + var(--tm-space-1)); margin-left: 0; display: flex; align-items: center; gap: var(--tm-space-1); flex-shrink: 0; }   /* vertically centred on the 24px head (= disc centre), 4px left of the disc */
.tm-ds .kpi-livebadge {
  display: inline-flex;
  align-items: center;
  gap: var(--tm-space-1);
  padding: 0 var(--tm-space-2);
  height: 18px;
  border-radius: var(--tm-radius-full);
  background: var(--tm-danger-bg);
  color: var(--tm-danger-fg);
  font-size: var(--tm-type-micro);
  line-height: var(--tm-type-micro-lh);
  font-weight: var(--tm-weight-semibold);
}
.tm-ds .kpi-livebadge .live-dot { width: 6px; height: 6px; border-radius: var(--tm-radius-full); background: currentColor; }
/* the per-card CANLI badge rendered as a lone red dot beside the label; the page header already says the data is live */
.tm-ds .kpi-livebadge { display: none; }
.tm-ds .kpi-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: 0;
  border-radius: var(--tm-radius-full);
  background: transparent;
  color: var(--kc-fg-3);
  cursor: pointer;
  opacity: 0;                      /* shown on card hover / keyboard focus / open — the label keeps its width */
}
.tm-ds .kpi-card:hover .kpi-info, .tm-ds .kpi-info:focus-visible, .tm-ds .kpi-info.open { opacity: 1; }
.tm-ds .kpi-info:hover, .tm-ds .kpi-info.open { background: var(--tm-bg-subtle); color: var(--kc-fg); }
.tm-ds .kpi-info i, .tm-ds .kpi-info svg { width: 12px; height: 12px; }

/* body */
.tm-ds .kpi-body { position: relative; z-index: 1; margin: 0; padding: var(--tm-space-1) 0 0; }
.tm-ds .kpi-value {
  display: flex;
  align-items: baseline;
  gap: var(--tm-space-1);
  font-family: var(--tm-font-sans);
  font-size: var(--tm-type-display-sm);
  line-height: var(--tm-type-display-sm-lh);
  font-weight: var(--tm-weight-bold);
  letter-spacing: var(--tm-tracking-tight);
  font-variant-numeric: tabular-nums;
  color: var(--kc-fg);
  min-width: 0;
  white-space: nowrap;
}
/* polish (2026-09-13, item 4, dark-mode text tiers): `dashboard-dark-mode.css`
   ships `html.dark-mode .kpi-value { color: var(--color-surface-3, #f1f5f9) }`
   at the SAME specificity (0,2,0) as `.tm-ds .kpi-value` above — a tie the
   later-loaded file wins (measured: non-accent KPI numbers rendered #f1f5f9,
   a stray near-white tier distinct from the #f4f4f5 primary every other
   label/value uses). Restated id-scoped (same pattern as the min-height rule
   above) so it wins on specificity regardless of load order. */
.tm-ds #tmOverviewKpiGrid .kpi-value,
.tm-ds #tmLiveKpiGrid .kpi-value { color: var(--kc-fg); }
.tm-ds .kpi-value .unit {
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  font-weight: var(--tm-weight-medium);
  color: var(--kc-fg-3);
}
/* delta on its own line, period text under it (5-up cards are ~140px wide) */
.tm-ds .kpi-deltarow { display: flex; flex-direction: column; align-items: flex-start; gap: 0; flex-wrap: nowrap; margin: 0; padding: var(--tm-space-1) 0 0; min-height: var(--tm-type-caption-lh); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); font-family: var(--tm-font-sans); }
/* delta = coloured text, no pill (reference: "↗ +12.5%  vs last month") */
.tm-ds .kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: var(--tm-space-1);
  height: auto;
  padding: 0;
  border-radius: 0;
  font-family: var(--tm-font-sans);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  font-weight: var(--tm-weight-semibold);
  font-variant-numeric: tabular-nums;
  background: transparent;
  color: var(--kc-fg-2);
}
.tm-ds .kpi-delta i, .tm-ds .kpi-delta svg { width: 12px; height: 12px; }
.tm-ds .kpi-delta.up   { background: transparent; color: var(--tm-success-fg); }
.tm-ds .kpi-delta.down { background: transparent; color: var(--tm-danger-fg); }
.tm-ds .kpi-delta.flat { background: transparent; color: var(--kc-fg-3); }
.tm-ds .kpi-vs { font-family: var(--tm-font-sans); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); color: var(--kc-fg-3); font-weight: var(--tm-weight-regular); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
/* a "flat" delta with no text is an empty span → do not draw it */
.tm-ds .kpi-delta.flat:has(> .kpi-delta-txt:empty) { display: none; }

/* sparkline — fixed box inside the card padding (2026-09-24: the full-bleed version ran into the
   16px rounded corners and got clipped by the card's overflow:hidden); empty box keeps height */
.tm-ds .kpi-chart {
  position: relative;
  left: 0;
  width: 100%;
  height: var(--tm-dash-sparkline-h);
  margin: auto 0 0;                  /* legacy tm-kpi.css `margin: 0 -16px` bleed cancelled too */
  padding: 0;
  overflow: visible;                 /* hover tooltip sits ABOVE the box (translateY(-100%)); `hidden` clipped it entirely (2026-09-24) — the card itself still clips */
}
/* 2026-09-24: uç nokta kartın sağ kenarında yarısı kesik kalıyordu (kullanıcı
   raporu; 3× büyütmede doğrulandı). Kart `overflow:hidden` + 16px yarıçap
   taşıyor, kıvılcım ise kartın iç kutusunu tam dolduruyordu; son veri
   noktasının dairesi kenarın dışına taşınca kırpılıyordu. Yuvaya sağdan
   nokta-yarıçapı kadar pay veriliyor — alan/çizgi görünümü korunur, yalnız son
   nokta içeride kalır. Sol tarafta sorun yok: seri soldan başlıyor. */
.tm-ds .kpi-chart { padding-right: 5px; }
.tm-ds .kpi-chart svg { display: block; width: 100%; height: 100%; }
/* polish (2026-09-13, item 5): buildCard() (tm-kpi.js) appends an EMPTY
   `.kpi-chart` (no svg child, aria-hidden) when `data.points` is missing/[]
   (İade Oranı: server sends `points: []` — no daily series exists to draw,
   docs contract 2026-09-08 forbids inventing one). The box already reserves
   the same height as a real sparkline so the row stays aligned; it was just
   blank (≈93% of the card empty). Quiet caption placeholder explaining why,
   same box, zero CLS (no size change) — decorative only, the real "why" for
   assistive tech stays in .kpi-vs's deltaText, this element's parent already
   carries aria-hidden.
   Cascade trap (found via computed-style probe): legacy tm-kpi.css:541
   `.kpi-card--nospark .kpi-chart { visibility: hidden }` (0,2,0) — written
   for the old "just hide the pointless empty box" behaviour — made the new
   ::after invisible too (visibility inherits into generated content).
   Restated at higher specificity (0,3,0) below; no !important needed. */
.tm-ds .kpi-card--nospark .kpi-chart { visibility: visible; }
.tm-ds .kpi-card--nospark .kpi-chart::after {
  content: "günlük seri yok";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--tm-text-muted);
  font-family: var(--tm-font-sans);
  font-size: var(--tm-type-micro);
  line-height: var(--tm-type-micro-lh);
}
.tm-ds .kpi-spark-line { stroke: var(--kc-tone); }
.tm-ds .kpi-spark-area { fill: var(--kc-tone); opacity: .12; }
.tm-ds .kpi-dot, .tm-ds .kpi-cross-dot { fill: var(--kc-tone); background: var(--kc-tone); width: 8px; height: 8px; border: 2px solid var(--tm-bg-surface); box-shadow: none; }   /* legacy: 3px tinted halo read as a dark ring */
.tm-ds .kpi-cross-line { stroke: var(--tm-border-strong); border-left-color: var(--tm-border-strong); }
.tm-ds .kpi-tip {
  position: absolute;
  padding: var(--tm-space-1) var(--tm-space-2);
  border-radius: var(--tm-radius-control);
  background: var(--tm-text-primary);
  color: var(--tm-bg-surface);
  font-size: var(--tm-type-micro);
  line-height: var(--tm-type-micro-lh);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* ---------- per-metric sparkline tone (token-driven) ----------
   Payload order is fixed by the builder (routes/pages/dashboard_server.py
   kpi_cards_overview): 1 ciro · 2 kâr · 3 kâr marjı · 4 sipariş · 5 iade oranı
   (tests/test_ui_faz4a_overview.py pins it). buildCard() writes the payload
   colour as an SVG attribute; CSS wins over presentation attributes, so the
   tone is restated here from the chart palette (same hues as the payload). */
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(2) { --kc-tone: var(--tm-chart-3); }   /* kâr — green */
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(3) { --kc-tone: var(--tm-chart-1); }   /* marj — blue */
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(4) { --kc-tone: var(--tm-chart-2); }   /* sipariş — orange */
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(5) { --kc-tone: var(--tm-chart-7); }   /* iade — red */
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(2) { --kc-ic-bg: var(--tm-success-bg); --kc-ic-fg: var(--tm-success-fg); }
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(4) { --kc-ic-bg: var(--tm-warning-bg); --kc-ic-fg: var(--tm-warning-fg); }
.tm-ds #tmOverviewKpiGrid > .kpi-card:nth-child(5) { --kc-ic-bg: var(--tm-danger-bg);  --kc-ic-fg: var(--tm-danger-fg); }

/* ---------- accent card (Ciro): the ONE solid accent surface on the page.
   Legacy `.kpi-card.accent .kpi-*` colour rules (tm-kpi.css, gradient + glow)
   outrank `.tm-ds .kpi-*` by specificity → restated at (0,3,0)+ ---------- */
.tm-ds .kpi-card.accent {
  --kc-tone: var(--tm-on-accent);
  --kc-fg: var(--tm-on-accent);
  --kc-fg-2: color-mix(in srgb, var(--tm-on-accent) 82%, transparent);
  --kc-fg-3: color-mix(in srgb, var(--tm-on-accent) 70%, transparent);
  background: var(--tm-accent-solid);
  background-image: none;
  border-color: var(--tm-accent-solid);
  color: var(--kc-fg);
}
.tm-ds .kpi-card.accent:hover { border-color: var(--tm-accent-solid); }
.tm-ds .kpi-card.accent .kpi-ic { background: color-mix(in srgb, var(--tm-on-accent) 18%, transparent); color: var(--tm-on-accent); }
.tm-ds .kpi-card.accent .kpi-label { color: var(--kc-fg-2); }
.tm-ds .kpi-card.accent .kpi-value { color: var(--kc-fg); }
.tm-ds .kpi-card.accent .kpi-value .unit { color: var(--kc-fg-3); }
.tm-ds .kpi-card.accent .kpi-info { color: var(--kc-fg-3); background: transparent; }
.tm-ds .kpi-card.accent .kpi-info:hover, .tm-ds .kpi-card.accent .kpi-info.open { background: color-mix(in srgb, var(--tm-on-accent) 18%, transparent); color: var(--kc-fg); }
.tm-ds .kpi-card.accent .kpi-delta.up,
.tm-ds .kpi-card.accent .kpi-delta.down,
.tm-ds .kpi-card.accent .kpi-delta.flat { background: transparent; color: var(--kc-fg); }
.tm-ds .kpi-card.accent .kpi-vs { color: var(--kc-fg-3); }
.tm-ds .kpi-card.accent .kpi-livebadge { background: color-mix(in srgb, var(--tm-on-accent) 20%, transparent); color: var(--kc-fg); }
.tm-ds .kpi-card.accent .kpi-dot { border-color: var(--tm-accent-solid); box-shadow: none; }
.tm-ds .kpi-card.accent .kpi-cross-line { border-left-color: color-mix(in srgb, var(--tm-on-accent) 60%, transparent); }
.tm-ds .kpi-card.accent .kpi-spark-line { stroke: var(--tm-on-accent); }
.tm-ds .kpi-card.accent .kpi-spark-area { fill: var(--tm-on-accent); opacity: .18; }
.tm-ds .kpi-card.accent .kpi-tip { background: var(--tm-on-accent); color: var(--tm-accent-solid); }

/* info popover — floating layer */
.tm-ds .kpi-pop {
  position: fixed;   /* tm-kpi.js wireInfo() writes viewport coordinates → fixed, not absolute */
  z-index: 5;
  padding: var(--tm-space-3);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-overlay);
  background: var(--tm-bg-surface);
  box-shadow: var(--tm-shadow-overlay);
  color: var(--tm-text-primary);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
}
.tm-ds .kpi-pop-title { font-weight: var(--tm-weight-semibold); }
.tm-ds .kpi-pop-text, .tm-ds .kpi-pop-formula { color: var(--tm-text-secondary); }
.tm-ds .kpi-pop-formula { font-family: var(--tm-font-mono); font-size: var(--tm-type-micro); }

/* live update: a ~600 ms fading highlight behind the value, nothing else moves */
@keyframes tmKpiFlash {
  from { background-color: var(--tm-accent-bg); }
  to   { background-color: transparent; }
}
.tm-ds .kpi-value.flash { animation: tmKpiFlash var(--tm-dur-morph) var(--tm-ease-out) 1; border-radius: var(--tm-radius-control); }
.tm-ds .kpi-card.accent .kpi-value.flash { animation: none; }
.tm-ds .kpi-delta.flash { animation: none; }
.tm-ds .kpi-card.enter { animation: none; opacity: 1; transform: none; }

/* error state — same height, message in the body */
.tm-ds .kpi-card--error { border-color: var(--tm-danger-fg); }
.tm-ds .kpi-card--error .kpi-ic { background: var(--tm-danger-bg); color: var(--tm-danger-fg); }
.tm-ds .kpi-card--error .kpi-error-msg { color: var(--tm-danger-fg); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); padding: var(--tm-space-2) 0 0; }

/* skeleton — identical box; bars where text will be */
.tm-ds .kpi-card--ph { animation: none; }
.tm-ds .kpi-card--ph .kpi-label,
.tm-ds .kpi-card--ph .kpi-value,
.tm-ds .kpi-card--ph .kpi-delta,
.tm-ds .kpi-card--ph .kpi-vs { color: transparent; background: var(--tm-bg-subtle); border-radius: var(--tm-radius-control); }
.tm-ds .kpi-card--ph .kpi-value { width: 60%; height: var(--tm-type-display-lh); }
.tm-ds .kpi-card--ph .kpi-label { width: 40%; height: var(--tm-type-caption-lh); }
.tm-ds .kpi-card--ph .kpi-chart { background: linear-gradient(to top, var(--tm-bg-subtle), transparent); }

@media (min-width: 1440px) {
  .tm-ds .kpi-value { font-size: var(--tm-type-display); line-height: var(--tm-type-display-lh); }
}

/* live page: legacy `.tm-live-page .kpi-card.accent { grid-column: span 2 }` outranked the (0,3,0) rule above */
.tm-ds .kpi-grid > .kpi-card.accent { grid-column: auto; }

/* Artışın KÖTÜ olduğu göstergeler (iade oranı) — 2026-09-24, kullanıcı isteği.
   Ok yönü değişmez (artış = yukarı ok, dürüst), yalnız RENK ters çevrilir:
   artış kırmızı, düşüş yeşil. `.invert` sınıfını tm-kpi.js `deltaInvert`
   alanından basar. */
.tm-ds .kpi-delta.invert.up { color: var(--tm-danger-fg); }
.tm-ds .kpi-delta.invert.down { color: var(--tm-success-fg); }


/* 2026-09-28 (ölçüldü): vurgulu (mavi) KPI kartında `günlük seri yok` yazısı
   okunmuyordu — kart arka planı rgb(1,117,228), yazı `--tm-text-muted`
   rgb(85,97,115) → kontrast ~1,5:1 (WCAG AA için 4,5:1 gerekir).
   Vurgulu kartta beyaza çekiliyor (≈4,9:1). Ölçüm: scripts/qa probe,
   getComputedStyle(.kpi-chart, '::after').color. */
.tm-ds .kpi-card.accent .kpi-chart::after { color: rgba(255, 255, 255, .82); }


/* ===== css/ds/ds-badge.css ===== */
/* =================================================================
   DS — STATUS BADGE (Faz 3). One component, one fixed status→colour map.
   Soft background + strong-ish text; never a solid fill.

     <span class="tm-badge tm-badge--shipped">Kargoda</span>

   Status map (order lifecycle, docs/manuel-sync + orders tab):
     pending    bekleyen        → warning
     picking    hazırlanıyor    → info
     shipped    kargoda         → accent
     delivered  teslim edildi   → success
     cancelled  iptal           → danger
     returned   iade            → muted (neutral)
   Generic tones for non-order contexts: --neutral --success --warning --danger --info --accent
   Legacy modifiers from v2/components/badges.css (--ok --warn --bad --brand) are
   aliased so existing markup inside .tm-ds keeps rendering.
   ================================================================= */

.tm-ds .tm-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--tm-space-1);
  height: 22px;
  padding: 0 var(--tm-space-2);
  border: 1px solid transparent;
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-subtle);
  color: var(--tm-text-secondary);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  font-weight: var(--tm-weight-medium);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  vertical-align: middle;
  box-shadow: none;
}
.tm-ds .tm-badge--sm { height: 18px; font-size: var(--tm-type-micro); line-height: var(--tm-type-micro-lh); }
.tm-ds .tm-badge__dot { width: 6px; height: 6px; border-radius: var(--tm-radius-full); background: currentColor; flex-shrink: 0; }
.tm-ds .tm-badge--outline { background: transparent; border-color: var(--tm-border); }

.tm-ds .tm-badge--neutral,
.tm-ds .tm-badge--returned  { background: var(--tm-bg-subtle);   color: var(--tm-text-secondary); }
.tm-ds .tm-badge--success,
.tm-ds .tm-badge--ok,
.tm-ds .tm-badge--delivered { background: var(--tm-success-bg); color: var(--tm-success-fg); }
.tm-ds .tm-badge--warning,
.tm-ds .tm-badge--warn,
.tm-ds .tm-badge--pending   { background: var(--tm-warning-bg); color: var(--tm-warning-fg); }
.tm-ds .tm-badge--danger,
.tm-ds .tm-badge--bad,
.tm-ds .tm-badge--cancelled { background: var(--tm-danger-bg);  color: var(--tm-danger-fg); }
.tm-ds .tm-badge--info,
.tm-ds .tm-badge--picking   { background: var(--tm-info-bg);    color: var(--tm-info-fg); }
.tm-ds .tm-badge--accent,
.tm-ds .tm-badge--brand,
.tm-ds .tm-badge--shipped   { background: var(--tm-accent-bg);  color: var(--tm-accent-fg); }

/* Count bubble on tab buttons (orders-tab-badge is a JS hook — restyle only) */
.tm-ds .orders-tab-badge,
.tm-ds .tm-badge--count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 var(--tm-space-1);
  border-radius: var(--tm-radius-full);
  background: var(--tm-bg-subtle);
  color: var(--tm-text-secondary);
  font-size: var(--tm-type-micro);
  line-height: var(--tm-type-micro-lh);
  font-weight: var(--tm-weight-semibold);
  font-variant-numeric: tabular-nums;
  box-shadow: none;
}


/* ===== css/ds/ds-table.css ===== */
/* =================================================================
   DS — DATA TABLE (Faz 3). Built on the existing shell class
   .tm-table-shell (tm-page-shell.css) + .tm-table (v2/components/tables.css);
   this file re-skins them inside .tm-ds.

     <div class="tm-table-shell tm-table-shell--sticky">
       <table class="tm-table tm-table--sticky-first [tm-table--dense]">
         <thead><tr><th>…</th><th class="tm-num">Tutar</th></tr></thead>
         <tbody><tr><td>…</td><td class="tm-num">1.234,00</td></tr></tbody>
       </table>
     </div>

   Rules: text left, numbers right (.tm-num, tabular), status/date fixed
   width (.tm-col-status / .tm-col-date), sticky header, sticky first column,
   1px row divider, hover = --tm-bg-subtle only, no zebra, no shadow.
   Density: comfortable --tm-row-h (52px) / .tm-table--dense --tm-row-h-dense (40px).
   ================================================================= */

.tm-ds .tm-table-shell {
  position: relative;
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  -webkit-overflow-scrolling: touch;
}
.tm-ds .tm-table-shell--sticky { overflow-y: auto; max-height: var(--tm-table-max-h, 70vh); }

.tm-ds .tm-table {
  width: 100%;
  min-width: 0;
  border-collapse: separate;
  border-spacing: 0;
  background: transparent;
  color: var(--tm-text-primary);
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
}
.tm-ds .tm-table th,
.tm-ds .tm-table td {
  height: var(--tm-row-h);
  padding: 0 var(--tm-space-3);
  text-align: left;
  vertical-align: middle;
  border: 0;
  border-bottom: 1px solid var(--tm-border);
  background: var(--tm-bg-surface);
  white-space: nowrap;
}
.tm-ds .tm-table--dense th,
.tm-ds .tm-table--dense td { height: var(--tm-row-h-dense); padding: 0 var(--tm-space-2); }
.tm-ds .tm-table--wrap td { white-space: normal; }

.tm-ds .tm-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--tm-bg-subtle);
  color: var(--tm-text-secondary);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  font-weight: var(--tm-weight-medium);
  letter-spacing: var(--tm-tracking-wide);
  text-transform: uppercase;
  border-bottom: 1px solid var(--tm-border-strong);
}
.tm-ds .tm-table tbody tr:hover > td { background: var(--tm-bg-subtle); }
.tm-ds .tm-table tbody tr:last-child > td { border-bottom-color: transparent; }
.tm-ds .tm-table tbody tr.is-selected > td { background: var(--tm-accent-bg); }

/* sticky first column (order number) — reference never scrolls away */
.tm-ds .tm-table--sticky-first th:first-child,
.tm-ds .tm-table--sticky-first td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  border-right: 1px solid var(--tm-border);
}
.tm-ds .tm-table--sticky-first thead th:first-child { z-index: 3; }

/* column roles */
.tm-ds .tm-table .tm-num,
.tm-ds .tm-table th.tm-num,
.tm-ds .tm-table td.tm-num { text-align: right; font-variant-numeric: tabular-nums; }
.tm-ds .tm-table .tm-col-status { width: 120px; min-width: 120px; }
.tm-ds .tm-table .tm-col-date   { width: 136px; min-width: 136px; font-variant-numeric: tabular-nums; }
.tm-ds .tm-table .tm-col-check  { width: 40px; min-width: 40px; text-align: center; padding: 0 var(--tm-space-2); }
.tm-ds .tm-table .tm-col-actions { width: 1%; white-space: nowrap; text-align: right; }
.tm-ds .tm-table td.tm-cell-muted { color: var(--tm-text-muted); }
.tm-ds .tm-table td.tm-cell-strong { font-weight: var(--tm-weight-medium); }

/* row skeleton: same row height as data rows → no jump */
.tm-ds .tm-table tr.tm-row-skeleton > td > span {
  display: block;
  height: var(--tm-type-body-lh);
  width: 70%;
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-subtle);
}
.tm-ds .tm-table tr.tm-row-skeleton > td.tm-num > span { margin-left: auto; width: 50%; }

/* empty / error rows live inside the table body at data-row height ×3 */
.tm-ds .tm-table td.tm-table__state {
  height: calc(var(--tm-row-h) * 3);
  text-align: center;
  white-space: normal;
  color: var(--tm-text-muted);
}

@media (max-width: 767.98px) {
  .tm-ds .tm-table th, .tm-ds .tm-table td { padding: 0 var(--tm-space-2); }
  .tm-ds .tm-table--sticky-first th:first-child,
  .tm-ds .tm-table--sticky-first td:first-child { position: static; border-right: 0; }
}


/* ===== css/ds/ds-toolbar.css ===== */
/* =================================================================
   DS — TOOLBAR · FILTER CHIPS · BULK ACTION BAR (Faz 3)

     <div class="tm-toolbar">
       <div class="tm-toolbar__search">   search input (left)
       <div class="tm-toolbar__filters">  filter controls + active chips (center)
       <div class="tm-toolbar__actions">  primary action (right)
     </div>
     <div class="tm-bulkbar" hidden> … </div>   ← swaps INTO the toolbar's place

   One row on desktop; wraps on narrow screens. No margins; gap only.
   ================================================================= */

.tm-ds .tm-toolbar {
  display: flex;
  align-items: center;
  gap: var(--tm-space-3);
  min-height: 40px;
  min-width: 0;
  flex-wrap: wrap;
}
.tm-ds .tm-toolbar__search  { flex: 0 1 280px; min-width: 200px; }
.tm-ds .tm-toolbar__filters { flex: 1 1 auto; display: flex; align-items: center; gap: var(--tm-space-2); flex-wrap: wrap; min-width: 0; }
.tm-ds .tm-toolbar__actions { flex: 0 0 auto; display: flex; align-items: center; gap: var(--tm-space-2); margin-left: auto; }

.tm-ds .tm-toolbar .tm-input,
.tm-ds .tm-toolbar input[type="search"],
.tm-ds .tm-toolbar input[type="text"],
.tm-ds .tm-toolbar select {
  height: 36px;
  padding: 0 var(--tm-space-3);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-surface);
  color: var(--tm-text-primary);
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  box-shadow: none;
  width: 100%;
}
.tm-ds .tm-toolbar input::placeholder { color: var(--tm-text-muted); }

/* segmented status filter buttons (data-status hooks stay on the buttons) */
.tm-ds .tm-seg { display: inline-flex; gap: 0; border: 1px solid var(--tm-border); border-radius: var(--tm-radius-control); overflow: hidden; }
.tm-ds .tm-seg > button,
.tm-ds .tm-seg > a {
  display: inline-flex;
  align-items: center;
  gap: var(--tm-space-1);
  height: 34px;
  padding: 0 var(--tm-space-3);
  border: 0;
  border-right: 1px solid var(--tm-border);
  background: var(--tm-bg-surface);
  color: var(--tm-text-secondary);
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  font-weight: var(--tm-weight-medium);
  cursor: pointer;
  white-space: nowrap;
}
.tm-ds .tm-seg > :last-child { border-right: 0; }
.tm-ds .tm-seg > .is-active,
.tm-ds .tm-seg > [aria-pressed="true"],
.tm-ds .tm-seg > .orders-tab-active { background: var(--tm-accent-bg); color: var(--tm-accent-fg); }

/* active filter chip — removable */
.tm-ds .tm-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--tm-space-1);
  height: 26px;
  padding: 0 var(--tm-space-1) 0 var(--tm-space-2);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-full);
  background: var(--tm-bg-subtle);
  color: var(--tm-text-primary);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  white-space: nowrap;
}
.tm-ds .tm-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: var(--tm-radius-full);
  background: transparent;
  color: var(--tm-text-muted);
  cursor: pointer;
}
.tm-ds .tm-chip__remove:hover { background: var(--tm-border); color: var(--tm-text-primary); }

/* bulk action bar — replaces the toolbar in place when rows are selected */
.tm-ds .tm-bulkbar {
  display: flex;
  align-items: center;
  gap: var(--tm-space-3);
  min-height: 40px;
  padding: 0 var(--tm-space-3);
  border: 1px solid var(--tm-accent-fg);
  border-radius: var(--tm-radius-control);
  background: var(--tm-accent-bg);
  color: var(--tm-text-primary);
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  box-shadow: none;
}
.tm-ds .tm-bulkbar[hidden] { display: none; }
.tm-ds .tm-bulkbar__meta { font-weight: var(--tm-weight-medium); font-variant-numeric: tabular-nums; }
.tm-ds .tm-bulkbar__actions { display: flex; align-items: center; gap: var(--tm-space-2); margin-left: auto; }

/* density toggle (orders): two states, same tokens */
.tm-ds .tm-density { display: inline-flex; border: 1px solid var(--tm-border); border-radius: var(--tm-radius-control); overflow: hidden; }
.tm-ds .tm-density > button { height: 34px; padding: 0 var(--tm-space-2); border: 0; background: var(--tm-bg-surface); color: var(--tm-text-muted); cursor: pointer; }
.tm-ds .tm-density > button[aria-pressed="true"] { background: var(--tm-bg-subtle); color: var(--tm-text-primary); }

@media (max-width: 767.98px) {
  .tm-ds .tm-toolbar__search { flex-basis: 100%; }
  .tm-ds .tm-toolbar__actions { margin-left: 0; width: 100%; }
  .tm-ds .tm-toolbar__actions > * { flex: 1 1 auto; }
}


/* ===== css/ds/ds-states.css ===== */
/* =================================================================
   DS — STATES (Faz 3): skeleton · empty · error · pagination
   Every data component supports loading / empty / error / filled; the
   loading skeleton has the SAME height as the filled state.

   Empty / error reuse the v2 class family (.tm-empty, __icon/__title/__desc/
   __actions — v2/components/empty-state.css) so existing markup keeps
   working; .tm-error is the danger-toned variant. The empty state says WHY
   it is empty and WHAT to do (an action slot), not just "kayıt yok".
   ================================================================= */

/* ---------- skeleton ---------- */
@keyframes tmSkeletonShimmer {
  from { background-position: -200px 0; }
  to   { background-position: calc(200px + 100%) 0; }
}
.tm-ds .tm-skeleton,
.tm-ds .skeleton {
  display: block;
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-subtle);
  background-image: linear-gradient(90deg, var(--tm-bg-subtle) 0, var(--tm-border) 50%, var(--tm-bg-subtle) 100%);
  background-size: 200px 100%;
  background-repeat: no-repeat;
  animation: tmSkeletonShimmer 1.2s var(--tm-ease) infinite;
  color: transparent;
  box-shadow: none;
}
.tm-ds .tm-skeleton--text { height: var(--tm-type-body-lh); width: 60%; }
.tm-ds .tm-skeleton--title { height: var(--tm-type-title-lh); width: 40%; }
.tm-ds .tm-skeleton--block { height: 100%; min-height: 120px; width: 100%; }
.tm-ds .tm-skeleton--circle { border-radius: var(--tm-radius-full); }
.tm-ds .tm-skeleton-stack { display: flex; flex-direction: column; gap: var(--tm-space-2); }
/* reduced-motion: ds-primitives.css `.tm-ds *` kuralı süreyi 0.001ms'e indirir — ayrı blok yok (PR-1, 2026-10-10). */

/* ---------- empty / error ---------- */
/* 🔴 GİRİŞ ANİMASYONU (kullanıcı: "animasyon yok" — canlı kusur raporu,
   2026-09-17). GLOBAL — bu dosya zaten TÜM boş/hata durumlarını kapsıyor
   ("Every data component supports loading / empty / error / filled"),
   ikinci bir sayfa-özel kopya YAZILMADI. Yumuşak, tek-seferlik; sonsuz
   döngü YOK (boş durum bir bildirimdir, sürekli hareket eden bir şey değil
   — `tm-live-empty`in "no decorative motion" ilkesiyle aynı çizgide). */
@keyframes tmEmptyStateIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.tm-ds .tm-empty,
.tm-ds .tm-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tm-space-2);
  min-height: var(--tm-state-min-h, 160px);
  padding: var(--tm-space-4);
  text-align: center;
  color: var(--tm-text-secondary);
  background: transparent;
  border: 0;
  box-shadow: none;
  animation: tmEmptyStateIn var(--tm-dur-base) var(--tm-ease-out) backwards;
}
.tm-ds .tm-empty--compact, .tm-ds .tm-error--compact { min-height: 96px; padding: var(--tm-space-2); }
.tm-ds .tm-empty__icon,
.tm-ds .tm-error__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--tm-radius-full);
  background: var(--tm-bg-subtle);
  color: var(--tm-text-muted);
}
.tm-ds .tm-error__icon { background: var(--tm-danger-bg); color: var(--tm-danger-fg); }
.tm-ds .tm-empty__icon svg, .tm-ds .tm-error__icon svg, .tm-ds .tm-empty__icon i, .tm-ds .tm-error__icon i { width: 18px; height: 18px; }
.tm-ds .tm-empty__title,
.tm-ds .tm-error__title {
  margin: 0;
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  font-weight: var(--tm-weight-semibold);
  color: var(--tm-text-primary);
}
.tm-ds .tm-empty__desc,
.tm-ds .tm-error__desc {
  margin: 0;
  max-width: 44ch;
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  color: var(--tm-text-secondary);
}
.tm-ds .tm-empty__actions,
.tm-ds .tm-error__actions { display: flex; gap: var(--tm-space-2); flex-wrap: wrap; justify-content: center; }

/* ---------- pagination (fixed under the table) ---------- */
.tm-ds .tm-pagination {
  display: flex;
  align-items: center;
  gap: var(--tm-space-3);
  min-height: 40px;
  flex-wrap: wrap;
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  color: var(--tm-text-secondary);
}
.tm-ds .tm-pagination__total { font-variant-numeric: tabular-nums; }
.tm-ds .tm-pagination__size { display: inline-flex; align-items: center; gap: var(--tm-space-2); }
.tm-ds .tm-pagination__size select {
  height: 32px;
  padding: 0 var(--tm-space-2);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-surface);
  color: var(--tm-text-primary);
}
.tm-ds .tm-pagination__pages { display: inline-flex; align-items: center; gap: var(--tm-space-1); margin-left: auto; }
.tm-ds .tm-pagination__pages > button,
.tm-ds .tm-pagination__pages > a,
.tm-ds .tm-pagination .page-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--tm-space-2);
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-surface);
  color: var(--tm-text-primary);
  font-variant-numeric: tabular-nums;
  box-shadow: none;
  cursor: pointer;
}
.tm-ds .tm-pagination__pages > .is-active,
.tm-ds .tm-pagination__pages > [aria-current="page"],
.tm-ds .tm-pagination .page-item.active .page-link { background: var(--tm-accent-bg); color: var(--tm-accent-fg); border-color: var(--tm-accent-fg); }
.tm-ds .tm-pagination__pages > [disabled],
.tm-ds .tm-pagination .page-item.disabled .page-link { opacity: .5; cursor: default; }

/* chart empty state (renderer markup .tm-live-empty, styled by v2/components/empty-state.css with an entrance
   animation and an infinite float): no decorative motion in DS scope — the empty state is a static message. */
.tm-ds .tm-live-empty, .tm-ds .tm-live-empty svg { animation: none; }



/* ===== css/ds/ds-chart-card.css ===== */
/* =================================================================
   DS — CHART CARD (Faz 3). Canonical wrapper = macros/tm_chart_panel.html
   (.ch-panel > .ch-head > .ch-head-l > .ch-h3 / .ch-sub · .ch-actions ·
   .ch-body > .ch-mount · .ch-state--{loading|empty|error}). Those class
   names are the tm-charts.js / bridge contract; this file re-skins them
   inside .tm-ds to be exactly a .tm-card with a .tm-card__header.
   The chart library's own title/legend is not used for the header.
   ================================================================= */

.tm-ds .ch-grid,
.tm-ds .tm-dashboard-ch-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--tm-space-6) var(--tm-space-4);   /* reference: rows 24 · columns 16 */
  align-items: stretch;
  width: 100%;
  min-width: 0;
}
.tm-ds .ch-panel.span-3  { grid-column: span 3; }
.tm-ds .ch-panel.span-4  { grid-column: span 4; }
.tm-ds .ch-panel.span-6  { grid-column: span 6; }
.tm-ds .ch-panel.span-8  { grid-column: span 8; }
.tm-ds .ch-panel.span-12 { grid-column: span 12; }
@media (max-width: 1023.98px) {
  .tm-ds .ch-panel.span-6, .tm-ds .ch-panel.span-8 { grid-column: span 12; }
  .tm-ds .ch-panel.span-3, .tm-ds .ch-panel.span-4 { grid-column: span 6; }
}
@media (max-width: 767.98px) {
  .tm-ds .ch-grid > .ch-panel, .tm-ds .tm-dashboard-ch-grid > .ch-panel { grid-column: span 12; }
  .tm-ds .ch-grid, .tm-ds .tm-dashboard-ch-grid { gap: var(--tm-space-4); }   /* phones: one 16px rhythm, same as the page gap (strict lint: 16 vs 24 mixed) */
}

.tm-ds .ch-panel {
  display: flex;
  flex-direction: column;
  gap: var(--tm-space-3);
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
  padding: var(--tm-space-6);
  background: var(--tm-bg-surface);
  background-image: none;
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-card);
  box-shadow: var(--tm-shadow-card);
  color: var(--tm-text-primary);
  transition: none;
}
.tm-ds .ch-panel:hover { box-shadow: var(--tm-shadow-card); transform: none; }

.tm-ds .ch-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tm-space-3);
  min-height: 24px;
  padding: 0 0 var(--tm-space-2);
  border: 0;
}
.tm-ds .ch-head-l { display: flex; flex-direction: column; gap: var(--tm-space-1); min-width: 0; }
.tm-ds .ch-h3 {
  display: flex;
  align-items: center;
  gap: var(--tm-space-2);
  margin: 0;
  font-size: var(--tm-type-heading);
  line-height: var(--tm-type-heading-lh);
  font-weight: var(--tm-weight-semibold);
  color: var(--tm-text-primary);
}
.tm-ds .ch-h3__icon { display: inline-flex; color: var(--tm-text-muted); }
.tm-ds .ch-h3__icon svg, .tm-ds .ch-h3__icon i { width: 14px; height: 14px; }
.tm-ds .ch-sub { margin: 0; font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); color: var(--tm-text-muted); }
/* Grafik anatomisi (PR-4, 2026-10-10 — referans): başlık · neyi ölçer · BÜYÜK SAYI + delta + referans dönemi · grafik.
   Sayı tabular; delta semantik renk; "önceki N gün: X" soluk. */
.tm-ds .ch-kpi { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--tm-space-2); min-width: 0; }
.tm-ds .ch-kpi[hidden] { display: none; }
.tm-ds .ch-kpi__v { font-size: var(--tm-type-display); line-height: var(--tm-type-display-lh); font-weight: var(--tm-weight-bold); font-variant-numeric: tabular-nums; color: var(--tm-text-primary); }
.tm-ds .ch-kpi__d { font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); font-weight: var(--tm-weight-semibold); font-variant-numeric: tabular-nums; }
.tm-ds .ch-kpi__d.up { color: var(--tm-success-fg); }
.tm-ds .ch-kpi__d.down { color: var(--tm-danger-fg); }
.tm-ds .ch-kpi__d.flat { color: var(--tm-text-muted); }
.tm-ds .ch-kpi__vs { font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); color: var(--tm-text-muted); }
/* eksen: gövde fontu, mikro boy, soluk; grid hairline (referans: 3–4 tik, neredeyse görünmez çizgi) */
.tm-ds .ch-axis-ylabel, .tm-ds .ch-bar-xlabel { font-family: var(--tm-font-sans); font-size: var(--tm-type-micro); fill: var(--tm-chart-axis); }
.tm-ds .ch-grid-line { stroke: var(--tm-chart-grid); }
/* çubuk hover: üstüne gelinen çubuk mürekkep rengine döner (referans), opaklık oyunu yok */
.tm-ds .ch-bar-col:hover .ch-bar { fill: var(--tm-text-primary); opacity: 1; }
.tm-ds .ch-actions, .tm-ds .ch-tools { display: flex; align-items: center; gap: var(--tm-space-2); flex-shrink: 0; }
.tm-ds .ch-live-badge {
  display: inline-flex; align-items: center; gap: var(--tm-space-1);
  height: 18px; padding: 0 var(--tm-space-2);
  border-radius: var(--tm-radius-full);
  background: var(--tm-danger-bg); color: var(--tm-danger-fg);
  font-size: var(--tm-type-micro); line-height: var(--tm-type-micro-lh); font-weight: var(--tm-weight-semibold);
}
.tm-ds .ch-live-badge__dot { width: 6px; height: 6px; border-radius: var(--tm-radius-full); background: currentColor; }

.tm-ds .ch-body { position: relative; flex: 1 1 auto; min-height: 0; min-width: 0; padding: 0; }
.tm-ds .ch-mount { position: relative; width: 100%; min-height: var(--tm-ch-mount-h, 240px); }
.tm-ds .ch-mount svg { display: block; width: 100%; }

/* chart chrome: axis text and legends use tokens, not library defaults */
.tm-ds .ch-panel text { fill: var(--tm-chart-axis); font-family: var(--tm-font-sans); font-size: var(--tm-type-micro); }
.tm-ds .ch-legend { display: flex; flex-wrap: wrap; gap: var(--tm-space-2) var(--tm-space-3); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); color: var(--tm-text-secondary); }
.tm-ds .ch-legend .dot { width: 8px; height: 8px; border-radius: var(--tm-radius-full); }
.tm-ds .ch-rank-track { height: 6px; border-radius: var(--tm-radius-full); background: var(--tm-chart-track); overflow: hidden; }
.tm-ds .ch-rank-fill { height: 100%; border-radius: var(--tm-radius-full); }
.tm-ds .ch-zoom-chip { border: 1px solid var(--tm-border); border-radius: var(--tm-radius-full); background: var(--tm-bg-surface); color: var(--tm-text-secondary); font-size: var(--tm-type-micro); padding: 0 var(--tm-space-2); height: 20px; }
.tm-ds .ch-zoom-chip.is-active, .tm-ds .ch-zoom-chip[aria-pressed="true"] { background: var(--tm-accent-bg); color: var(--tm-accent-fg); border-color: var(--tm-accent-fg); }

/* states share the mount height → no jump between loading / empty / error / ready */
.tm-ds .ch-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--tm-space-2);
  min-height: var(--tm-ch-mount-h, 240px);
  text-align: center;
  color: var(--tm-text-secondary);
  font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh);
}
.tm-ds .ch-state__icon { display: inline-flex; width: 36px; height: 36px; align-items: center; justify-content: center; border-radius: var(--tm-radius-full); background: var(--tm-bg-subtle); color: var(--tm-text-muted); }
.tm-ds .ch-state--error .ch-state__icon { background: var(--tm-danger-bg); color: var(--tm-danger-fg); }
.tm-ds .ch-state__title { margin: 0; font-weight: var(--tm-weight-semibold); color: var(--tm-text-primary); }
.tm-ds .ch-state__sub { margin: 0; color: var(--tm-text-muted); }
.tm-ds .ch-state--loading { background: var(--tm-bg-subtle); border-radius: var(--tm-radius-control); }

/* Chart.js external HTML tooltip (tm-chart-tooltip.css) — floating layer */
.tm-ds .tm-chart-tooltip {
  border: 1px solid var(--tm-border);
  border-radius: var(--tm-radius-control);
  background: var(--tm-bg-surface);
  color: var(--tm-text-primary);
  box-shadow: var(--tm-shadow-overlay);
  font-size: var(--tm-type-caption);
  line-height: var(--tm-type-caption-lh);
  padding: var(--tm-space-2) var(--tm-space-3);
}

/* Week chart SVG fills its mount (was `#overviewTab #tmOvWeek.ch-mount svg.ch-svg { height:100% !important }`
   in tm-vanilla-dashboard-charts.css, now excluded from DS scope). */
.tm-ds .ch-mount svg.ch-svg { display: block; width: 100%; height: 100%; overflow: hidden; }

/* donut = ring + legend; the legend wraps under the ring when the card is narrow
   (legacy tm-charts.css laid them out for a wider mount and let the legend overflow) */
/* donut row (Genel Bakış: two span-6 cards): ring left, legend beside it in one column */
.tm-ds .ch-donut-wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: var(--tm-space-4) var(--tm-space-8); min-width: 0; width: 100%; }
/* wide card (Genel Bakış span-6): ring + legend side by side; narrow card (Canlı span-4): legend wraps under the ring
   with its labels intact — nowrap squeezed it to dots only */
.tm-ds .ch-donut { flex: 0 0 auto; max-width: 100%; }
.tm-ds .ch-legend { flex: 1 1 160px; min-width: 0; max-width: 320px; display: flex; flex-direction: column; gap: var(--tm-space-2); margin: 0; padding: 0; list-style: none; }
.tm-ds .ch-legend.row { flex-direction: row; flex-wrap: wrap; gap: var(--tm-space-2) var(--tm-space-4); }
.tm-ds .ch-legend li { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto auto; gap: var(--tm-space-3); align-items: center; min-width: 0; }
.tm-ds .ch-legend .nm { font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); color: var(--tm-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tm-ds .ch-legend .vl, .tm-ds .ch-legend .sh { font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); font-variant-numeric: tabular-nums; }
.tm-ds .ch-legend .vl { color: var(--tm-text-primary); font-weight: var(--tm-weight-semibold); }
.tm-ds .ch-legend .sh { color: var(--tm-text-muted); }
.tm-ds .ch-panel text.ch-donut-center-v { fill: var(--tm-text-primary); font-family: var(--tm-font-sans); font-size: var(--tm-type-display); font-weight: var(--tm-weight-bold); }
.tm-ds .ch-panel text.ch-donut-center-l { fill: var(--tm-text-muted); font-family: var(--tm-font-sans); font-size: var(--tm-type-caption); }
/* the ring keeps the size the renderer gave it (the generic `.ch-mount svg { width:100% }` must not stretch it) */
.tm-ds .ch-mount svg.ch-donut { width: auto; height: auto; max-width: 100%; flex: 0 0 auto; }
.tm-ds .ch-donut-wrap .ch-legend { flex: 1 1 auto; width: auto; max-width: none; min-width: 0; }
/* donut mounts size to the ring (reference donut card ≈250px); the generic 240px mount floor is for time series */
.tm-ds #tmOvPlatform.ch-mount, .tm-ds #tmOvStatus.ch-mount, .tm-ds .ch-mount:has(> .ch-donut-wrap) { min-height: 0; }   /* any donut mount, any page (Canlı's donut card was 97px taller than its ring) */
/* zoom chip sits in the empty top-right corner of the plot, not on the x-axis labels */
.tm-ds .ch-zoom-chip { position: absolute; top: auto; right: 0; bottom: var(--tm-space-6); }
/* legend text in the body face (legacy set the value/share cells in mono, which ate the name column: "Trend…") */
.tm-ds .ch-legend .nm, .tm-ds .ch-legend .vl, .tm-ds .ch-legend .sh { font-family: var(--tm-font-sans); }
/* legacy section margin between the KPI grid and the chart grid (24 page gap + 12 = 36): the page gap is the only spacing */
.tm-ds .ch-grid, .tm-ds .tm-dashboard-ch-grid { margin: 0; }
/* rankings (En Çok Satanlar): one tone — the renderer reads --tm-chart-N from its mount, so the palette is collapsed there;
   the two donuts keep the full palette (legend colours carry meaning) */
.tm-ds #tmOvTop, .tm-ds #tmOvTopQty { --tm-chart-2: var(--tm-chart-1); --tm-chart-3: var(--tm-chart-1); --tm-chart-4: var(--tm-chart-1); --tm-chart-5: var(--tm-chart-1); --tm-chart-6: var(--tm-chart-1); --tm-chart-7: var(--tm-chart-1); }
/* segmented control (renderer markup .ch-seg > button) — token type, no 11.5px */
.tm-ds .ch-seg { display: inline-flex; gap: var(--tm-space-1); padding: var(--tm-space-1); border-radius: var(--tm-radius-control); background: var(--tm-bg-subtle); }
.tm-ds .ch-seg button { border: 0; background: transparent; padding: 0 var(--tm-space-2); height: 24px; border-radius: var(--tm-radius-control); font-family: var(--tm-font-sans); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); font-weight: var(--tm-weight-medium); color: var(--tm-text-secondary); box-shadow: none; transition: color var(--tm-dur-base) var(--tm-ease); }
.tm-ds .ch-seg button.on { background: var(--tm-bg-surface); color: var(--tm-text-primary); box-shadow: none; }
/* Kayan gösterge (PR-3, ds-motion.css [data-tm-slide]): pil ::before ile kayar; hazır olunca .on'un kendi zemini kalkar */
.tm-ds .ch-seg[data-tm-slide] button { position: relative; z-index: 1; }
.tm-ds .ch-seg[data-tm-slide].is-slide-ready button.on { background: transparent; }

/* rankings: token type + colours (legacy tm-charts.css literals) */
.tm-ds .ch-rank-row { border-bottom: 1px solid var(--tm-border); }
.tm-ds .ch-rank-row:last-child { border-bottom: 0; }
.tm-ds .ch-rank-nm { color: var(--tm-text-primary); font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); }
.tm-ds .ch-rank-meta, .tm-ds .ch-rank-no { color: var(--tm-text-muted); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); }
.tm-ds .ch-rank-vl { color: var(--tm-text-primary); font-size: var(--tm-type-body); font-variant-numeric: tabular-nums; font-family: var(--tm-font-sans); }
.tm-ds .ch-rank-thumb { border-radius: var(--tm-radius-control); border: 1px solid var(--tm-border); }
@media (max-width: 767.98px) {
  /* narrow cards: the product name wraps to two lines instead of "Suni Deri Kadın…" */
  .tm-ds .ch-rank-nm { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
}

@media (max-width: 767.98px) {
  /* chart header: title line first, segment controls on their own full-width row */
  .tm-ds .ch-head { flex-wrap: wrap; }
  .tm-ds .ch-actions, .tm-ds .ch-tools { width: 100%; flex-wrap: wrap; }
  .tm-ds .ch-head-l { flex: 1 1 100%; }
}

/* ---------- chart chrome, reference-derived (Apex area chart): dashed hairline grid, one 2px line, soft fill,
   no end-pulse ornament. tm-charts.js writes these as SVG attributes; CSS wins over presentation attributes. ---------- */
.tm-ds .ch-svg .ch-grid-line { stroke: var(--tm-chart-grid); stroke-width: 1; stroke-dasharray: 3 4; }
.tm-ds .ch-svg .ch-grid-line.zero { stroke-dasharray: none; }
.tm-ds .ch-svg .ch-area-line { stroke-width: 2; }
.tm-ds .ch-svg .ch-area-line.is-dashed { stroke-width: 1.5; }
.tm-ds .ch-svg .ch-area-fill { opacity: 0.55; }
.tm-ds .ch-svg .ch-live-end-pulse { display: none; }
.tm-ds .ch-svg .ch-live-end-dot { stroke: var(--tm-bg-surface); }

/* Aylık karşılaştırma kartı — 2026-09-24, kullanıcı isteği.
   Grafiğin altında aynı dönem toplamları: bu ay, geçen ay, yüzde fark. */
.tm-ds .ch-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--tm-space-3);
  padding: var(--tm-space-3) var(--tm-space-4) 0;
  font-size: 12px; color: var(--tm-text-muted);
}
.tm-ds .ch-foot-item b { color: var(--tm-text); font-weight: 600; }
.tm-ds .ch-foot-item--prev b { color: var(--tm-text-muted); }
.tm-ds .ch-foot-fark { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.tm-ds .ch-foot-fark.up { color: var(--tm-success-fg); }
.tm-ds .ch-foot-fark.down { color: var(--tm-danger-fg); }
.tm-ds .ch-foot-fark.flat { color: var(--tm-text-muted); }

/* Kesikli karşılaştırma çizgisi ve ipucundaki göstergesi. */
.tm-ds .ch-tip-dot--dash { border-radius: 1px; height: 3px; }
.tm-ds .ch-tip-row--fark { border-top: 1px solid var(--tm-border); margin-top: 4px; padding-top: 4px; }


/* ===== css/ds/ds-utilities.css ===== */
/* =================================================================
   DS — UTILITIES (Faz 3). Tiny, token-only. No margin utilities on purpose
   (sibling spacing is the parent's gap).
   ================================================================= */

.tm-ds .tm-num        { font-variant-numeric: tabular-nums; }
.tm-ds .tm-right      { text-align: right; }
.tm-ds .tm-center     { text-align: center; }
.tm-ds .tm-truncate   { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.tm-ds .tm-text-muted { color: var(--tm-text-muted); }
.tm-ds .tm-text-secondary { color: var(--tm-text-secondary); }
.tm-ds .tm-text-caption { font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); }
.tm-ds .tm-text-body-sm { font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); }
.tm-ds .tm-text-title { font-size: var(--tm-type-title); line-height: var(--tm-type-title-lh); font-weight: var(--tm-weight-semibold); }

/* Yeşilin ÜÇ TONU — büyüklük hâlâ boyut/ağırlıkla anlatılıyor (`gapTone`,
   `bb3-render.js`), renk onu KAYBETMEZ, TAŞIR (koordinatör kararı,
   2026-09-17: kullanıcı 990 TL/4,83 TL ayrımını beğendi, tek düz renge
   indirmek o hiyerarşiyi siler). `color-mix` zaten dark-mode-aware iki
   token'ı karıştırıyor (ds-stat-card.css'teki `color-mix` deseniyle AYNI) —
   yeni ham hex YOK, koyu modda ayrı bakım gerekmiyor. */
.tm-ds .tm-text-success      { color: var(--tm-success-fg); }
.tm-ds .tm-text-success-mid  { color: color-mix(in srgb, var(--tm-success-fg) 75%, var(--tm-text-secondary) 25%); }
.tm-ds .tm-text-success-soft { color: color-mix(in srgb, var(--tm-success-fg) 45%, var(--tm-text-muted) 55%); }
/* 🔴 2026-09-17 (kullanıcı: "daha okunaklı yazı renkleri"). Sihirbazın 3.
   adımındaki üç sayı aynı siyahtı; hangisinin HEDEF, hangisinin SINIR,
   hangisinin KAZANÇ olduğu yalnız etiketten okunuyordu.
   ⚠️ Bu ikisi DS'te YOKTU — şablonda `tm-text-accent` / `tm-text-warning`
   yazıp bırakmak İKİ ÖLÜ SINIF demek olurdu (sessiz no-op). Ölçülüp eklendi.
   ⛔ Renk etiketin YERİNE geçmez, ÜSTÜNE gelir: renk körü kullanıcı için
   "Hedef fiyat / Kâr tabanı / Tahmini kâr" metinleri yerinde duruyor. */
.tm-ds .tm-text-accent  { color: var(--tm-accent-fg); }
.tm-ds .tm-text-warning { color: var(--tm-warning-fg); }

/* Kanıt rozetine EK canlılık noktası — metni KISALTMAZ, rengi metnin YERİNE
   koymaz (koordinatör + bbx-design kararı, 2026-09-17: renk erişilebilir
   değil + `title` dokunmatikte yok, bilgi metinden renge devredilemez).
   `.tm-badge__dot` zaten `currentColor` kullanıyor (ds-badge.css) — uyarı
   tonunda badge'in KENDİ rengini (amber) miras alır, ek sınıfa gerek yok;
   nötr/kanıtlı tonda gri kalırdı, bu sınıf ona vurgu verir. Arka plan
   DEĞİŞMEDİ → `test_evidence_badges_share_one_format`in "ikisi de AYNI
   arka plan" kilidine dokunmuyor. */
.tm-ds .tm-badge__dot--accent { color: var(--tm-accent-fg); }
/* Doubled class = specificity (0,3,0), deliberately. A utility must beat the component it is
   put on; at (0,2,0) `.tm-ds .tm-hidden` TIED with `.tm-ds .tm-btn { display: inline-flex }`
   40 lines below and LOST on source order — `class="tm-btn tm-hidden"` stayed visible.
   Hard to spot because it works on anything that does not set `display` itself. Same trap
   for the native `[hidden]` attribute (0,1,1). Moving the rule to the end of this file would
   only fix same-file collisions; ds-live/-shell/-catalog load AFTER it. `!important` is the
   usual answer (Bootstrap does it) but the DS guard forbids it outside the dark-mode shim,
   and specificity gets us there without it. */
.tm-ds .tm-hidden.tm-hidden,
.tm-ds [hidden][hidden]     { display: none; }
.tm-ds .tm-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* status dot (the only other use of radius-full besides avatars) */
.tm-ds .tm-dot { display: inline-block; width: 8px; height: 8px; border-radius: var(--tm-radius-full); background: currentColor; }
.tm-ds .tm-dot--success { color: var(--tm-success-fg); }
.tm-ds .tm-dot--warning { color: var(--tm-warning-fg); }
.tm-ds .tm-dot--danger  { color: var(--tm-danger-fg); }
.tm-ds .tm-dot--muted   { color: var(--tm-text-muted); }

/* avatar / product thumb */
.tm-ds .tm-avatar { width: 32px; height: 32px; border-radius: var(--tm-radius-full); object-fit: cover; background: var(--tm-bg-subtle); flex-shrink: 0; }
.tm-ds .tm-thumb  { width: 36px; height: 36px; border-radius: var(--tm-radius-control); object-fit: cover; background: var(--tm-bg-subtle); border: 1px solid var(--tm-border); flex-shrink: 0; }
/* 🔴 --lg: LİSTE bağlamı için. Kullanıcı (2026-09-17): "listedeki ürünlerin
   görselleri çok küçük". 36px yoğun/ikincil yerler için doğru (kural
   yöneticisi satırı), ama ürünün TANINMASI gereken fırsat listesinde değil —
   36px'te bir ürün görselinden ne olduğu anlaşılmıyor.
   ⚠️ `.tm-thumb` DEĞİŞTİRİLMEDİ, modifier eklendi: ölçüldü, `tm-thumb` yalnız
   buybox v3'te (3 dosya) kullanılıyor, yani tabanı büyütmek bugün güvenli
   OLURDU — ama utility'nin anlamı ("küçük küçük resim") korunarak bağlama
   göre seçim yapmak, bir sonraki sayfanın 64px'i miras almasından iyi. */
.tm-ds .tm-thumb--lg { width: 64px; height: 64px; }

/* buttons inside DS scope: flat, bordered, no shadow (Bootstrap .btn stays functional) */
.tm-ds .tm-btn,
.tm-ds .btn {
  box-shadow: none;
  border-radius: var(--tm-radius-control);
  font-size: var(--tm-type-body);
  line-height: var(--tm-type-body-lh);
  font-weight: var(--tm-weight-medium);
}
.tm-ds .tm-btn { display: inline-flex; align-items: center; gap: var(--tm-space-1); height: 36px; padding: 0 var(--tm-space-3); border: 1px solid var(--tm-border); background: var(--tm-bg-surface); color: var(--tm-text-primary); cursor: pointer; }
.tm-ds .tm-btn:not(:disabled):not([aria-disabled="true"]):hover { background: var(--tm-bg-subtle); }
.tm-ds .tm-btn--primary { background: var(--tm-accent); border-color: var(--tm-accent); color: var(--tm-text-on-brand); }
.tm-ds .tm-btn--primary:not(:disabled):not([aria-disabled="true"]):hover { background: var(--tm-accent-fg); border-color: var(--tm-accent-fg); }
/* Disabled state — MISSING from the DS layer until 2026-09-16. Measured on a REAL page (full
   base.html chain, 59 stylesheets, #tmv2App + .tm-ds), not in isolation — the two differ and the
   difference is the whole point. Legacy tm-ui-system.css:132 (`.tm-btn:disabled{opacity:.55;
   cursor:not-allowed}`) loads on every base.html page via base_head_v2_links.html:19, so the
   DIMMING already reached the button. What did not reach it: `.tm-ds .tm-btn{cursor:pointer}`
   (0,2,0) outranked that rule's cursor, and nothing suppressed hover. Develop, buybox v4
   #bb3CampaignCta (`tm-btn tm-btn--primary`, disabled by bb3-state.js:220 when no row is
   pickable): bg rgb(1,117,228) · opacity .55 · cursor POINTER · hover still lit to rgb(1,99,194).
   The button was dimmed yet still INVITED the pointer and ANSWERED the hover — so the dimming read
   as styling, not as "dead". The only real explanation was the `title` tooltip: absent on touch,
   unreachable by a screen reader.
   ⚠ In the DS layer alone (tokens.css + ds.css, as tests/e2e/ds-button-disabled-2026-09-16.cjs
   loads it) the same button computes opacity 1. That is a fixture artifact — do not cite it as
   what a user saw.
   Hover is EXCLUDED (`:not(:disabled)`) rather than overridden per variant, so ghost/primary/sm
   stay correct without counter-rules. `pointer-events: none` is deliberately NOT used: it would
   suppress the very tooltip that explains WHY the button is dead, and hiding the reason is worse. */
.tm-ds .tm-btn:disabled,
.tm-ds .tm-btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.tm-ds .tm-btn--sm { height: 30px; padding: 0 var(--tm-space-2); font-size: var(--tm-type-caption); }
.tm-ds .tm-btn--ghost { border-color: transparent; background: transparent; }

/* Canlı Performansım promo band (includes/dashboard/dashboard_tabbed_live_promo_banner.html, inline <style>):
   inside DS scope it is a plain surface row with token text — no tinted wash, readable in dark. Dismissible +
   remembered by its own script (localStorage). */
.tm-ds .tm-live-promo { background: var(--tm-bg-surface); border: 1px solid var(--tm-border); border-radius: var(--tm-radius-card); box-shadow: var(--tm-shadow-card); animation: none; }
.tm-ds .tm-live-promo__inner, .tm-ds .tm-live-promo__text { color: var(--tm-text-secondary); }   /* dark-mode.css forces `span { color: inherit }` → the parent must carry the token */
.tm-ds .tm-live-promo__text strong { color: var(--tm-text-primary); font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); }
.tm-ds .tm-live-promo__sub { color: var(--tm-text-secondary); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); }
.tm-ds .tm-live-promo__pulse { animation: none; box-shadow: none; background: var(--tm-accent); }

/* Demo tour invitation band (templates/pages/dashboard_live.html #tmDemoTourCta): same surface language as the
   Canlı Performansım promo above — no gradient wash, hairline card, one accent button. `.is-hidden` stays display:none
   (JS contract). */
.tm-ds .tm-demo-tour-cta { display: flex; align-items: center; gap: var(--tm-space-3); padding: var(--tm-space-2) var(--tm-space-4); background: var(--tm-bg-surface); background-image: none; border: 1px solid var(--tm-border); border-radius: var(--tm-radius-card); box-shadow: var(--tm-shadow-card); animation: none; }
.tm-ds .tm-demo-tour-cta.is-hidden { display: none; }
.tm-ds .tm-demo-tour-cta__icon { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--tm-radius-full); background: var(--tm-accent-bg); color: var(--tm-accent-fg); flex-shrink: 0; }
.tm-ds .tm-demo-tour-cta__icon i, .tm-ds .tm-demo-tour-cta__icon svg { width: 13px; height: 13px; }
.tm-ds .tm-demo-tour-cta__copy { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: var(--tm-space-2); flex-wrap: wrap; }
.tm-ds .tm-demo-tour-cta__title { margin: 0; font-size: var(--tm-type-body); line-height: var(--tm-type-body-lh); font-weight: var(--tm-weight-semibold); color: var(--tm-text-primary); }
.tm-ds .tm-demo-tour-cta__lead { margin: 0; font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); color: var(--tm-text-secondary); }
.tm-ds .tm-demo-tour-cta__btn { display: inline-flex; align-items: center; gap: var(--tm-space-1); height: 28px; padding: 0 var(--tm-space-3); border: 0; border-radius: var(--tm-radius-control); background: var(--tm-accent-solid); color: var(--tm-on-accent); font-size: var(--tm-type-caption); line-height: var(--tm-type-caption-lh); font-weight: var(--tm-weight-semibold); box-shadow: none; transition: none; }
.tm-ds .tm-demo-tour-cta__btn i, .tm-ds .tm-demo-tour-cta__btn svg { width: 12px; height: 12px; }
.tm-ds .tm-demo-tour-cta__close { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: 0; border-radius: var(--tm-radius-full); background: transparent; color: var(--tm-text-muted); }
.tm-ds .tm-demo-tour-cta__close:hover { background: var(--tm-bg-subtle); color: var(--tm-text-primary); }

