diff --git a/css/gui/tooltips/hasTooltip.css b/css/gui/tooltips/hasTooltip.css
deleted file mode 100644
index d5eafa88bc64b3e50a015fe8ece4b33f5f52a8d5..0000000000000000000000000000000000000000
--- a/css/gui/tooltips/hasTooltip.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* interactable tooltip-like container - created/destroyed dynamically */
-.details-overlay {
-    display: inline-block;
-    font-size: smaller;
-    width: max-content;
-    height: max-content;
-    border-style: solid;
-    border-color: slategray;
-    border-width: 2px;
-    border-radius: 3px;
-    background-color: rgb(17, 17, 17);
-    padding: 3px;
-    position: absolute;
-    z-index: 2;
-    text-indent: 0;
-}
diff --git a/css/gui/tooltips/tippy.css b/css/gui/tooltips/tippy.css
index fb794aeb62b0d1f4826c808f364fe28fc13c376c..72ffb2760d993eedc924831c52faf1c88168a1b5 100644
--- a/css/gui/tooltips/tippy.css
+++ b/css/gui/tooltips/tippy.css
@@ -1,5 +1,9 @@
 .has-tooltip {
-    text-decoration: underline dotted lightblue;
+    text-decoration: underline dotted;
+}
+
+.tippy-content {
+    text-indent: initial;
 }
 
 .tippy-content ul {
@@ -8,3 +12,7 @@
 .tippy-content ul:not(.choices-strip) {
     padding-left: 10px;
 }
+
+.tippy-content .tip-details {
+    font-size: smaller;
+}
diff --git a/src/endWeek/saSocialEffects.js b/src/endWeek/saSocialEffects.js
index e8e53e7c8ea833918011f944859610f202b2fe96..bc135392d95fe43667bbde7f30c5edd24c182481 100644
--- a/src/endWeek/saSocialEffects.js
+++ b/src/endWeek/saSocialEffects.js
@@ -916,37 +916,33 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 	}
 
 	function renderTooltip() {
-		const el = document.createDocumentFragment();
+		const el = document.createElement("div");
+		el.classList.add("tip-details", "grid-2columns-auto");
 		for (const effect of socialEffects) {
-			const domLine = document.createElement('div');
-			domLine.style.display = "float";
-			const domCell = document.createElement('span');
-			domCell.style.float = "left";
-			domCell.style.width = "3em";
-
 			let text = "";
-			if (Math.round(effect.magnitude) === 0) {
-				text = '0';
-				domCell.className = "gray";
-			} else if (effect.magnitude > 0) {
+			let className;
+			if (effect.magnitude > 0) {
 				for (let i = 0; i < effect.magnitude; ++i) {
 					text += "+";
 				}
-				domCell.className = "green";
+				className = "green";
 			} else if (effect.magnitude < 0) {
 				for (let i = 0; i > effect.magnitude; --i) {
 					text += "-";
 				}
-				domCell.className = "red";
+				className = "red";
+			} else {
+				text = '0';
+				className = "gray";
 			}
-			domCell.append(text);
-			domLine.append(domCell);
+			App.UI.DOM.appendNewElement("span", el, text, className);
 
-			domLine.append(effect.shortDesc);
+			const div = document.createElement('div');
+			div.append(effect.shortDesc);
 			if (effect.FS) {
-				domLine.append(` (${effect.FS})`);
+				div.append(` (${effect.FS})`);
 			}
-			el.appendChild(domLine);
+			el.appendChild(div);
 		}
 		return el;
 	}
@@ -965,14 +961,17 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 		const sum = positiveSum + negativeSum;
 		frag.append(`Society has a `);
 		if (sum > 0) {
-			const details = new App.UI.DOM.InteractiveDetails("positive", renderTooltip, ["green", "major-link", "underline"]);
-			frag.append(details.render(), ` overall view of ${slave.slaveName} `, sumFrag, `, which improves your reputation and advances social progress.`);
+			const opinion = App.UI.DOM.makeElement("span", "positive", ["green", "major-link", "has-tooltip"]);
+			tippy(opinion, {content: renderTooltip(), placement: "right"});
+			frag.append(opinion, ` overall view of ${slave.slaveName} `, sumFrag, `, which improves your reputation and advances social progress.`);
 		} else if (sum === 0) {
-			const details = new App.UI.DOM.InteractiveDetails("neutral", renderTooltip, ["yellow", "major-link", "underline"]);
-			frag.append(details.render(), ` overall view of ${slave.slaveName} `, sumFrag, `; ${he} had no net impact on your reputation or social progress this week.`);
+			const opinion = App.UI.DOM.makeElement("span", "neutral", ["yellow", "major-link", "has-tooltip"]);
+			tippy(opinion, {content: renderTooltip(), placement: "right"});
+			frag.append(opinion, ` overall view of ${slave.slaveName} `, sumFrag, `; ${he} had no net impact on your reputation or social progress this week.`);
 		} else {
-			const details = new App.UI.DOM.InteractiveDetails("negative", renderTooltip, ["red", "major-link", "underline"]);
-			frag.append(details.render(), ` overall view of ${slave.slaveName} `, sumFrag, `, which decreases your reputation and retards social progress.`);
+			const opinion = App.UI.DOM.makeElement("span", "negative", ["red", "major-link", "has-tooltip"]);
+			tippy(opinion, {content: renderTooltip(), placement: "right"});
+			frag.append(opinion, ` overall view of ${slave.slaveName} `, sumFrag, `, which decreases your reputation and retards social progress.`);
 		}
 	}
 
diff --git a/src/gui/interactiveDetails.js b/src/gui/interactiveDetails.js
deleted file mode 100644
index 548e501761cd932478733926f30d1fcab011cbd8..0000000000000000000000000000000000000000
--- a/src/gui/interactiveDetails.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Displays a value that you can click on to get some details in an overlay
- */
-App.UI.DOM.InteractiveDetails = class {
-	/**
-	 * @param {string} linkText text to use for the show/hide link
-	 * @param {function(): HTMLElement|DocumentFragment} [detailsGenerator] function which generates the contents of the details overlay (omit to disable details)
-	 * @param {string[]} [linkClasses=[]] list of extra CSS classes to apply to the link
-	 */
-	constructor(linkText, detailsGenerator, linkClasses = []) {
-		this.span = App.UI.DOM.makeElement("span", "", "details-overlay");
-		this.span.style.visibility = "hidden";
-		this.link = detailsGenerator ? App.UI.DOM.link(linkText, () => this.toggle()) : App.UI.DOM.makeElement("span", linkText);
-		this.link.classList.add(...linkClasses);
-		this.func = detailsGenerator;
-		this.shown = false;
-	}
-
-	/**
-	 * Toggle the visibility of the overlay (changing the render state)
-	 */
-	toggle() {
-		this.shown = !this.shown;
-		if (this.shown) {
-			$(this.span).empty().append(this.func());
-			// by default, the CSS will try to show it as inline-block on the right
-			this.span.style.removeProperty("right"); // reset style in case we'd moved it last time it was shown
-			const overlayRect = this.span.getBoundingClientRect();
-			const linkRect = this.link.getBoundingClientRect();
-			if (overlayRect.right > window.innerWidth) {
-				if (overlayRect.width < linkRect.left) {
-					// if it won't fit in the default position, but it *will* fit on the left, force it to move over to the left side of the link
-					this.span.style.right = "100%";
-				}
-				// if it won't fit on either side, give up and leave it where it is. TODO: maybe do something smarter?
-			}
-			this.span.style.visibility = "visible";
-		} else {
-			$(this.span).empty();
-			this.span.style.visibility = "hidden";
-		}
-	}
-
-	/**
-	 * Render the object to the page
-	 * @returns {HTMLSpanElement}
-	 */
-	render() {
-		const containingSpan = document.createElement("span");
-		containingSpan.style.position = "relative"; // required for absolute positioning to work correctly later
-		containingSpan.append(this.link, this.span);
-		return containingSpan;
-	}
-};
diff --git a/src/js/slaveCostJS.js b/src/js/slaveCostJS.js
index 778d7f550bdff8e9eb1c1fd130d985616c0da4d1..09267c6cf09e4a80d81f602d33b186a694a15d23 100644
--- a/src/js/slaveCostJS.js
+++ b/src/js/slaveCostJS.js
@@ -1637,9 +1637,16 @@ globalThis.Beauty = function(s) {
 globalThis.BeautyTooltip = function(slave) {
 	// Make a link. Text should be slave's beauty. Clicking the link will display detailed info about that beauty over the top of the page (tooltip-style)
 	const beauty = Beauty(slave);
-	const displayFunc = V.cheatMode || V.debugMode ? BeautyDisplay : undefined;
-	const interactor = new App.UI.DOM.InteractiveDetails(beauty.toString(), displayFunc, ["pink", "bold"]);
-	return interactor.render();
+	const span = App.UI.DOM.makeElement("span", beauty.toString(), ["pink", "bold"]);
+	if (V.cheatMode || V.debugMode) {
+		span.tabIndex = 0;
+		span.classList.add("has-tooltip");
+		tippy(span, {
+			content: BeautyDisplay(),
+			placement: "right", interactive: true, trigger: "click"
+		});
+	}
+	return span;
 
 	// Upon the link being clicked, set up some links to sort the info and a span to show it in
 	function BeautyDisplay() {
@@ -1648,6 +1655,7 @@ globalThis.BeautyTooltip = function(slave) {
 
 		// Heading line that handles sorting
 		let el = document.createElement('div');
+		el.classList.add("tip-details");
 
 		el.appendChild(document.createTextNode(`Sort by: `));
 		el.appendChild(App.UI.DOM.generateLinksStrip([
@@ -1676,37 +1684,27 @@ globalThis.BeautyTooltip = function(slave) {
 
 		// Set up the frame that contains the info
 		function BeautyFrame() {
-			let el = document.createDocumentFragment();
-			let beautyArray;
+			let el = document.createElement("div");
+			el.classList.add("grid-2columns-auto");
 
+			let beautyArray;
 			if ((criteria === "text" && direction === "descending") || (criteria === "value" && direction === "ascending")) {
 				beautyArray = BeautyArray(slave).sort((a, b) => (a[criteria] > b[criteria]) ? 1 : -1);
 			} else {
 				beautyArray = BeautyArray(slave).sort((a, b) => (a[criteria] < b[criteria]) ? 1 : -1);
 			}
 
-			let domLine;
-			let domCell;
 			beautyArray.forEach((line) => {
 				line.value = (Math.floor(line.value * 10) / 10);
-				domLine = document.createElement('div');
-				domLine.style.display = "float";
-				domCell = document.createElement('span');
-				domCell.style.float = "left";
-				domCell.style.width = "3em";
 
-				let textNode = document.createTextNode(line.value);
+				let className;
 				if (line.value > 0) {
-					domCell.className = "green";
+					className = "green";
 				} else if (line.value < 0) {
-					domCell.className = "red";
+					className = "red";
 				}
-				domCell.appendChild(textNode);
-				domLine.appendChild(domCell);
-
-				textNode = document.createTextNode(line.text);
-				domLine.appendChild(textNode);
-				el.appendChild(domLine);
+				App.UI.DOM.appendNewElement("div", el, line.value.toString(), className);
+				App.UI.DOM.appendNewElement("div", el, line.text);
 			});
 			return el;
 		}
@@ -2186,9 +2184,16 @@ globalThis.FResult = function(s, forSale = 0) {
  */
 globalThis.FResultTooltip = function(slave, forSale = 0) {
 	// Make a link. Text should be slave's FResult. Clicking the link will display detailed info about that FResult over the top of the page (tooltip-style)
-	const displayFunc = V.cheatMode || V.debugMode ? FResultDisplay : undefined;
-	const interactor = new App.UI.DOM.InteractiveDetails(FResult(slave, forSale).toString(), displayFunc, ["lightcoral", "bold"]);
-	return interactor.render();
+	const span = App.UI.DOM.makeElement("span", FResult(slave, forSale).toString(), ["lightcoral", "bold"]);
+	if (V.cheatMode || V.debugMode) {
+		span.tabIndex = 0;
+		span.classList.add("has-tooltip");
+		tippy(span, {
+			content: FResultDisplay(),
+			placement: "right", interactive: true, trigger: "click"
+		});
+	}
+	return span;
 
 	/** Upon the link being clicked, set up some links to sort the info and a span to show it in
 	 * @returns {HTMLElement}
@@ -2199,6 +2204,7 @@ globalThis.FResultTooltip = function(slave, forSale = 0) {
 
 		// Heading line that handles sorting
 		const el = document.createElement('div');
+		el.classList.add("tip-details");
 
 		el.appendChild(document.createTextNode(`Sort by: `));
 		el.appendChild(App.UI.DOM.generateLinksStrip([
@@ -2225,40 +2231,31 @@ globalThis.FResultTooltip = function(slave, forSale = 0) {
 		return el;
 
 		/** Set up the frame that contains the info
-		 * @returns {DocumentFragment}
+		 * @returns {HTMLDivElement}
 		 */
 		function FResultFrame() {
-			let el = document.createDocumentFragment();
-			let fResultArray;
+			let el = document.createElement("div");
+			el.classList.add("grid-2columns-auto");
 
+			let fResultArray;
 			if ((criteria === "text" && direction === "descending") || (criteria === "value" && direction === "ascending")) {
 				fResultArray = FResultArray(slave, forSale).sort((a, b) => (a[criteria] > b[criteria]) ? 1 : -1);
 			} else {
 				fResultArray = FResultArray(slave, forSale).sort((a, b) => (a[criteria] < b[criteria]) ? 1 : -1);
 			}
 
-			let domLine;
-			let domCell;
 			fResultArray.forEach((line) => {
 				line.value = (Math.floor(line.value * 10) / 10);
-				domLine = document.createElement('div');
-				domLine.style.display = "float";
-				domCell = document.createElement('span');
-				domCell.style.float = "left";
-				domCell.style.width = "3em";
 
-				let textNode = document.createTextNode(line.value.toString());
+				let className;
 				if (line.value > 0) {
-					domCell.className = "green";
+					className = "green";
 				} else if (line.value < 0) {
-					domCell.className = "red";
+					className = "red";
 				}
-				domCell.appendChild(textNode);
-				domLine.appendChild(domCell);
+				App.UI.DOM.appendNewElement("div", el, line.value.toString(), className);
 
-				textNode = document.createTextNode(line.text);
-				domLine.appendChild(textNode);
-				el.appendChild(domLine);
+				App.UI.DOM.appendNewElement("div", el, line.text);
 			});
 			return el;
 		}