mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-22 08:38:08 +02:00
chore: move docs site workspace
This commit is contained in:
parent
0ae9b6effd
commit
a46563bb01
52 changed files with 3 additions and 3 deletions
110
docs-site/components/code-block.tsx
Normal file
110
docs-site/components/code-block.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
type ReactNode,
|
||||
type ReactElement,
|
||||
isValidElement,
|
||||
} from "react";
|
||||
import { CopyButton } from "./copy-button";
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
// rehype-pretty-code adds data attributes such as data-language; capture them via index signature
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const TERMINAL_LANGS = new Set(["bash", "sh", "shell", "zsh"]);
|
||||
const WIZARD_GLYPHS = /^\s*[◆◇◯◐○●]/;
|
||||
|
||||
function extractText(node: ReactNode): string {
|
||||
if (typeof node === "string") return node;
|
||||
if (typeof node === "number") return String(node);
|
||||
if (Array.isArray(node)) return node.map(extractText).join("");
|
||||
if (isValidElement(node)) {
|
||||
const props = (node as ReactElement<{ children?: ReactNode }>).props;
|
||||
return extractText(props.children);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function detectLanguage(props: Props, children: ReactNode): string | null {
|
||||
const dataLang = props["data-language"];
|
||||
if (typeof dataLang === "string" && dataLang) return dataLang;
|
||||
|
||||
const className = typeof props.className === "string" ? props.className : "";
|
||||
const m = className.match(/language-([\w-]+)/);
|
||||
if (m) return m[1];
|
||||
|
||||
if (isValidElement(children)) {
|
||||
const childProps = (children as ReactElement<{ className?: string }>).props;
|
||||
const childClass = typeof childProps.className === "string" ? childProps.className : "";
|
||||
const cm = childClass.match(/language-([\w-]+)/);
|
||||
if (cm) return cm[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CodeBlock(props: Props) {
|
||||
const { children, title, className: _ignored, ...rest } = props;
|
||||
const language = detectLanguage(props, children);
|
||||
const codeText = extractText(children);
|
||||
|
||||
const isTerminal =
|
||||
(language !== null && TERMINAL_LANGS.has(language)) ||
|
||||
WIZARD_GLYPHS.test(codeText);
|
||||
const hasTitle = typeof title === "string" && title.length > 0;
|
||||
|
||||
// Mode A — Terminal
|
||||
if (isTerminal) {
|
||||
return (
|
||||
<div className="ktx-code ktx-code-terminal group">
|
||||
<div className="ktx-code-terminal-head">
|
||||
<span className="ktx-tl-dot" style={{ background: "#ff5f57" }} />
|
||||
<span className="ktx-tl-dot" style={{ background: "#febc2e" }} />
|
||||
<span className="ktx-tl-dot" style={{ background: "#28c840" }} />
|
||||
<span className="ktx-code-terminal-label">
|
||||
{hasTitle ? title : "zsh"}
|
||||
</span>
|
||||
<CopyButton
|
||||
text={codeText}
|
||||
className="ml-auto text-white/80"
|
||||
/>
|
||||
</div>
|
||||
<pre {...rest} className="ktx-code-body ktx-code-body-terminal">
|
||||
{children}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Mode B — VS Code tab (filename present)
|
||||
if (hasTitle) {
|
||||
return (
|
||||
<div className="ktx-code ktx-code-tab group">
|
||||
<div className="ktx-code-tab-head">
|
||||
<span className="ktx-file-glyph" data-lang={language ?? ""} />
|
||||
<span className="ktx-code-tab-filename">{title}</span>
|
||||
{language && <span className="ktx-lang-pill">{language}</span>}
|
||||
<CopyButton text={codeText} className="ml-auto" />
|
||||
</div>
|
||||
<pre {...rest} className="ktx-code-body ktx-code-body-tab">
|
||||
{children}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Mode C — Minimal default
|
||||
return (
|
||||
<div className="ktx-code ktx-code-minimal group relative">
|
||||
{language && <span className="ktx-code-minimal-lang">{language}</span>}
|
||||
<CopyButton text={codeText} className="ktx-code-minimal-copy" />
|
||||
<pre {...rest} className="ktx-code-body ktx-code-body-minimal">
|
||||
{children}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
docs-site/components/copy-button.tsx
Normal file
64
docs-site/components/copy-button.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
type Props = {
|
||||
text: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function CopyButton({ text, className = "" }: Props) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const onClick = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch {
|
||||
// Older browsers or denied permission — fail silently
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-label={copied ? "Copied" : "Copy code"}
|
||||
className={`inline-flex items-center justify-center w-7 h-7 rounded-md transition-all hover:bg-white/5 ${className}`}
|
||||
>
|
||||
{copied ? (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="opacity-70"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
56
docs-site/components/logo.tsx
Normal file
56
docs-site/components/logo.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
export function Logo() {
|
||||
return (
|
||||
<div className="flex items-center gap-2 group">
|
||||
<div className="relative flex items-center justify-center transition-transform duration-300 ease-out group-hover:rotate-[-4deg]">
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="ktx-grad-a" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="var(--color-fd-primary)" />
|
||||
<stop offset="100%" stopColor="var(--color-fd-primary)" stopOpacity="0.55" />
|
||||
</linearGradient>
|
||||
<linearGradient id="ktx-grad-b" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="var(--color-fd-primary)" stopOpacity="0.85" />
|
||||
<stop offset="100%" stopColor="var(--color-fd-primary)" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{/* Bottom layer */}
|
||||
<path
|
||||
d="M3 17 L12 21.5 L21 17 L12 12.5 Z"
|
||||
fill="url(#ktx-grad-a)"
|
||||
opacity="0.4"
|
||||
/>
|
||||
{/* Middle layer */}
|
||||
<path
|
||||
d="M3 12 L12 16.5 L21 12 L12 7.5 Z"
|
||||
fill="url(#ktx-grad-b)"
|
||||
opacity="0.7"
|
||||
/>
|
||||
{/* Top layer */}
|
||||
<path
|
||||
d="M3 7 L12 11.5 L21 7 L12 2.5 Z"
|
||||
fill="var(--color-fd-primary)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
className="text-[15px] font-semibold text-fd-foreground tracking-tight"
|
||||
style={{ fontFamily: "var(--font-display), var(--font-sans), sans-serif" }}
|
||||
>
|
||||
KTX
|
||||
</span>
|
||||
<span
|
||||
className="text-[13px] font-medium text-fd-muted-foreground/80 tracking-tight border-l border-fd-border pl-2 ml-0.5"
|
||||
style={{ fontFamily: "var(--font-display), var(--font-sans), sans-serif" }}
|
||||
>
|
||||
Docs
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
docs-site/components/scroll-reveal.tsx
Normal file
58
docs-site/components/scroll-reveal.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
stagger?: boolean;
|
||||
threshold?: number;
|
||||
};
|
||||
|
||||
export function ScrollReveal({
|
||||
children,
|
||||
className = "",
|
||||
stagger = false,
|
||||
threshold = 0.1,
|
||||
}: Props) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = ref.current;
|
||||
if (!node) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("visible");
|
||||
if (stagger) {
|
||||
entry.target.querySelectorAll(".rv").forEach((el) => {
|
||||
el.classList.add("visible");
|
||||
});
|
||||
}
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ threshold, rootMargin: "0px 0px -40px 0px" }
|
||||
);
|
||||
|
||||
if (stagger) {
|
||||
observer.observe(node);
|
||||
} else {
|
||||
node.querySelectorAll(".rv").forEach((el) => observer.observe(el));
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [stagger, threshold]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`${stagger ? "rv rv-stagger" : ""} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
56
docs-site/components/terminal-preview.tsx
Normal file
56
docs-site/components/terminal-preview.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
export function TerminalPreview() {
|
||||
return (
|
||||
<div className="terminal-frame sheen w-full max-w-[560px]">
|
||||
<div className="terminal-head">
|
||||
<span className="terminal-dot" style={{ background: "#ff5f57" }} />
|
||||
<span className="terminal-dot" style={{ background: "#febc2e" }} />
|
||||
<span className="terminal-dot" style={{ background: "#28c840" }} />
|
||||
<span className="ml-2 text-[11px] text-zinc-500 font-medium tracking-wide">
|
||||
~/analytics
|
||||
</span>
|
||||
</div>
|
||||
<div className="terminal-body">
|
||||
<div>
|
||||
<span className="term-prompt">$</span>{" "}
|
||||
<span className="term-cmd">ktx setup</span>
|
||||
</div>
|
||||
<div className="h-2" />
|
||||
<div className="term-dim">◆ Welcome to KTX setup</div>
|
||||
<div className="term-dim">│</div>
|
||||
<div>
|
||||
<span className="term-dim">◇</span>{" "}
|
||||
<span className="term-key">LLM</span>{" "}
|
||||
<span className="term-ok">✓ claude-sonnet-4-6</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="term-dim">◇</span>{" "}
|
||||
<span className="term-key">Embeddings</span>{" "}
|
||||
<span className="term-ok">✓ openai · text-embedding-3-small</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="term-dim">◇</span>{" "}
|
||||
<span className="term-key">Database</span>{" "}
|
||||
<span className="term-ok">✓ postgres-warehouse · 42 tables</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="term-dim">◇</span>{" "}
|
||||
<span className="term-key">Sources</span>{" "}
|
||||
<span className="term-ok">✓ dbt-main · 218 models</span>
|
||||
</div>
|
||||
<div className="h-2" />
|
||||
<div className="term-info">◐ Building context for agents…</div>
|
||||
<div className="pl-3 text-[12px] term-dim">
|
||||
enriching schema · detecting relationships · ingesting dbt
|
||||
</div>
|
||||
<div className="h-2" />
|
||||
<div className="term-ok">✓ KTX context is ready for agents.</div>
|
||||
<div className="h-2" />
|
||||
<div>
|
||||
<span className="term-prompt">$</span>{" "}
|
||||
<span className="term-cmd">ktx serve</span>
|
||||
<span className="term-cursor ml-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue