(null);
+ const showFavicon =
+ faviconUrl !== undefined &&
+ !failedFaviconUrls.has(faviconUrl) &&
+ localFailedUrl !== faviconUrl;
+
+ const baseStyle = {
+ width: `${size}px`,
+ height: `${size}px`,
+ borderRadius: '4px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flex: '0 0 auto',
+ overflow: 'hidden',
+ fontFamily: APP_FONT,
+ fontSize: size <= 14 ? '8px' : '9px',
+ fontWeight: 600,
+ lineHeight: 1,
+ } as const;
+
+ if (showFavicon) {
+ return (
+
+ {
+ failedFaviconUrls.add(faviconUrl);
+ setLocalFailedUrl(faviconUrl);
+ }}
+ />
+
+ );
+ }
+
+ const initials = label ? getInitials(label) : '?';
+
+ return (
+
+ {initials.slice(0, 1)}
+
+ );
+}
+
+// -- Sidebar icon rendering --
+
+function renderSidebarIcon(icon: HeroSidebarIcon): ReactNode {
+ if (icon.kind === 'brand') {
+ return (
+
+
+ {icon.overlay === 'link' ? (
+
+
+
+ ) : null}
+
+ );
+ }
+
+ if (icon.kind === 'avatar') {
+ const tone = SIDEBAR_TONES[icon.tone] ?? SIDEBAR_TONES.gray;
+
+ return (
+
+
+ {icon.label}
+
+
+ );
+ }
+
+ const tone = SIDEBAR_TONES[icon.tone] ?? SIDEBAR_TONES.gray;
+ const TablerIcon = TABLER_ICON_MAP[icon.name];
+
+ return (
+
+ {TablerIcon ? (
+
+ ) : null}
+ {icon.overlay === 'link' ? (
+
+
+
+ ) : null}
+
+ );
+}
+
+// -- Sidebar item component --
+
+function SidebarItemComponent({
+ depth = 0,
+ interactive = true,
+ item,
+ onSelect,
+ selectedLabel,
+}: {
+ depth?: number;
+ interactive?: boolean;
+ item: HeroSidebarItem;
+ onSelect?: (label: string) => void;
+ selectedLabel?: string;
+}) {
+ const rowActive =
+ interactive && selectedLabel !== undefined && item.label === selectedLabel;
+
+ return (
+ <>
+ onSelect?.(item.label) : undefined}
+ style={{ cursor: interactive ? 'pointer' : 'default' }}
+ >
+ {renderSidebarIcon(item.icon)}
+
+ {item.label}
+ {item.meta ? ยท {item.meta} : null}
+
+ {item.showChevron || (item.children && item.children.length > 0) ? (
+
+
+
+ ) : null}
+
+ {item.children && item.children.length > 0 ? (
+
+
+ {item.children.map((child) => (
+
+ ))}
+
+ ) : null}
+ >
+ );
+}
+
+// -- Cell rendering components --
+
+function PersonTokenCell({
+ token,
+ hovered = false,
+ withCopyAction = true,
+}: {
+ token: HeroCellPerson;
+ hovered?: boolean;
+ withCopyAction?: boolean;
+}) {
+ const tone = PERSON_TONES[token.tone ?? 'gray'] ?? PERSON_TONES.gray;
+ const square =
+ token.kind === 'api' ||
+ token.kind === 'system' ||
+ token.kind === 'workflow';
+
+ return (
+
+
+ {token.avatarUrl ? (
+
+ ) : (
+ (token.shortLabel ?? getInitials(token.name))
+ )}
+
+ }
+ />
+
+ {withCopyAction ? (
+
+
+
+ ) : null}
+
+
+
+
+
+ );
+}
+
+function EntityCellComponent({
+ cell,
+ hovered,
+ isFirstColumn,
+}: {
+ cell: HeroCellEntity;
+ hovered: boolean;
+ isFirstColumn: boolean;
+}) {
+ if (isFirstColumn) {
+ return (
+
+
+
+
+ }
+ variant={ChipVariant.Highlighted}
+ />
+
+
+
+
+
+
+ );
+ }
+
+ return (
+ }
+ />
+ );
+}
+
+function RelationCellComponent({
+ cell,
+ hovered,
+}: {
+ cell: HeroCellRelation;
+ hovered: boolean;
+}) {
+ return (
+
+
+ {cell.items.map((item) => {
+ const tone = PERSON_TONES[item.tone ?? 'gray'] ?? PERSON_TONES.gray;
+
+ return (
+
+ {item.shortLabel ?? getInitials(item.name)}
+
+ }
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function renderCellValue(
+ cell: HeroCellValue,
+ hovered: boolean,
+ isFirstColumn: boolean,
+): ReactNode {
+ switch (cell.type) {
+ case 'text':
+ return {cell.value};
+ case 'number':
+ return {cell.value};
+ case 'link':
+ return (
+
+ );
+ case 'boolean':
+ return (
+
+ {cell.value ? : }
+ {cell.value ? 'True' : 'False'}
+
+ );
+ case 'tag':
+ return {cell.value};
+ case 'person':
+ return ;
+ case 'entity':
+ return (
+
+ );
+ case 'relation':
+ return ;
+ }
+}
+
+// -- Main component --
+
+export function HomeVisual({ visual }: { visual: HeroVisualType }) {
+ const shellRef = useRef(null);
+ const viewportRef = useRef(null);
+ const dragRef = useRef({
+ active: false,
+ pointerId: -1,
+ startScrollLeft: 0,
+ startX: 0,
+ });
+
+ const defaultActiveLabel =
+ visual.workspaceNav.find((entry) => !isFolder(entry) && entry.active)
+ ?.label ??
+ visual.workspaceNav[0]?.label ??
+ '';
+
+ const [activeLabel, setActiveLabel] = useState(defaultActiveLabel);
+ const [dragging, setDragging] = useState(false);
+ const [hoveredRowId, setHoveredRowId] = useState(null);
+ const [tilt, setTilt] = useState({ x: 0, y: 0 });
+
+ const activeItem = useMemo(
+ () => findActiveItem(visual.workspaceNav, activeLabel),
+ [activeLabel, visual.workspaceNav],
+ );
+
+ const columns: HeroColumnDef[] = activeItem?.columns ?? [];
+ const rows: HeroRowDef[] = activeItem?.rows ?? [];
+ const viewLabel = activeItem?.viewLabel ?? activeLabel;
+ const viewCount = activeItem?.viewCount ?? rows.length;
+ const totalTableWidth = visual.tableWidth ?? DEFAULT_TABLE_WIDTH;
+ const actions = visual.actions ?? [];
+
+ const columnWidth = columns.reduce((sum, column) => sum + column.width, 0);
+ const fillerWidth = Math.max(totalTableWidth - columnWidth, 0);
+
+ const handleShellPointerMove = (event: ReactPointerEvent) => {
+ if (event.pointerType !== 'mouse' || dragging || !shellRef.current) {
+ return;
+ }
+
+ const bounds = shellRef.current.getBoundingClientRect();
+ const horizontal = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2;
+ const vertical = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2;
+
+ setTilt({
+ x: Number((-vertical * 2.2).toFixed(2)),
+ y: Number((horizontal * 3.8).toFixed(2)),
+ });
+ };
+
+ const resetTilt = () => {
+ setTilt({ x: 0, y: 0 });
+ };
+
+ const handlePointerDown = (event: ReactPointerEvent) => {
+ if (
+ event.pointerType !== 'mouse' ||
+ event.button !== 0 ||
+ !viewportRef.current
+ ) {
+ return;
+ }
+
+ dragRef.current = {
+ active: true,
+ pointerId: event.pointerId,
+ startScrollLeft: viewportRef.current.scrollLeft,
+ startX: event.clientX,
+ };
+
+ viewportRef.current.setPointerCapture(event.pointerId);
+ setDragging(true);
+ event.preventDefault();
+ };
+
+ const handlePointerMove = (event: ReactPointerEvent) => {
+ if (!dragRef.current.active || !viewportRef.current) {
+ return;
+ }
+
+ viewportRef.current.scrollLeft =
+ dragRef.current.startScrollLeft -
+ (event.clientX - dragRef.current.startX);
+ };
+
+ const endDragging = () => {
+ dragRef.current.active = false;
+ dragRef.current.pointerId = -1;
+ setDragging(false);
+ };
+
+ const handlePointerUp = (event: ReactPointerEvent) => {
+ if (!viewportRef.current || dragRef.current.pointerId !== event.pointerId) {
+ return;
+ }
+
+ viewportRef.current.releasePointerCapture(event.pointerId);
+ endDragging();
+ };
+
+ useEffect(() => {
+ const node = viewportRef.current;
+
+ if (!node) {
+ return;
+ }
+
+ const onWheel: EventListener = (event) => {
+ if (!(event instanceof WheelEvent)) {
+ return;
+ }
+
+ if (
+ node.scrollWidth <= node.clientWidth ||
+ Math.abs(event.deltaY) <= Math.abs(event.deltaX)
+ ) {
+ return;
+ }
+
+ node.scrollLeft += event.deltaY;
+ event.preventDefault();
+ };
+
+ node.addEventListener('wheel', onWheel, { passive: false });
+
+ return () => {
+ node.removeEventListener('wheel', onWheel);
+ };
+ }, []);
+
+ const renderSidebarEntry = (entry: HeroSidebarEntry) => {
+ if (isFolder(entry)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ };
+
return (
-
-
+
+
+
+
+
+
+
+
+
+ {visual.workspace.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New chat
+
+
+
+
+ {visual.favoritesNav && visual.favoritesNav.length > 0 ? (
+
+ Favorites
+ {visual.favoritesNav.map((item) => (
+
+ ))}
+
+ ) : null}
+
+ Workspace
+ {visual.workspaceNav.map(renderSidebarEntry)}
+
+
+
+
+
+
+
+
+
+
+
+ {activeLabel}
+
+
+
+
+
+
+
+
+
+ New Record
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {viewLabel}
+
+ {viewCount}
+
+
+
+ {actions.map((action) => (
+ {action}
+ ))}
+
+
+
+
+
+
+ {rows.map((row) => (
+
+ ))}
+
+
+
+
+
+
+ {columns.map((column) => (
+
+
+ {column.isFirstColumn ? (
+ <>
+
+
+
+ {renderHeaderIcon(column.id)}
+ {column.label}
+
+
+
+ >
+ ) : (
+ <>
+ {renderHeaderIcon(column.id)}
+ {column.label}
+ >
+ )}
+
+
+ ))}
+
+ {fillerWidth > 0 ? (
+
+
+
+ ) : null}
+
+
+
+ {rows.map((row) => {
+ const hovered = hoveredRowId === row.id;
+
+ return (
+ setHoveredRowId(row.id)}
+ onMouseLeave={() =>
+ setHoveredRowId((current) =>
+ current === row.id ? null : current,
+ )
+ }
+ >
+ {columns.map((column) => {
+ const cell = row.cells[column.id];
+
+ return (
+
+ {cell
+ ? renderCellValue(
+ cell,
+ hovered,
+ !!column.isFirstColumn,
+ )
+ : null}
+
+ );
+ })}
+
+
+ );
+ })}
+
+
+ {columns.length > 0 ? (
+
+
+ Calculate
+
+
+
+ ) : null}
+ {columns.slice(1).map((column) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
);
}
+
+function renderHeaderIcon(columnId: string): ReactNode {
+ const Icon = HEADER_ICON_MAP[columnId];
+
+ if (Icon) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx
new file mode 100644
index 0000000000..d55c0a68b5
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx
@@ -0,0 +1,138 @@
+import { styled } from '@linaria/react';
+import { type ReactNode } from 'react';
+
+import { VISUAL_TOKENS } from './homeVisualTokens';
+
+export enum ChipVariant {
+ Highlighted = 'highlighted',
+ Regular = 'regular',
+ Transparent = 'transparent',
+ Rounded = 'rounded',
+ Static = 'static',
+}
+
+export type ChipProps = {
+ label: string;
+ clickable?: boolean;
+ isBold?: boolean;
+ leftComponent?: ReactNode | null;
+ className?: string;
+ maxWidth?: number;
+ variant?: ChipVariant;
+};
+
+const StyledContainer = styled.div<
+ Pick
+>`
+ --chip-horizontal-padding: ${VISUAL_TOKENS.spacing[1]};
+ --chip-vertical-padding: ${VISUAL_TOKENS.spacing[1]};
+
+ align-items: center;
+ background-color: ${({ variant }) =>
+ variant === ChipVariant.Static
+ ? VISUAL_TOKENS.background.transparent.lighter
+ : variant === ChipVariant.Highlighted
+ ? VISUAL_TOKENS.background.transparent.light
+ : 'inherit'};
+ border: ${({ variant }) =>
+ variant === ChipVariant.Static
+ ? `1px solid ${VISUAL_TOKENS.border.color.strong}`
+ : 'none'};
+ border-radius: ${({ variant }) =>
+ variant === ChipVariant.Rounded || variant === ChipVariant.Static
+ ? VISUAL_TOKENS.border.radius.pill
+ : VISUAL_TOKENS.border.radius.sm};
+ color: ${VISUAL_TOKENS.font.color.primary};
+ cursor: ${({ clickable, variant }) =>
+ variant === ChipVariant.Transparent
+ ? 'inherit'
+ : clickable
+ ? 'pointer'
+ : 'inherit'};
+ display: inline-flex;
+ gap: ${VISUAL_TOKENS.spacing[1]};
+ height: 20px;
+ justify-content: flex-start;
+ max-width: ${({ maxWidth }) =>
+ maxWidth
+ ? `calc(${maxWidth}px - 2 * var(--chip-horizontal-padding))`
+ : '100%'};
+ overflow: hidden;
+ padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
+ padding-left: ${({ variant }) =>
+ variant === ChipVariant.Transparent
+ ? VISUAL_TOKENS.spacing[0]
+ : variant === ChipVariant.Static
+ ? VISUAL_TOKENS.spacing[2]
+ : 'var(--chip-horizontal-padding)'};
+ padding-right: ${({ variant }) =>
+ variant === ChipVariant.Static
+ ? VISUAL_TOKENS.spacing[2]
+ : 'var(--chip-horizontal-padding)'};
+ user-select: none;
+
+ font-weight: ${({ isBold }) =>
+ isBold
+ ? VISUAL_TOKENS.font.weight.medium
+ : VISUAL_TOKENS.font.weight.regular};
+
+ &:hover {
+ background-color: ${({ variant }) =>
+ variant === ChipVariant.Regular
+ ? VISUAL_TOKENS.background.transparent.light
+ : variant === ChipVariant.Highlighted
+ ? VISUAL_TOKENS.background.transparent.medium
+ : variant === ChipVariant.Static
+ ? VISUAL_TOKENS.background.transparent.light
+ : 'inherit'};
+ }
+
+ &:active {
+ background-color: ${({ variant }) =>
+ variant === ChipVariant.Regular
+ ? VISUAL_TOKENS.background.transparent.medium
+ : variant === ChipVariant.Highlighted
+ ? VISUAL_TOKENS.background.transparent.strong
+ : variant === ChipVariant.Static
+ ? VISUAL_TOKENS.background.transparent.light
+ : 'inherit'};
+ }
+
+ & > svg {
+ flex-shrink: 0;
+ }
+`;
+
+const StyledLabel = styled.span`
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ max-width: 100%;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+export const Chip = ({
+ label,
+ clickable = false,
+ isBold = false,
+ leftComponent = null,
+ className,
+ maxWidth,
+ variant = ChipVariant.Regular,
+}: ChipProps) => {
+ return (
+
+ {leftComponent}
+ {label}
+
+ );
+};
diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts
new file mode 100644
index 0000000000..c553754c8e
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts
@@ -0,0 +1,67 @@
+// Resolved CSS token values that replace twenty-ui's themeCssVariables.
+// These are the light-theme defaults for Twenty's design system.
+export const VISUAL_TOKENS = {
+ font: {
+ family: "'Inter', sans-serif",
+ color: {
+ primary: '#333333',
+ secondary: '#666666',
+ tertiary: '#999999',
+ light: '#b3b3b3',
+ },
+ size: {
+ md: '13px',
+ },
+ weight: {
+ regular: '400',
+ medium: '500',
+ },
+ },
+ spacing: {
+ 0: '0px',
+ 1: '4px',
+ 2: '8px',
+ },
+ border: {
+ color: {
+ strong: '#d6d6d6',
+ medium: '#ebebeb',
+ light: '#f1f1f1',
+ blue: '#c6d4f9',
+ },
+ radius: {
+ sm: '4px',
+ pill: '999px',
+ },
+ },
+ background: {
+ noisy:
+ "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.5' numOctaves='6' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.03'/%3E%3C/svg%3E\")",
+ primary: '#ffffff',
+ secondary: '#fcfcfc',
+ transparent: {
+ primary: 'rgba(255, 255, 255, 0.9)',
+ medium: 'rgba(0, 0, 0, 0.04)',
+ light: 'rgba(0, 0, 0, 0.03)',
+ lighter: 'rgba(0, 0, 0, 0.02)',
+ strong: 'rgba(0, 0, 0, 0.07)',
+ blue: 'rgba(62, 99, 221, 0.04)',
+ },
+ },
+ accent: {
+ accent9: '#3e63dd',
+ primary: '#d9e2fc',
+ },
+ boxShadow: {
+ light:
+ '0px 2px 4px rgba(0, 0, 0, 0.04), 0px 0px 4px rgba(0, 0, 0, 0.08)',
+ },
+ icon: {
+ size: {
+ sm: '14',
+ },
+ stroke: {
+ sm: '1.6',
+ },
+ },
+} as const;
diff --git a/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts b/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts
index 4ab69897af..be90e1290b 100644
--- a/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts
+++ b/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts
@@ -1,7 +1,108 @@
-import { ImageType } from '@/design-system/components/Image/types/Image';
-import { HeroBaseDataType } from '@/sections/Hero/types/HeroBaseData';
+import type { HeroBaseDataType } from '@/sections/Hero/types/HeroBaseData';
+
+// -- Cell value types --
+
+export type HeroCellText = { type: 'text'; value: string };
+export type HeroCellNumber = { type: 'number'; value: string };
+export type HeroCellLink = { type: 'link'; value: string };
+export type HeroCellBoolean = { type: 'boolean'; value: boolean };
+export type HeroCellTag = { type: 'tag'; value: string };
+
+export type HeroCellPerson = {
+ type: 'person';
+ name: string;
+ avatarUrl?: string;
+ kind?: 'api' | 'person' | 'system' | 'workflow';
+ shortLabel?: string;
+ tone?: string;
+};
+
+export type HeroCellEntity = {
+ type: 'entity';
+ name: string;
+ domain?: string;
+};
+
+export type HeroCellRelation = {
+ type: 'relation';
+ items: { name: string; shortLabel?: string; tone?: string }[];
+};
+
+export type HeroCellValue =
+ | HeroCellText
+ | HeroCellNumber
+ | HeroCellLink
+ | HeroCellBoolean
+ | HeroCellTag
+ | HeroCellPerson
+ | HeroCellEntity
+ | HeroCellRelation;
+
+// -- Column & row --
+
+export type HeroColumnDef = {
+ id: string;
+ label: string;
+ width: number;
+ align?: 'left' | 'right';
+ isFirstColumn?: boolean;
+};
+
+export type HeroRowDef = {
+ id: string;
+ cells: Record;
+};
+
+// -- Sidebar icon --
+
+export type HeroSidebarIcon =
+ | { kind: 'tabler'; name: string; tone: string; overlay?: 'link' }
+ | { kind: 'brand'; brand: string; overlay?: 'link' }
+ | {
+ kind: 'avatar';
+ label: string;
+ tone: string;
+ shape?: 'circle' | 'square';
+ };
+
+// -- Sidebar navigation --
+
+export type HeroSidebarItem = {
+ id: string;
+ label: string;
+ icon: HeroSidebarIcon;
+ meta?: string;
+ active?: boolean;
+ showChevron?: boolean;
+ children?: HeroSidebarItem[];
+ columns?: HeroColumnDef[];
+ rows?: HeroRowDef[];
+ viewLabel?: string;
+ viewCount?: number;
+};
+
+export type HeroSidebarFolder = {
+ id: string;
+ label: string;
+ icon: HeroSidebarIcon;
+ defaultOpen?: boolean;
+ showChevron?: boolean;
+ children?: HeroSidebarItem[];
+ items: HeroSidebarItem[];
+};
+
+export type HeroSidebarEntry = HeroSidebarItem | HeroSidebarFolder;
+
+// -- Top-level visual --
+
+export type HeroVisualType = {
+ workspace: { icon: string; name: string };
+ favoritesNav?: HeroSidebarItem[];
+ workspaceNav: HeroSidebarEntry[];
+ tableWidth?: number;
+ actions?: string[];
+};
export type HeroHomeDataType = HeroBaseDataType & {
- background: ImageType;
- foreground: ImageType;
+ visual: HeroVisualType;
};
diff --git a/packages/twenty-website-new/src/sections/Hero/types/index.ts b/packages/twenty-website-new/src/sections/Hero/types/index.ts
index 66c4e59e85..c186d5606b 100644
--- a/packages/twenty-website-new/src/sections/Hero/types/index.ts
+++ b/packages/twenty-website-new/src/sections/Hero/types/index.ts
@@ -1,4 +1,22 @@
export type { HeroBaseDataType } from './HeroBaseData';
-export type { HeroHomeDataType } from './HeroHomeData';
+export type {
+ HeroCellBoolean,
+ HeroCellEntity,
+ HeroCellLink,
+ HeroCellNumber,
+ HeroCellPerson,
+ HeroCellRelation,
+ HeroCellTag,
+ HeroCellText,
+ HeroCellValue,
+ HeroColumnDef,
+ HeroHomeDataType,
+ HeroRowDef,
+ HeroSidebarEntry,
+ HeroSidebarFolder,
+ HeroSidebarIcon,
+ HeroSidebarItem,
+ HeroVisualType,
+} from './HeroHomeData';
export type { HeroIllustrationDataType } from './HeroIllustrationData';
export type { HeroWhyTwentyDataType } from './HeroWhyTwentyData';
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx
index 160e5cee6b..961a249149 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx
+++ b/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx
@@ -1,29 +1,23 @@
-import { UsersIcon } from '@/icons';
-import { theme } from "@/theme";
-import { styled } from "@linaria/react";
-import { ClientCountShape } from "./ClientCountShape";
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+import NextImage from 'next/image';
const StyledBlock = styled.div`
display: flex;
align-items: center;
gap: ${theme.spacing(2)};
- padding-top: ${theme.spacing(1)};
- padding-right: ${theme.spacing(3.75)};
- padding-bottom: ${theme.spacing(1)};
- padding-left: ${theme.spacing(1)};
- border-radius: ${theme.radius(2)};
+ height: 48px;
+ padding: ${theme.spacing(1)} ${theme.spacing(3.75)} ${theme.spacing(1)}
+ ${theme.spacing(1)};
position: relative;
overflow: clip;
flex-shrink: 0;
+ width: 157px;
`;
const ShapeWrapper = styled.div`
position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
- height: 48px;
- width: 234px;
+ inset: 0;
z-index: 0;
`;
@@ -34,12 +28,14 @@ const IconWrapper = styled.div`
width: 40px;
height: 40px;
background-color: ${theme.colors.primary.text[5]};
- border-radius: ${theme.radius(1)};
+ border-radius: 2px;
position: relative;
overflow: clip;
+ z-index: 1;
`;
const StyledText = styled.p`
+ font-family: ${theme.font.family.sans};
font-weight: ${theme.font.weight.medium};
font-size: ${theme.font.size(4)};
line-height: ${theme.lineHeight(5.5)};
@@ -57,10 +53,23 @@ export function ClientCount({ label }: ClientCountProps) {
return (
-
+
-
+
{label}
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx
index cfd4b4b70d..72fcc883c5 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx
+++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx
@@ -1,21 +1,34 @@
-import { styled } from "@linaria/react";
-import type { ReactNode } from "react";
+import type { TrustedByLogosType } from '@/sections/TrustedBy/types';
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+import NextImage from 'next/image';
const StyledLogo = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- width: 140px;
- height: inherit;
- position: relative;
+ height: 32px;
overflow: clip;
flex-shrink: 0;
+ position: relative;
+ width: 64px;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ height: 40px;
+ width: 80px;
+ }
`;
-type LogoProps = {
- children: ReactNode;
-}
+type LogoProps = TrustedByLogosType;
-export function Logo({ children }: LogoProps) {
- return {children};
+export function Logo({ fit = 'contain', src }: LogoProps) {
+ return (
+
+
+
+ );
}
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx
index f6784c807d..dfe34f0b64 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx
+++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx
@@ -1,11 +1,9 @@
-import { CLIENT_ICONS } from '@/icons';
import {
- TrustedByClientCountLabelType,
- TrustedByLogosType,
+ type TrustedByClientCountLabelType,
+ type TrustedByLogosType,
} from '@/sections/TrustedBy/types';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
-import React from 'react';
import { ClientCount } from '../ClientCount/ClientCount';
import { Logo } from '../Logo/Logo';
@@ -13,15 +11,27 @@ const StyledLogos = styled.div`
align-items: center;
display: flex;
flex-direction: column;
- row-gap: ${theme.spacing(4)};
+ gap: ${theme.spacing(5)};
justify-content: center;
position: relative;
width: 100%;
@media (min-width: ${theme.breakpoints.md}px) {
flex-direction: row;
- column-gap: ${theme.spacing(14)};
- height: 48px;
+ gap: ${theme.spacing(14)};
+ }
+`;
+
+const LogoStrip = styled.div`
+ align-items: center;
+ display: grid;
+ gap: ${theme.spacing(4)} ${theme.spacing(6)};
+ grid-template-columns: repeat(2, minmax(0, max-content));
+ justify-items: center;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ display: flex;
+ gap: ${theme.spacing(14)};
}
`;
@@ -33,22 +43,12 @@ type LogosProps = {
export function Logos({ clientCountLabel, logos }: LogosProps) {
return (
- {logos.map((logo, index) => {
- const IconComponent = CLIENT_ICONS[logo.icon];
- if (!IconComponent) return null;
-
- return (
-
- {index === 1 && }
-
-
-
-
- );
- })}
+
+ {logos.map((logo, index) => (
+
+ ))}
+
+
);
}
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx
index 6f9283343c..d0badd67bd 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx
+++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx
@@ -30,7 +30,7 @@ interface RootProps {
export function Root({ children }: RootProps) {
return (
-
+
{children}
);
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts
index 224baf100c..d535b4e646 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts
+++ b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts
@@ -1,9 +1,9 @@
-import { TrustedByClientCountLabelType } from "./TrustedByClientCountLabel";
-import { TrustedBySeparatorType } from "./TrustedBySeparator"
-import { TrustedByLogosType } from "./TrustedByLogos";
+import type { TrustedByClientCountLabelType } from './TrustedByClientCountLabel';
+import type { TrustedByLogosType } from './TrustedByLogos';
+import type { TrustedBySeparatorType } from './TrustedBySeparator';
export type TrustedByDataType = {
- separator: TrustedBySeparatorType;
- clientCountLabel: TrustedByClientCountLabelType;
- logos: TrustedByLogosType[];
-}
\ No newline at end of file
+ clientCountLabel: TrustedByClientCountLabelType;
+ logos: TrustedByLogosType[];
+ separator: TrustedBySeparatorType;
+};
diff --git a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts
index d8d018bbcb..1654f0c21f 100644
--- a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts
+++ b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts
@@ -1,3 +1,4 @@
export type TrustedByLogosType = {
- icon: string;
-}
\ No newline at end of file
+ fit?: 'contain' | 'cover';
+ src: string;
+};
diff --git a/yarn.lock b/yarn.lock
index 62c5ca04ea..2f6a7a0344 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -23445,6 +23445,17 @@ __metadata:
languageName: node
linkType: hard
+"@tabler/icons-react@npm:^3.41.1":
+ version: 3.41.1
+ resolution: "@tabler/icons-react@npm:3.41.1"
+ dependencies:
+ "@tabler/icons": "npm:3.41.1"
+ peerDependencies:
+ react: ">= 16"
+ checksum: 10c0/d103816d91df212ef6a4fc05e9f8d5f01deec2197d3accc4a398d325d1cfa8910e06b4daaf82cd08749f242c3908885ac60b21c4129b21094ece9fe0ecd46e4c
+ languageName: node
+ linkType: hard
+
"@tabler/icons@npm:3.31.0":
version: 3.31.0
resolution: "@tabler/icons@npm:3.31.0"
@@ -23452,6 +23463,13 @@ __metadata:
languageName: node
linkType: hard
+"@tabler/icons@npm:3.41.1":
+ version: 3.41.1
+ resolution: "@tabler/icons@npm:3.41.1"
+ checksum: 10c0/b1238157fe6e57c0202f1f7153621ad937a6f73ff7c0f0af5a71fcbdace18a3281cddd6632229029c3a3df7c530ae8bcefa75c18e35db4b65e7f884282762cf1
+ languageName: node
+ linkType: hard
+
"@tanstack/react-store@npm:0.7.7":
version: 0.7.7
resolution: "@tanstack/react-store@npm:0.7.7"
@@ -60236,6 +60254,7 @@ __metadata:
"@base-ui/utils": "npm:^0.2.6"
"@linaria/core": "npm:^7.0.0"
"@linaria/react": "npm:^7.0.1"
+ "@tabler/icons-react": "npm:^3.41.1"
"@types/node": "npm:^20"
"@types/react": "npm:^19"
"@types/react-dom": "npm:^19"