Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
63d3cca
feat: implement Micro-Interaction Ads Platform with branded component…
tomeredlich Feb 11, 2026
26dc5d7
fix: lint, ts, build
capJavert Mar 18, 2026
99c53cd
feat: connect data and events, mock boot still, remove everything unused
capJavert Mar 20, 2026
3fccba7
feat: connect real boot data
capJavert Apr 16, 2026
e15e8e1
fix: spacing
capJavert Apr 16, 2026
927c8e6
chore: strict exclude
capJavert Apr 16, 2026
179f828
fix: review feedback for engagement ads UI
capJavert Apr 23, 2026
df9818a
fix: hero in light mode
capJavert Apr 23, 2026
9adb435
feat: user stack support in profile
capJavert Apr 23, 2026
3808a5d
Merge branch 'main' into Micro-interactions-ads
capJavert Apr 23, 2026
812faa4
feat: cleanup and pr feedback
capJavert Apr 24, 2026
4bb3a2a
feat: cleanup and pr feedback 2
capJavert Apr 24, 2026
40a1856
chore: cleanup ts
capJavert Apr 24, 2026
06838a5
fix: tests/lint
capJavert Apr 24, 2026
0a158c1
chore: cleanup
capJavert Apr 24, 2026
1238578
feat: component tests
capJavert Apr 24, 2026
b23f19f
fix: lint
capJavert Apr 24, 2026
10aa6e8
fix: tool item label order
capJavert Apr 24, 2026
8feacc0
feat: restore roadmap.sh integration on tags page
capJavert Apr 27, 2026
a7f6bca
feat: color for cta
capJavert Apr 27, 2026
e995b66
fix: spacing hero
capJavert Apr 27, 2026
5111461
Merge branch 'main' into Micro-interactions-ads
capJavert Apr 27, 2026
6095eb5
Merge branch 'main' into Micro-interactions-ads
capJavert Apr 27, 2026
dbc98de
fix: header width
capJavert Apr 27, 2026
df9058f
feat: disable ega
capJavert Apr 27, 2026
9865c55
feat: add user stack
capJavert Apr 27, 2026
cdc125a
feat: additional events
capJavert Apr 27, 2026
d468d9a
Merge branch 'main' into Micro-interactions-ads
capJavert Apr 28, 2026
27a7f5e
feat: add hover for branded tag and tool
capJavert Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ figma-images/
node-compile-cache/

# Plan files
plans/*.md
plans/*.md
.cursor/plans/
14 changes: 14 additions & 0 deletions packages/shared/src/components/FeedItemComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import { LogExtraContextProvider } from '../contexts/LogExtraContext';
import { SquadAdList } from './cards/ad/squad/SquadAdList';
import { SquadAdGrid } from './cards/ad/squad/SquadAdGrid';
import { adLogEvent, feedHighlightsLogEvent, feedLogExtra } from '../lib/feed';
import { findCreativeForTags } from '../lib/engagementAds';
import { useEngagementAdsContext } from '../contexts/EngagementAdsContext';
import { useLogContext } from '../contexts/LogContext';
import { MarketingCtaVariant } from './marketingCta/common';
import { MarketingCtaBriefing } from './marketingCta/MarketingCtaBriefing';
Expand Down Expand Up @@ -192,6 +194,7 @@ export const withFeedLogExtraContext = (
props: FeedItemComponentProps,
): ReactElement | null => {
const { item } = props;
const { creatives } = useEngagementAdsContext();

if ([FeedItemType.Ad, FeedItemType.Post].includes(item?.type)) {
return (
Expand All @@ -213,6 +216,17 @@ export const withFeedLogExtraContext = (
extraData.referrer_target_type = post?.id
? TargetType.Post
: undefined;

if (
item.type === FeedItemType.Post &&
post?.tags &&
creatives.length > 0
) {
const creative = findCreativeForTags(creatives, post.tags);
if (creative) {
extraData.gen_id = creative.genId;
}
}
}

if (isBoostedSquadAd(item)) {
Expand Down
62 changes: 62 additions & 0 deletions packages/shared/src/components/brand/BrandedTag.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* BrandedTag - Micro-interaction animations for sponsored tags */

.brandedTag {
cursor: pointer;
transition: transform 0.2s ease;
}

.brandedTag:hover {
transform: scale(1.02);
}

/* Tag content */
.tagContent {
opacity: 1;
transform: translateY(0);
}

/* Branded content */
.brandedContent {
opacity: 0;
transform: scale(0.9);
}

.brandedContent.fadeIn {
opacity: 1;
transform: scale(1);
}

/* Brand logo pulse effect */
.brandLogo {
animation: logoPulse 2s ease-in-out infinite;
}

@keyframes logoPulse {
0%, 100% {
transform: scale(1);
filter: drop-shadow(0 0 0 transparent);
}
50% {
transform: scale(1.1);
filter: drop-shadow(0 0 4px currentColor);
}
}

/* Brand text */
.brandText {
white-space: nowrap;
}

/* Animated (branded) state — continuous glow, no entrance animation */
.animated {
animation: tagGlow 2s ease-in-out infinite;
}

@keyframes tagGlow {
0%, 100% {
box-shadow: 0 0 0 0 transparent;
}
50% {
box-shadow: 0 0 8px 0 var(--brand-primary, #6e40c9);
}
}
74 changes: 74 additions & 0 deletions packages/shared/src/components/brand/BrandedTag.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { QueryClient } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react';
import { TestBootProvider } from '../../../__tests__/helpers/boot';
import { defaultQueryClientTestingConfig } from '../../../__tests__/helpers/tanstack-query';
import type { EngagementCreative } from '../../lib/engagementAds';
import { EngagementAdsProvider } from '../../contexts/EngagementAdsContext';
import { BrandedTag } from './BrandedTag';

const creative: EngagementCreative = {
gen_id: 'c1',
promoted_name: 'Copilot',
promoted_body: 'AI pair programming',
promoted_cta: 'Try free',
promoted_url: 'https://example.com',
promoted_logo_img: {
dark: 'https://example.com/logo-dark.png',
light: 'https://example.com/logo-light.png',
},
promoted_icon_img: {
dark: 'https://example.com/icon-dark.png',
light: 'https://example.com/icon-light.png',
},
promoted_gradient_start: { dark: '#000', light: '#fff' },
promoted_gradient_end: { dark: '#111', light: '#eee' },
tools: ['vscode'],
keywords: ['AI'],
tags: ['ai'],
};

let queryClient: QueryClient;

beforeEach(() => {
queryClient = new QueryClient(defaultQueryClientTestingConfig);
});

const renderTag = (tag: string, creatives: EngagementCreative[] = []) =>
render(
<TestBootProvider client={queryClient}>
<EngagementAdsProvider rawCreatives={creatives}>
<BrandedTag tag={tag} />
</EngagementAdsProvider>
</TestBootProvider>,
);

describe('BrandedTag', () => {
it('renders plain #tag when no creative matches', () => {
renderTag('react');
expect(screen.getByText('#react')).toBeInTheDocument();
expect(screen.queryByAltText('Copilot')).not.toBeInTheDocument();
});

it('renders brand name and logo when the tag is sponsored', () => {
renderTag('ai', [creative]);
expect(screen.getByText('#ai - powered by Copilot')).toBeInTheDocument();
// Don't pin the theme: either dark or light resolution is acceptable
expect(screen.getByAltText('Copilot')).toHaveAttribute(
'src',
expect.stringMatching(/https:\/\/example\.com\/logo-(dark|light)\.png/),
);
});

it('falls back to plain tag when branding is disabled', () => {
render(
<TestBootProvider client={queryClient}>
<EngagementAdsProvider rawCreatives={[creative]}>
<BrandedTag tag="ai" disableBranding />
</EngagementAdsProvider>
</TestBootProvider>,
);
expect(screen.getByText('#ai')).toBeInTheDocument();
expect(screen.queryByText(/powered by/)).not.toBeInTheDocument();
});
});
Loading
Loading