web/app/landing.tsxblame
View source
4bb999b1"use client";
4bb999b2
4bb999b3import { useEffect, useRef, useState } from "react";
4bb999b4import { GroveLogo } from "@/app/components/grove-logo";
44863ab5import { useTheme } from "@/lib/theme";
4bb999b6
4bb999b7export function LandingPage() {
4bb999b8 const [islDomain, setIslDomain] = useState("");
44863ab9 const { theme } = useTheme();
44863ab10 const islRef = useRef<HTMLIFrameElement>(null);
4bb999b11
4bb999b12 useEffect(() => {
4bb999b13 const host = window.location.hostname;
4bb999b14 if (host !== "localhost" && !host.match(/^\d/)) {
7e5aa7715 setIslDomain("https://isl.grove.host");
4bb999b16 }
4bb999b17 }, []);
4bb999b18
44863ab19 // Send theme changes to ISL iframe via postMessage
44863ab20 useEffect(() => {
44863ab21 if (islRef.current?.contentWindow) {
44863ab22 islRef.current.contentWindow.postMessage({ type: "theme", value: theme }, "*");
44863ab23 }
44863ab24 }, [theme]);
44863ab25
4bb999b26 return (
d6f162027 <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
44863ab28 {/* Hero + Terminal + Features */}
44863ab29 <div style={{ maxWidth: "800px", margin: "0 auto", padding: "60px 24px 0", width: "100%" }}>
44863ab30
4bb999b31 {/* Hero */}
4bb999b32 <div style={{ textAlign: "center", marginBottom: "64px" }}>
4bb999b33 <div style={{ display: "inline-block", marginBottom: "24px" }}>
4bb999b34 <GroveLogo size={64} />
4bb999b35 </div>
4bb999b36 <h1
4bb999b37 style={{
4bb999b38 fontSize: "2rem",
4bb999b39 fontWeight: 400,
4bb999b40 color: "var(--text-primary)",
4bb999b41 marginBottom: "12px",
4bb999b42 }}
4bb999b43 >
4bb999b44 Source control, self-hosted
4bb999b45 </h1>
4bb999b46 <p
4bb999b47 style={{
4bb999b48 fontSize: "1rem",
4bb999b49 color: "var(--text-muted)",
4bb999b50 maxWidth: "480px",
4bb999b51 margin: "0 auto 32px",
4bb999b52 lineHeight: 1.6,
4bb999b53 }}
4bb999b54 >
4bb999b55 Grove is a complete code hosting platform built on Sapling SCM and Mononoke.
4bb999b56 Stacked diffs, interactive smartlog, CI pipelines — on your own infrastructure.
4bb999b57 </p>
4bb999b58 <a
4bb999b59 href="/login"
4bb999b60 style={{
4bb999b61 display: "inline-block",
4bb999b62 padding: "10px 24px",
4bb999b63 backgroundColor: "var(--accent)",
4bb999b64 color: "var(--accent-text)",
4bb999b65 fontSize: "0.875rem",
4bb999b66 textDecoration: "none",
4bb999b67 }}
4bb999b68 >
4bb999b69 Get started
4bb999b70 </a>
4bb999b71 </div>
4bb999b72
44863ab73 </div>{/* end centered wrapper */}
44863ab74
44863ab75 {/* ISL — fills remaining viewport */}
44863ab76 {islDomain && (
d6f162077 <section
d6f162078 style={{
d6f162079 padding: "24px",
d6f162080 }}
d6f162081 >
d6f162082 <iframe
d6f162083 ref={islRef}
d6f162084 src={`${islDomain}?theme=${theme}`}
44863ab85 style={{
d6f162086 width: "100%",
d6f162087 height: "calc(100vh - 3.5rem)",
d6f162088 border: "1px solid var(--border-subtle)",
d6f162089 display: "block",
d6f162090 }}
d6f162091 title="Interactive Smartlog"
d6f162092 onLoad={() => {
d6f162093 islRef.current?.contentWindow?.postMessage({ type: "theme", value: theme }, "*");
44863ab94 }}
d6f162095 />
44863ab96 </section>
44863ab97 )}
4bb999b98 </div>
4bb999b99 );
4bb999b100}