diff --git a/app/components/Button.tsx b/app/components/Button.tsx
new file mode 100644
index 0000000..be8be90
--- /dev/null
+++ b/app/components/Button.tsx
@@ -0,0 +1,52 @@
+import type { JSX } from "solid-js";
+
+import { Link } from "./Link";
+import clsx from "clsx";
+
+const variantStyles = {
+ primary:
+ "bg-violet-300 font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500",
+ secondary:
+ "bg-slate-800 font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400",
+ ghost:
+ "bg-transparent font-medium text-slate-900 dark:text-slate-400 hover:bg-slate-100 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-300/50 active:bg-slate-200",
+};
+
+const sizeStyles = {
+ sm: "rounded-md py-1 px-2 text-xs",
+ md: "rounded-full py-2 px-4 text-sm",
+ lg: "rounded-full py-3 px-6 text-base",
+};
+
+type ButtonProps = {
+ variant?: keyof typeof variantStyles;
+ size?: keyof typeof sizeStyles;
+ className?: string;
+} & (
+ | JSX.IntrinsicElements["button"]
+ | (JSX.IntrinsicElements["a"] & { href?: undefined })
+);
+
+export function Button({
+ variant = "primary",
+ size = "md",
+ className,
+ ...props
+}: ButtonProps) {
+ className = clsx(
+ variantStyles[variant],
+ sizeStyles[size],
+ "cursor-pointer",
+ className,
+ );
+
+ return "href" in props && props.href ? (
+
+ ) : (
+
+ );
+}
diff --git a/app/components/Callout.tsx b/app/components/Callout.tsx
new file mode 100644
index 0000000..5b87c6e
--- /dev/null
+++ b/app/components/Callout.tsx
@@ -0,0 +1,74 @@
+import type { JSX } from "solid-js";
+
+import { Icon } from "./Icon";
+import clsx from "clsx";
+
+const styles = {
+ note: {
+ container:
+ "bg-violet-50 dark:bg-violet-800/60 dark:ring-1 dark:ring-violet-300/10",
+ title: "text-violet-900 dark:text-violet-400",
+ body: "text-slate-800 [--tw-prose-background:var(--color-slate-50)] prose-a:text-slate-900 prose-code:text-slate-900 dark:text-slate-300 dark:prose-code:text-slate-300",
+ },
+ warning: {
+ container:
+ "bg-amber-50 dark:bg-amber-800/60 dark:ring-1 dark:ring-amber-300/10",
+ title: "text-amber-900 dark:text-amber-500",
+ body: "text-slate-800 [--tw-prose-underline:var(--color-slate-400)] [--tw-prose-background:var(--color-slate-50)] prose-a:text-slate-900 prose-code:text-slate-900 dark:text-slate-300 dark:[--tw-prose-underline:var(--color-slate-700)] dark:prose-code:text-slate-300",
+ },
+ question: {
+ container:
+ "bg-amber-50 dark:bg-amber-800/60 dark:ring-1 dark:ring-amber-300/10",
+ title: "text-amber-900 dark:text-amber-500",
+ body: "text-slate-800 [--tw-prose-underline:var(--color-slate-400)] [--tw-prose-background:var(--color-slate-50)] prose-a:text-slate-900 prose-code:text-slate-900 dark:text-slate-300 dark:[--tw-prose-underline:var(--color-slate-700)] dark:prose-code:text-slate-300",
+ },
+};
+
+const icons = {
+ note: (props: { class?: string }) => ,
+ warning: (props: { class?: string }) => (
+
+ ),
+ question: (props: { class?: string }) => (
+
+ ),
+};
+
+export function Callout({
+ title,
+ children,
+ type = "note",
+ collapsible = false,
+}: {
+ title: string;
+ children: JSX.Element;
+ type?: keyof typeof styles;
+ collapsible?: boolean;
+}) {
+ const IconComponent = icons[type];
+
+ return (
+
+ );
+}
diff --git a/app/components/Form.tsx b/app/components/Form.tsx
new file mode 100644
index 0000000..6530311
--- /dev/null
+++ b/app/components/Form.tsx
@@ -0,0 +1,51 @@
+import clsx from "clsx";
+
+type ToggleProps = {
+ id: string;
+ label: string;
+ onChange?: (checked: boolean) => void;
+ checked: boolean;
+};
+
+export function Toggle(props: ToggleProps) {
+ return (
+
+ props.onChange?.(e.target.checked)}
+ checked={props.checked}
+ aria-checked={props.checked}
+ role="switch"
+ aria-label={props.label}
+ />
+
+
+
+ );
+}
diff --git a/app/components/Icon.tsx b/app/components/Icon.tsx
new file mode 100644
index 0000000..283e2e8
--- /dev/null
+++ b/app/components/Icon.tsx
@@ -0,0 +1,83 @@
+import type { JSX } from "solid-js";
+
+import { InstallationIcon } from "@/icons/InstallationIcon";
+import { LightbulbIcon } from "@/icons/LightbulbIcon";
+import { QuestionIcon } from "@/icons/QuestionIcon";
+import { PluginsIcon } from "@/icons/PluginsIcon";
+import { PresetsIcon } from "@/icons/PresetsIcon";
+import { ThemingIcon } from "@/icons/ThemingIcon";
+import { WarningIcon } from "@/icons/WarningIcon";
+import { createUniqueId, For } from "solid-js";
+import clsx from "clsx";
+
+const icons = {
+ installation: InstallationIcon,
+ presets: PresetsIcon,
+ plugins: PluginsIcon,
+ theming: ThemingIcon,
+ lightbulb: LightbulbIcon,
+ warning: WarningIcon,
+ question: QuestionIcon,
+};
+
+const iconStyles = {
+ blue: "[--icon-foreground:var(--color-slate-900)] [--icon-background:var(--color-white)]",
+ amber:
+ "[--icon-foreground:var(--color-amber-900)] [--icon-background:var(--color-amber-100)]",
+};
+
+export type IconColor = keyof typeof iconStyles;
+
+type IconProps = JSX.IntrinsicElements["svg"] & {
+ color?: IconColor;
+ icon: keyof typeof icons;
+};
+
+export function Icon(props: IconProps) {
+ const id = createUniqueId();
+ const IconComponent = icons[props.icon];
+
+ return (
+
+ );
+}
+
+const gradients = {
+ blue: [
+ { stopColor: "#0EA5E9" },
+ { stopColor: "#22D3EE", offset: ".527" },
+ { stopColor: "#818CF8", offset: 1 },
+ ],
+ amber: [
+ { stopColor: "#FDE68A", offset: ".08" },
+ { stopColor: "#F59E0B", offset: ".837" },
+ ],
+};
+
+type GradientProps = JSX.IntrinsicElements["radialGradient"] & {
+ color?: keyof typeof gradients;
+};
+
+export function Gradient(props: GradientProps) {
+ return (
+
+
+ {(stop) => }
+
+
+ );
+}
diff --git a/app/components/Iframe.tsx b/app/components/Iframe.tsx
new file mode 100644
index 0000000..fcd070c
--- /dev/null
+++ b/app/components/Iframe.tsx
@@ -0,0 +1,21 @@
+import clsx from "clsx";
+
+type IframeProps = {
+ src: string;
+ title: string;
+ width?: string;
+ height?: string;
+ class?: string;
+};
+
+export function Iframe(props: IframeProps) {
+ return (
+
+ );
+}
diff --git a/app/icons/InstallationIcon.tsx b/app/icons/InstallationIcon.tsx
new file mode 100644
index 0000000..04a552d
--- /dev/null
+++ b/app/icons/InstallationIcon.tsx
@@ -0,0 +1,36 @@
+import type { IconColor } from "@/components/Icon";
+
+import { Gradient } from "@/components/Icon";
+
+type GradientProps = {
+ id: string;
+ color?: IconColor;
+};
+
+export function InstallationIcon(props: GradientProps) {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/LightbulbIcon.tsx b/app/icons/LightbulbIcon.tsx
new file mode 100644
index 0000000..137f02e
--- /dev/null
+++ b/app/icons/LightbulbIcon.tsx
@@ -0,0 +1,39 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function LightbulbIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/PluginsIcon.tsx b/app/icons/PluginsIcon.tsx
new file mode 100644
index 0000000..5dd5de1
--- /dev/null
+++ b/app/icons/PluginsIcon.tsx
@@ -0,0 +1,48 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function PluginsIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/PresetsIcon.tsx b/app/icons/PresetsIcon.tsx
new file mode 100644
index 0000000..4342463
--- /dev/null
+++ b/app/icons/PresetsIcon.tsx
@@ -0,0 +1,36 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function PresetsIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/QuestionIcon.tsx b/app/icons/QuestionIcon.tsx
new file mode 100644
index 0000000..002f853
--- /dev/null
+++ b/app/icons/QuestionIcon.tsx
@@ -0,0 +1,48 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function QuestionIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/ThemingIcon.tsx b/app/icons/ThemingIcon.tsx
new file mode 100644
index 0000000..e68d30c
--- /dev/null
+++ b/app/icons/ThemingIcon.tsx
@@ -0,0 +1,52 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function ThemingIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/icons/WarningIcon.tsx b/app/icons/WarningIcon.tsx
new file mode 100644
index 0000000..24e12df
--- /dev/null
+++ b/app/icons/WarningIcon.tsx
@@ -0,0 +1,48 @@
+import { DarkMode, Gradient, LightMode } from "@syntax/Icon";
+import React from "react";
+
+export function WarningIcon({ id, color }: { id: string; color?: React.ComponentProps["color"] }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}