introducing tooltips
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 59s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 59s
This commit is contained in:
343
frontend/app.js
343
frontend/app.js
@@ -49,6 +49,89 @@ const CHART_COLORS = [
|
||||
"#9b59b6", // violet
|
||||
];
|
||||
|
||||
// Term definitions for tooltips
|
||||
const TERM_DEFINITIONS = {
|
||||
rwm_expected: {
|
||||
title: "RWM Expected Standard",
|
||||
description:
|
||||
"The percentage of pupils meeting the expected standard in Reading, Writing and Maths combined at the end of Key Stage 2 (Year 6).",
|
||||
note: "National average: 61%",
|
||||
},
|
||||
rwm_higher: {
|
||||
title: "RWM Higher Standard",
|
||||
description:
|
||||
"The percentage of pupils exceeding the expected standard and reaching the higher standard in Reading, Writing and Maths combined.",
|
||||
note: "National average: 8%",
|
||||
},
|
||||
gps_expected: {
|
||||
title: "GPS Expected Standard",
|
||||
description:
|
||||
"The percentage of pupils meeting the expected standard in Grammar, Punctuation and Spelling at the end of Key Stage 2.",
|
||||
note: "National average: 72%",
|
||||
},
|
||||
science_expected: {
|
||||
title: "Science Expected Standard",
|
||||
description:
|
||||
"The percentage of pupils meeting the expected standard in Science, assessed by teacher judgement at the end of Key Stage 2.",
|
||||
note: "National average: 80%",
|
||||
},
|
||||
reading_progress: {
|
||||
title: "Reading Progress Score",
|
||||
description:
|
||||
"A value-added measure showing how much progress pupils made in Reading between KS1 and KS2, compared to pupils with similar starting points nationally.",
|
||||
note: "A score of 0 is average. Positive = above-average progress.",
|
||||
},
|
||||
writing_progress: {
|
||||
title: "Writing Progress Score",
|
||||
description:
|
||||
"A value-added measure showing how much progress pupils made in Writing between KS1 and KS2, compared to pupils with similar starting points nationally.",
|
||||
note: "A score of 0 is average. Positive = above-average progress.",
|
||||
},
|
||||
maths_progress: {
|
||||
title: "Maths Progress Score",
|
||||
description:
|
||||
"A value-added measure showing how much progress pupils made in Maths between KS1 and KS2, compared to pupils with similar starting points nationally.",
|
||||
note: "A score of 0 is average. Positive = above-average progress.",
|
||||
},
|
||||
disadvantaged_pct: {
|
||||
title: "% Disadvantaged",
|
||||
description:
|
||||
"The percentage of pupils eligible for free school meals or who have been at any point in the last six years, or are looked-after children.",
|
||||
note: "Affects school funding through the Pupil Premium.",
|
||||
},
|
||||
eal_pct: {
|
||||
title: "% EAL",
|
||||
description:
|
||||
"The percentage of pupils whose first language is known or believed to be other than English. These pupils may need additional language support.",
|
||||
note: null,
|
||||
},
|
||||
sen_support_pct: {
|
||||
title: "% SEN Support",
|
||||
description:
|
||||
"The percentage of pupils receiving Special Educational Needs Support. These pupils need extra help but do not have an Education, Health and Care Plan.",
|
||||
note: "Does not include pupils with EHCPs.",
|
||||
},
|
||||
total_pupils: {
|
||||
title: "Total Pupils",
|
||||
description: "The total number of pupils enrolled at the school.",
|
||||
note: null,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an info trigger button for a term tooltip
|
||||
* @param {string} termKey - Key from TERM_DEFINITIONS
|
||||
* @returns {string} HTML string for the info trigger
|
||||
*/
|
||||
function createInfoTrigger(termKey) {
|
||||
const definition = TERM_DEFINITIONS[termKey];
|
||||
if (!definition) return "";
|
||||
|
||||
const label = `What is ${definition.title}?`;
|
||||
|
||||
return `<button class="info-trigger" type="button" data-term="${termKey}" aria-label="${label}" aria-expanded="false"><svg class="info-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v4"/><circle cx="8" cy="5" r="0.5" fill="currentColor" stroke="none"/></svg></button>`;
|
||||
}
|
||||
|
||||
// Map instances (stored to allow cleanup)
|
||||
const schoolMaps = new Map();
|
||||
|
||||
@@ -290,6 +373,9 @@ async function init() {
|
||||
// Always set up event listeners and routing, even if data loading fails
|
||||
setupEventListeners();
|
||||
|
||||
// Initialize tooltip manager
|
||||
tooltipManager = new TooltipManager();
|
||||
|
||||
// Handle initial route
|
||||
handleRoute();
|
||||
|
||||
@@ -523,15 +609,15 @@ function renderFeaturedSchools(schools) {
|
||||
${formatMetricValue(school.rwm_expected_pct, "rwm_expected_pct")}
|
||||
${getTrendIndicator(school.rwm_expected_pct, school.prev_rwm_expected_pct)}
|
||||
</div>
|
||||
<div class="stat-label">RWM Expected</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">RWM Expected${createInfoTrigger("rwm_expected")}</span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">${formatMetricValue(school.rwm_high_pct, "rwm_high_pct")}</div>
|
||||
<div class="stat-label">RWM Higher</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">RWM Higher${createInfoTrigger("rwm_higher")}</span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">${school.total_pupils || "-"}</div>
|
||||
<div class="stat-label">Pupils</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">Pupils${createInfoTrigger("total_pupils")}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
${mapContainer}
|
||||
@@ -547,8 +633,8 @@ function renderFeaturedSchools(schools) {
|
||||
// Add click handlers
|
||||
elements.schoolsGrid.querySelectorAll(".school-card").forEach((card) => {
|
||||
card.addEventListener("click", (e) => {
|
||||
// Don't trigger if clicking on map
|
||||
if (e.target.closest(".school-map")) return;
|
||||
// Don't trigger if clicking on map or info trigger
|
||||
if (e.target.closest(".school-map") || e.target.closest(".info-trigger")) return;
|
||||
const urn = parseInt(card.dataset.urn);
|
||||
openSchoolModal(urn);
|
||||
});
|
||||
@@ -839,15 +925,15 @@ function renderSchools(schools) {
|
||||
${formatMetricValue(school.rwm_expected_pct, "rwm_expected_pct")}
|
||||
${getTrendIndicator(school.rwm_expected_pct, school.prev_rwm_expected_pct)}
|
||||
</div>
|
||||
<div class="stat-label">RWM Expected</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">RWM Expected${createInfoTrigger("rwm_expected")}</span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">${formatMetricValue(school.rwm_high_pct, "rwm_high_pct")}</div>
|
||||
<div class="stat-label">RWM Higher</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">RWM Higher${createInfoTrigger("rwm_higher")}</span></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">${school.total_pupils || "-"}</div>
|
||||
<div class="stat-label">Pupils</div>
|
||||
<div class="stat-label"><span class="stat-label-with-info">Pupils${createInfoTrigger("total_pupils")}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
${mapContainer}
|
||||
@@ -878,8 +964,8 @@ function renderSchools(schools) {
|
||||
// Add click handlers
|
||||
elements.schoolsGrid.querySelectorAll(".school-card").forEach((card) => {
|
||||
card.addEventListener("click", (e) => {
|
||||
// Don't trigger if clicking on map
|
||||
if (e.target.closest(".school-map")) return;
|
||||
// Don't trigger if clicking on map or info trigger
|
||||
if (e.target.closest(".school-map") || e.target.closest(".info-trigger")) return;
|
||||
const urn = parseInt(card.dataset.urn);
|
||||
openSchoolModal(urn);
|
||||
});
|
||||
@@ -1259,19 +1345,19 @@ async function openSchoolModal(urn) {
|
||||
<div class="modal-stats-grid">
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.rwm_expected_pct, "rwm_expected_pct")} ${getTrendIndicator(latest.rwm_expected_pct, prevRwm)}</div>
|
||||
<div class="modal-stat-label">RWM Expected</div>
|
||||
<div class="modal-stat-label">RWM Expected${createInfoTrigger("rwm_expected")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.rwm_high_pct, "rwm_high_pct")}</div>
|
||||
<div class="modal-stat-label">RWM Higher</div>
|
||||
<div class="modal-stat-label">RWM Higher${createInfoTrigger("rwm_higher")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.gps_expected_pct, "gps_expected_pct")}</div>
|
||||
<div class="modal-stat-label">GPS Expected</div>
|
||||
<div class="modal-stat-label">GPS Expected${createInfoTrigger("gps_expected")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.science_expected_pct, "science_expected_pct")}</div>
|
||||
<div class="modal-stat-label">Science Expected</div>
|
||||
<div class="modal-stat-label">Science Expected${createInfoTrigger("science_expected")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1280,15 +1366,15 @@ async function openSchoolModal(urn) {
|
||||
<div class="modal-stats-grid">
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value ${getProgressClass(latest.reading_progress)}">${formatMetricValue(latest.reading_progress, "reading_progress")}</div>
|
||||
<div class="modal-stat-label">Reading</div>
|
||||
<div class="modal-stat-label">Reading${createInfoTrigger("reading_progress")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value ${getProgressClass(latest.writing_progress)}">${formatMetricValue(latest.writing_progress, "writing_progress")}</div>
|
||||
<div class="modal-stat-label">Writing</div>
|
||||
<div class="modal-stat-label">Writing${createInfoTrigger("writing_progress")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value ${getProgressClass(latest.maths_progress)}">${formatMetricValue(latest.maths_progress, "maths_progress")}</div>
|
||||
<div class="modal-stat-label">Maths</div>
|
||||
<div class="modal-stat-label">Maths${createInfoTrigger("maths_progress")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1297,19 +1383,19 @@ async function openSchoolModal(urn) {
|
||||
<div class="modal-stats-grid">
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${latest.total_pupils || "-"}</div>
|
||||
<div class="modal-stat-label">Total Pupils</div>
|
||||
<div class="modal-stat-label">Total Pupils${createInfoTrigger("total_pupils")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.disadvantaged_pct, "disadvantaged_pct")}</div>
|
||||
<div class="modal-stat-label">% Disadvantaged</div>
|
||||
<div class="modal-stat-label">% Disadvantaged${createInfoTrigger("disadvantaged_pct")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.eal_pct, "eal_pct")}</div>
|
||||
<div class="modal-stat-label">% EAL</div>
|
||||
<div class="modal-stat-label">% EAL${createInfoTrigger("eal_pct")}</div>
|
||||
</div>
|
||||
<div class="modal-stat">
|
||||
<div class="modal-stat-value">${formatMetricValue(latest.sen_support_pct, "sen_support_pct")}</div>
|
||||
<div class="modal-stat-label">% SEN Support</div>
|
||||
<div class="modal-stat-label">% SEN Support${createInfoTrigger("sen_support_pct")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1740,3 +1826,218 @@ function setupEventListeners() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TOOLTIP MANAGER
|
||||
// =============================================================================
|
||||
|
||||
class TooltipManager {
|
||||
constructor() {
|
||||
this.activeTooltip = null;
|
||||
this.showTimeout = null;
|
||||
this.hideTimeout = null;
|
||||
this.isTouchDevice =
|
||||
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Create tooltip container element (singleton)
|
||||
this.tooltipEl = document.createElement("div");
|
||||
this.tooltipEl.className = "tooltip";
|
||||
this.tooltipEl.setAttribute("role", "tooltip");
|
||||
this.tooltipEl.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(this.tooltipEl);
|
||||
|
||||
// Event delegation on document
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
if (this.isTouchDevice) {
|
||||
document.addEventListener("click", this.handleTouchClick.bind(this));
|
||||
} else {
|
||||
document.addEventListener(
|
||||
"mouseenter",
|
||||
this.handleMouseEnter.bind(this),
|
||||
true
|
||||
);
|
||||
document.addEventListener(
|
||||
"mouseleave",
|
||||
this.handleMouseLeave.bind(this),
|
||||
true
|
||||
);
|
||||
document.addEventListener("focusin", this.handleFocusIn.bind(this));
|
||||
document.addEventListener("focusout", this.handleFocusOut.bind(this));
|
||||
}
|
||||
|
||||
// Escape key closes tooltip
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && this.activeTooltip) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseEnter(e) {
|
||||
const trigger = e.target.closest(".info-trigger");
|
||||
if (!trigger) return;
|
||||
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.showTimeout = setTimeout(() => {
|
||||
this.show(trigger);
|
||||
}, 150);
|
||||
}
|
||||
|
||||
handleMouseLeave(e) {
|
||||
const trigger = e.target.closest(".info-trigger");
|
||||
const tooltip = e.target.closest(".tooltip");
|
||||
|
||||
if (!trigger && !tooltip) return;
|
||||
|
||||
// Check if moving between trigger and tooltip
|
||||
const relatedTarget = e.relatedTarget;
|
||||
if (
|
||||
relatedTarget?.closest(".info-trigger") === this.activeTooltip ||
|
||||
relatedTarget?.closest(".tooltip")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.showTimeout);
|
||||
this.hideTimeout = setTimeout(() => {
|
||||
this.hide();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
handleFocusIn(e) {
|
||||
const trigger = e.target.closest(".info-trigger");
|
||||
if (!trigger) return;
|
||||
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.show(trigger);
|
||||
}
|
||||
|
||||
handleFocusOut(e) {
|
||||
const trigger = e.target.closest(".info-trigger");
|
||||
if (!trigger) return;
|
||||
|
||||
this.hideTimeout = setTimeout(() => {
|
||||
this.hide();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
handleTouchClick(e) {
|
||||
const trigger = e.target.closest(".info-trigger");
|
||||
|
||||
if (trigger) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.activeTooltip === trigger) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show(trigger);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tap outside closes tooltip
|
||||
if (this.activeTooltip && !e.target.closest(".tooltip")) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
show(trigger) {
|
||||
const termKey = trigger.dataset.term;
|
||||
const definition = TERM_DEFINITIONS[termKey];
|
||||
|
||||
if (!definition) {
|
||||
console.warn(`No definition found for term: ${termKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build tooltip content
|
||||
let content = "";
|
||||
if (definition.title) {
|
||||
content += `<div class="tooltip-title">${definition.title}</div>`;
|
||||
}
|
||||
content += `<div class="tooltip-description">${definition.description}</div>`;
|
||||
if (definition.note) {
|
||||
content += `<div class="tooltip-note">${definition.note}</div>`;
|
||||
}
|
||||
|
||||
this.tooltipEl.innerHTML = content;
|
||||
|
||||
// Make tooltip visible first so we can measure it
|
||||
this.tooltipEl.style.visibility = "hidden";
|
||||
this.tooltipEl.style.opacity = "0";
|
||||
this.tooltipEl.classList.add("visible");
|
||||
|
||||
// Position tooltip
|
||||
this.position(trigger);
|
||||
|
||||
// Show tooltip with animation
|
||||
this.tooltipEl.style.visibility = "";
|
||||
this.tooltipEl.style.opacity = "";
|
||||
this.tooltipEl.setAttribute("aria-hidden", "false");
|
||||
trigger.setAttribute("aria-expanded", "true");
|
||||
|
||||
this.activeTooltip = trigger;
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.activeTooltip) return;
|
||||
|
||||
this.tooltipEl.classList.remove("visible");
|
||||
this.tooltipEl.setAttribute("aria-hidden", "true");
|
||||
this.activeTooltip.setAttribute("aria-expanded", "false");
|
||||
|
||||
this.activeTooltip = null;
|
||||
}
|
||||
|
||||
position(trigger) {
|
||||
const triggerRect = trigger.getBoundingClientRect();
|
||||
const tooltipRect = this.tooltipEl.getBoundingClientRect();
|
||||
|
||||
// Determine placement: prefer top, fall back to bottom if not enough space
|
||||
const spaceAbove = triggerRect.top;
|
||||
const tooltipHeight = tooltipRect.height || 100;
|
||||
|
||||
let placement = "top";
|
||||
let top;
|
||||
|
||||
if (spaceAbove < tooltipHeight + 20) {
|
||||
placement = "bottom";
|
||||
top = triggerRect.bottom + 10 + window.scrollY;
|
||||
} else {
|
||||
top = triggerRect.top - tooltipHeight - 10 + window.scrollY;
|
||||
}
|
||||
|
||||
// Horizontal centering with edge detection
|
||||
let left =
|
||||
triggerRect.left +
|
||||
triggerRect.width / 2 -
|
||||
tooltipRect.width / 2 +
|
||||
window.scrollX;
|
||||
|
||||
// Prevent overflow on left
|
||||
if (left < 10) {
|
||||
left = 10;
|
||||
}
|
||||
|
||||
// Prevent overflow on right
|
||||
const rightEdge = left + tooltipRect.width;
|
||||
if (rightEdge > window.innerWidth - 10) {
|
||||
left = window.innerWidth - tooltipRect.width - 10;
|
||||
}
|
||||
|
||||
this.tooltipEl.style.top = `${top}px`;
|
||||
this.tooltipEl.style.left = `${left}px`;
|
||||
this.tooltipEl.dataset.placement = placement;
|
||||
}
|
||||
}
|
||||
|
||||
// Global tooltip manager instance
|
||||
let tooltipManager = null;
|
||||
|
||||
@@ -1396,3 +1396,128 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
TOOLTIP SYSTEM
|
||||
============================================================================= */
|
||||
|
||||
/* Info Icon Trigger */
|
||||
.info-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
margin-left: 0.25rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: help;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.6;
|
||||
transition: var(--transition);
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info-trigger:hover,
|
||||
.info-trigger:focus {
|
||||
color: var(--accent-teal);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.info-trigger:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--accent-teal);
|
||||
}
|
||||
|
||||
.info-trigger:focus:not(:focus-visible) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.info-trigger:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--accent-teal);
|
||||
}
|
||||
|
||||
/* Info Icon SVG */
|
||||
.info-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-stat-label .info-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
/* Tooltip Container */
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
z-index: 3000;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bg-accent);
|
||||
color: var(--text-inverse);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-medium);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 150ms ease, visibility 150ms ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Tooltip Arrow - Top Placement (arrow points down) */
|
||||
.tooltip[data-placement="top"]::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 8px solid transparent;
|
||||
border-top-color: var(--bg-accent);
|
||||
}
|
||||
|
||||
/* Tooltip Arrow - Bottom Placement (arrow points up) */
|
||||
.tooltip[data-placement="bottom"]::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 8px solid transparent;
|
||||
border-bottom-color: var(--bg-accent);
|
||||
}
|
||||
|
||||
/* Tooltip Title */
|
||||
.tooltip-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Tooltip Note (for context like national average) */
|
||||
.tooltip-note {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid rgba(250, 247, 242, 0.2);
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Label wrapper for inline icon */
|
||||
.stat-label-with-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user