+
-
-
- {/* Light mode image */}
-
- {/* Dark mode image */}
-
-
+
+
);
@@ -236,24 +210,23 @@ const BackgroundGrids = () => {
);
};
-const CollisionMechanism = React.forwardRef<
- HTMLDivElement,
- {
- containerRef: React.RefObject
;
- parentRef: React.RefObject;
- beamOptions?: {
- initialX?: number;
- translateX?: number;
- initialY?: number;
- translateY?: number;
- rotate?: number;
- className?: string;
- duration?: number;
- delay?: number;
- repeatDelay?: number;
- };
- }
->(({ parentRef, containerRef, beamOptions = {} }, ref) => {
+const CollisionMechanism = ({
+ parentRef,
+ beamOptions = {},
+}: {
+ parentRef: React.RefObject;
+ beamOptions?: {
+ initialX?: number;
+ translateX?: number;
+ initialY?: number;
+ translateY?: number;
+ rotate?: number;
+ className?: string;
+ duration?: number;
+ delay?: number;
+ repeatDelay?: number;
+ };
+}) => {
const beamRef = useRef(null);
const [collision, setCollision] = useState<{
detected: boolean;
@@ -264,14 +237,14 @@ const CollisionMechanism = React.forwardRef<
useEffect(() => {
const checkCollision = () => {
- if (beamRef.current && containerRef.current && parentRef.current && !cycleCollisionDetected) {
+ if (beamRef.current && parentRef.current && !cycleCollisionDetected) {
const beamRect = beamRef.current.getBoundingClientRect();
- const containerRect = containerRef.current.getBoundingClientRect();
const parentRect = parentRef.current.getBoundingClientRect();
+ const rightEdge = parentRect.right;
- if (beamRect.bottom >= containerRect.top) {
- const relativeX = beamRect.left - parentRect.left + beamRect.width / 2;
- const relativeY = beamRect.bottom - parentRect.top;
+ if (beamRect.right >= rightEdge - 20) {
+ const relativeX = parentRect.width - 20;
+ const relativeY = beamRect.top - parentRect.top + beamRect.height / 2;
setCollision({
detected: true,
@@ -288,7 +261,7 @@ const CollisionMechanism = React.forwardRef<
const animationInterval = setInterval(checkCollision, 100);
return () => clearInterval(animationInterval);
- }, [cycleCollisionDetected, containerRef]);
+ }, [cycleCollisionDetected, parentRef]);
useEffect(() => {
if (collision.detected && collision.coordinates) {
@@ -354,9 +327,7 @@ const CollisionMechanism = React.forwardRef<
>
);
-});
-
-CollisionMechanism.displayName = "CollisionMechanism";
+};
const Explosion = ({ ...props }: React.HTMLProps) => {
const spans = Array.from({ length: 20 }, (_, index) => ({
diff --git a/surfsense_web/components/homepage/use-cases-grid.tsx b/surfsense_web/components/homepage/use-cases-grid.tsx
new file mode 100644
index 000000000..21287f587
--- /dev/null
+++ b/surfsense_web/components/homepage/use-cases-grid.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import { AnimatePresence, motion } from "motion/react";
+import { ExpandedGifOverlay, useExpandedGif } from "@/components/ui/expanded-gif-overlay";
+
+const useCases = [
+ {
+ title: "Search & Citation",
+ description: "Ask questions and get Perplexity-style cited responses from your knowledge base.",
+ src: "/homepage/hero_tutorial/BSNCGif.gif",
+ },
+ {
+ title: "Document Mention QNA",
+ description: "Mention specific documents in your queries for targeted answers.",
+ src: "/homepage/hero_tutorial/BQnaGif_compressed.gif",
+ },
+ {
+ title: "Report Generation",
+ description: "Generate and export reports in many formats.",
+ src: "/homepage/hero_tutorial/ReportGenGif_compressed.gif",
+ },
+ {
+ title: "Podcast Generation",
+ description: "Turn your knowledge into podcasts in under 20 seconds.",
+ src: "/homepage/hero_tutorial/PodcastGenGif.gif",
+ },
+ {
+ title: "Image Generation",
+ description: "Generate images directly from your conversations.",
+ src: "/homepage/hero_tutorial/ImageGenGif.gif",
+ },
+];
+
+function UseCaseCard({
+ title,
+ description,
+ src,
+ className,
+}: {
+ title: string;
+ description: string;
+ src: string;
+ className?: string;
+}) {
+ const { expanded, open, close } = useExpandedGif();
+
+ return (
+ <>
+
+
+

+
+
+
{title}
+
{description}
+
+
+
+
+ {expanded && }
+
+ >
+ );
+}
+
+export function UseCasesGrid() {
+ return (
+
+
+
+ What You Can Do
+
+
+
+ {/* First row: 2 larger cards */}
+
+ {useCases.slice(0, 2).map((useCase) => (
+
+ ))}
+
+
+ {/* Second row: 3 equal cards */}
+
+ {useCases.slice(2).map((useCase) => (
+
+ ))}
+
+
+
+ And more coming soon.
+
+
+ );
+}
diff --git a/surfsense_web/components/tool-ui/delete-notion-page.tsx b/surfsense_web/components/tool-ui/delete-notion-page.tsx
index c7a9b282e..bc388b537 100644
--- a/surfsense_web/components/tool-ui/delete-notion-page.tsx
+++ b/surfsense_web/components/tool-ui/delete-notion-page.tsx
@@ -68,7 +68,12 @@ interface WarningResult {
message?: string;
}
-type DeleteNotionPageResult = InterruptResult | SuccessResult | ErrorResult | InfoResult | WarningResult;
+type DeleteNotionPageResult =
+ | InterruptResult
+ | SuccessResult
+ | ErrorResult
+ | InfoResult
+ | WarningResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
@@ -341,22 +346,23 @@ function SuccessCard({ result }: { result: SuccessResult }) {