diff --git a/app/_components/blog/BlogList.tsx b/app/_components/blog/BlogList.tsx index 55397d5..5c9089c 100644 --- a/app/_components/blog/BlogList.tsx +++ b/app/_components/blog/BlogList.tsx @@ -16,7 +16,7 @@ export default function BlogList({ posts }: { posts: Item[] }) { if (filteredPosts.length === 0) { return ( -

No results found.

+

No results found.

) } diff --git a/app/_components/common/Player.tsx b/app/_components/common/Player.tsx index c7b2880..b4ee990 100644 --- a/app/_components/common/Player.tsx +++ b/app/_components/common/Player.tsx @@ -8,17 +8,23 @@ import { Maximize2, Minimize2, Pause, Play, Volume2, VolumeX } from 'lucide-reac import dynamic from 'next/dynamic' import { ReactPlayerProps } from 'react-player' +type ControlOption = "muted" | "full-screen" | "play" | "progress" | "watch"; + const ReactPlayer = dynamic(() => import('react-player'), { ssr: false }) -interface PlayerProps extends ReactPlayerProps { - aspect?: "square" | "video" +interface PlayerProps extends Omit { + aspect?: "square" | "video"; + controls?: ControlOption[] | boolean; } -export default function Player({ aspect = "video", playing: initialPlaying, muted: initialMuted, ...props }: PlayerProps) { +export default function Player({ aspect = "video", controls = true, playing: initialPlaying, muted: initialMuted, ...props }: PlayerProps) { const { containerRef, playerRef, playing, muted, played, duration, isFullScreen, handleProgress, handleDuration, handlePlayPause, handleMuted, handleSeekChange, handleSeekMouseDown, handleSeekMouseUp, handleFullscreen } = usePlayer({ playing: initialPlaying, muted: initialMuted }); const buttonStyle = cx("w-11 h-11 flex items-center justify-center rounded-full bg-[#67675780] outline-[0.8px] outline-[#FFFFFF] -outline-offset-[3px] hover:outline-[#06474C] text-[#FFFFFF] duration-200 transition-all hover:bg-[#FFFFFF] hover:text-[#06474C] cursor-pointer") + const showControl = (name: ControlOption) => + controls === true || (Array.isArray(controls) && controls.includes(name)); + return (
- - - {secondsToMinutes((played * duration).toFixed(1))} / {secondsToMinutes(duration.toFixed(1))} - + {showControl("play") && ( + + )} + {showControl("muted") && ( + + {secondsToMinutes((played * duration).toFixed(1))} / {secondsToMinutes(duration.toFixed(1))} + + )}
- + {showControl("muted") && ( + + )} - + {showControl("full-screen") && ( + + )}
- + {showControl("progress") && ( + + )}
) diff --git a/app/_components/home/Client.tsx b/app/_components/home/Client.tsx index 70397ec..55ddb38 100644 --- a/app/_components/home/Client.tsx +++ b/app/_components/home/Client.tsx @@ -24,7 +24,7 @@ export default function HomeClient({ posts }: { posts: Item[] }) { -
+
diff --git a/app/_components/home/HowItWorks.tsx b/app/_components/home/HowItWorks.tsx index f3f5e92..2fa734c 100644 --- a/app/_components/home/HowItWorks.tsx +++ b/app/_components/home/HowItWorks.tsx @@ -5,8 +5,11 @@ import { Button } from "../common/button"; import Image from "next/image"; import { openToast } from "../common/toast"; import { addBasePath } from "@/app/_lib/add-base-path"; +import { useScreenSize } from "@/app/_hooks/useScreenSize"; export default function HowItWorks() { + const { isMobile } = useScreenSize(); + return (

Coming soon

@@ -21,7 +24,16 @@ export default function HowItWorks() { -
+ + {isMobile ?
+ thumbnail +
:
thumbnail -
+
} + {/*
+ thumbnail +
*/}
); } diff --git a/app/_components/home/Roadmap.tsx b/app/_components/home/Roadmap.tsx index 7f48db4..795da44 100644 --- a/app/_components/home/Roadmap.tsx +++ b/app/_components/home/Roadmap.tsx @@ -6,7 +6,7 @@ import { ROADMAP } from "@/app/_constants/roadmap"; export default function Roadmap() { return (
-
+

Roadmap

diff --git a/app/_components/home/Welcome.tsx b/app/_components/home/Welcome.tsx index e2d677a..63aa9d9 100644 --- a/app/_components/home/Welcome.tsx +++ b/app/_components/home/Welcome.tsx @@ -1,9 +1,11 @@ import { useState } from "react"; import Player from "../common/Player"; import { PlayCircle } from "lucide-react"; +import { useScreenSize } from "@/app/_hooks/useScreenSize"; export default function Welcome() { const [viewPromotion, setViewPromotion] = useState(false); + const { isMobile } = useScreenSize(); return (
@@ -22,8 +24,8 @@ export default function Welcome() { {viewPromotion ? ( ) : (
@@ -37,9 +39,9 @@ export default function Welcome() { -
) diff --git a/app/_hooks/useMediaQuery.ts b/app/_hooks/useMediaQuery.ts new file mode 100644 index 0000000..91f40f3 --- /dev/null +++ b/app/_hooks/useMediaQuery.ts @@ -0,0 +1,44 @@ +import { useIsomorphicLayoutEffect } from "framer-motion"; +import React from "react"; + +type UseMediaQueryOptions = { + defaultValue?: boolean; + initializeWithValue?: boolean; +}; + +export function useMediaQuery( + query: string, + { + defaultValue = false, + initializeWithValue = true, + }: UseMediaQueryOptions = {}, +): boolean { + const isServer = typeof window === "undefined"; + + const getMatches = (query: string): boolean => { + if (isServer) return defaultValue; + return window.matchMedia(query).matches; + }; + + const [matches, setMatches] = React.useState(() => { + if (initializeWithValue) { + return getMatches(query); + } + return defaultValue; + }); + + const handleChange = () => setMatches(getMatches(query)); + + useIsomorphicLayoutEffect(() => { + if (isServer) return; + + const matchMedia = window.matchMedia(query); + + handleChange(); + matchMedia.addEventListener("change", handleChange); + + return () => matchMedia.removeEventListener("change", handleChange); + }, [query]); + + return matches; +} diff --git a/app/_hooks/useScreenSize.ts b/app/_hooks/useScreenSize.ts new file mode 100644 index 0000000..0e4ad34 --- /dev/null +++ b/app/_hooks/useScreenSize.ts @@ -0,0 +1,16 @@ +import { useMediaQuery } from "./useMediaQuery"; + +export function useScreenSize() { + const sm = useMediaQuery(`(max-width: ${980 - 1}px)`); + const md = useMediaQuery( + `(min-width: ${980}px) and (max-width: ${1280 - 1}px)`, + ); + const lg = useMediaQuery(`(min-width: ${1280}px)`); + + return { + bp: sm ? "sm" : md ? "md" : "lg", + isMobile: sm, + isTablet: md, + isDesktop: lg, + }; +} diff --git a/content/meet-our-new-member.mdx b/content/meet-our-new-member.mdx index 670446f..7524d00 100644 --- a/content/meet-our-new-member.mdx +++ b/content/meet-our-new-member.mdx @@ -35,7 +35,13 @@ But what if we could leverage AI technology to bring the conversational depth an # Real Thoughts Emerge Through Conversations - + The Wrtn Labs team leveraged [**Agentica**](https://wrtnlabs.io/agentica/) to develop and test an interactive **Interview AI** that dynamically generates follow-up questions based on user responses. The approach involves clearly defining interview goals, the type of information desired, and the insights valuable to the product team. If a user provides a vague or brief response, the AI agent is designed to ask follow-up questions to gather more detailed and relevant information. @@ -47,7 +53,13 @@ Yet, not every user initially provided detailed feedback. Those without specific # Can AI Really Ask the Right Questions to User? - + To effectively capture interview objectives and desired information, while dynamically generating appropriate follow-up questions based on user responses, meticulous refinement of the prompts was necessary. We analyzed questions and response patterns from actual face-to-face interviews and reflected various examples of interview guides, primary questions, and potential follow-up questions in the prompts. @@ -59,7 +71,13 @@ Of course, due to the nature of Large Language Models (LLMs), prompts are not re # Organizing Insights Through AI Agents - + However, unlike structured surveys, conversational data gathered from many users proved challenging to organize clearly. Since insights were recorded conversationally, it was essential to structure them into charts or allow queries to focus on specific data points. Here, Agentica’s core functionality—the Connector—played a pivotal role. Within Agentica, any callable function can be turned into a Connector accessible by the AI agents whenever required. For example, when an Insight Extraction agent received a command such as "Show me user churn complaints," it could invoke a Connector to access records from the Interview agent, retrieve relevant data, and summarize the results clearly in text form. Thus, agents with different purposes and tools could communicate seamlessly to fulfill user requests. diff --git a/public/images/main_mobile.png b/public/images/main_mobile.png new file mode 100644 index 0000000..c75ba79 Binary files /dev/null and b/public/images/main_mobile.png differ