Search

Events

Interactions

Explore all pointer, keyboard, and touch events.

Events
View live example →

Interact with the timeline using mouse, keyboard and touch and see the object passed to the event handler in the Event Details tab.

You can also view the list of fired events or select which events you want to include or exclude during interactions.

See Also

Interact with the timeline using mouse, keyboard and touch and see the object passed to the event handler in the Event Details tab.

You can also view the list of fired events or select which events you want to include or exclude during interactions.

See Also

import { createTimeline } from "kronograph";
import data from "./data";

function byId(id) {
  return document.getElementById(id);
}

function checkboxValue(id) {
  return byId(id).checked === true;
}

function setEventSelectionMode() {
  const eventSelectionMode = checkboxValue("event-selection-mode");
  if (eventSelectionMode) {
    timeline.eventSelectionMode(true);
  } else {
    timeline.eventSelection([]);
    timeline.eventSelectionMode(false);
  }
}

function setEntitySelectionEnabled() {
  const entitySelectionEnabled = checkboxValue("entity-selection-enabled");
  if (entitySelectionEnabled) {
    timeline.entitySelectionEnabled(true);
  } else {
    timeline.entitySelection([]);
    timeline.entitySelectionEnabled(false);
  }
}

function setupTabPanels() {
  const tabNames = ["last-event", "history", "filter-events"];
  const tabs = tabNames.map((tabName) => document.getElementById(`${tabName}-tab`));
  const panels = tabNames.map((tabName) => document.getElementById(`${tabName}-panel`));
  tabs.forEach((tab, tabIdx) => {
    const panel = panels[tabIdx];
    tab.addEventListener("click", () => {
      tabs.forEach((tabElement) => tabElement.classList.remove("tab--active"));
      panels.forEach((panelElement) => panelElement.classList.remove("tab-panel--active"));
      panel.classList.add("tab-panel--active");
      tab.classList.add("tab--active");
    });
  });
}

function populateDetailsPanel({ name, details }) {
  eventNameHeader.innerHTML = name;
  eventDetailsElement.innerText = details;
}

function selectListElement(listElement) {
  previousDisplayedListElement = displayedListElement;
  displayedListElement = listElement;
  displayedListElement.classList.add("list-item--active");
  if (previousDisplayedListElement !== undefined) {
    previousDisplayedListElement.classList.remove("list-item--active");
  }
  const { type: eventName, info: eventDetails } = JSON.parse(
    displayedListElement.getAttribute("data-event-details")
  );
  populateDetailsPanel({
    name: eventName,
    details: eventDetails ? JSON.stringify(eventDetails, null, 2) : "",
  });
}

function addEvent(event) {
  const listElement = document.createElement("li");
  listElement.classList.add("list-item");
  listElement.innerText = event.type;
  listElement.setAttribute("data-event-details", JSON.stringify(event));
  eventListElement.insertBefore(listElement, eventListElement.querySelector("li"));
  return listElement;
}

function setActiveModifiers(modifierKeys) {
  Object.entries(modifierKeys).forEach(([modifierKey, isActive]) => {
    const span = modifiersContainer.querySelector(`[data-event-name="${modifierKey}"]`);
    if (isActive) {
      span.classList.add("modifier-key--active");
    } else {
      span.classList.remove("modifier-key--active");
    }
  });
}

function genericEventHandler(eventName, event) {
  if (selectedEvents[eventName]) {
    const newListElement = addEvent({ type: eventName, info: event });
    // display the details for the new event
    selectListElement(newListElement);
    // show which modifier keys are down
    if (event && event.modifierKeys) {
      setActiveModifiers(event.modifierKeys);
    }
  }
}

const modifiersContainer = document.getElementById("modifiers");
const eventListElement = document.getElementById("event-list");
const clearEventsListButton = document.getElementById("clear-event-list");
const eventNameHeader = document.querySelector(".header--plain");
const eventDetailsElement = document.querySelector(".json-block");
const eventCheckboxes = [...document.querySelectorAll('.event-filter-list input[type="checkbox"]')];
const selectAll = document.getElementById("select-all");
const deselectAll = document.getElementById("deselect-all");

const eventMap = {
  annotations: true,
  click: true,
  "context-menu": true,
  "double-click": true,
  "drag-start": true,
  "drag-move": true,
  "drag-end": true,
  draw: true,
  "entity-selection-change": true,
  "event-selection-change": true,
  focus: true,
  highlight: true,
  hover: true,
  "key-down": true,
  "key-up": true,
  lens: true,
  pin: true,
  "pointer-down": true,
  "pointer-up": true,
  "pointer-move": true,
  range: true,
  wheel: true,
};

const initialSelectedEvents = {
  ...eventMap,
  draw: false,
  highlight: false,
  "pointer-move": false,
};

let selectedEvents = initialSelectedEvents;
let displayedListElement;
let previousDisplayedListElement;

setupTabPanels();

clearEventsListButton.addEventListener("click", () => {
  eventListElement.innerHTML = "";
  populateDetailsPanel({ name: " ", details: "{}" });
});

eventListElement.addEventListener("click", (evt) => {
  if (evt.target instanceof HTMLLIElement) {
    selectListElement(evt.target);
  }
});

selectAll.addEventListener("click", () => {
  eventCheckboxes.forEach((checkbox) => {
    checkbox.checked = true;
  });
  selectedEvents = eventMap;
});

deselectAll.addEventListener("click", () => {
  eventCheckboxes.forEach((checkbox) => {
    checkbox.checked = false;
  });
  selectedEvents = {};
});

eventCheckboxes.forEach((inputElement) => {
  inputElement.addEventListener("change", (evt) => {
    const eventName = evt.target.getAttribute("data-event-name");
    selectedEvents = { ...selectedEvents, [eventName]: evt.target.checked };
  });
});

byId("event-selection-mode")?.addEventListener("click", setEventSelectionMode);
byId("entity-selection-enabled")?.addEventListener("click", setEntitySelectionEnabled);

const timeline = createTimeline("my-timeline");

timeline.set({
  ...data,
  entityTypes: {
    ip: {
      groupBy: ["country", "continent"],
      typeLabel: "IP Addresses",
    },
  },
});
timeline.options({ events: { heatmapThreshold: 30 } });
timeline.annotations({
  annotation1: { label: "This is an annotation", subject: ["event-12"] },
});
timeline.fit();
setEventSelectionMode();
setEntitySelectionEnabled();

// register event handlers
Object.keys(eventMap).forEach((eventName) => {
  timeline.on(eventName, (event) => genericEventHandler(eventName, event));
});
export default {
  entities: {
    "119.184.100.174": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "80.255.245.230": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "78.248.27.2": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "27.195.134.201": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "101.226.51.230": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "123.184.218.198": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "191.7.178.2": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "118.37.6.215": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "1.180.208.152": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "81.161.59.17": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "163.172.66.164": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "65.48.139.133": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "66.249.64.128": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "117.144.246.77": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "1.180.208.148": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "213.184.105.182": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "138.128.6.115": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "163.172.66.171": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "101.226.125.119": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "163.172.66.15": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "101.226.33.217": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "131.161.224.94": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "179.181.219.24": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "195.55.121.5": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "163.172.66.40": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "220.181.108.103": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "179.181.219.44": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "220.181.108.109": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "222.182.107.110": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "222.184.107.114": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "222.184.1033.144": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "157.55.39.189": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "157.55.39.1838": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
    "157.55.39.1828": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
    "157.55.39.122": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "157.55.39.788": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
  },
  events: {
    "event-0": { entityIds: ["119.184.100.174", "123.184.218.198"], time: 1629372675131 },
    "event-1": { entityIds: ["80.255.245.230", "27.195.134.201"], time: 1629372681796 },
    "event-2": { entityIds: ["78.248.27.2", "163.172.66.40"], time: 1629372725809 },
    "event-3": { entityIds: ["27.195.134.201", "27.195.134.201"], time: 1629372759572 },
    "event-4": { entityIds: ["101.226.51.230", "157.55.39.189"], time: 1629372815459 },
    "event-5": { entityIds: ["123.184.218.198", "101.226.33.217"], time: 1629372855089 },
    "event-6": { entityIds: ["191.7.178.2", "191.7.178.2"], time: 1629372966112 },
    "event-7": { entityIds: ["118.37.6.215", "81.161.59.17"], time: 1629372996298 },
    "event-8": { entityIds: ["1.180.208.152", "101.226.33.217"], time: 1629373110447 },
    "event-9": { entityIds: ["81.161.59.17", "220.181.108.103"], time: 1629373112501 },
    "event-10": { entityIds: ["163.172.66.164", "65.48.139.133"], time: 1629373122325 },
    "event-11": { entityIds: ["65.48.139.133", "220.181.108.109"], time: 1629373137553 },
    "event-12": { entityIds: ["66.249.64.128", "131.161.224.94"], time: 1629373183424 },
    "event-13": { entityIds: ["117.144.246.77", "119.184.100.174"], time: 1629373242803 },
    "event-14": { entityIds: ["1.180.208.148", "191.7.178.2"], time: 1629373261835 },
    "event-15": { entityIds: ["213.184.105.182", "157.55.39.1838"], time: 1629373292078 },
    "event-16": { entityIds: ["138.128.6.115", "101.226.51.230"], time: 1629373304447 },
    "event-17": { entityIds: ["163.172.66.171", "163.172.66.40"], time: 1629373309421 },
    "event-18": { entityIds: ["101.226.125.119", "179.181.219.44"], time: 1629373309589 },
    "event-19": { entityIds: ["163.172.66.15", "81.161.59.17"], time: 1629373367674 },
    "event-20": { entityIds: ["101.226.33.217", "220.181.108.103"], time: 1629373392081 },
    "event-21": { entityIds: ["131.161.224.94", "101.226.125.119"], time: 1629373402184 },
    "event-22": { entityIds: ["179.181.219.24", "65.48.139.133"], time: 1629373427921 },
    "event-23": {
      entityIds: ["195.55.121.5", "157.55.39.122"],
      time: 1629373485503,
      label: { text: "This is an event label" },
    },
    "event-24": { entityIds: ["163.172.66.40", "157.55.39.1828"], time: 1629373527572 },
    "event-25": { entityIds: ["220.181.108.103", "65.48.139.133"], time: 1629373563266 },
    "event-26": { entityIds: ["179.181.219.44", "123.184.218.198"], time: 1629373632758 },
    "event-27": { entityIds: ["220.181.108.109", "157.55.39.788"], time: 1629373671302 },
    "event-28": { entityIds: ["222.182.107.110", "101.226.51.230"], time: 1629373689030 },
    "event-29": {
      entityIds: ["222.184.107.114", "222.182.107.110"],
      time: 1629373711184,
    },
    "event-30": { entityIds: ["222.184.1033.144", "65.48.139.133"], time: 1629373763739 },
    "event-31": { entityIds: ["157.55.39.189", "157.55.39.1838"], time: 1629373814735 },
    "event-32": { entityIds: ["157.55.39.1838", "65.48.139.133"], time: 1629373970716 },
    "event-33": { entityIds: ["157.55.39.1828", "157.55.39.1828"], time: 1629373997509 },
    "event-34": { entityIds: ["157.55.39.122", "157.55.39.1838"], time: 1629374017194 },
    "event-35": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
    "event-36": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
  },
  timeSeriesCharts: {
    networkPerformance: {
      color: "#ff8838",
      label: "Network Performance",
      labelColor: "#ff8838",
      data: [
        { time: 1629372675131, value: 28 },
        { time: 1629372681796, value: 28 },
        { time: 1629372701960, value: 27 },
        { time: 1629372725809, value: 25 },
        { time: 1629372759572, value: 27.5 },
        { time: 1629372815459, value: 28 },
        { time: 1629372855089, value: 27 },
        { time: 1629372920345, value: 26 },
        { time: 1629372966112, value: 23 },
        { time: 1629372996298, value: 22 },
        { time: 1629373026503, value: 24 },
        { time: 1629373093819, value: 24.5 },
        { time: 1629373110447, value: 24 },
        { time: 1629373112501, value: 23.5 },
        { time: 1629373122325, value: 23 },
        { time: 1629373134240, value: 23 },
        { time: 1629373137553, value: 22 },
        { time: 1629373140815, value: 22 },
        { time: 1629373183424, value: 21.5 },
        { time: 1629373242803, value: 28 },
        { time: 1629373261835, value: 27 },
        { time: 1629373292078, value: 28 },
        { time: 1629373304447, value: 28.5 },
        { time: 1629373309421, value: 28 },
        { time: 1629373309589, value: 28 },
        { time: 1629373353664, value: 27 },
        { time: 1629373367674, value: 26.5 },
        { time: 1629373392081, value: 25 },
        { time: 1629373402184, value: 24.8 },
        { time: 1629373427921, value: 23 },
        { time: 1629373485503, value: 24 },
        { time: 1629373506794, value: 23 },
        { time: 1629373514358, value: 22.5 },
        { time: 1629373527572, value: 21 },
        { time: 1629373540573, value: 21 },
        { time: 1629373563266, value: 20.5 },
        { time: 1629373566218, value: 20.5 },
        { time: 1629373632758, value: 19.5 },
        { time: 1629373660451, value: 20 },
        { time: 1629373671302, value: 21 },
        { time: 1629373689030, value: 20 },
        { time: 1629373699825, value: 22 },
        { time: 1629373711184, value: 21 },
        { time: 1629373736779, value: 22 },
        { time: 1629373763739, value: 22.5 },
        { time: 1629373806565, value: 21 },
        { time: 1629373814735, value: 23 },
        { time: 1629373913182, value: 28 },
        { time: 1629373970716, value: 28 },
        { time: 1629373996005, value: 27 },
        { time: 1629373997509, value: 26.5 },
        { time: 1629374017194, value: 27.5 },
        { time: 1629374048968, value: 28 },
        { time: 1629374087269, value: 27 },
      ],
    },
  },
};
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div class="story">
      <div style="height: 100%; display: flex; flex-direction: column; max-height: 100%">
        <div style="display: flex; flex: 1 1 auto; box-sizing: border-box; min-height: 0px">
          <div id="modifiers" class="modifiers-container stack absolute">
            <span class="modifier-key" data-event-name="alt">alt</span>
            <span class="modifier-key" data-event-name="ctrl">ctrl</span>
            <span class="modifier-key" data-event-name="consistentCtrl">consistentCtrl</span>
            <span class="modifier-key" data-event-name="meta">meta</span>
            <span class="modifier-key" data-event-name="shift">shift</span>
          </div>
          <div id="my-timeline" style="flex: 2 0 50%" class="story__timeline"></div>
          <div class="tab-panel-container">
            <div style="display: flex">
              <button id="last-event-tab" class="tab tab--active">Event Details</button>
              <button id="history-tab" class="tab">History</button>
              <button id="filter-events-tab" class="tab">Filter Events</button>
            </div>
            <div id="last-event-panel" class="tab-panel tab-panel--active">
              <h3 class="header--plain">&nbsp;</h3>
              <pre class="json-block">{}</pre>
            </div>
            <div
              id="history-panel"
              class="tab-panel"
              style="flex-wrap: nowrap; align-items: center"
            >
              <button id="clear-event-list" class="button button--small" style="flex: 0 0 auto">
                clear
              </button>
              <ul id="event-list" class="event-list"></ul>
            </div>
            <div id="filter-events-panel" class="tab-panel" style="flex-wrap: nowrap">
              <div class="filter-buttons">
                <button id="select-all" class="button button--small">select all</button>
                <button id="deselect-all" class="button button--small">deselect all</button>
              </div>
              <div class="event-filter-list">
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="annotations" />annotations
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="click" />click
                </label>
                <label class="event-label">
                  <input
                    checked="true"
                    type="checkbox"
                    data-event-name="context-menu"
                  />context-menu
                </label>
                <label class="event-label">
                  <input
                    checked="true"
                    type="checkbox"
                    data-event-name="double-click"
                  />double-click
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="drag-start" />drag-start
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="drag-move" />drag-move
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="drag-end" />drag-end
                </label>
                <label class="event-label">
                  <input type="checkbox" data-event-name="draw" />draw
                </label>
                <label class="event-label">
                  <input
                    checked="true"
                    type="checkbox"
                    data-event-name="entity-selection-change"
                  />entity-selection-change
                </label>
                <label class="event-label">
                  <input
                    checked="true"
                    type="checkbox"
                    data-event-name="event-selection-change"
                  />event-selection-change
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="focus" />focus
                </label>
                <label class="event-label">
                  <input type="checkbox" data-event-name="highlight" />highlight
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="hover" />hover
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="key-down" />key-down
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="key-up" />key-up
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="lens" />lens
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="pin" />pin
                </label>
                <label class="event-label">
                  <input
                    checked="true"
                    type="checkbox"
                    data-event-name="pointer-down"
                  />pointer-down
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="pointer-up" />pointer-up
                </label>
                <label class="event-label">
                  <input type="checkbox" data-event-name="pointer-move" />pointer-move
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="range" />range
                </label>
                <label class="event-label">
                  <input checked="true" type="checkbox" data-event-name="wheel" />wheel
                </label>
              </div>
            </div>
          </div>
        </div>
        <div id="controls" class="controls-container">
          <div class="row selection-switch gap-2">
            Event Selection Mode
            <label class="switch">
              <input type="checkbox" id="event-selection-mode" />
              <span class="slider round"></span>
            </label>
          </div>
          <div class="row selection-switch gap-2">
            Entity Selection Enabled
            <label class="switch">
              <input type="checkbox" id="entity-selection-enabled" />
              <span class="slider round"></span>
            </label>
          </div>
        </div>
      </div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
#selection-switch {
  display: flex;
  flex-direction: row;
  align-items: center;
  white-space: nowrap;
}

.modifier-key {
  margin: 0;
  padding: 0px 10px;
}

.modifiers-container {
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: 10px 2rem;
  padding: 1rem;
  align-items: start;
  position: absolute;
  top: 2rem;
  display: flex;
  flex-direction: column;
  font-size: medium;
  font-weight: bold;
  pointer-events: none;
}

.controls-container {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 2rem;
  padding: 0.5rem 0.5rem 0rem 0.5rem;
}

.row {
  display: flex;
  align-items: center;
}

.gap-2 {
  column-gap: 1em;
}

.switch {
  position: relative;
  display: inline-block;
  width: 25px;
  height: 16px;
  padding: 2px;
  margin-left: 5px;
  vertical-align: middle;
}

/* Hide default HTML checkbox */
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--grey-color-300);
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 12px;
  width: 12px;
  left: 2px;
  bottom: 2px;
  background-color: var(--white-color);
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

input:checked + .slider {
  background-color: var(--primary-color300);
}

input:focus + .slider {
  box-shadow: 0 0 1px var(--primary-color300);
}

input:checked + .slider:before {
  -webkit-transform: translateX(9px);
  -ms-transform: translateX(9px);
  transform: translateX(9px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 99px;
}

.slider.round:before {
  border-radius: 50%;
}

.slider-label {
  padding-left: 0.5rem;
  vertical-align: middle;
}
import React, { useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import Timeline from "kronograph/react/Timeline";
import data from "./data";

const entityTypes = {
  ip: {
    groupBy: ["country", "continent"],
    typeLabel: "IP Addresses",
  },
};

const timelineOptions = { events: { heatmapThreshold: 30 } };

function FilterEvents({ changeHandler, selectedEvents }) {
  return (
    <div className="event-filter-list">
      {Object.keys(eventMap).map((propName) => (
        <label className="event-label" style={{ width: "265px" }} key={propName}>
          <input
            onChange={() => changeHandler(propName)}
            checked={selectedEvents[propName]}
            type="checkbox"
          />
          {propName}
        </label>
      ))}
    </div>
  );
}

function EventList({ events, handleClick, displayedEventIndex }) {
  return (
    <ul className="event-list">
      {events.map((event, eventIdx) => (
        <li
          key={eventIdx}
          className={`list-item${displayedEventIndex === eventIdx ? "list-item--active" : ""}`}
          onClick={() => handleClick(eventIdx)}
        >
          {event.type}
        </li>
      ))}
    </ul>
  );
}

function TabPanels({ tabs, setSelectedTab, selectedTab }) {
  return (
    <div className="tab-panel-container">
      <div style={{ display: "flex" }}>
        {tabs.map((tab) => (
          <button
            onClick={() => setSelectedTab(tab.id)}
            className={`tab${selectedTab === tab.id ? "tab--active" : ""}`}
            key={tab.id}
          >
            {tab.title}
          </button>
        ))}
      </div>
      <div style={{ flexGrow: "1", position: "relative" }}>
        <div style={{ position: "absolute", height: "100%", width: "100%" }}>
          {tabs.map((tab) => (
            <div
              key={tab.id}
              style={{ ...tab.panelStyle, overflow: "hidden" }}
              className={`tab-panel${selectedTab === tab.id ? "tab-panel--active" : ""}`}
            >
              {tab.panel}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

const eventMap = {
  onTimelineChange: true,
  onTimelineContextMenu: true,
  onTimelineClick: true,
  onTimelineDoubleClick: true,
  onTimelineDragEnd: true,
  onTimelineDragMove: true,
  onTimelineDragStart: true,
  onTimelineDraw: true,
  onTimelineHover: true,
  onTimelineKeyDown: true,
  onTimelineKeyUp: true,
  onTimelinePointerDown: true,
  onTimelinePointerMove: true,
  onTimelinePointerUp: true,
  onTimelineWheel: true,
};

export const Demo = () => {
  const [eventList, setEventList] = useState([]);
  const [activeModifiers, setActiveModifiers] = useState({});
  const [selectedTab, setSelectedTab] = useState("event-details");
  const [selectedEvents, setSelectedEvents] = useState({
    ...eventMap,
    onTimelineDraw: false,
    onTimelinePointerMove: false,
  });
  const [displayedEventIndex, setDisplayedEventIndex] = useState(0);
  const [eventSelectionMode, setEventSelectionMode] = useState(false);
  const [entitySelectionEnabled, setEntitySelectionEnabled] = useState(false);
  const [annotations] = useState({
    annotation1: { label: "This is an annotation", subject: ["event-12"] },
  });
  const eventSelectionSliderRef = useRef();
  const entitySelectionSliderRef = useRef();
  const timelineRef = useRef(null);

  const displayedEvent = eventList[displayedEventIndex];

  function handleSetEventSelectionMode() {
    const eventSelectionModeValue = eventSelectionSliderRef.current.checked;
    setEventSelectionMode(eventSelectionModeValue);
  }

  function handleSetEntitySelectionEnabled() {
    const entitySelectionEnabledValue = entitySelectionSliderRef.current.checked;
    setEntitySelectionEnabled(entitySelectionEnabledValue);
  }

  function Controls() {
    return (
      <div id="controls" className="controls-container">
        <div className="row selection-switch gap-2">
          Event Selection Mode
          <label className="switch">
            <input
              type="checkbox"
              id="selection-mode"
              ref={eventSelectionSliderRef}
              onChange={handleSetEventSelectionMode}
              checked={eventSelectionMode}
            />
            <span className="slider round"></span>
          </label>
        </div>
        <div className="row selection-switch gap-2">
          Entity Selection Enabled
          <label className="switch">
            <input
              type="checkbox"
              id="entity-selection-enabled"
              ref={entitySelectionSliderRef}
              onChange={handleSetEntitySelectionEnabled}
              checked={entitySelectionEnabled}
            />
            <span className="slider round"></span>
          </label>
        </div>
      </div>
    );
  }

  function Modifiers() {
    return (
      <div id="modifiers" className="modifiers-container">
        {["alt", "ctrl", "consistentCtrl", "meta", "shift"].map((modifier) => (
          <span
            key={modifier}
            className={`modifier-key${activeModifiers[modifier] ? "modifier-key--active" : ""}`}
          >
            {modifier}
          </span>
        ))}
      </div>
    );
  }

  function genericEventHandler(propName, event) {
    if (selectedEvents[propName]) {
      setDisplayedEventIndex(0);
      setEventList((prevEventList) => [{ type: propName, info: event }, ...prevEventList]);
      if (event && event.modifierKeys) {
        setActiveModifiers(event.modifierKeys);
      }
    }
  }

  const eventDetailsPanel = (
    <>
      <h3 className="header--plain">{displayedEvent ? displayedEvent.type : <br />}</h3>
      <pre className="json-block">
        {JSON.stringify(displayedEvent ? displayedEvent.info : {}, null, 2)}
      </pre>
    </>
  );

  const historyPanel = (
    <>
      <button
        className="button button--small"
        style={{ flex: "0 0 auto" }}
        onClick={() => setEventList([])}
      >
        clear
      </button>
      <EventList
        events={eventList}
        handleClick={(idx) => setDisplayedEventIndex(idx)}
        displayedEventIndex={displayedEventIndex}
      />
    </>
  );

  const filterEventsPanel = (
    <>
      <div className="filter-buttons">
        <button className="button button--small" onClick={() => setSelectedEvents(eventMap)}>
          select all
        </button>
        <button className="button button--small" onClick={() => setSelectedEvents({})}>
          deselect all
        </button>
      </div>
      <FilterEvents
        changeHandler={(propName) => {
          setSelectedEvents({
            ...selectedEvents,
            [propName]: !selectedEvents[propName],
          });
        }}
        selectedEvents={selectedEvents}
      />
    </>
  );

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column", maxHeight: "100%" }}>
      <div
        style={{
          display: "flex",
          flex: "1 0 auto",
          boxSizing: "border-box",
        }}
      >
        <div style={{ flex: "2 0 50%", position: "relative" }}>
          <Timeline
            style={{ position: "absolute", height: "100%", width: "100%" }}
            className="story__timeline"
            ref={timelineRef}
            events={data.events}
            entities={data.entities}
            timeSeriesCharts={data.timeSeriesCharts}
            entityTypes={entityTypes}
            options={timelineOptions}
            eventSelectionMode={eventSelectionMode}
            entitySelectionEnabled={entitySelectionEnabled}
            annotations={annotations}
            onTimelineChange={(event) => genericEventHandler("onTimelineChange", event)}
            onTimelineContextMenu={(event) => genericEventHandler("onTimelineContextMenu", event)}
            onTimelineClick={(event) => genericEventHandler("onTimelineClick", event)}
            onTimelineDoubleClick={(event) => genericEventHandler("onTimelineDoubleClick", event)}
            onTimelineDragEnd={(event) => genericEventHandler("onTimelineDragEnd", event)}
            onTimelineDragMove={(event) => genericEventHandler("onTimelineDragMove", event)}
            onTimelineDragStart={(event) => genericEventHandler("onTimelineDragStart", event)}
            onTimelineDraw={() => genericEventHandler("onTimelineDraw")}
            onTimelineHover={(event) => genericEventHandler("onTimelineHover", event)}
            onTimelineKeyDown={(event) => genericEventHandler("onTimelineKeyDown", event)}
            onTimelineKeyUp={(event) => genericEventHandler("onTimelineKeyUp", event)}
            onTimelinePointerDown={(event) => genericEventHandler("onTimelinePointerDown", event)}
            onTimelinePointerMove={(event) => genericEventHandler("onTimelinePointerMove", event)}
            onTimelinePointerUp={(event) => genericEventHandler("onTimelinePointerUp", event)}
            onTimelineWheel={(event) => genericEventHandler("onTimelineWheel", event)}
          />
        </div>
        <TabPanels
          tabs={[
            {
              title: "Event Details",
              id: "event-details",
              panel: eventDetailsPanel,
            },
            {
              title: "History",
              id: "event-history",
              panelStyle: { flexWrap: "nowrap", alignItems: "center" },
              panel: historyPanel,
            },
            {
              title: "Filter Events",
              id: "filter-events",
              panelStyle: { flexWrap: "nowrap" },
              panel: filterEventsPanel,
            },
          ]}
          setSelectedTab={setSelectedTab}
          selectedTab={selectedTab}
        />
      </div>
      <Modifiers />
      <Controls />
    </div>
  );
};

const root = createRoot(document.getElementById("my-timeline"));
root.render(<Demo />);
export default {
  entities: {
    "119.184.100.174": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "80.255.245.230": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "78.248.27.2": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "27.195.134.201": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "101.226.51.230": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "123.184.218.198": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "South Korea",
      },
      color: "#f86868",
    },
    "191.7.178.2": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "118.37.6.215": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "1.180.208.152": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "81.161.59.17": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "163.172.66.164": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "65.48.139.133": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "66.249.64.128": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "117.144.246.77": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "1.180.208.148": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "213.184.105.182": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "138.128.6.115": {
      type: "ip",
      data: {
        continent: "North America",
        country: "United States",
      },
      color: "#5dbbb4",
    },
    "163.172.66.171": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "United Kingdom",
      },
      color: "#f7d06e",
    },
    "101.226.125.119": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Thailand",
      },
      color: "#f86868",
    },
    "163.172.66.15": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "101.226.33.217": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "131.161.224.94": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "179.181.219.24": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "195.55.121.5": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Spain",
      },
      color: "#f7d06e",
    },
    "163.172.66.40": {
      type: "ip",
      data: {
        continent: "Europe",
        country: "Germany",
      },
      color: "#f7d06e",
    },
    "220.181.108.103": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "179.181.219.44": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Peru",
      },
      color: "#2676c7",
    },
    "220.181.108.109": {
      type: "ip",
      data: {
        continent: "Asia",
        country: "Japan",
      },
      color: "#f86868",
    },
    "222.182.107.110": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "222.184.107.114": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "222.184.1033.144": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Mexico",
      },
      color: "#5dbbb4",
    },
    "157.55.39.189": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Argentina",
      },
      color: "#2676c7",
    },
    "157.55.39.1838": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
    "157.55.39.1828": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
    "157.55.39.122": {
      type: "ip",
      data: {
        continent: "South America",
        country: "Brazil",
      },
      color: "#2676c7",
    },
    "157.55.39.788": {
      type: "ip",
      data: {
        continent: "North America",
        country: "Canada",
      },
      color: "#5dbbb4",
    },
  },
  events: {
    "event-0": { entityIds: ["119.184.100.174", "123.184.218.198"], time: 1629372675131 },
    "event-1": { entityIds: ["80.255.245.230", "27.195.134.201"], time: 1629372681796 },
    "event-2": { entityIds: ["78.248.27.2", "163.172.66.40"], time: 1629372725809 },
    "event-3": { entityIds: ["27.195.134.201", "27.195.134.201"], time: 1629372759572 },
    "event-4": { entityIds: ["101.226.51.230", "157.55.39.189"], time: 1629372815459 },
    "event-5": { entityIds: ["123.184.218.198", "101.226.33.217"], time: 1629372855089 },
    "event-6": { entityIds: ["191.7.178.2", "191.7.178.2"], time: 1629372966112 },
    "event-7": { entityIds: ["118.37.6.215", "81.161.59.17"], time: 1629372996298 },
    "event-8": { entityIds: ["1.180.208.152", "101.226.33.217"], time: 1629373110447 },
    "event-9": { entityIds: ["81.161.59.17", "220.181.108.103"], time: 1629373112501 },
    "event-10": { entityIds: ["163.172.66.164", "65.48.139.133"], time: 1629373122325 },
    "event-11": { entityIds: ["65.48.139.133", "220.181.108.109"], time: 1629373137553 },
    "event-12": { entityIds: ["66.249.64.128", "131.161.224.94"], time: 1629373183424 },
    "event-13": { entityIds: ["117.144.246.77", "119.184.100.174"], time: 1629373242803 },
    "event-14": { entityIds: ["1.180.208.148", "191.7.178.2"], time: 1629373261835 },
    "event-15": { entityIds: ["213.184.105.182", "157.55.39.1838"], time: 1629373292078 },
    "event-16": { entityIds: ["138.128.6.115", "101.226.51.230"], time: 1629373304447 },
    "event-17": { entityIds: ["163.172.66.171", "163.172.66.40"], time: 1629373309421 },
    "event-18": { entityIds: ["101.226.125.119", "179.181.219.44"], time: 1629373309589 },
    "event-19": { entityIds: ["163.172.66.15", "81.161.59.17"], time: 1629373367674 },
    "event-20": { entityIds: ["101.226.33.217", "220.181.108.103"], time: 1629373392081 },
    "event-21": { entityIds: ["131.161.224.94", "101.226.125.119"], time: 1629373402184 },
    "event-22": { entityIds: ["179.181.219.24", "65.48.139.133"], time: 1629373427921 },
    "event-23": {
      entityIds: ["195.55.121.5", "157.55.39.122"],
      time: 1629373485503,
      label: { text: "This is an event label" },
    },
    "event-24": { entityIds: ["163.172.66.40", "157.55.39.1828"], time: 1629373527572 },
    "event-25": { entityIds: ["220.181.108.103", "65.48.139.133"], time: 1629373563266 },
    "event-26": { entityIds: ["179.181.219.44", "123.184.218.198"], time: 1629373632758 },
    "event-27": { entityIds: ["220.181.108.109", "157.55.39.788"], time: 1629373671302 },
    "event-28": { entityIds: ["222.182.107.110", "101.226.51.230"], time: 1629373689030 },
    "event-29": {
      entityIds: ["222.184.107.114", "222.182.107.110"],
      time: 1629373711184,
    },
    "event-30": { entityIds: ["222.184.1033.144", "65.48.139.133"], time: 1629373763739 },
    "event-31": { entityIds: ["157.55.39.189", "157.55.39.1838"], time: 1629373814735 },
    "event-32": { entityIds: ["157.55.39.1838", "65.48.139.133"], time: 1629373970716 },
    "event-33": { entityIds: ["157.55.39.1828", "157.55.39.1828"], time: 1629373997509 },
    "event-34": { entityIds: ["157.55.39.122", "157.55.39.1838"], time: 1629374017194 },
    "event-35": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
    "event-36": { entityIds: ["157.55.39.788", "195.55.121.5"], time: 1629374087269 },
  },
  timeSeriesCharts: {
    networkPerformance: {
      color: "#ff8838",
      label: "Network Performance",
      labelColor: "#ff8838",
      data: [
        { time: 1629372675131, value: 28 },
        { time: 1629372681796, value: 28 },
        { time: 1629372701960, value: 27 },
        { time: 1629372725809, value: 25 },
        { time: 1629372759572, value: 27.5 },
        { time: 1629372815459, value: 28 },
        { time: 1629372855089, value: 27 },
        { time: 1629372920345, value: 26 },
        { time: 1629372966112, value: 23 },
        { time: 1629372996298, value: 22 },
        { time: 1629373026503, value: 24 },
        { time: 1629373093819, value: 24.5 },
        { time: 1629373110447, value: 24 },
        { time: 1629373112501, value: 23.5 },
        { time: 1629373122325, value: 23 },
        { time: 1629373134240, value: 23 },
        { time: 1629373137553, value: 22 },
        { time: 1629373140815, value: 22 },
        { time: 1629373183424, value: 21.5 },
        { time: 1629373242803, value: 28 },
        { time: 1629373261835, value: 27 },
        { time: 1629373292078, value: 28 },
        { time: 1629373304447, value: 28.5 },
        { time: 1629373309421, value: 28 },
        { time: 1629373309589, value: 28 },
        { time: 1629373353664, value: 27 },
        { time: 1629373367674, value: 26.5 },
        { time: 1629373392081, value: 25 },
        { time: 1629373402184, value: 24.8 },
        { time: 1629373427921, value: 23 },
        { time: 1629373485503, value: 24 },
        { time: 1629373506794, value: 23 },
        { time: 1629373514358, value: 22.5 },
        { time: 1629373527572, value: 21 },
        { time: 1629373540573, value: 21 },
        { time: 1629373563266, value: 20.5 },
        { time: 1629373566218, value: 20.5 },
        { time: 1629373632758, value: 19.5 },
        { time: 1629373660451, value: 20 },
        { time: 1629373671302, value: 21 },
        { time: 1629373689030, value: 20 },
        { time: 1629373699825, value: 22 },
        { time: 1629373711184, value: 21 },
        { time: 1629373736779, value: 22 },
        { time: 1629373763739, value: 22.5 },
        { time: 1629373806565, value: 21 },
        { time: 1629373814735, value: 23 },
        { time: 1629373913182, value: 28 },
        { time: 1629373970716, value: 28 },
        { time: 1629373996005, value: 27 },
        { time: 1629373997509, value: 26.5 },
        { time: 1629374017194, value: 27.5 },
        { time: 1629374048968, value: 28 },
        { time: 1629374087269, value: 27 },
      ],
    },
  },
};
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="my-timeline" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>
#selection-switch {
  display: flex;
  flex-direction: row;
  align-items: center;
  white-space: nowrap;
}

.modifier-key {
  margin: 0;
  padding: 0px 10px;
}

.modifiers-container {
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: 10px 2rem;
  padding: 1rem;
  align-items: start;
  position: absolute;
  top: 2rem;
  display: flex;
  flex-direction: column;
  font-size: medium;
  font-weight: bold;
  pointer-events: none;
}

.controls-container {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 2rem;
  padding: 0.5rem 0.5rem 0rem 0.5rem;
}

.row {
  display: flex;
  align-items: center;
}

.gap-2 {
  column-gap: 1em;
}

.switch {
  position: relative;
  display: inline-block;
  width: 25px;
  height: 16px;
  padding: 2px;
  margin-left: 5px;
  vertical-align: middle;
}

/* Hide default HTML checkbox */
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--grey-color-300);
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 12px;
  width: 12px;
  left: 2px;
  bottom: 2px;
  background-color: var(--white-color);
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

input:checked + .slider {
  background-color: var(--primary-color300);
}

input:focus + .slider {
  box-shadow: 0 0 1px var(--primary-color300);
}

input:checked + .slider:before {
  -webkit-transform: translateX(9px);
  -ms-transform: translateX(9px);
  transform: translateX(9px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 99px;
}

.slider.round:before {
  border-radius: 50%;
}

.slider-label {
  padding-left: 0.5rem;
  vertical-align: middle;
}

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.