diff --git a/packages/lint/src/rules/gsap.test.ts b/packages/lint/src/rules/gsap.test.ts
index 33f3083c60..ebb0e7e7dc 100644
--- a/packages/lint/src/rules/gsap.test.ts
+++ b/packages/lint/src/rules/gsap.test.ts
@@ -480,6 +480,32 @@ describe("GSAP rules", () => {
expect(finding?.selector).toBe("#hero");
});
+ it("quotes a combined scale+translate declaration once", async () => {
+ const html = `
+
+
+
+
+`;
+ const result = await lintHyperframeHtml(html);
+ const finding = result.findings.find((f) => f.code === "gsap_css_transform_conflict");
+ expect(finding).toBeDefined();
+ // One declaration matches both the translate and the scale selector map,
+ // so the two lookups return the same text and it must not be repeated.
+ const transform = "scale(1.08) translate3d(1.5%, 0, 0)";
+ expect(finding?.message.split(transform)).toHaveLength(2);
+ expect(finding?.fixHint?.split(transform)).toHaveLength(2);
+ });
+
it("does NOT warn when tl.to targets element without CSS transform", async () => {
const html = `
diff --git a/packages/lint/src/rules/gsap.ts b/packages/lint/src/rules/gsap.ts
index c87d75e136..9584e14a32 100644
--- a/packages/lint/src/rules/gsap.ts
+++ b/packages/lint/src/rules/gsap.ts
@@ -1332,7 +1332,10 @@ export const gsapRules: LintRule[] = [
scaleProps.length > 0 ? matchCssTransform(sel, cssScaleSelectors) : undefined;
if (!cssFromTranslate && !cssFromScale) continue;
const existing = conflicts.get(sel) ?? {
- cssTransform: [cssFromTranslate, cssFromScale].filter(Boolean).join(" "),
+ // A single declaration such as `transform: scale(...) translate3d(...)`
+ // matches both selector maps, so the two lookups return the same text.
+ // Dedupe before joining, or the message quotes it twice.
+ cssTransform: [...new Set([cssFromTranslate, cssFromScale].filter(Boolean))].join(" "),
props: new Set(),
raw: call.raw,
};