diff --git a/surfsense_web/app/globals.css b/surfsense_web/app/globals.css index cf6f48437..3b87e4356 100644 --- a/surfsense_web/app/globals.css +++ b/surfsense_web/app/globals.css @@ -187,5 +187,24 @@ button { background-color: hsl(var(--muted-foreground) / 0.4); } +/* Integrations section — vertical column auto-scroll */ +@keyframes integrations-scroll-up { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-50%); + } +} + +@keyframes integrations-scroll-down { + 0% { + transform: translateY(-50%); + } + 100% { + transform: translateY(0); + } +} + @source '../node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}'; @source '../node_modules/streamdown/dist/*.js'; diff --git a/surfsense_web/components/homepage/integrations.tsx b/surfsense_web/components/homepage/integrations.tsx index 53aaf624a..d8361d721 100644 --- a/surfsense_web/components/homepage/integrations.tsx +++ b/surfsense_web/components/homepage/integrations.tsx @@ -1,5 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; + +import type React from "react"; interface Integration { name: string; @@ -8,181 +9,197 @@ interface Integration { const INTEGRATIONS: Integration[] = [ // Search - { name: "Tavily", icon: "https://www.tavily.com/images/logo.svg" }, - { - name: "LinkUp", - icon: "https://framerusercontent.com/images/7zeIm6t3f1HaSltkw8upEvsD80.png?scale-down-to=512", - }, - { name: "Elasticsearch", icon: "https://cdn.simpleicons.org/elastic/00A9E5" }, + { name: "Tavily", icon: "/connectors/tavily.svg" }, + { name: "Elasticsearch", icon: "/connectors/elasticsearch.svg" }, + { name: "Baidu Search", icon: "/connectors/baidu-search.svg" }, + { name: "SearXNG", icon: "/connectors/searxng.svg" }, // Communication - { - name: "Slack", - icon: "https://upload.wikimedia.org/wikipedia/commons/d/d5/Slack_icon_2019.svg", - }, - { name: "Discord", icon: "https://cdn.simpleicons.org/discord/5865F2" }, - { name: "Gmail", icon: "https://cdn.simpleicons.org/gmail/EA4335" }, + { name: "Slack", icon: "/connectors/slack.svg" }, + { name: "Discord", icon: "/connectors/discord.svg" }, + { name: "Gmail", icon: "/connectors/google-gmail.svg" }, + { name: "Microsoft Teams", icon: "/connectors/microsoft-teams.svg" }, // Project Management - { name: "Linear", icon: "https://cdn.simpleicons.org/linear/5E6AD2" }, - { name: "Jira", icon: "https://cdn.simpleicons.org/jira/0052CC" }, - { name: "ClickUp", icon: "https://cdn.simpleicons.org/clickup/7B68EE" }, - { name: "Airtable", icon: "https://cdn.simpleicons.org/airtable/18BFFF" }, + { name: "Linear", icon: "/connectors/linear.svg" }, + { name: "Jira", icon: "/connectors/jira.svg" }, + { name: "ClickUp", icon: "/connectors/clickup.svg" }, + { name: "Airtable", icon: "/connectors/airtable.svg" }, // Documentation & Knowledge - { name: "Confluence", icon: "https://cdn.simpleicons.org/confluence/172B4D" }, - { name: "Notion", icon: "https://cdn.simpleicons.org/notion/000000/ffffff" }, - { name: "Web Pages", icon: "https://cdn.jsdelivr.net/npm/lucide-static@0.294.0/icons/globe.svg" }, + { name: "Confluence", icon: "/connectors/confluence.svg" }, + { name: "Notion", icon: "/connectors/notion.svg" }, + { name: "BookStack", icon: "/connectors/bookstack.svg" }, + { name: "Obsidian", icon: "/connectors/obsidian.svg" }, // Cloud Storage - { name: "Google Drive", icon: "https://cdn.simpleicons.org/googledrive/4285F4" }, - { name: "Dropbox", icon: "https://cdn.simpleicons.org/dropbox/0061FF" }, - { - name: "Amazon S3", - icon: "https://upload.wikimedia.org/wikipedia/commons/b/bc/Amazon-S3-Logo.svg", - }, + { name: "Google Drive", icon: "/connectors/google-drive.svg" }, // Development - { name: "GitHub", icon: "https://cdn.simpleicons.org/github/181717/ffffff" }, + { name: "GitHub", icon: "/connectors/github.svg" }, // Productivity - { name: "Google Calendar", icon: "https://cdn.simpleicons.org/googlecalendar/4285F4" }, - { name: "Luma", icon: "https://images.lumacdn.com/social-images/default-social-202407.png" }, + { name: "Google Calendar", icon: "/connectors/google-calendar.svg" }, + { name: "Luma", icon: "/connectors/luma.svg" }, // Media - { name: "YouTube", icon: "https://cdn.simpleicons.org/youtube/FF0000" }, + { name: "YouTube", icon: "/connectors/youtube.svg" }, ]; -function SemiCircleOrbit({ radius, centerX, centerY, count, iconSize, startIndex }: any) { +// 5 vertical columns — 21 icons spread across categories +const COLUMNS: number[][] = [ + [2, 5, 10, 0, 11], + [1, 7, 20, 17], + [13, 6, 4, 16], + [12, 8, 15, 18], + [3, 9, 14, 19], +]; + +// Different scroll speeds per column for organic feel (seconds) +const SCROLL_DURATIONS = [26, 32, 22, 30, 28]; + +function IntegrationCard({ integration }: { integration: Integration }) { return ( - <> - {/* Semi-circle glow background */} -
-
+
+ {integration.name} +
+ ); +} + +function ScrollingColumn({ + cards, + scrollUp, + duration, + colIndex, + isEdge, + isEdgeAdjacent, +}: { + cards: number[]; + scrollUp: boolean; + duration: number; + colIndex: number; + isEdge: boolean; + isEdgeAdjacent: boolean; +}) { + // Edge columns get a heavy vertical mask; edge-adjacent columns get a lighter one to smooth the transition + const columnMask = isEdge + ? { + maskImage: + "linear-gradient(to bottom, transparent 0%, transparent 20%, black 40%, black 60%, transparent 80%, transparent 100%)", + WebkitMaskImage: + "linear-gradient(to bottom, transparent 0%, transparent 20%, black 40%, black 60%, transparent 80%, transparent 100%)", + } + : isEdgeAdjacent + ? { + maskImage: + "linear-gradient(to bottom, transparent 0%, transparent 10%, black 30%, black 70%, transparent 90%, transparent 100%)", + WebkitMaskImage: + "linear-gradient(to bottom, transparent 0%, transparent 10%, black 30%, black 70%, transparent 90%, transparent 100%)", + } + : {}; + + const cardSet = cards.map((integrationIndex, i) => ( + + )); + + return ( +
+ {/* Outer div has NO gap — each inner copy uses pb matching the gap so both halves are identical in height → seamless -50% loop */} +
+
+ {cardSet} +
+
+ {cardSet} +
- - {/* Orbit icons */} - {Array.from({ length: count }).map((_, index) => { - const actualIndex = startIndex + index; - // Skip if we've run out of integrations - if (actualIndex >= INTEGRATIONS.length) return null; - - const angle = (index / (count - 1)) * 180; - const x = radius * Math.cos((angle * Math.PI) / 180); - const y = radius * Math.sin((angle * Math.PI) / 180); - const integration = INTEGRATIONS[actualIndex]; - - // Tooltip positioning — above or below based on angle - const tooltipAbove = angle > 90; - - return ( -
- {integration.name} - - {/* Tooltip */} - -
- ); - })} - +
); } export default function ExternalIntegrations() { - const [size, setSize] = useState({ width: 0, height: 0 }); - - useEffect(() => { - const updateSize = () => setSize({ width: window.innerWidth, height: window.innerHeight }); - updateSize(); - window.addEventListener("resize", updateSize); - return () => window.removeEventListener("resize", updateSize); - }, []); - - const baseWidth = Math.min(size.width * 0.8, 700); - const centerX = baseWidth / 2; - const centerY = baseWidth * 0.5; - - const iconSize = - size.width < 480 - ? Math.max(24, baseWidth * 0.05) - : size.width < 768 - ? Math.max(28, baseWidth * 0.06) - : Math.max(32, baseWidth * 0.07); - return ( -
-
-

Integrations

-

- Integrate with your team's most important tools -

+
+ {/* Heading */} +
+

+ Integrate with your +
+ team's most important tools +

+
-
- - - + {/* 5 scrolling columns */} +
+ {COLUMNS.map((column, colIndex) => ( + + ))}
diff --git a/surfsense_web/public/connectors/composio.svg b/surfsense_web/public/connectors/composio.svg deleted file mode 100644 index 7c06babeb..000000000 --- a/surfsense_web/public/connectors/composio.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - -