diff --git a/src/components/CigaretteEquivalent.jsx b/src/components/CigaretteEquivalent.jsx
new file mode 100644
index 0000000..79090cb
--- /dev/null
+++ b/src/components/CigaretteEquivalent.jsx
@@ -0,0 +1,29 @@
+import { aqiToCigarettes, pm25ToCigarettes } from "../utils/cigaretteEquivalent";
+
+/**
+ * Surface the "cigarettes/day" equivalent of the current air quality — a
+ * visceral way to read an AQI number. Pass either `aqi` or a raw `pm25`
+ * concentration. Renders nothing when the equivalent rounds to zero (clean
+ * air) so it never shows a meaningless "0 cigarettes".
+ *
+ * @param {{ aqi?: number, pm25?: number }} params
+ */
+export default function CigaretteEquivalent({ aqi, pm25 }) {
+ const cigarettes =
+ typeof pm25 === "number" ? pm25ToCigarettes(pm25) : aqiToCigarettes(aqi);
+
+ if (!cigarettes) return null;
+
+ const label = cigarettes === 1 ? "cigarette" : "cigarettes";
+
+ return (
+
+
+ 🚬
+
+
+ ≈ {cigarettes} {label}/day
+
+
+ );
+}
diff --git a/src/components/CigaretteEquivalent.test.jsx b/src/components/CigaretteEquivalent.test.jsx
new file mode 100644
index 0000000..dc30f29
--- /dev/null
+++ b/src/components/CigaretteEquivalent.test.jsx
@@ -0,0 +1,22 @@
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import CigaretteEquivalent from "./CigaretteEquivalent";
+
+describe("CigaretteEquivalent", () => {
+ it("renders the equivalent for a polluted AQI", () => {
+ render();
+ expect(screen.getByTestId("cigarette-equivalent")).toBeInTheDocument();
+ expect(screen.getByText("1.6")).toBeInTheDocument();
+ });
+
+ it("accepts a raw pm25 value", () => {
+ render();
+ expect(screen.getByText("1")).toBeInTheDocument();
+ expect(screen.getByText(/cigarette\/day/)).toBeInTheDocument();
+ });
+
+ it("renders nothing for clean air (AQI 0)", () => {
+ const { container } = render();
+ expect(container).toBeEmptyDOMElement();
+ });
+});
diff --git a/src/utils/cigaretteEquivalent.js b/src/utils/cigaretteEquivalent.js
new file mode 100644
index 0000000..0a78026
--- /dev/null
+++ b/src/utils/cigaretteEquivalent.js
@@ -0,0 +1,36 @@
+import { aqiToPm25 } from "./exposureModel";
+
+/**
+ * Berkeley Earth rule of thumb: breathing ~22 µg/m³ of PM2.5 for a day is
+ * roughly equivalent, in particulate intake, to smoking one cigarette.
+ */
+export const PM25_PER_CIGARETTE = 22;
+
+/**
+ * Convert a PM2.5 concentration to an equivalent number of cigarettes per day.
+ *
+ * @param {number} pm25 - PM2.5 concentration in µg/m³.
+ * @returns {number} Equivalent cigarettes/day, rounded to 1 decimal. Non-finite
+ * or non-positive input returns 0.
+ * @example
+ * pm25ToCigarettes(22); // 1
+ * pm25ToCigarettes(220); // 10
+ */
+export function pm25ToCigarettes(pm25) {
+ if (typeof pm25 !== "number" || !Number.isFinite(pm25) || pm25 <= 0) return 0;
+ return Math.round((pm25 / PM25_PER_CIGARETTE) * 10) / 10;
+}
+
+/**
+ * Convert a US AQI value to an equivalent number of cigarettes per day, by first
+ * mapping AQI back to a PM2.5 concentration.
+ *
+ * @param {number} aqi - US AQI value.
+ * @returns {number} Equivalent cigarettes/day, rounded to 1 decimal.
+ * @example
+ * aqiToCigarettes(50); // 0.5 (12 µg/m³)
+ * aqiToCigarettes(100); // 1.6 (35.4 µg/m³)
+ */
+export function aqiToCigarettes(aqi) {
+ return pm25ToCigarettes(aqiToPm25(aqi));
+}
diff --git a/src/utils/cigaretteEquivalent.test.js b/src/utils/cigaretteEquivalent.test.js
new file mode 100644
index 0000000..a3f5068
--- /dev/null
+++ b/src/utils/cigaretteEquivalent.test.js
@@ -0,0 +1,36 @@
+import { describe, it, expect } from "vitest";
+import { pm25ToCigarettes, aqiToCigarettes } from "./cigaretteEquivalent";
+
+describe("pm25ToCigarettes", () => {
+ it("maps the reference concentration to one cigarette", () => {
+ expect(pm25ToCigarettes(22)).toBe(1);
+ });
+
+ it("scales linearly", () => {
+ expect(pm25ToCigarettes(11)).toBe(0.5);
+ expect(pm25ToCigarettes(220)).toBe(10);
+ });
+
+ it("rounds to one decimal", () => {
+ expect(pm25ToCigarettes(35.4)).toBe(1.6);
+ });
+
+ it("returns 0 for non-positive, non-finite, or non-number input", () => {
+ expect(pm25ToCigarettes(0)).toBe(0);
+ expect(pm25ToCigarettes(-5)).toBe(0);
+ expect(pm25ToCigarettes(NaN)).toBe(0);
+ expect(pm25ToCigarettes("12")).toBe(0);
+ expect(pm25ToCigarettes(undefined)).toBe(0);
+ });
+});
+
+describe("aqiToCigarettes", () => {
+ it("converts AQI via PM2.5", () => {
+ expect(aqiToCigarettes(50)).toBe(0.5); // 12 µg/m³
+ expect(aqiToCigarettes(100)).toBe(1.6); // 35.4 µg/m³
+ });
+
+ it("returns 0 at AQI 0", () => {
+ expect(aqiToCigarettes(0)).toBe(0);
+ });
+});