CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
Trackio.svelte1026 linesDownload Raw Back to trackio
1<script>2  import * as d3 from "d3";3  import { formatAbbrev, smoothMetricData } from "./core/chart-utils.js";4  import {5    generateRunNames,6    genCurves,7    Random,8    Performance,9    generateMassiveTestDataset,10  } from "./core/data-generator.js";11  import Legend from "./components/Legend.svelte";12  import Cell from "./components/Cell.svelte";13  import FullscreenModal from "./components/FullscreenModal.svelte";14  import { onMount, onDestroy } from "svelte";15  import { jitterTrigger } from "./core/store.js";16 17  export let variant = "classic"; // 'classic' | 'oblivion'18  export let normalizeLoss = true;19  export let logScaleX = false;20  export let smoothing = false;21 22  let hostEl;23  let gridEl;24  let legendItems = [];25  const cellsDef = [26    { metric: "epoch", title: "Epoch" },27    { metric: "train_accuracy", title: "Train accuracy" },28    { metric: "train_loss", title: "Train loss" },29    { metric: "val_accuracy", title: "Val accuracy" },30    { metric: "val_loss", title: "Val loss", wide: true },31  ];32  let preparedData = {};33  let colorsByRun = {};34 35  // Variables for data management (will be initialized in onMount)36  let dataByMetric = new Map();37  let metricsToDraw = [];38  let currentRunList = [];39  let cycleIdx = 2;40 41  // Dynamic color palette using color-palettes.js helper42  let dynamicPalette = [43    "#0ea5e9",44    "#8b5cf6",45    "#f59e0b",46    "#ef4444",47    "#10b981",48    "#f97316",49    "#3b82f6",50    "#8b5ad6",51  ]; // fallback52 53  const updateDynamicPalette = () => {54    if (55      typeof window !== "undefined" &&56      window.ColorPalettes &&57      currentRunList.length > 058    ) {59      try {60        dynamicPalette = window.ColorPalettes.getColors(61          "categorical",62          currentRunList.length,63        );64      } catch (e) {65        console.warn("Failed to generate dynamic palette:", e);66        // Keep fallback palette67      }68    }69  };70 71  const colorForRun = (name) => {72    const idx = currentRunList.indexOf(name);73    return idx >= 0 ? dynamicPalette[idx % dynamicPalette.length] : "#999";74  };75 76  // Jitter function - generates completely new data with new runs77  function jitterData() {78    console.log(79      "jitterData called - generating new data with random number of runs",80    ); // Debug log81 82    // Generate new random data with weighted probability for fewer runs83    // Higher probability for 2-3 runs, lower for 4-5-6 runs84    const rand = Math.random();85    let wantRuns;86    if (rand < 0.4)87      wantRuns = 2; // 40% chance88    else if (rand < 0.7)89      wantRuns = 3; // 30% chance90    else if (rand < 0.85)91      wantRuns = 4; // 15% chance92    else if (rand < 0.95)93      wantRuns = 5; // 10% chance94    else wantRuns = 6; // 5% chance95    // Use realistic ML training step counts96    const stepsCount = Random.trainingSteps();97    const runsSim = generateRunNames(wantRuns, stepsCount);98    const steps = Array.from({ length: stepsCount }, (_, i) => i + 1);99    const nextByMetric = new Map();100    const TARGET_METRICS = [101      "epoch",102      "train_accuracy",103      "train_loss",104      "val_accuracy",105      "val_loss",106    ];107 108    // Initialize data structure109    TARGET_METRICS.forEach((tgt) => {110      const map = {};111      runsSim.forEach((r) => {112        map[r] = [];113      });114      nextByMetric.set(tgt, map);115    });116 117    // Generate curves for each run118    runsSim.forEach((run) => {119      const curves = genCurves(stepsCount);120      steps.forEach((s, i) => {121        nextByMetric.get("epoch")[run].push({ step: s, value: s });122        nextByMetric123          .get("train_accuracy")124          [run].push({ step: s, value: curves.accTrain[i] });125        nextByMetric126          .get("val_accuracy")127          [run].push({ step: s, value: curves.accVal[i] });128        nextByMetric129          .get("train_loss")130          [run].push({ step: s, value: curves.lossTrain[i] });131        nextByMetric132          .get("val_loss")133          [run].push({ step: s, value: curves.lossVal[i] });134      });135    });136 137    // Update all reactive data138    nextByMetric.forEach((v, k) => dataByMetric.set(k, v));139    metricsToDraw = TARGET_METRICS;140    currentRunList = runsSim.slice();141    updateDynamicPalette(); // Generate new colors based on run count142    legendItems = currentRunList.map((name) => ({143      name,144      color: colorForRun(name),145    }));146    updatePreparedData();147    colorsByRun = Object.fromEntries(148      currentRunList.map((name) => [name, colorForRun(name)]),149    );150 151    console.log(152      `jitterData completed - generated ${wantRuns} runs with ${stepsCount} steps`,153    ); // Debug log154  }155 156  // Public API: allow external theme switch157  function setTheme(name) {158    variant = name === "oblivion" ? "oblivion" : "classic";159    updateThemeClass();160 161    // Debug log for font application162    if (typeof window !== "undefined") {163      console.log(`Theme switched to: ${variant}`);164      if (hostEl) {165        const computedStyle = getComputedStyle(hostEl);166        const appliedFont = computedStyle.fontFamily;167        console.log(`Applied font-family: ${appliedFont}`);168      }169    }170  }171 172  // Public API: allow external log scale X toggle173  function setLogScaleX(enabled) {174    logScaleX = enabled;175    console.log(`Log scale X set to: ${logScaleX}`);176  }177 178  // Public API: allow external smoothing toggle179  function setSmoothing(enabled) {180    smoothing = enabled;181    console.log(`Smoothing set to: ${smoothing}`);182    // Re-prepare data with smoothing applied183    updatePreparedData();184  }185 186  // Public API: generate massive test dataset187  function generateMassiveDataset(steps = null, runs = 3) {188    console.log(189      "๐Ÿงช Generating massive test dataset for sampling validation...",190    );191 192    const result = generateMassiveTestDataset(steps, runs);193 194    // Update reactive data with massive dataset195    result.dataByMetric.forEach((v, k) => dataByMetric.set(k, v));196    metricsToDraw = [197      "epoch",198      "train_accuracy",199      "train_loss",200      "val_accuracy",201      "val_loss",202    ];203    currentRunList = result.runNames.slice();204    updateDynamicPalette();205    legendItems = currentRunList.map((name) => ({206      name,207      color: colorForRun(name),208    }));209    updatePreparedData();210    colorsByRun = Object.fromEntries(211      currentRunList.map((name) => [name, colorForRun(name)]),212    );213 214    console.log(215      `โœ… Massive dataset loaded: ${result.stepCount} steps ร— ${result.runNames.length} runs`,216    );217    console.log(`๐Ÿ“Š Total data points: ${result.totalPoints.toLocaleString()}`);218    console.log(`๐ŸŽฏ Description: ${result.description}`);219 220    return result;221  }222 223  // Public API: add live data point for simulation224  function addLiveDataPoint(runName, dataPoint) {225    console.log(`Adding live data point for run "${runName}":`, dataPoint);226 227    // Add run to currentRunList if it doesn't exist228    if (!currentRunList.includes(runName)) {229      currentRunList = [...currentRunList, runName];230      updateDynamicPalette();231      colorsByRun = Object.fromEntries(232        currentRunList.map((name) => [name, colorForRun(name)]),233      );234      legendItems = currentRunList.map((name) => ({235        name,236        color: colorForRun(name),237      }));238    }239 240    // Initialize data structures for the run if needed241    const TARGET_METRICS = [242      "epoch",243      "train_accuracy",244      "train_loss",245      "val_accuracy",246      "val_loss",247    ];248    TARGET_METRICS.forEach((metric) => {249      if (!dataByMetric.has(metric)) {250        dataByMetric.set(metric, {});251      }252      const metricData = dataByMetric.get(metric);253      if (!metricData[runName]) {254        metricData[runName] = [];255      }256    });257 258    // Add the new data points to each metric259    const step = dataPoint.step;260 261    // Add epoch data262    const epochData = dataByMetric.get("epoch");263    epochData[runName].push({ step, value: step });264 265    // Add accuracy data (train and val get the same value for simplicity)266    if (dataPoint.accuracy !== undefined) {267      const trainAccData = dataByMetric.get("train_accuracy");268      const valAccData = dataByMetric.get("val_accuracy");269 270      // Add some noise between train and val accuracy271      const trainAcc = dataPoint.accuracy;272      const valAcc = Math.max(273        0,274        Math.min(1, dataPoint.accuracy - 0.01 - Math.random() * 0.03),275      );276 277      trainAccData[runName].push({ step, value: trainAcc });278      valAccData[runName].push({ step, value: valAcc });279    }280 281    // Add loss data (train and val get the same value for simplicity)282    if (dataPoint.loss !== undefined) {283      const trainLossData = dataByMetric.get("train_loss");284      const valLossData = dataByMetric.get("val_loss");285 286      // Add some noise between train and val loss287      const trainLoss = dataPoint.loss;288      const valLoss = dataPoint.loss + 0.05 + Math.random() * 0.1;289 290      trainLossData[runName].push({ step, value: trainLoss });291      valLossData[runName].push({ step, value: valLoss });292    }293 294    // Update all metrics to draw295    metricsToDraw = TARGET_METRICS;296 297    // Update prepared data with new values298    updatePreparedData();299 300    console.log(301      `Live data point added successfully. Total runs: ${currentRunList.length}`,302    );303  }304 305  // Update prepared data with optional smoothing306  let preparedRawData = {}; // Store original data for background display307 308  function updatePreparedData() {309    const TARGET_METRICS = [310      "epoch",311      "train_accuracy",312      "train_loss",313      "val_accuracy",314      "val_loss",315    ];316    let dataToUse = {};317    let rawDataToStore = {};318 319    TARGET_METRICS.forEach((metric) => {320      const rawData = dataByMetric.get(metric);321      if (rawData) {322        // Store original data323        rawDataToStore[metric] = rawData;324 325        // Apply smoothing if enabled (except for epoch which should stay exact)326        dataToUse[metric] =327          smoothing && metric !== "epoch"328            ? smoothMetricData(rawData, 5) // Window size of 5329            : rawData;330      }331    });332 333    preparedData = dataToUse;334    preparedRawData = rawDataToStore;335    console.log(`Prepared data updated, smoothing: ${smoothing}`);336  }337 338  function updateThemeClass() {339    if (!hostEl) return;340    hostEl.classList.toggle("theme--classic", variant === "classic");341    hostEl.classList.toggle("theme--oblivion", variant === "oblivion");342    hostEl.setAttribute("data-variant", variant);343  }344 345  $: updateThemeClass();346 347  // Chart logic now handled by Cell.svelte348 349  // Fullscreen navigation state350  let currentFullscreenIndex = 0;351  let isModalOpen = false;352 353  function handleNavigate(newIndex) {354    currentFullscreenIndex = newIndex;355  }356 357  function openModal(index) {358    currentFullscreenIndex = index;359    isModalOpen = true;360  }361 362  function closeModal() {363    isModalOpen = false;364  }365 366  // Prepare all charts data for navigation367  $: allChartsData = cellsDef.map((c) => ({368    metricKey: c.metric,369    titleText: c.title,370    metricData: (preparedData && preparedData[c.metric]) || {},371    rawMetricData: (preparedRawData && preparedRawData[c.metric]) || {},372  }));373 374  // Color function for the modal375  $: modalColorForRun = (name) => colorsByRun[name] || "#999";376 377  let cleanup = null;378  onMount(() => {379    if (!hostEl || !gridEl) return;380    hostEl.__setTheme = setTheme;381 382    // Jitter & Simulate functions383    function rebuildLegend() {384      updateDynamicPalette(); // Update colors when adding new data385      legendItems = currentRunList.map((name) => ({386        name,387        color: colorForRun(name),388      }));389    }390 391    function simulateData() {392      // Generate new random data with weighted probability for fewer runs393      // Higher probability for 2-3 runs, lower for 4-5-6 runs394      const rand = Math.random();395      let wantRuns;396      if (rand < 0.4)397        wantRuns = 2; // 40% chance398      else if (rand < 0.7)399        wantRuns = 3; // 30% chance400      else if (rand < 0.85)401        wantRuns = 4; // 15% chance402      else if (rand < 0.95)403        wantRuns = 5; // 10% chance404      else wantRuns = 6; // 5% chance405      // Use realistic ML training step counts with cycling scenarios406      let stepsCount;407      if (cycleIdx === 0) {408        stepsCount = Random.trainingStepsForScenario("prototyping");409      } else if (cycleIdx === 1) {410        stepsCount = Random.trainingStepsForScenario("development");411      } else if (cycleIdx === 2) {412        stepsCount = Random.trainingStepsForScenario("production");413      } else if (cycleIdx === 3) {414        stepsCount = Random.trainingStepsForScenario("research");415      } else if (cycleIdx === 4) {416        stepsCount = Random.trainingStepsForScenario("llm");417      } else if (cycleIdx === 5) {418        stepsCount = Random.trainingStepsForScenario("massive");419      } else {420        stepsCount = Random.trainingSteps(); // Full range for variety421      }422      cycleIdx = (cycleIdx + 1) % 7; // Cycle through 7 scenarios now423 424      const runsSim = generateRunNames(wantRuns, stepsCount);425      const steps = Array.from({ length: stepsCount }, (_, i) => i + 1);426      const nextByMetric = new Map();427      const TARGET_METRICS = [428        "epoch",429        "train_accuracy",430        "train_loss",431        "val_accuracy",432        "val_loss",433      ];434      const mList =435        metricsToDraw && metricsToDraw.length ? metricsToDraw : TARGET_METRICS;436      mList.forEach((tgt) => {437        const map = {};438        runsSim.forEach((r) => {439          map[r] = [];440        });441        nextByMetric.set(tgt, map);442      });443      runsSim.forEach((run) => {444        const curves = genCurves(stepsCount);445        steps.forEach((s, i) => {446          if (mList.includes("epoch"))447            nextByMetric.get("epoch")[run].push({ step: s, value: s });448          if (mList.includes("train_accuracy"))449            nextByMetric450              .get("train_accuracy")451              [run].push({ step: s, value: curves.accTrain[i] });452          if (mList.includes("val_accuracy"))453            nextByMetric454              .get("val_accuracy")455              [run].push({ step: s, value: curves.accVal[i] });456          if (mList.includes("train_loss"))457            nextByMetric458              .get("train_loss")459              [run].push({ step: s, value: curves.lossTrain[i] });460          if (mList.includes("val_loss"))461            nextByMetric462              .get("val_loss")463              [run].push({ step: s, value: curves.lossVal[i] });464        });465      });466      nextByMetric.forEach((v, k) => dataByMetric.set(k, v));467      currentRunList = runsSim.slice();468      rebuildLegend();469      updatePreparedData();470      updateDynamicPalette(); // Update colors when rebuilding471      colorsByRun = Object.fromEntries(472        currentRunList.map((name) => [name, colorForRun(name)]),473      );474    }475    // No need for event listeners anymore - we'll use reactive statement476 477    // Start with level 3 long synthetic data for consistency478    simulateData();479    // Svelte Cells will react to preparedData/colorsByRun updates480 481    cleanup = () => {482      // No cleanup needed for reactive statements483    };484  });485 486  onDestroy(() => {487    if (cleanup) cleanup();488  });489 490  // Expose instance for debugging and external theme control491  onMount(() => {492    window.trackioInstance = {493      jitterData,494      addLiveDataPoint,495      generateMassiveDataset,496    };497    if (hostEl) {498      hostEl.__trackioInstance = {499        setTheme,500        setLogScaleX,501        setSmoothing,502        jitterData,503        addLiveDataPoint,504        generateMassiveDataset,505      };506    }507 508    // Initialize dynamic palette509    updateDynamicPalette();510 511    // Listen for palette updates from color-palettes.js512    const handlePaletteUpdate = () => {513      updateDynamicPalette();514      // Rebuild legend and colors if needed515      if (currentRunList.length > 0) {516        legendItems = currentRunList.map((name) => ({517          name,518          color: colorForRun(name),519        }));520        colorsByRun = Object.fromEntries(521          currentRunList.map((name) => [name, colorForRun(name)]),522        );523      }524    };525 526    document.addEventListener("palettes:updated", handlePaletteUpdate);527 528    // Cleanup listener on destroy529    return () => {530      document.removeEventListener("palettes:updated", handlePaletteUpdate);531    };532  });533 534  // React to jitter trigger from store535  $: {536    console.log(537      "Reactive statement triggered, jitterTrigger value:",538      $jitterTrigger,539    );540    if ($jitterTrigger > 0) {541      console.log(542        "Jitter trigger activated:",543        $jitterTrigger,544        "calling jitterData()",545      );546      jitterData();547    }548  }549 550  // Legend ghost helpers (hover effects)551  function ghostRun(run) {552    try {553      hostEl.classList.add("hovering");554 555      // Ghost the chart lines and points556      hostEl.querySelectorAll(".cell").forEach((cell) => {557        cell558          .querySelectorAll("svg .lines path.run-line")559          .forEach((p) =>560            p.classList.toggle("ghost", p.getAttribute("data-run") !== run),561          );562        cell563          .querySelectorAll("svg .lines path.raw-line")564          .forEach((p) =>565            p.classList.toggle("ghost", p.getAttribute("data-run") !== run),566          );567        cell568          .querySelectorAll("svg .points circle.pt")569          .forEach((c) =>570            c.classList.toggle("ghost", c.getAttribute("data-run") !== run),571          );572      });573 574      // Ghost the legend items575      hostEl.querySelectorAll(".legend-bottom .item").forEach((item) => {576        const itemRun = item.getAttribute("data-run");577        item.classList.toggle("ghost", itemRun !== run);578      });579    } catch (_) {}580  }581  function clearGhost() {582    try {583      hostEl.classList.remove("hovering");584 585      // Clear ghost from chart lines and points586      hostEl.querySelectorAll(".cell").forEach((cell) => {587        cell588          .querySelectorAll("svg .lines path.run-line")589          .forEach((p) => p.classList.remove("ghost"));590        cell591          .querySelectorAll("svg .lines path.raw-line")592          .forEach((p) => p.classList.remove("ghost"));593        cell594          .querySelectorAll("svg .points circle.pt")595          .forEach((c) => c.classList.remove("ghost"));596      });597 598      // Clear ghost from legend items599      hostEl.querySelectorAll(".legend-bottom .item").forEach((item) => {600        item.classList.remove("ghost");601      });602    } catch (_) {}603  }604</script>605 606<div class="trackio theme--classic" bind:this={hostEl} data-variant={variant}>607  <div class="trackio__header">608    <Legend609      items={legendItems}610      on:legend-hover={(e) => {611        const run = e?.detail?.name;612        if (!run) return;613        ghostRun(run);614      }}615      on:legend-leave={() => {616        clearGhost();617      }}618    />619  </div>620  <div class="trackio__grid" bind:this={gridEl}>621    {#each cellsDef as c, i}622      <Cell623        metricKey={c.metric}624        titleText={c.title}625        wide={c.wide}626        {variant}627        {normalizeLoss}628        {logScaleX}629        {smoothing}630        metricData={(preparedData && preparedData[c.metric]) || {}}631        rawMetricData={(preparedRawData && preparedRawData[c.metric]) || {}}632        colorForRun={(name) => colorsByRun[name] || "#999"}633        {hostEl}634        currentIndex={i}635        onOpenModal={openModal}636      />637    {/each}638  </div>639  <div class="trackio__footer">640    <small>641      Built with <a642        href="https://github.com/huggingface/trackio"643        target="_blank"644        rel="noopener noreferrer">TrackIO</a645      >646      <span class="separator">โ€ข</span>647      <a648        href="https://huggingface.co/docs/hub/spaces-sdks-docker"649        target="_blank"650        rel="noopener noreferrer">Use via API</a651      >652    </small>653  </div>654</div>655 656<!-- Centralized Fullscreen Modal -->657<FullscreenModal658  visible={isModalOpen}659  title={allChartsData[currentFullscreenIndex]?.titleText || ""}660  metricData={allChartsData[currentFullscreenIndex]?.metricData || {}}661  rawMetricData={allChartsData[currentFullscreenIndex]?.rawMetricData || {}}662  colorForRun={modalColorForRun}663  {variant}664  {logScaleX}665  {smoothing}666  {normalizeLoss}667  metricKey={allChartsData[currentFullscreenIndex]?.metricKey || ""}668  titleText={allChartsData[currentFullscreenIndex]?.titleText || ""}669  currentIndex={currentFullscreenIndex}670  totalCharts={cellsDef.length}671  onNavigate={handleNavigate}672  on:close={closeModal}673/>674 675<style>676  /* =========================677     TRACKIO THEME SYSTEM678     ========================= */679 680  /* Font imports for themes - ensure Roboto Mono is loaded for Oblivion theme */681  @import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;600;700&display=swap");682 683  /* Fallback font-face declaration */684  @font-face {685    font-family: "Roboto Mono Fallback";686    src: url("https://fonts.gstatic.com/s/robotomono/v23/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vq_ROW4AJi8SJQt.woff2")687      format("woff2");688    font-weight: 400;689    font-style: normal;690    font-display: swap;691  }692 693  /* Base variables - all themes inherit these */694  .trackio {695    position: relative;696    --z-tooltip: 50;697    --z-overlay: 99999999;698 699    /* Typography */700    --trackio-font-family: var(701      --font-mono,702      ui-monospace,703      SFMono-Regular,704      Menlo,705      monospace706    );707    --trackio-font-weight-normal: 400;708    --trackio-font-weight-medium: 600;709    --trackio-font-weight-bold: 700;710 711    /* Apply font-family to root element */712    font-family: var(--trackio-font-family);713 714    /* Base color system for Classic theme */715    --trackio-base: #323232;716    --trackio-primary: var(--trackio-base);717    --trackio-dim: color-mix(in srgb, var(--trackio-base) 28%, transparent);718    --trackio-text: color-mix(in srgb, var(--trackio-base) 60%, transparent);719    --trackio-subtle: color-mix(in srgb, var(--trackio-base) 8%, transparent);720 721    /* Chart rendering */722    --trackio-chart-grid-type: "lines"; /* 'lines' | 'dots' */723    --trackio-chart-axis-stroke: var(--trackio-dim);724    --trackio-chart-axis-text: var(--trackio-text);725    --trackio-chart-grid-stroke: var(--trackio-subtle);726    --trackio-chart-grid-opacity: 1;727  }728 729  /* Dark mode overrides for Classic theme */730  :global([data-theme="dark"]) .trackio.theme--classic {731    --trackio-base: #ffffff;732    --trackio-primary: var(--trackio-base);733    --trackio-dim: color-mix(in srgb, var(--trackio-base) 25%, transparent);734    --trackio-text: color-mix(in srgb, var(--trackio-base) 60%, transparent);735    --trackio-subtle: color-mix(in srgb, var(--trackio-base) 8%, transparent);736 737    /* Cell background for dark mode */738    --trackio-cell-background: rgba(255, 255, 255, 0.03);739  }740 741  .trackio.theme--classic {742    /* Cell styling */743    --trackio-cell-background: rgba(0, 0, 0, 0.02);744    --trackio-cell-border: var(--border-color, rgba(0, 0, 0, 0.1));745    --trackio-cell-corner-inset: 0px;746    --trackio-cell-gap: 12px;747 748    /* Typography */749    --trackio-text-primary: var(--text-color, rgba(0, 0, 0, 0.9));750    --trackio-text-secondary: var(--muted-color, rgba(0, 0, 0, 0.6));751    --trackio-text-accent: var(--primary-color);752 753    /* Tooltip */754    --trackio-tooltip-background: var(--surface-bg, white);755    --trackio-tooltip-border: var(--border-color, rgba(0, 0, 0, 0.1));756    --trackio-tooltip-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);757 758    /* Legend */759    --trackio-legend-text: var(--text-color, rgba(0, 0, 0, 0.9));760    --trackio-legend-swatch-border: var(--border-color, rgba(0, 0, 0, 0.1));761  }762 763  /* Dark mode adjustments */764  :global([data-theme="dark"]) .trackio {765    --trackio-chart-axis-stroke: rgba(255, 255, 255, 0.18);766    --trackio-chart-axis-text: rgba(255, 255, 255, 0.6);767    --trackio-chart-grid-stroke: rgba(255, 255, 255, 0.08);768  }769 770  /* =========================771     THEME: CLASSIC (Default)772     ========================= */773 774  .trackio.theme--classic {775    /* Keep default values - no overrides needed */776  }777 778  /* =========================779     THEME: OBLIVION780     ========================= */781 782  .trackio.theme--oblivion {783    /* Core oblivion color system - Light mode: darker colors for visibility */784    --trackio-oblivion-base: #2a2a2a;785    --trackio-oblivion-primary: var(--trackio-oblivion-base);786    --trackio-oblivion-dim: color-mix(787      in srgb,788      var(--trackio-oblivion-base) 30%,789      transparent790    );791    --trackio-oblivion-subtle: color-mix(792      in srgb,793      var(--trackio-oblivion-base) 8%,794      transparent795    );796    --trackio-oblivion-ghost: color-mix(797      in srgb,798      var(--trackio-oblivion-base) 4%,799      transparent800    );801 802    /* Chart rendering overrides */803    --trackio-chart-grid-type: "dots";804    --trackio-chart-axis-stroke: var(--trackio-oblivion-dim);805    --trackio-chart-axis-text: var(--trackio-oblivion-primary);806    --trackio-chart-grid-stroke: var(--trackio-oblivion-dim);807    --trackio-chart-grid-opacity: 0.6;808  }809 810  /* Dark mode overrides for Oblivion theme */811  :global([data-theme="dark"]) .trackio.theme--oblivion {812    --trackio-oblivion-base: #ffffff;813    --trackio-oblivion-primary: var(--trackio-oblivion-base);814    --trackio-oblivion-dim: color-mix(815      in srgb,816      var(--trackio-oblivion-base) 25%,817      transparent818    );819    --trackio-oblivion-subtle: color-mix(820      in srgb,821      var(--trackio-oblivion-base) 8%,822      transparent823    );824    --trackio-oblivion-ghost: color-mix(825      in srgb,826      var(--trackio-oblivion-base) 4%,827      transparent828    );829  }830 831  .trackio.theme--oblivion {832    /* Cell styling overrides */833    --trackio-cell-background: var(--trackio-oblivion-subtle);834    --trackio-cell-border: var(--trackio-oblivion-dim);835    --trackio-cell-corner-inset: 6px;836    --trackio-cell-gap: 0px;837 838    /* HUD-specific variables */839    --trackio-oblivion-hud-gap: 10px;840    --trackio-oblivion-hud-corner-size: 8px;841    --trackio-oblivion-hud-bg-gradient: radial-gradient(842        1200px 200px at 20% -10%,843        var(--trackio-oblivion-ghost),844        transparent 80%845      ),846      radial-gradient(847        900px 200px at 80% 110%,848        var(--trackio-oblivion-ghost),849        transparent 80%850      );851 852    /* Typography overrides */853    --trackio-text-primary: var(--trackio-oblivion-primary);854    --trackio-text-secondary: var(--trackio-oblivion-dim);855    --trackio-text-accent: var(--trackio-oblivion-primary);856 857    /* Tooltip overrides */858    --trackio-tooltip-background: var(--trackio-oblivion-subtle);859    --trackio-tooltip-border: var(--trackio-oblivion-dim);860    --trackio-tooltip-shadow: 0 8px 32px861        color-mix(in srgb, var(--trackio-oblivion-base) 8%, transparent),862      0 2px 8px color-mix(in srgb, var(--trackio-oblivion-base) 6%, transparent);863 864    /* Legend overrides */865    --trackio-legend-text: var(--trackio-oblivion-primary);866    --trackio-legend-swatch-border: var(--trackio-oblivion-dim);867 868    /* Font styling overrides */869    --trackio-font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace,870      SFMono-Regular, Menlo, monospace;871    font-family: var(--trackio-font-family) !important;872    color: var(--trackio-text-primary);873  }874 875  /* Force Roboto Mono application in Oblivion theme */876  .trackio.theme--oblivion,877  .trackio.theme--oblivion * {878    font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace,879      SFMono-Regular, Menlo, monospace !important;880  }881 882  /* Specific overrides for different elements in Oblivion */883  .trackio.theme--oblivion .cell-title,884  .trackio.theme--oblivion .legend-bottom,885  .trackio.theme--oblivion .legend-title,886  .trackio.theme--oblivion .item {887    font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace,888      SFMono-Regular, Menlo, monospace !important;889  }890 891  /* Dark mode adjustments for Oblivion */892  :global([data-theme="dark"]) .trackio.theme--oblivion {893    --trackio-oblivion-base: #ffffff;894    --trackio-oblivion-hud-bg-gradient: radial-gradient(895        1400px 260px at 20% -10%,896        color-mix(in srgb, var(--trackio-oblivion-base) 6.5%, transparent),897        transparent 80%898      ),899      radial-gradient(900        1100px 240px at 80% 110%,901        color-mix(in srgb, var(--trackio-oblivion-base) 6%, transparent),902        transparent 80%903      ),904      linear-gradient(905        180deg,906        color-mix(in srgb, var(--trackio-oblivion-base) 3.5%, transparent),907        transparent 45%908      );909 910    --trackio-tooltip-shadow: 0 8px 32px911        color-mix(in srgb, var(--trackio-oblivion-base) 5%, transparent),912      0 2px 8px color-mix(in srgb, black 10%, transparent);913 914    background: #0f1115;915  }916 917  /* =========================918     LAYOUT & COMPONENTS919     ========================= */920 921  .trackio__grid {922    display: grid;923    grid-template-columns: repeat(2, minmax(0, 1fr));924    gap: var(--trackio-cell-gap);925  }926 927  @media (max-width: 980px) {928    .trackio__grid {929      grid-template-columns: 1fr;930    }931  }932 933  .trackio__header {934    display: flex;935    align-items: flex-start;936    justify-content: center;937    gap: 12px;938    margin: 0 0 10px 0;939    flex-wrap: wrap;940    width: 100%;941  }942 943  /* Legacy axis/grid selectors - for compatibility with Cell.svelte */944  .trackio .axes path,945  .trackio .axes line {946    stroke: var(--trackio-chart-axis-stroke);947  }948 949  .trackio .axes text {950    fill: var(--trackio-chart-axis-text);951    font-family: var(--trackio-font-family);952  }953 954  /* Force font-family for SVG text in Oblivion */955  .trackio.theme--oblivion .axes text {956    font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace,957      SFMono-Regular, Menlo, monospace !important;958  }959 960  .trackio .grid line {961    stroke: var(--trackio-chart-grid-stroke);962    opacity: var(--trackio-chart-grid-opacity);963  }964 965  /* Grid type switching */966  .trackio .grid-dots {967    display: none;968  }969  .trackio.theme--oblivion .grid {970    display: none;971  }972  .trackio.theme--oblivion .grid-dots {973    display: block;974  }975  .trackio.theme--oblivion .cell-bg,976  .trackio.theme--oblivion .cell-corners {977    display: block;978  }979 980  /* =========================981     FOOTER982     ========================= */983 984  .trackio__footer {985    display: flex;986    justify-content: center;987    align-items: center;988    margin-top: 12px;989    padding-top: 6px;990    opacity: 1;991  }992 993  .trackio__footer small {994    font-size: 10px;995    color: var(--trackio-text-secondary);996    font-family: var(--trackio-font-family);997    opacity: 0.7;998  }999 1000  .trackio__footer a {1001    color: var(--trackio-text-secondary);1002    text-decoration: none;1003    border-top: 1px solid var(--trackio-chart-grid-stroke);1004    font-weight: var(--trackio-font-weight-normal);1005    transition: opacity 0.15s ease;1006  }1007 1008  .trackio__footer a:hover {1009    text-decoration: none;1010  }1011 1012  .trackio__footer .separator {1013    margin: 0 6px;1014  }1015 1016  /* Oblivion theme footer adjustments */1017  .trackio.theme--oblivion .trackio__footer {1018    border-top-color: var(--trackio-oblivion-dim);1019  }1020 1021  .trackio.theme--oblivion .trackio__footer small {1022    font-family: "Roboto Mono", "Roboto Mono Fallback", ui-monospace,1023      SFMono-Regular, Menlo, monospace !important;1024  }1025</style>1026