diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..c2e7775 --- /dev/null +++ b/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": ["next/babel"], + "env": { + "development": { + "plugins": [ + "./add-data-locator.cjs" + ] + } + } +} diff --git a/add-data-locator.cjs b/add-data-locator.cjs new file mode 100644 index 0000000..af2482b --- /dev/null +++ b/add-data-locator.cjs @@ -0,0 +1,40 @@ +module.exports = function addDataLocator({ types: t }) { + return { + name: "add-data-locator", + visitor: { + JSXOpeningElement(path, state) { + if (process.env.NODE_ENV !== "development") { + return; + } + + const filePath = state.file.opts.filename; + + if (!filePath || filePath.includes("node_modules")) { + return; + } + + const attributeExists = path.node.attributes.some( + (attribute) => + t.isJSXAttribute(attribute) && + t.isJSXIdentifier(attribute.name) && + attribute.name.name === "data-locator", + ); + + if (attributeExists) { + return; + } + + const lineNumber = path.node.loc?.start.line ?? "unknown"; + const columnNumber = path.node.loc?.start.column ?? "unknown"; + const locatorValue = `${filePath}:${lineNumber}:${columnNumber}`; + + path.node.attributes.push( + t.jsxAttribute( + t.jsxIdentifier("data-locator"), + t.stringLiteral(locatorValue), + ), + ); + }, + }, + }; +}; diff --git a/app/api/open-in-ide/route.ts b/app/api/open-in-ide/route.ts new file mode 100644 index 0000000..bcfe767 --- /dev/null +++ b/app/api/open-in-ide/route.ts @@ -0,0 +1,67 @@ +import { NextResponse } from "next/server"; +import { exec } from "child_process"; +import os from "os"; + +export async function POST(req: Request) { + // Never run in production + if (process.env.NODE_ENV !== "development") { + return NextResponse.json( + { message: "Not available in production" }, + { status: 404 } + ); + } + + try { + const { locator } = await req.json(); + + if (!locator) { + return NextResponse.json( + { message: "Locator is required" }, + { status: 400 } + ); + } + + const isWindows = os.platform() === "win32"; + const bin = isWindows ? "antigravity-ide.cmd" : "antigravity-ide"; + const cmd = `${bin} -r -g "${locator}"`; + + return new Promise((resolve) => { + exec(cmd, (error) => { + if (error) { + // If primary IDE launcher isn't available, try fallback to code/cursor + const fallbackBin = isWindows ? "code.cmd" : "code"; + const fallbackCmd = `${fallbackBin} -r -g "${locator}"`; + + exec(fallbackCmd, (fallbackError) => { + if (fallbackError) { + console.error( + `[open-in-ide] Error opening file: ${error.message} / Fallback error: ${fallbackError.message}` + ); + resolve( + NextResponse.json( + { + message: "Failed to open file in IDE", + error: error.message, + }, + { status: 500 } + ) + ); + } else { + resolve(NextResponse.json({ success: true, via: "code" })); + } + }); + return; + } + + resolve(NextResponse.json({ success: true, via: "antigravity" })); + }); + }); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + console.error("[open-in-ide] Handler error:", err); + return NextResponse.json( + { message: "Internal server error", error: message }, + { status: 500 } + ); + } +} diff --git a/app/globals.css b/app/globals.css index ae38436..03fafbe 100644 --- a/app/globals.css +++ b/app/globals.css @@ -57,10 +57,52 @@ body { z-index: 1; } +.nav-bar-glass { + position: relative; + background: rgba(8, 15, 26, 0.4); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); + border-radius: 48px; + isolation: isolate; +} + +.nav-bar-glass::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1px; + background: linear-gradient(50deg, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0.03) 10%, rgba(255, 255, 255, 0.03) 90%, rgba(255, 255, 255, 0.2) 100%); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask-composite: exclude; + pointer-events: none; + z-index: 1; +} + .tab-active-gradient { + position: relative; background: linear-gradient(180deg, #0029b2 0%, #0038bf 10%, #0e4acc 20%, #255cd9 30%, #3d6fe5 40%, #487eff 62%, #4b80ff 75%, #5d8bff 100%); - border: 1px solid rgba(255, 255, 255, 0.35); box-shadow: inset 0 0 12px rgba(255, 255, 255, 0.4); + border-radius: 24px; + isolation: isolate; +} + +.tab-active-gradient::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 43.27%, rgba(255, 255, 255, 0.8) 100%); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask-composite: exclude; + pointer-events: none; + z-index: 1; } .tab-active-text { diff --git a/app/layout.tsx b/app/layout.tsx index edbebdc..041212a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Inter, Poppins } from "next/font/google"; import "./globals.css"; +import { DevClickToComponent } from "@/components/dev-click-to-component"; const inter = Inter({ subsets: ["latin"], @@ -37,6 +38,7 @@ export default function RootLayout({ {children} + {process.env.NODE_ENV === "development" && } ); diff --git a/components/dev-click-to-component.tsx b/components/dev-click-to-component.tsx new file mode 100644 index 0000000..96124ae --- /dev/null +++ b/components/dev-click-to-component.tsx @@ -0,0 +1,785 @@ +"use client"; + +import { useEffect, useState, useRef, useCallback } from "react"; + +const IDE_SCHEMES = [ + { + matches: ["antigravity"], + createUrl: (locator: string) => `antigravity://file/${locator}`, + }, + { + matches: ["cursor"], + createUrl: (locator: string) => `cursor://file/${locator}`, + }, + { + matches: ["vscode", "code"], + createUrl: (locator: string) => `vscode://file/${locator}`, + }, + { + matches: ["webstorm", "intellij"], + createUrl: (locator: string) => `webstorm://open?file=${locator}`, + }, + { + matches: ["sublime"], + createUrl: (locator: string) => `subl://open?url=file://${locator}`, + }, + { + matches: ["atom", "nova"], + createUrl: (locator: string) => `atom://open?url=file://${locator}`, + }, +] as const; + +function parseLocator(locator: string) { + const match = locator.match(/^(.*):(\d+|unknown):(\d+|unknown)$/); + + if (!match) { + return { filePath: locator, line: null, column: null }; + } + + const [, filePath, line, column] = match; + + return { + filePath, + line: line === "unknown" ? null : Number(line), + column: column === "unknown" ? null : Number(column), + }; +} + +function formatLocatorLabel(locator: string) { + const { filePath, line } = parseLocator(locator); + const parts = filePath.split(/[/\\]/); + const shortPath = parts.slice(-2).join("/"); + const lineInfo = line !== null ? `:${line}` : ""; + return `${shortPath}${lineInfo}`; +} + +function getHierarchy(startEl: Element | null): Element[] { + const list: Element[] = []; + let curr: Element | null = startEl; + while (curr && curr !== document.body && curr !== document.documentElement) { + if (curr.hasAttribute("data-locator")) { + list.push(curr); + } + curr = curr.parentElement; + } + return list; // [0: innermost leaf, ..., N-1: outermost container] +} + +export type HierarchyMode = "OUTER" | "PARENT" | "INNER" | "LEVEL"; + +interface HierarchyTarget { + element: Element; + index: number; + total: number; + mode: HierarchyMode; +} + +function selectTargetFromHierarchy( + hierarchy: Element[], + baseMode: "OUTER" | "PARENT" | "INNER", + customIndex: number | null +): HierarchyTarget | null { + if (hierarchy.length === 0) return null; + const total = hierarchy.length; + + if (customIndex !== null && customIndex >= 0 && customIndex < total) { + let mode: HierarchyMode = "LEVEL"; + if (customIndex === 0) mode = "INNER"; + else if (customIndex === total - 1) mode = "OUTER"; + else if (customIndex === 1) mode = "PARENT"; + + return { + element: hierarchy[customIndex], + index: customIndex, + total, + mode, + }; + } + + if (baseMode === "INNER" || total === 1) { + return { + element: hierarchy[0], + index: 0, + total, + mode: "INNER", + }; + } + + if (baseMode === "PARENT") { + const parentIdx = Math.min(1, total - 1); + return { + element: hierarchy[parentIdx], + index: parentIdx, + total, + mode: "PARENT", + }; + } + + // Outer Mode: find outermost component container, filtering out root page/layout if deeper elements exist + const nonRootIndices: number[] = []; + hierarchy.forEach((el, idx) => { + const loc = el.getAttribute("data-locator") || ""; + if (!loc.includes("app/layout.") && !loc.includes("app/page.")) { + nonRootIndices.push(idx); + } + }); + + const outerIdx = + nonRootIndices.length > 0 + ? nonRootIndices[nonRootIndices.length - 1] + : total - 1; + + return { + element: hierarchy[outerIdx], + index: outerIdx, + total, + mode: "OUTER", + }; +} + +interface HoveredTargetState { + rect: DOMRect; + locator: string; + borderRadius: string; + tagName: string; + mode: HierarchyMode; + levelIndex: number; + totalLevels: number; + hierarchyTags: string[]; +} + +export function DevClickToComponent() { + const [isInspecting, setIsInspecting] = useState(false); + const [isMobile, setIsMobile] = useState(false); + const [position, setPosition] = useState<{ x: number; y: number } | null>(null); + const [isDragging, setIsDragging] = useState(false); + const [hoveredTarget, setHoveredTarget] = useState(null); + const [isOpening, setIsOpening] = useState(false); + const [customDepthIndex, setCustomDepthIndex] = useState(null); + + const currentElementRef = useRef(null); + const currentHierarchyRef = useRef([]); + const currentDepthIndexRef = useRef(null); + const lastPointerPos = useRef<{ x: number; y: number } | null>(null); + const lastHoveredLeafRef = useRef(null); + const dragStart = useRef({ x: 0, y: 0, buttonX: 0, buttonY: 0, hasMoved: false }); + + // Keep ref synced + useEffect(() => { + currentDepthIndexRef.current = customDepthIndex; + }, [customDepthIndex]); + + // Detect mobile / small screen size + useEffect(() => { + const checkSize = () => { + setIsMobile(window.innerWidth <= 768); + }; + checkSize(); + window.addEventListener("resize", checkSize); + return () => window.removeEventListener("resize", checkSize); + }, []); + + // Keep button in bounds on resize + useEffect(() => { + const handleResize = () => { + if (!isMobile) return; + setPosition((prev) => { + if (!prev) return null; + return { + x: Math.max(16, Math.min(window.innerWidth - 66, prev.x)), + y: Math.max(16, Math.min(window.innerHeight - 66, prev.y)), + }; + }); + }; + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, [isMobile]); + + const openInIde = useCallback(async (locator: string) => { + const userAgent = navigator.userAgent.toLowerCase(); + const { filePath, line, column } = parseLocator(locator); + const positionSuffix = + line === null ? "" : `:${line}${column === null ? "" : `:${column}`}`; + const locatorString = `${filePath}${positionSuffix}`; + + setIsOpening(true); + setTimeout(() => setIsOpening(false), 600); + + try { + await fetch("/api/open-in-ide", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ locator: locatorString, userAgent }), + }); + } catch (err) { + console.error("[DevClickToComponent] Error sending API request:", err); + + // Fallback to URL scheme + const ideUrl = + IDE_SCHEMES.find(({ matches }) => + matches.some((match) => userAgent.includes(match)) + )?.createUrl(locatorString) ?? `antigravity://file/${locatorString}`; + + try { + window.location.href = ideUrl; + } catch { + window.open(`file://${filePath}`, "_blank", "noopener,noreferrer"); + } + } + }, []); + + // Desktop: Alt (Outer) vs Alt+Shift (Parent) vs Alt+Ctrl (Inner) vs Scroll Wheel / Arrow depth stepping + useEffect(() => { + if (process.env.NODE_ENV !== "development") { + return; + } + + const updateHoverTarget = ( + el: Element | null, + baseMode: "OUTER" | "PARENT" | "INNER", + overrideDepthIndex?: number | null + ) => { + const hierarchy = getHierarchy(el); + currentHierarchyRef.current = hierarchy; + + const leafEl = hierarchy[0] || null; + let depthIdx = overrideDepthIndex !== undefined ? overrideDepthIndex : currentDepthIndexRef.current; + + // If moved to a completely different leaf element, reset custom depth index + if (leafEl !== lastHoveredLeafRef.current) { + lastHoveredLeafRef.current = leafEl; + if (overrideDepthIndex === undefined) { + depthIdx = null; + setCustomDepthIndex(null); + } + } + + const target = selectTargetFromHierarchy(hierarchy, baseMode, depthIdx); + + if (target) { + currentElementRef.current = target.element; + const rect = target.element.getBoundingClientRect(); + const computed = window.getComputedStyle(target.element); + setHoveredTarget({ + rect, + locator: target.element.getAttribute("data-locator") || "", + borderRadius: computed.borderRadius, + tagName: target.element.tagName.toLowerCase(), + mode: target.mode, + levelIndex: target.index, + totalLevels: target.total, + hierarchyTags: hierarchy.map((h) => h.tagName.toLowerCase()), + }); + } else { + currentElementRef.current = null; + setHoveredTarget(null); + } + }; + + const determineBaseMode = (e: { ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean }): "OUTER" | "PARENT" | "INNER" => { + if (e.ctrlKey || e.metaKey) return "INNER"; + if (e.shiftKey) return "PARENT"; + return "OUTER"; + }; + + const handlePointerMove = (e: PointerEvent) => { + lastPointerPos.current = { x: e.clientX, y: e.clientY }; + + if (e.altKey) { + const target = e.target instanceof Element ? e.target : null; + const mode = determineBaseMode(e); + updateHoverTarget(target, mode); + } else if (currentElementRef.current) { + currentElementRef.current = null; + setHoveredTarget(null); + setCustomDepthIndex(null); + } + }; + + const handleKeyDown = (e: KeyboardEvent) => { + const isAlt = e.altKey || e.key === "Alt"; + if (!isAlt || !lastPointerPos.current) return; + + const hierarchy = currentHierarchyRef.current; + const total = hierarchy.length; + + // Level Stepping: ArrowDown or '[' steps deeper (towards inner leaf 0) + if (e.key === "ArrowDown" || e.key === "[") { + e.preventDefault(); + if (total > 0) { + const current = currentDepthIndexRef.current ?? (e.shiftKey ? 1 : total - 1); + const next = Math.max(0, current - 1); + setCustomDepthIndex(next); + const el = document.elementFromPoint(lastPointerPos.current.x, lastPointerPos.current.y); + updateHoverTarget(el, "LEVEL" as any, next); + } + return; + } + + // Level Stepping: ArrowUp or ']' steps outwards (towards outer container N-1) + if (e.key === "ArrowUp" || e.key === "]") { + e.preventDefault(); + if (total > 0) { + const current = currentDepthIndexRef.current ?? (e.shiftKey ? 1 : 0); + const next = Math.min(total - 1, current + 1); + setCustomDepthIndex(next); + const el = document.elementFromPoint(lastPointerPos.current.x, lastPointerPos.current.y); + updateHoverTarget(el, "LEVEL" as any, next); + } + return; + } + + const mode = determineBaseMode({ + ctrlKey: e.ctrlKey || e.key === "Control", + metaKey: e.metaKey || e.key === "Meta", + shiftKey: e.shiftKey || e.key === "Shift", + }); + + const el = document.elementFromPoint( + lastPointerPos.current.x, + lastPointerPos.current.y + ); + updateHoverTarget(el, mode, null); + }; + + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === "Alt" || !e.altKey) { + currentElementRef.current = null; + setHoveredTarget(null); + setCustomDepthIndex(null); + } else if (e.altKey && lastPointerPos.current) { + // Modifier key released while keeping Alt pressed -> update target accordingly + const mode = determineBaseMode(e); + const el = document.elementFromPoint( + lastPointerPos.current.x, + lastPointerPos.current.y + ); + updateHoverTarget(el, mode, null); + } + }; + + const handleWheel = (e: WheelEvent) => { + if (!e.altKey || !currentHierarchyRef.current.length) return; + + e.preventDefault(); + e.stopPropagation(); + + const total = currentHierarchyRef.current.length; + if (total <= 1) return; + + // Scroll Down (deltaY > 0) -> Step Deeper / Inner (towards index 0) + // Scroll Up (deltaY < 0) -> Step Outer (towards index total - 1) + const current = + currentDepthIndexRef.current ?? + (hoveredTarget?.levelIndex ?? (e.ctrlKey || e.metaKey ? 0 : total - 1)); + + const delta = e.deltaY > 0 ? -1 : 1; + const next = Math.max(0, Math.min(total - 1, current + delta)); + + setCustomDepthIndex(next); + + if (lastPointerPos.current) { + const el = document.elementFromPoint( + lastPointerPos.current.x, + lastPointerPos.current.y + ); + updateHoverTarget(el, "LEVEL" as any, next); + } + }; + + const handleWindowBlur = () => { + currentElementRef.current = null; + setHoveredTarget(null); + setCustomDepthIndex(null); + }; + + const handleScrollOrResize = () => { + if (currentElementRef.current) { + const rect = currentElementRef.current.getBoundingClientRect(); + const computed = window.getComputedStyle(currentElementRef.current); + setHoveredTarget((prev) => + prev + ? { + ...prev, + rect, + borderRadius: computed.borderRadius, + } + : null + ); + } + }; + + const handleDesktopClick = (event: MouseEvent) => { + if (!event.altKey) { + return; + } + + const target = event.target instanceof Element ? event.target : null; + const hierarchy = getHierarchy(target); + const mode = determineBaseMode(event); + const selected = selectTargetFromHierarchy( + hierarchy, + mode, + currentDepthIndexRef.current + ); + + const locator = selected?.element?.getAttribute("data-locator"); + + if (!locator) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + openInIde(locator); + }; + + window.addEventListener("pointermove", handlePointerMove, true); + window.addEventListener("keydown", handleKeyDown, true); + window.addEventListener("keyup", handleKeyUp, true); + window.addEventListener("wheel", handleWheel, { capture: true, passive: false }); + window.addEventListener("blur", handleWindowBlur); + document.addEventListener("mouseleave", handleWindowBlur); + window.addEventListener("scroll", handleScrollOrResize, true); + window.addEventListener("resize", handleScrollOrResize, true); + document.addEventListener("click", handleDesktopClick, true); + + return () => { + window.removeEventListener("pointermove", handlePointerMove, true); + window.removeEventListener("keydown", handleKeyDown, true); + window.removeEventListener("keyup", handleKeyUp, true); + window.removeEventListener("wheel", handleWheel, true); + window.removeEventListener("blur", handleWindowBlur); + document.removeEventListener("mouseleave", handleWindowBlur); + window.removeEventListener("scroll", handleScrollOrResize, true); + window.removeEventListener("resize", handleScrollOrResize, true); + document.removeEventListener("click", handleDesktopClick, true); + }; + }, [openInIde, hoveredTarget?.levelIndex]); + + // Mobile: Inspect Mode click interception + useEffect(() => { + if (process.env.NODE_ENV !== "development" || !isMobile || !isInspecting) return; + + const handleInspectClick = async (event: MouseEvent) => { + const target = event.target; + if (!(target instanceof Element)) return; + + // Ignore clicks on the inspect button itself + if (target.closest(".dev-inspect-btn")) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + const hierarchy = getHierarchy(target); + // On mobile tap, choose innermost leaf by default or first available element + const selectedEl = hierarchy[0] || target.closest("[data-locator]"); + const locator = selectedEl?.getAttribute("data-locator"); + + if (locator && selectedEl) { + console.log(`[DevClickToComponent] Mobile inspect matched element. Opening in IDE:`, locator); + + if (selectedEl instanceof HTMLElement) { + const originalOutline = selectedEl.style.outline; + selectedEl.style.outline = "3px solid #38bdf8"; + setTimeout(() => { + selectedEl.style.outline = originalOutline; + }, 400); + } + + openInIde(locator); + } + + setIsInspecting(false); + }; + + document.addEventListener("click", handleInspectClick, true); + + return () => { + document.removeEventListener("click", handleInspectClick, true); + }; + }, [isMobile, isInspecting, openInIde]); + + // Dragging event handlers for mobile floating button + const handlePointerDown = (e: React.PointerEvent) => { + e.currentTarget.setPointerCapture(e.pointerId); + setIsDragging(true); + const startX = position?.x ?? window.innerWidth - 66; + const startY = position?.y ?? window.innerHeight - 66; + dragStart.current = { + x: e.clientX, + y: e.clientY, + buttonX: startX, + buttonY: startY, + hasMoved: false, + }; + }; + + const handlePointerMoveButton = (e: React.PointerEvent) => { + if (!isDragging) return; + const dx = e.clientX - dragStart.current.x; + const dy = e.clientY - dragStart.current.y; + + if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { + dragStart.current.hasMoved = true; + } + + const newX = Math.max(16, Math.min(window.innerWidth - 66, dragStart.current.buttonX + dx)); + const newY = Math.max(16, Math.min(window.innerHeight - 66, dragStart.current.buttonY + dy)); + + setPosition({ x: newX, y: newY }); + }; + + const handlePointerUpButton = (e: React.PointerEvent) => { + if (!isDragging) return; + e.currentTarget.releasePointerCapture(e.pointerId); + setIsDragging(false); + }; + + const handleToggleInspect = () => { + if (!dragStart.current.hasMoved) { + setIsInspecting(!isInspecting); + } + }; + + // Theme colors for different modes + // INNER: Cyan (#38bdf8), PARENT / LEVEL: Amber (#fbbf24) / Purple (#a78bfa), OUTER: Emerald (#2dd9a4) + let accentColor = "#2dd9a4"; + let accentBg = "rgba(45, 217, 164, 0.08)"; + let accentShadow = "0 0 14px rgba(45, 217, 164, 0.45), inset 0 0 6px rgba(45, 217, 164, 0.15)"; + let modeBadgeLabel = "OUTER"; + + if (hoveredTarget?.mode === "INNER") { + accentColor = "#38bdf8"; + accentBg = "rgba(56, 189, 248, 0.08)"; + accentShadow = "0 0 14px rgba(56, 189, 248, 0.5), inset 0 0 6px rgba(56, 189, 248, 0.2)"; + modeBadgeLabel = "INNER LEAF"; + } else if (hoveredTarget?.mode === "PARENT") { + accentColor = "#a78bfa"; + accentBg = "rgba(167, 139, 250, 0.08)"; + accentShadow = "0 0 14px rgba(167, 139, 250, 0.45), inset 0 0 6px rgba(167, 139, 250, 0.15)"; + modeBadgeLabel = "PARENT"; + } else if (hoveredTarget?.mode === "LEVEL") { + accentColor = "#fbbf24"; + accentBg = "rgba(251, 191, 36, 0.08)"; + accentShadow = "0 0 14px rgba(251, 191, 36, 0.45), inset 0 0 6px rgba(251, 191, 36, 0.15)"; + modeBadgeLabel = `DEPTH ${hoveredTarget.levelIndex + 1}/${hoveredTarget.totalLevels}`; + } + + if (process.env.NODE_ENV !== "development") { + return null; + } + + return ( + <> + {/* Component Bounding Box Highlight Overlay */} + {hoveredTarget && ( +
+ {/* Smart Component Info Tag */} +
+ {/* Top row: Mode + Tag Name + File Location */} +
+ + {modeBadgeLabel} + + + + <{hoveredTarget.tagName}> + + + {formatLocatorLabel(hoveredTarget.locator)} + + + {isOpening && ( + + ⚡ Opening IDE... + + )} +
+ + {/* Bottom row: Breadcrumb Hierarchy Trail & Interactive Shortcut Hints */} + {!isOpening && hoveredTarget.totalLevels > 1 && ( +
+ {/* Visual breadcrumbs */} + Hierarchy: + {hoveredTarget.hierarchyTags.map((tag, idx) => ( + + {tag} + {idx < hoveredTarget.hierarchyTags.length - 1 && " › "} + + ))} + + {/* Shortcut Guide */} + + (Scroll / ↑↓ / [ ]: Level | Ctrl: Inner | Shift: Parent) + +
+ )} +
+
+ )} + + {/* Floating Action Draggable Button (Inspect Mode for Mobile/Tablet) */} + {isMobile && ( +
+ +
+ )} + + ); +} + +export default DevClickToComponent; diff --git a/components/sections/AiCopilotSection.tsx b/components/sections/AiCopilotSection.tsx index ec1fc57..46ed327 100644 --- a/components/sections/AiCopilotSection.tsx +++ b/components/sections/AiCopilotSection.tsx @@ -30,10 +30,12 @@ export function AiCopilotSection() { {/* Top Header Block (Text_1429_12117: w: 864, h: 132, y: 0) */}
- {/* Glow Aura behind badge (Back_1429_12118: Ellipse 1578 & 1579) */} -
-
-
+ {/* Glow Aura behind badge (Clipped from half of icon to top) */} +
+
+
+
+
{/* Badge (3_1429_12123: w: 40, h: 40) */} @@ -62,9 +64,9 @@ export function AiCopilotSection() {
- {/* Content Frame: AI Orb & Feature Matrix (Content_Frame_1429_12129: w: 1216, h: 437, y: 212) */} -
-
+ {/* Content Frame: AI Orb & Feature Matrix */} +
+
AI Management Co-Pilot Matrix {/* 3D Visual Graphic (Group 1000011246, id: 4193:10668, w: 360, h: 360, x: 767, y: 43) */} -
+
{/* Active Tab: Time Tracking (Header_4178_61407: w: 190, h: 46, radius: 24) */} {/* Line 5 Divider */} -
+
Chat App {/* Line 3 Divider */} -
+
Project Management {/* Line 4 Divider */} -
+
File Manager {/* Line 2 Divider */} -
+
Finance{" "} diff --git a/components/sections/FaqSection.tsx b/components/sections/FaqSection.tsx index 36dbd9f..7cedfeb 100644 --- a/components/sections/FaqSection.tsx +++ b/components/sections/FaqSection.tsx @@ -44,12 +44,12 @@ export function FaqSection() { {/* Header Block: Text (Type: FRAME, id: 4193:10508, width: 1010, height: 132, x: 0, y: 0) */}
- {/* Background Glow Aura: Back (Type: FRAME, id: 4193:10509, width: 104, height: 104, x: 453, y: -32) */} -
- {/* Ellipse 1578 (id: 4193:10510, width: 104, height: 104, fill: #001ac7, blur: 200) */} -
- {/* Ellipse 1579 (id: 4193:10511, width: 64, height: 64, fill: #4e66ff, blur: 100) */} -
+ {/* Background Glow Aura: Back (Clipped from half of icon to top) */} +
+
+
+
+
{/* Badge: 3 (Type: FRAME, id: 4193:10514, width: 40, height: 40, x: 485, y: 0, cornerRadius: 50) */} diff --git a/components/sections/HeroSection.tsx b/components/sections/HeroSection.tsx index 4c52b1e..b77fd30 100644 --- a/components/sections/HeroSection.tsx +++ b/components/sections/HeroSection.tsx @@ -44,7 +44,7 @@ export function HeroSection() { {/* Left Floating 3D Shape (Object_4178_61355: x: 479, y: 436, w: 56, h: 56) */}
{/* Top Header Block (Text, id: 4193:10401, width: 1010, height: 132) */}
- {/* Glow Aura behind badge (Back, id: 4193:10402: Ellipse 1578 & 1579) */} -
-
-
+ {/* Glow Aura behind badge (Clipped from half of icon to top) */} +
+
+
+
+
{/* Badge (3, id: 4193:10407, width: 40, height: 40) */} diff --git a/components/sections/StepsSection.tsx b/components/sections/StepsSection.tsx index 8742fff..3208d8a 100644 --- a/components/sections/StepsSection.tsx +++ b/components/sections/StepsSection.tsx @@ -26,10 +26,12 @@ export function StepsSection() { > {/* Top Header Block (Text_1429_10886: w: 1010, h: 132, y: 0) */}
- {/* Glow Aura behind badge (Back_1429_10887: Ellipse 1578 & 1579) */} -
-
-
+ {/* Glow Aura behind badge (Clipped from half of icon to top) */} +
+
+
+
+
{/* Badge (3_1429_10892: w: 40, h: 40) */} diff --git a/components/sections/TaskAnalysisSection.tsx b/components/sections/TaskAnalysisSection.tsx index da737cf..ee85e4e 100644 --- a/components/sections/TaskAnalysisSection.tsx +++ b/components/sections/TaskAnalysisSection.tsx @@ -11,23 +11,34 @@ export function TaskAnalysisSection() { {/* Frame 2147225107 (Type: FRAME, id: 4193:9607, width: 802, height: 219, layoutMode: VERTICAL, itemSpacing: 48) */}
{/* Frame 2147225110 (Type: FRAME, id: 4193:9608, width: 101, height: 48, layoutMode: VERTICAL, itemSpacing: -8) */} -
- {/* Frame 2147225109 (id: 4193:9609, width: 32, height: 32) Star Orb */} -
+
+ {/* Ellipse 1071 Glow Aura */} +
- {/* Header (id: 4193:9615, width: 101, height: 24, fill: #0f1a47 opacity 0.7, cornerRadius: 24) */} -
- {/* Title (id: 4193:9616, text: "Task analysis", 12px, Inter Medium, #dae5ff) */} - + {/* Star Sphere Icon */} +
+ +
+ + {/* Badge Glass Pill */} +
+ Task analysis
diff --git a/components/sections/TeambyAssistSection.tsx b/components/sections/TeambyAssistSection.tsx index 9c382ab..d8eb39c 100644 --- a/components/sections/TeambyAssistSection.tsx +++ b/components/sections/TeambyAssistSection.tsx @@ -15,23 +15,34 @@ export function TeambyAssistSection() {
{/* Badge (Frame 2147225110, id: 4193:9749, w: 108, h: 48, itemSpacing: -8) */} -
- {/* Badge Sparkle Star + Blur Halo (Frame 2147225109 & 3_4193_9751, w: 32, h: 32) */} -
+
+ {/* Ellipse 1071 Glow Aura */} +
- {/* Badge Pill (Header, id: 4193:9756, w: 108, h: 24, padding: 12px 4px, rounded: 24px) */} -
- {/* Title (id: 4193:9757, 12px, Inter Medium, #dae5ff) */} - + {/* Star Sphere Icon */} +
+ +
+ + {/* Badge Glass Pill */} +
+ Teamby Assist
@@ -155,7 +166,7 @@ export function TeambyAssistSection() {
{/* Vertical Gradient Fade Mask (Rectangle 1000001631, id: 4193:10209, w: 286, h: 328) */} -
+
diff --git a/components/sections/VoiceAgentSection.tsx b/components/sections/VoiceAgentSection.tsx index 317a408..7f48506 100644 --- a/components/sections/VoiceAgentSection.tsx +++ b/components/sections/VoiceAgentSection.tsx @@ -40,6 +40,17 @@ export function VoiceAgentSection() { {/* 2. Card (id: 1429:12454, width: 389, height: 481, fills: #080f1a opacity 0.5, cornerRadius: 32) */}
+ {/* Background Glow (Vector 6071 / id: 1429:12456) */} +
+ +
+ {/* Star particle constellation (id: 1429:12463) */}
- {/* Frame 2147225100: 250% + Arrow up-right (x: 32, y: 72, w: 188.79, h: 47.55) */} -
+ {/* Frame 2147225100: 250% (HTML text) + Arrow up-right (x: 32, y: 72, w: 188.79, h: 47.55) */} +
+ + 250% + 250%
diff --git a/public/assets/images/call_to_action_particles.svg b/public/assets/images/call_to_action_particles.svg new file mode 100644 index 0000000..d6146f9 --- /dev/null +++ b/public/assets/images/call_to_action_particles.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/images/copilot_content_frame.png b/public/assets/images/copilot_content_frame.png index cd1e866..a699313 100644 Binary files a/public/assets/images/copilot_content_frame.png and b/public/assets/images/copilot_content_frame.png differ diff --git a/public/assets/images/feature_grid_card_glow.png b/public/assets/images/feature_grid_card_glow.png new file mode 100644 index 0000000..92d1af5 Binary files /dev/null and b/public/assets/images/feature_grid_card_glow.png differ diff --git a/public/assets/images/feature_grid_card_orbit1.png b/public/assets/images/feature_grid_card_orbit1.png new file mode 100644 index 0000000..833fb44 Binary files /dev/null and b/public/assets/images/feature_grid_card_orbit1.png differ diff --git a/public/assets/images/feature_grid_card_orbit2.png b/public/assets/images/feature_grid_card_orbit2.png new file mode 100644 index 0000000..250f37e Binary files /dev/null and b/public/assets/images/feature_grid_card_orbit2.png differ diff --git a/public/assets/images/footer_object_left.png b/public/assets/images/footer_object_left.png index 46d0055..f11abdf 100644 Binary files a/public/assets/images/footer_object_left.png and b/public/assets/images/footer_object_left.png differ diff --git a/public/assets/images/footer_object_right.png b/public/assets/images/footer_object_right.png index e6280e4..d756a05 100644 Binary files a/public/assets/images/footer_object_right.png and b/public/assets/images/footer_object_right.png differ diff --git a/public/assets/images/object_left.png b/public/assets/images/object_left.png index 46d0055..f11abdf 100644 Binary files a/public/assets/images/object_left.png and b/public/assets/images/object_left.png differ diff --git a/public/assets/images/object_left.svg b/public/assets/images/object_left.svg index b2cdc17..da2d71a 100644 --- a/public/assets/images/object_left.svg +++ b/public/assets/images/object_left.svg @@ -1,20 +1,3 @@ - - - - - - - - - - - - - - - - - - - + + diff --git a/public/assets/images/object_right.png b/public/assets/images/object_right.png index e6280e4..d756a05 100644 Binary files a/public/assets/images/object_right.png and b/public/assets/images/object_right.png differ