diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index c7562e4514e..6fa586e9c18 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -41,6 +41,7 @@ import { loadInstalledExtensions } from "./lib/extensions/extension-manager"; // Aliased as getHostServiceManager to minimize diff with fork's quit lifecycle code import { getHostServiceCoordinator as getHostServiceManager } from "./lib/host-service-coordinator"; import { closeLocalDb, localDb } from "./lib/local-db"; +import { requestLocalNetworkAccess } from "./lib/local-network-permission"; import { ensureProjectIconsDir, getProjectIconPath } from "./lib/project-icons"; import { reportError } from "./lib/report-error"; import { initSentry } from "./lib/sentry"; @@ -617,6 +618,7 @@ if (!gotTheLock) { await app.whenReady(); registerWithMacOSNotificationCenter(); requestAppleEventsAccess(); + requestLocalNetworkAccess(); initializeBrowserIdentityManager(); initializeBrowserWebviewCompat(); browserSitePermissionManager.initialize(); diff --git a/apps/desktop/src/main/lib/dock-icon.ts b/apps/desktop/src/main/lib/dock-icon.ts index 5971c602cf3..e3e53d801fc 100644 --- a/apps/desktop/src/main/lib/dock-icon.ts +++ b/apps/desktop/src/main/lib/dock-icon.ts @@ -4,24 +4,20 @@ import { app, nativeImage } from "electron"; import { env } from "main/env.main"; import { prerelease } from "semver"; import { getWorkspaceName } from "shared/env.shared"; -import twColors from "tailwindcss/colors"; type RGB = [number, number, number]; type Bounds = { top: number; left: number; bottom: number; right: number }; /** - * Deterministic workspace-name → RGB picker using Tailwind's 500-level palette. + * Deterministic workspace-name → RGB picker. Hashes the name to a hue via the + * golden angle (137.508°) so successive workspaces land far apart on the color + * wheel, then converts a fixed-lightness/chroma OKLCH point to sRGB. */ const pickWorkspaceColor = (() => { - const FALLBACK: RGB = [59, 130, 246]; // blue-500 - - function parseOklch(str: string): { l: number; c: number; h: number } | null { - const m = str.match(/oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)/); - return m - ? { l: Number(m[1]) / 100, c: Number(m[2]), h: Number(m[3]) } - : null; - } + const L = 0.68; + const C = 0.18; + const GOLDEN_ANGLE = 137.508; function oklchToRgb(l: number, c: number, h: number): RGB { const hRad = (h * Math.PI) / 180; @@ -47,14 +43,6 @@ const pickWorkspaceColor = (() => { ]; } - const skip = new Set(["inherit", "current", "transparent", "black", "white"]); - const palette: RGB[] = []; - for (const [name, val] of Object.entries(twColors)) { - if (skip.has(name) || typeof val !== "object" || !("500" in val)) continue; - const parsed = parseOklch((val as Record)["500"]); - if (parsed) palette.push(oklchToRgb(parsed.l, parsed.c, parsed.h)); - } - function hash(seed: string): number { let h = 0; for (let i = 0; i < seed.length; i++) { @@ -64,8 +52,10 @@ const pickWorkspaceColor = (() => { return Math.abs(h); } - return (workspaceName: string): RGB => - palette[hash(workspaceName) % palette.length] ?? FALLBACK; + return (workspaceName: string): RGB => { + const hue = (hash(workspaceName) * GOLDEN_ANGLE) % 360; + return oklchToRgb(L, C, hue); + }; })(); /** diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx index a8c6f2f03d1..b781725cd42 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx @@ -1,9 +1,12 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { cn } from "@superset/ui/utils"; import { useMatchRoute, useNavigate } from "@tanstack/react-router"; +import { HiOutlineClipboardDocumentList } from "react-icons/hi2"; import { LuFolderPlus, LuLayers, LuPlus } from "react-icons/lu"; +import { GATED_FEATURES, usePaywall } from "renderer/components/Paywall"; import { useHotkeyDisplay } from "renderer/hotkeys"; import { OrganizationDropdown } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown"; +import { useTasksFilterStore } from "renderer/routes/_authenticated/_dashboard/tasks/stores/tasks-filter-state"; import { STROKE_WIDTH_THICK } from "renderer/screens/main/components/WorkspaceSidebar/constants"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; @@ -18,12 +21,30 @@ export function DashboardSidebarHeader({ const shortcutText = useHotkeyDisplay("NEW_WORKSPACE").text; const navigate = useNavigate(); const matchRoute = useMatchRoute(); + const { gateFeature } = usePaywall(); const isWorkspacesListOpen = !!matchRoute({ to: "/v2-workspaces" }); + const isTasksOpen = !!matchRoute({ to: "/tasks", fuzzy: true }); + + const { + tab: lastTab, + assignee: lastAssignee, + search: lastSearch, + } = useTasksFilterStore(); const handleWorkspacesClick = () => { navigate({ to: "/v2-workspaces" }); }; + const handleTasksClick = () => { + gateFeature(GATED_FEATURES.TASKS, () => { + const search: Record = {}; + if (lastTab !== "all") search.tab = lastTab; + if (lastAssignee) search.assignee = lastAssignee; + if (lastSearch) search.search = lastSearch; + navigate({ to: "/tasks", search }); + }); + }; + if (isCollapsed) { return (
@@ -47,6 +68,24 @@ export function DashboardSidebarHeader({ Workspaces + + + + + Tasks + + + + - )} + setIsWaitlistOpen(true)} + /> {waitlistModal} ); diff --git a/apps/marketing/src/app/components/CTASection/CTASection.tsx b/apps/marketing/src/app/components/CTASection/CTASection.tsx index 9b4ca00e2ed..508cf4b1784 100644 --- a/apps/marketing/src/app/components/CTASection/CTASection.tsx +++ b/apps/marketing/src/app/components/CTASection/CTASection.tsx @@ -11,11 +11,8 @@ export function CTASection() { <>
-

- Get Superset Today +

+ Try Superset now.

setIsWaitlistOpen(true)} /> diff --git a/apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx b/apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx index ab70b0621d0..526ed6e0404 100644 --- a/apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx +++ b/apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx @@ -24,7 +24,7 @@ export function DownloadButton({ ? "px-2 sm:px-4 py-2 text-sm" : "px-3 sm:px-6 py-2 sm:py-3 text-sm sm:text-base"; - const buttonClasses = `bg-foreground text-background ${sizeClasses} font-normal hover:bg-foreground/80 transition-colors flex items-center gap-2 ${className}`; + const buttonClasses = `bg-brand/10 text-[#ff8c3a] border border-brand/20 ${sizeClasses} font-normal hover:bg-brand/15 hover:border-brand/35 transition-colors flex items-center gap-2 ${className}`; if (isMobile) { const appleIcon = ( diff --git a/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/FeatureDemo.tsx b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/FeatureDemo.tsx index c5179d1b0fa..749577a64b2 100644 --- a/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/FeatureDemo.tsx +++ b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/FeatureDemo.tsx @@ -1,7 +1,5 @@ -"use client"; - -import { MeshGradient } from "@superset/ui/mesh-gradient"; import type { ReactNode } from "react"; +import { DitheredBackground } from "./components/DitheredBackground"; interface FeatureDemoProps { children: ReactNode; @@ -16,12 +14,12 @@ export function FeatureDemo({ }: FeatureDemoProps) { return (
{/* Background gradient */} - {/* Content overlay */} diff --git a/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx new file mode 100644 index 00000000000..5d9958577c9 --- /dev/null +++ b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/DitheredBackground.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { lazy, Suspense } from "react"; + +const Dithering = lazy(() => + import("@paper-design/shaders-react").then((mod) => ({ + default: mod.Dithering, + })), +); + +interface DitheredBackgroundProps { + colors: readonly [string, string, string, string]; + className?: string; +} + +export function DitheredBackground({ + colors, + className = "", +}: DitheredBackgroundProps) { + return ( +
+ + + +
+ ); +} diff --git a/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/index.ts b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/index.ts new file mode 100644 index 00000000000..cfaafce3c76 --- /dev/null +++ b/apps/marketing/src/app/components/FeaturesSection/components/FeatureDemo/components/DitheredBackground/index.ts @@ -0,0 +1 @@ +export { DitheredBackground } from "./DitheredBackground"; diff --git a/apps/marketing/src/app/components/Footer/Footer.tsx b/apps/marketing/src/app/components/Footer/Footer.tsx index f4d47fbfd88..1c17d82cdc8 100644 --- a/apps/marketing/src/app/components/Footer/Footer.tsx +++ b/apps/marketing/src/app/components/Footer/Footer.tsx @@ -9,8 +9,8 @@ import { SocialLinks } from "../SocialLinks"; function SupersetLogo() { return ( @@ -33,66 +60,71 @@ export function Footer() { whileInView={{ opacity: 1 }} viewport={{ once: true }} transition={{ duration: 0.5 }} - className="max-w-7xl mx-auto px-6 sm:px-8 py-10 sm:py-14" + className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20" > - {/* Main footer content */} -
- {/* Left side - Logo and legal links */} -
+
+
- + +

+ © {new Date().getFullYear()} Superset Inc. +

- {/* Right side - Social links */} - -
- - {/* Bottom - Copyright */} -
-

- © {new Date().getFullYear()} Superset Inc. All rights reserved. -

+ + +
); } + +function FooterColumn({ + title, + links, +}: { + title: string; + links: FooterLink[]; +}) { + return ( +
+

{title}

+
    + {links.map((link) => ( +
  • + +
  • + ))} +
+
+ ); +} + +function FooterLinkItem({ link }: { link: FooterLink }) { + const className = + "group inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"; + if (link.external) { + return ( + + {link.label} + + + ); + } + return ( + + {link.label} + + ); +} diff --git a/apps/marketing/src/app/components/Header/Header.tsx b/apps/marketing/src/app/components/Header/Header.tsx index f6d150465ba..bf08bc2c5ec 100644 --- a/apps/marketing/src/app/components/Header/Header.tsx +++ b/apps/marketing/src/app/components/Header/Header.tsx @@ -1,37 +1,10 @@ "use client"; -import { COMPANY } from "@superset/shared/constants"; -import { AnimatePresence, motion } from "framer-motion"; -import { Menu, X } from "lucide-react"; +import { motion } from "framer-motion"; import Link from "next/link"; -import { useState } from "react"; - -function SupersetLogo() { - return ( - - Superset - - - ); -} - -const NAV_LINKS = [ - { href: COMPANY.DOCS_URL, label: "Docs", external: true }, - { href: "/changelog", label: "Changelog", external: false }, - { href: "/blog", label: "Blog", external: false }, - { href: "/team", label: "About", external: false }, - { href: "/community", label: "Community", external: false }, - { href: "/enterprise", label: "Enterprise", external: false }, -]; +import { DesktopNav } from "./components/DesktopNav"; +import { MobileNav } from "./components/MobileNav"; +import { SupersetLogo } from "./components/SupersetLogo"; interface HeaderProps { ctaButtons: React.ReactNode; @@ -39,115 +12,38 @@ interface HeaderProps { } export function Header({ ctaButtons, starCounter }: HeaderProps) { - const [isMenuOpen, setIsMenuOpen] = useState(false); - return (
- {/* Logo */} - - - + + + + - {/* Desktop Navigation */} - -
{ctaButtons}
+ +
+ {starCounter} +
{ctaButtons}
- {/* Mobile: Hamburger button */} - setIsMenuOpen(!isMenuOpen)} - aria-label={isMenuOpen ? "Close menu" : "Open menu"} - aria-expanded={isMenuOpen} - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - transition={{ duration: 0.3, delay: 0.1 }} - > - {isMenuOpen ? ( - - ) : ( - - )} - +
- - {/* Mobile menu */} - - {isMenuOpen && ( - -
- {NAV_LINKS.map((link) => - link.external ? ( - - {link.label} - - ) : ( - setIsMenuOpen(false)} - > - {link.label} - - ), - )} -
{starCounter}
-
- {ctaButtons} -
-
-
- )} -
); diff --git a/apps/marketing/src/app/components/Header/components/DesktopNav/DesktopNav.tsx b/apps/marketing/src/app/components/Header/components/DesktopNav/DesktopNav.tsx new file mode 100644 index 00000000000..252ccd193ed --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/DesktopNav/DesktopNav.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from "@superset/ui/navigation-menu"; +import { cn } from "@superset/ui/utils"; +import Link from "next/link"; +import { + type NavLink, + PRODUCT_LINKS, + RESOURCE_LINKS, + TOP_LEVEL_LINKS, +} from "../../constants"; +import { SupersetLogo } from "../SupersetLogo"; + +const triggerClass = cn( + navigationMenuTriggerStyle(), + "h-8 bg-transparent px-3 text-sm font-normal text-muted-foreground hover:bg-accent/40 hover:text-foreground focus:bg-accent/40 focus:text-foreground data-[state=open]:bg-accent/40 data-[state=open]:text-foreground", +); + +export function DesktopNav() { + return ( + + + + + Product + + +
+ +
    + {PRODUCT_LINKS.map((link) => ( + + ))} +
+
+
+
+ + + + Resources + + +
    + {RESOURCE_LINKS.map((link) => ( + + ))} +
+
+
+ + {TOP_LEVEL_LINKS.map((link) => ( + + + {link.label} + + + ))} +
+
+ ); +} + +function FeatureCard() { + return ( + + +
+ +
+
+

+ The terminal for coding agents +

+

+ Run 10+ parallel coding agents on your machine and switch between + tasks as they need your attention. +

+
+ +
+ ); +} + +function NavListItem({ link }: { link: NavLink }) { + const content = ( + <> +
+ {link.label} +
+ {link.description && ( +

+ {link.description} +

+ )} + + ); + + return ( +
  • + + {link.external ? ( + + {content} + + ) : ( + {content} + )} + +
  • + ); +} diff --git a/apps/marketing/src/app/components/Header/components/DesktopNav/index.ts b/apps/marketing/src/app/components/Header/components/DesktopNav/index.ts new file mode 100644 index 00000000000..dfe57058671 --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/DesktopNav/index.ts @@ -0,0 +1 @@ +export { DesktopNav } from "./DesktopNav"; diff --git a/apps/marketing/src/app/components/Header/components/MobileNav/MobileNav.tsx b/apps/marketing/src/app/components/Header/components/MobileNav/MobileNav.tsx new file mode 100644 index 00000000000..5e1006e778b --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/MobileNav/MobileNav.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { AnimatePresence, motion } from "framer-motion"; +import { Menu, X } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { + type NavLink, + PRODUCT_LINKS, + RESOURCE_LINKS, + TOP_LEVEL_LINKS, +} from "../../constants"; + +interface MobileNavProps { + ctaButtons: React.ReactNode; + starCounter?: React.ReactNode; +} + +export function MobileNav({ ctaButtons, starCounter }: MobileNavProps) { + const [isOpen, setIsOpen] = useState(false); + const close = () => setIsOpen(false); + + return ( +
    + + + + {isOpen && ( + +
    + + + +
    + {starCounter} + {ctaButtons} +
    +
    +
    + )} +
    +
    + ); +} + +function MobileSection({ + title, + links, + onNavigate, +}: { + title?: string; + links: NavLink[]; + onNavigate: () => void; +}) { + return ( +
    + {title && ( +

    + {title} +

    + )} + {links.map((link) => + link.external ? ( + + {link.label} + + ) : ( + + {link.label} + + ), + )} +
    + ); +} diff --git a/apps/marketing/src/app/components/Header/components/MobileNav/index.ts b/apps/marketing/src/app/components/Header/components/MobileNav/index.ts new file mode 100644 index 00000000000..0e0055fab18 --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/MobileNav/index.ts @@ -0,0 +1 @@ +export { MobileNav } from "./MobileNav"; diff --git a/apps/marketing/src/app/components/Header/components/SupersetLogo/SupersetLogo.tsx b/apps/marketing/src/app/components/Header/components/SupersetLogo/SupersetLogo.tsx new file mode 100644 index 00000000000..a8728055ef8 --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/SupersetLogo/SupersetLogo.tsx @@ -0,0 +1,17 @@ +export function SupersetLogo() { + return ( + + Superset + + + ); +} diff --git a/apps/marketing/src/app/components/Header/components/SupersetLogo/index.ts b/apps/marketing/src/app/components/Header/components/SupersetLogo/index.ts new file mode 100644 index 00000000000..f0b39d9214e --- /dev/null +++ b/apps/marketing/src/app/components/Header/components/SupersetLogo/index.ts @@ -0,0 +1 @@ +export { SupersetLogo } from "./SupersetLogo"; diff --git a/apps/marketing/src/app/components/Header/constants.ts b/apps/marketing/src/app/components/Header/constants.ts new file mode 100644 index 00000000000..e80941d9227 --- /dev/null +++ b/apps/marketing/src/app/components/Header/constants.ts @@ -0,0 +1,50 @@ +import { COMPANY } from "@superset/shared/constants"; + +export interface NavLink { + href: string; + label: string; + description?: string; + external?: boolean; +} + +export const PRODUCT_LINKS: NavLink[] = [ + { + href: "/", + label: "Overview", + description: "The terminal for coding agents.", + }, + { + href: "/changelog", + label: "Changelog", + description: "New releases and product updates.", + }, +]; + +export const RESOURCE_LINKS: NavLink[] = [ + { + href: COMPANY.DOCS_URL, + label: "Documentation", + description: "Guides, references, and integrations.", + external: true, + }, + { + href: "/blog", + label: "Blog", + description: "Engineering deep-dives and launches.", + }, + { + href: "/community", + label: "Community", + description: "Discord, GitHub, and office hours.", + }, + { + href: "/team", + label: "About", + description: "The people behind Superset.", + }, +]; + +export const TOP_LEVEL_LINKS: NavLink[] = [ + { href: "/pricing", label: "Pricing" }, + { href: "/enterprise", label: "Enterprise" }, +]; diff --git a/apps/marketing/src/app/components/HeroSection/HeroSection.tsx b/apps/marketing/src/app/components/HeroSection/HeroSection.tsx index 01976e7fe12..e5df199d90c 100644 --- a/apps/marketing/src/app/components/HeroSection/HeroSection.tsx +++ b/apps/marketing/src/app/components/HeroSection/HeroSection.tsx @@ -26,7 +26,9 @@ export function HeroSection() {

    - Orchestrate swarms of Claude Code, Codex, etc. in parallel. - Works for any agents. Built for the AI era. + Orchestrate 100+ coding agents in parallel. Works for any + agents. Built for the AI era.

    diff --git a/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx index 59f1608ede5..61a5c62b79c 100644 --- a/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx +++ b/apps/marketing/src/app/components/HeroSection/components/ProductDemo/ProductDemo.tsx @@ -56,12 +56,6 @@ export function ProductDemo({ scrollYProgress }: ProductDemoProps) { [containerWidth || 1, constrainedWidth || 1], ); - // Pills shift up to follow the shrinking mockup (minimal on mobile since scale is subtle) - const pillsY = useTransform( - scrollYProgress, - [0, 1], - [0, isMobile ? -6 : -40], - ); return (
    {/* Mockup with scroll-driven scale */} @@ -82,11 +76,8 @@ export function ProductDemo({ scrollYProgress }: ProductDemoProps) {
    - {/* Selector pills - below mockup, shift up as mockup scales */} - + {/* Selector pills - directly below mockup */} +
    {DEMO_OPTIONS.map((option) => ( setActiveOption(option.label as ActiveDemo)} /> ))} - +
    ); } diff --git a/apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx b/apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx index e925789b219..ec59a8961e1 100644 --- a/apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx +++ b/apps/marketing/src/app/components/HeroSection/components/TypewriterText/TypewriterText.tsx @@ -7,6 +7,7 @@ interface TextSegment { text: string; className?: string; style?: React.CSSProperties; + render?: (visibleText: string) => React.ReactNode; } interface TypewriterTextProps { @@ -71,6 +72,18 @@ export function TypewriterText({ Math.min(segment.text.length, displayedText.length - segStart), ); + if (segment.render) { + return ( + + {segment.render(visibleText)} + + ); + } + return ( + + + LinkedIn + + + + + + YouTube + + +
    ); } diff --git a/apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx b/apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx index a2c2380c78c..c4042a94ede 100644 --- a/apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx +++ b/apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx @@ -110,7 +110,7 @@ export function TrustedBySection() {
    -

    +

    Trusted by builders from

    @@ -120,7 +120,7 @@ export function TrustedBySection() { {CLIENT_LOGOS.map((client) => (
    (
    -
    -
    -
    -

    - Code 10x faster with no switching cost -

    -

    - Superset works with your existing tools. We provide - parallelization and better UX to enhance your Claude Code, - OpenCode, Cursor, etc. -

    -
    -
    - -
    -
    - {isPlaying ? ( -