670 lines
24 KiB
HTML
670 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Tool Usage Chart - Claude Code DevTools</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:root {
|
|
--bg-primary: #1a1b26;
|
|
--bg-secondary: #24283b;
|
|
--bg-tertiary: #414868;
|
|
--text-primary: #c0caf5;
|
|
--text-secondary: #9aa5ce;
|
|
--text-muted: #565f89;
|
|
--accent-blue: #7aa2f7;
|
|
--accent-green: #9ece6a;
|
|
--accent-purple: #bb9af7;
|
|
--accent-orange: #ff9e64;
|
|
--accent-red: #f7768e;
|
|
--accent-cyan: #7dcfff;
|
|
--border-color: #414868;
|
|
--shadow: rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
padding: 2rem;
|
|
}
|
|
|
|
header {
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
padding-bottom: 2rem;
|
|
border-bottom: 2px solid var(--border-color);
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--accent-blue);
|
|
}
|
|
|
|
.tagline {
|
|
font-size: 1.2rem;
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
}
|
|
|
|
main {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.tool-interface {
|
|
background: var(--bg-secondary);
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
border: 1px solid var(--border-color);
|
|
box-shadow: 0 4px 12px var(--shadow);
|
|
}
|
|
|
|
.control-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.control-group label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--text-secondary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
input[type="file"] {
|
|
display: none;
|
|
}
|
|
|
|
.file-upload-btn, .action-btn {
|
|
background: var(--accent-blue);
|
|
color: white;
|
|
padding: 0.75rem 1.5rem;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
display: inline-block;
|
|
}
|
|
|
|
.file-upload-btn:hover, .action-btn:hover {
|
|
background: var(--accent-purple);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px var(--shadow);
|
|
}
|
|
|
|
.action-btn {
|
|
background: var(--accent-green);
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
background: var(--accent-cyan);
|
|
}
|
|
|
|
.file-info {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
background: var(--bg-tertiary);
|
|
border-radius: 6px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.results {
|
|
background: var(--bg-secondary);
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
border: 1px solid var(--border-color);
|
|
box-shadow: 0 4px 12px var(--shadow);
|
|
}
|
|
|
|
.results h2 {
|
|
margin-bottom: 1.5rem;
|
|
color: var(--accent-green);
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.chart-container {
|
|
background: var(--bg-primary);
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
#toolChart {
|
|
width: 100%;
|
|
height: auto;
|
|
display: block;
|
|
}
|
|
|
|
/* SVG Styling */
|
|
.bar {
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.bar:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.bar-label {
|
|
font-family: 'Segoe UI', sans-serif;
|
|
font-size: 14px;
|
|
fill: var(--text-primary);
|
|
}
|
|
|
|
.bar-value {
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
fill: var(--text-secondary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.axis-label {
|
|
font-family: 'Segoe UI', sans-serif;
|
|
font-size: 12px;
|
|
fill: var(--text-muted);
|
|
}
|
|
|
|
.grid-line {
|
|
stroke: var(--border-color);
|
|
stroke-width: 1;
|
|
stroke-dasharray: 4 4;
|
|
}
|
|
|
|
.tooltip {
|
|
position: fixed;
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border-color);
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
z-index: 1000;
|
|
box-shadow: 0 4px 12px var(--shadow);
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.9rem;
|
|
max-width: 300px;
|
|
}
|
|
|
|
.tooltip.show {
|
|
opacity: 1;
|
|
}
|
|
|
|
.tooltip-title {
|
|
font-weight: 600;
|
|
color: var(--accent-blue);
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.tooltip-detail {
|
|
margin: 0.25rem 0;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.stats-summary {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-tertiary);
|
|
padding: 1.5rem;
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: var(--accent-blue);
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.stat-label {
|
|
color: var(--text-secondary);
|
|
margin-top: 0.5rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.docs {
|
|
background: var(--bg-secondary);
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
border: 1px solid var(--border-color);
|
|
box-shadow: 0 4px 12px var(--shadow);
|
|
}
|
|
|
|
.docs h2 {
|
|
color: var(--accent-purple);
|
|
margin-bottom: 1.5rem;
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.doc-content h3 {
|
|
color: var(--accent-cyan);
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.doc-content ul, .doc-content ol {
|
|
margin-left: 2rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.doc-content li {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.doc-content code {
|
|
background: var(--bg-tertiary);
|
|
padding: 0.2rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-family: 'Courier New', monospace;
|
|
color: var(--accent-orange);
|
|
}
|
|
|
|
footer {
|
|
text-align: center;
|
|
margin-top: 3rem;
|
|
padding-top: 2rem;
|
|
border-top: 2px solid var(--border-color);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
footer a {
|
|
color: var(--accent-blue);
|
|
text-decoration: none;
|
|
}
|
|
|
|
footer a:hover {
|
|
color: var(--accent-cyan);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Tool Usage Chart</h1>
|
|
<p class="tagline">Visualize Claude Code tool usage patterns with scalable SVG charts</p>
|
|
</header>
|
|
|
|
<main>
|
|
<section class="tool-interface">
|
|
<div class="control-group">
|
|
<label for="fileInput">Load Claude Code Transcript (JSONL)</label>
|
|
<input type="file" id="fileInput" accept=".jsonl,.json">
|
|
<label for="fileInput" class="file-upload-btn">Choose File</label>
|
|
<button class="action-btn" onclick="loadSampleData()">Load Sample Data</button>
|
|
<div id="fileInfo" class="file-info" style="display: none;"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="results">
|
|
<h2>Tool Usage Analysis</h2>
|
|
|
|
<div id="statsContainer" style="display: none;">
|
|
<div class="stats-summary">
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="totalToolCalls">0</div>
|
|
<div class="stat-label">Total Tool Calls</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="uniqueTools">0</div>
|
|
<div class="stat-label">Unique Tools Used</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="mostUsedTool">-</div>
|
|
<div class="stat-label">Most Used Tool</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<svg id="toolChart" viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
|
|
<!-- Chart will be rendered here -->
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="emptyState" class="empty-state">
|
|
<div class="empty-state-icon">📊</div>
|
|
<h3>No Data Loaded</h3>
|
|
<p>Upload a Claude Code transcript or load sample data to see tool usage visualization</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="docs">
|
|
<h2>About This Tool</h2>
|
|
<div class="doc-content">
|
|
<h3>Purpose</h3>
|
|
<p>This tool analyzes Claude Code transcripts and visualizes which tools were used and how frequently. It helps developers understand their coding patterns, identify frequently used tools, and gain insights into their workflow efficiency.</p>
|
|
|
|
<h3>Features</h3>
|
|
<ul>
|
|
<li>Parse JSONL transcript files to extract tool usage data</li>
|
|
<li>Interactive horizontal bar chart with hover tooltips</li>
|
|
<li>Responsive SVG visualization that scales perfectly to any screen size</li>
|
|
<li>Statistics summary showing total calls, unique tools, and most used tool</li>
|
|
<li>Sample data included for immediate exploration</li>
|
|
<li>Dark theme optimized for developer workflows</li>
|
|
<li>Tool usage sorted by frequency with percentage calculations</li>
|
|
</ul>
|
|
|
|
<h3>Web Research Integration</h3>
|
|
<p><strong>Source:</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial" target="_blank">MDN SVG Tutorial</a></p>
|
|
<p><strong>Techniques Applied:</strong></p>
|
|
<ul>
|
|
<li><code>viewBox</code> attribute for responsive scaling - The chart uses <code>viewBox="0 0 1000 600"</code> with <code>preserveAspectRatio</code> to create a perfectly scalable visualization that maintains proportions across all screen sizes</li>
|
|
<li>SVG <code>rect</code> elements for bar charts - Each tool's usage is represented by a dynamically sized rectangle element with smooth transitions and hover effects</li>
|
|
<li>SVG <code>text</code> elements with positioning - Labels, values, and percentages are positioned using SVG text elements with precise x/y coordinates for optimal readability</li>
|
|
<li>CSS styling of SVG elements - Applied CSS transitions, hover effects, and theming to SVG elements for a polished, interactive experience</li>
|
|
</ul>
|
|
|
|
<h3>Usage</h3>
|
|
<ol>
|
|
<li>Click "Choose File" and select a Claude Code transcript file (JSONL format), or click "Load Sample Data" to see an example</li>
|
|
<li>The tool will parse the transcript and extract all tool calls from assistant messages</li>
|
|
<li>View the statistics summary showing total calls, unique tools, and the most frequently used tool</li>
|
|
<li>Examine the horizontal bar chart showing each tool's usage frequency</li>
|
|
<li>Hover over any bar to see detailed information including tool name, count, and percentage</li>
|
|
<li>The chart automatically scales to fit your screen while maintaining perfect proportions</li>
|
|
</ol>
|
|
|
|
<h3>Technical Details</h3>
|
|
<p>The tool uses the File API to read transcript files, parses JSONL line-by-line, and extracts tool names from message content blocks. The SVG chart is dynamically generated using DOM manipulation, with each bar's width calculated proportionally to the maximum usage count. The viewBox technique ensures the chart remains crisp and perfectly scaled on any display resolution.</p>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>Claude Code DevTools | Generated via web-enhanced infinite loop</p>
|
|
<p>Web Source: <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial" target="_blank">MDN SVG Tutorial</a></p>
|
|
</footer>
|
|
|
|
<div class="tooltip" id="tooltip">
|
|
<div class="tooltip-title"></div>
|
|
<div class="tooltip-detail"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global state
|
|
let toolUsageData = {};
|
|
let totalCalls = 0;
|
|
|
|
// File input handler
|
|
const fileInput = document.getElementById('fileInput');
|
|
const fileInfo = document.getElementById('fileInfo');
|
|
|
|
fileInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
readTranscriptFile(file);
|
|
}
|
|
});
|
|
|
|
// Read and parse transcript file
|
|
function readTranscriptFile(file) {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
const content = e.target.result;
|
|
parseTranscript(content, file.name);
|
|
};
|
|
|
|
reader.onerror = () => {
|
|
alert('Error reading file. Please try again.');
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
// Parse JSONL transcript
|
|
function parseTranscript(content, fileName) {
|
|
toolUsageData = {};
|
|
totalCalls = 0;
|
|
|
|
const lines = content.split('\n').filter(line => line.trim());
|
|
let messageCount = 0;
|
|
|
|
lines.forEach(line => {
|
|
try {
|
|
const entry = JSON.parse(line);
|
|
messageCount++;
|
|
|
|
// Extract tool calls from assistant messages
|
|
if (entry.message && entry.message.role === 'assistant' && entry.message.content) {
|
|
const content = entry.message.content;
|
|
|
|
// Handle both string content and array of content blocks
|
|
if (Array.isArray(content)) {
|
|
content.forEach(block => {
|
|
if (block.type === 'tool_use' && block.name) {
|
|
const toolName = block.name;
|
|
toolUsageData[toolName] = (toolUsageData[toolName] || 0) + 1;
|
|
totalCalls++;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Error parsing line:', e);
|
|
}
|
|
});
|
|
|
|
// Update UI
|
|
fileInfo.textContent = `Loaded: ${fileName} (${messageCount} messages, ${totalCalls} tool calls)`;
|
|
fileInfo.style.display = 'block';
|
|
|
|
if (totalCalls > 0) {
|
|
renderChart();
|
|
} else {
|
|
alert('No tool usage data found in this transcript.');
|
|
}
|
|
}
|
|
|
|
// Load sample data for demonstration
|
|
function loadSampleData() {
|
|
toolUsageData = {
|
|
'Read': 145,
|
|
'Write': 82,
|
|
'Bash': 67,
|
|
'Edit': 58,
|
|
'Grep': 43,
|
|
'Glob': 38,
|
|
'WebFetch': 22,
|
|
'WebSearch': 18,
|
|
'TodoWrite': 15,
|
|
'NotebookEdit': 9,
|
|
'SlashCommand': 7,
|
|
'BashOutput': 5,
|
|
'KillShell': 2
|
|
};
|
|
|
|
totalCalls = Object.values(toolUsageData).reduce((a, b) => a + b, 0);
|
|
|
|
fileInfo.textContent = 'Loaded: Sample Data (511 tool calls from typical coding session)';
|
|
fileInfo.style.display = 'block';
|
|
|
|
renderChart();
|
|
}
|
|
|
|
// Render SVG bar chart
|
|
function renderChart() {
|
|
// Sort tools by usage count
|
|
const sortedTools = Object.entries(toolUsageData)
|
|
.sort((a, b) => b[1] - a[1]);
|
|
|
|
const uniqueTools = sortedTools.length;
|
|
const mostUsedTool = sortedTools[0][0];
|
|
|
|
// Update stats
|
|
document.getElementById('totalToolCalls').textContent = totalCalls;
|
|
document.getElementById('uniqueTools').textContent = uniqueTools;
|
|
document.getElementById('mostUsedTool').textContent = mostUsedTool;
|
|
document.getElementById('statsContainer').style.display = 'block';
|
|
document.getElementById('emptyState').style.display = 'none';
|
|
|
|
// SVG dimensions (viewBox coordinates)
|
|
const width = 1000;
|
|
const height = 600;
|
|
const margin = { top: 40, right: 180, bottom: 40, left: 180 };
|
|
const chartWidth = width - margin.left - margin.right;
|
|
const chartHeight = height - margin.top - margin.bottom;
|
|
|
|
// Calculate bar dimensions
|
|
const barHeight = Math.min(40, chartHeight / uniqueTools - 10);
|
|
const barSpacing = (chartHeight - (barHeight * uniqueTools)) / (uniqueTools + 1);
|
|
const maxCount = Math.max(...Object.values(toolUsageData));
|
|
|
|
// Color palette for bars
|
|
const colors = [
|
|
'#7aa2f7', '#9ece6a', '#bb9af7', '#ff9e64', '#f7768e',
|
|
'#7dcfff', '#e0af68', '#73daca', '#89ddff', '#c0caf5'
|
|
];
|
|
|
|
// Create SVG content
|
|
const svg = document.getElementById('toolChart');
|
|
svg.innerHTML = ''; // Clear existing content
|
|
|
|
// Create SVG group for the chart
|
|
const chartGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
chartGroup.setAttribute('transform', `translate(${margin.left}, ${margin.top})`);
|
|
svg.appendChild(chartGroup);
|
|
|
|
// Draw grid lines
|
|
for (let i = 0; i <= 5; i++) {
|
|
const x = (chartWidth / 5) * i;
|
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
line.setAttribute('class', 'grid-line');
|
|
line.setAttribute('x1', x);
|
|
line.setAttribute('y1', 0);
|
|
line.setAttribute('x2', x);
|
|
line.setAttribute('y2', chartHeight);
|
|
chartGroup.appendChild(line);
|
|
|
|
// Add axis labels
|
|
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
label.setAttribute('class', 'axis-label');
|
|
label.setAttribute('x', x);
|
|
label.setAttribute('y', chartHeight + 25);
|
|
label.setAttribute('text-anchor', 'middle');
|
|
label.textContent = Math.round((maxCount / 5) * i);
|
|
chartGroup.appendChild(label);
|
|
}
|
|
|
|
// Draw bars
|
|
sortedTools.forEach(([toolName, count], index) => {
|
|
const y = barSpacing + (barSpacing + barHeight) * index;
|
|
const barWidth = (count / maxCount) * chartWidth;
|
|
const percentage = ((count / totalCalls) * 100).toFixed(1);
|
|
const color = colors[index % colors.length];
|
|
|
|
// Bar rectangle
|
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
rect.setAttribute('class', 'bar');
|
|
rect.setAttribute('x', 0);
|
|
rect.setAttribute('y', y);
|
|
rect.setAttribute('width', barWidth);
|
|
rect.setAttribute('height', barHeight);
|
|
rect.setAttribute('fill', color);
|
|
rect.setAttribute('rx', 4);
|
|
chartGroup.appendChild(rect);
|
|
|
|
// Tool name label (left side)
|
|
const nameLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
nameLabel.setAttribute('class', 'bar-label');
|
|
nameLabel.setAttribute('x', -10);
|
|
nameLabel.setAttribute('y', y + barHeight / 2 + 5);
|
|
nameLabel.setAttribute('text-anchor', 'end');
|
|
nameLabel.textContent = toolName;
|
|
chartGroup.appendChild(nameLabel);
|
|
|
|
// Count and percentage label (right side of bar)
|
|
const valueLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
valueLabel.setAttribute('class', 'bar-value');
|
|
valueLabel.setAttribute('x', barWidth + 10);
|
|
valueLabel.setAttribute('y', y + barHeight / 2 + 5);
|
|
valueLabel.setAttribute('text-anchor', 'start');
|
|
valueLabel.textContent = `${count} (${percentage}%)`;
|
|
chartGroup.appendChild(valueLabel);
|
|
|
|
// Add hover event for tooltip
|
|
rect.addEventListener('mouseenter', (e) => showTooltip(e, toolName, count, percentage));
|
|
rect.addEventListener('mousemove', (e) => moveTooltip(e));
|
|
rect.addEventListener('mouseleave', hideTooltip);
|
|
});
|
|
|
|
// Chart title
|
|
const title = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
title.setAttribute('x', chartWidth / 2);
|
|
title.setAttribute('y', -10);
|
|
title.setAttribute('text-anchor', 'middle');
|
|
title.setAttribute('fill', '#7aa2f7');
|
|
title.setAttribute('font-size', '18');
|
|
title.setAttribute('font-weight', '600');
|
|
title.textContent = 'Tool Usage Frequency';
|
|
chartGroup.appendChild(title);
|
|
}
|
|
|
|
// Tooltip functions
|
|
const tooltip = document.getElementById('tooltip');
|
|
|
|
function showTooltip(event, toolName, count, percentage) {
|
|
const titleEl = tooltip.querySelector('.tooltip-title');
|
|
const detailEl = tooltip.querySelector('.tooltip-detail');
|
|
|
|
titleEl.textContent = toolName;
|
|
detailEl.innerHTML = `
|
|
<div>Calls: ${count}</div>
|
|
<div>Percentage: ${percentage}%</div>
|
|
<div>Total Session Calls: ${totalCalls}</div>
|
|
`;
|
|
|
|
tooltip.classList.add('show');
|
|
moveTooltip(event);
|
|
}
|
|
|
|
function moveTooltip(event) {
|
|
const x = event.clientX + 15;
|
|
const y = event.clientY + 15;
|
|
|
|
tooltip.style.left = x + 'px';
|
|
tooltip.style.top = y + 'px';
|
|
}
|
|
|
|
function hideTooltip() {
|
|
tooltip.classList.remove('show');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|