forgedb 0.2.1

ForgeDB — an application database generator. Compiles a declarative .forge schema into tailored Rust database code, a TypeScript SDK, and a REST API.
Documentation
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";

@custom-variant dark (&:is(.dark *));

/*
 * Inspector status palette — house convention (a single accent for primary/active,
 * plus semantic status colors). These back inspector-specific surfaces the shadcn
 * tokens don't cover: DB health (dead-row ratio), connection state (live/at-rest),
 * and mutation kinds (inserted/updated/deleted). Use as `bg-ok`, `text-warn`,
 * `border-danger`, `bg-info/15`, etc.
 */
@theme {
  --color-ok: oklch(0.72 0.15 155);
  --color-ok-foreground: oklch(0.985 0 0);
  --color-warn: oklch(0.8 0.16 85);
  --color-warn-foreground: oklch(0.205 0 0);
  --color-danger: oklch(0.7 0.19 25);
  --color-danger-foreground: oklch(0.985 0 0);
  --color-info: oklch(0.72 0.18 305);
  --color-info-foreground: oklch(0.985 0 0);

  /*
   * Brand accents (docs/brand palette). Violet is the house accent and already
   * lives in `--primary`; molten/ember are the "active forge" accent — reserved
   * for what's being generated right now, used sparingly, never as a general fill.
   *   molten #f59e0b  →  oklch(0.75 0.16 62)
   *   ember  #fbbf24  →  oklch(0.83 0.16 82)
   */
  --color-molten: oklch(0.75 0.16 62);
  --color-molten-foreground: oklch(0.145 0 0);
  --color-ember: oklch(0.83 0.16 82);
  --color-ember-foreground: oklch(0.145 0 0);
}

/*
 * Force-generate the full inspector status palette so every documented utility
 * exists in the compiled stylesheet (the design kit ships these to the design
 * agent; a class not compiled here silently no-ops). Covers solids, foregrounds,
 * and the common tint opacities used for status chips.
 */
@source inline("{bg,text,border}-{ok,warn,danger,info,molten,ember}");
@source inline("{bg,text}-{ok,warn,danger,info,molten,ember}-foreground");
@source inline("{bg,text,border}-{ok,warn,danger,info,molten,ember}/{10,15,20,30,40}");

@theme inline {
    --font-heading: var(--font-sans);
    --font-sans: var(--font-sans);
    --color-sidebar-ring: var(--sidebar-ring);
    --color-sidebar-border: var(--sidebar-border);
    --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
    --color-sidebar-accent: var(--sidebar-accent);
    --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
    --color-sidebar-primary: var(--sidebar-primary);
    --color-sidebar-foreground: var(--sidebar-foreground);
    --color-sidebar: var(--sidebar);
    --color-chart-5: var(--chart-5);
    --color-chart-4: var(--chart-4);
    --color-chart-3: var(--chart-3);
    --color-chart-2: var(--chart-2);
    --color-chart-1: var(--chart-1);
    --color-ring: var(--ring);
    --color-input: var(--input);
    --color-border: var(--border);
    --color-destructive: var(--destructive);
    --color-accent-foreground: var(--accent-foreground);
    --color-accent: var(--accent);
    --color-muted-foreground: var(--muted-foreground);
    --color-muted: var(--muted);
    --color-secondary-foreground: var(--secondary-foreground);
    --color-secondary: var(--secondary);
    --color-primary-foreground: var(--primary-foreground);
    --color-primary: var(--primary);
    --color-popover-foreground: var(--popover-foreground);
    --color-popover: var(--popover);
    --color-card-foreground: var(--card-foreground);
    --color-card: var(--card);
    --color-foreground: var(--foreground);
    --color-background: var(--background);
    --radius-sm: calc(var(--radius) * 0.6);
    --radius-md: calc(var(--radius) * 0.8);
    --radius-lg: var(--radius);
    --radius-xl: calc(var(--radius) * 1.4);
    --radius-2xl: calc(var(--radius) * 1.8);
    --radius-3xl: calc(var(--radius) * 2.2);
    --radius-4xl: calc(var(--radius) * 2.6);
}

:root {
    --background: oklch(1 0 0);
    --foreground: oklch(0.145 0 0);
    --card: oklch(1 0 0);
    --card-foreground: oklch(0.145 0 0);
    --popover: oklch(1 0 0);
    --popover-foreground: oklch(0.145 0 0);
    --primary: oklch(0.55 0.2 305);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.97 0 0);
    --secondary-foreground: oklch(0.205 0 0);
    --muted: oklch(0.97 0 0);
    --muted-foreground: oklch(0.556 0 0);
    --accent: oklch(0.97 0 0);
    --accent-foreground: oklch(0.205 0 0);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.922 0 0);
    --input: oklch(0.922 0 0);
    --ring: oklch(0.55 0.2 305);
    --chart-1: oklch(0.87 0 0);
    --chart-2: oklch(0.556 0 0);
    --chart-3: oklch(0.439 0 0);
    --chart-4: oklch(0.371 0 0);
    --chart-5: oklch(0.269 0 0);
    --radius: 0.625rem;
    --sidebar: oklch(0.985 0 0);
    --sidebar-foreground: oklch(0.145 0 0);
    --sidebar-primary: oklch(0.205 0 0);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.97 0 0);
    --sidebar-accent-foreground: oklch(0.205 0 0);
    --sidebar-border: oklch(0.922 0 0);
    --sidebar-ring: oklch(0.708 0 0);
}

.dark {
    --background: oklch(0.145 0 0);
    --foreground: oklch(0.985 0 0);
    --card: oklch(0.205 0 0);
    --card-foreground: oklch(0.985 0 0);
    --popover: oklch(0.205 0 0);
    --popover-foreground: oklch(0.985 0 0);
    --primary: oklch(0.72 0.18 305);
    --primary-foreground: oklch(0.145 0 0);
    --secondary: oklch(0.269 0 0);
    --secondary-foreground: oklch(0.985 0 0);
    --muted: oklch(0.269 0 0);
    --muted-foreground: oklch(0.708 0 0);
    --accent: oklch(0.269 0 0);
    --accent-foreground: oklch(0.985 0 0);
    --destructive: oklch(0.704 0.191 22.216);
    --border: oklch(1 0 0 / 10%);
    --input: oklch(1 0 0 / 15%);
    --ring: oklch(0.72 0.18 305);
    --chart-1: oklch(0.87 0 0);
    --chart-2: oklch(0.556 0 0);
    --chart-3: oklch(0.439 0 0);
    --chart-4: oklch(0.371 0 0);
    --chart-5: oklch(0.269 0 0);
    --sidebar: oklch(0.205 0 0);
    --sidebar-foreground: oklch(0.985 0 0);
    --sidebar-primary: oklch(0.488 0.243 264.376);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.269 0 0);
    --sidebar-accent-foreground: oklch(0.985 0 0);
    --sidebar-border: oklch(1 0 0 / 10%);
    --sidebar-ring: oklch(0.556 0 0);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
    }
  body {
    @apply bg-background text-foreground;
    }
  html {
    @apply font-sans;
    }

  /*
   * Canonical inline-code chip. Targets every inline `code` — MDX prose, callouts,
   * and hand-written TSX — in ONE place so they never drift. Fenced blocks
   * (`pre > code`) are excluded; those are styled by rehype-pretty-code inside <Pre>.
   * Kept tight (`py-px`) so chips hug the line instead of towering over it. Table
   * cells add `whitespace-nowrap` + `text-xs` on top (see the <td> in mdx-components).
   */
  code:not(pre code) {
    @apply rounded bg-muted/60 px-1 py-px font-mono text-[0.85em] text-foreground;
    }
}

/*
 * rehype-pretty-code dual theme: Shiki emits inline `--shiki-light` /
 * `--shiki-dark` CSS vars per token. Default to the light value; when the
 * `.dark` class is present (next-themes), switch every token to the dark value.
 */
@layer base {
  /* Our custom <Pre> replaces the default `.shiki` class (targets data-theme);
   * the landing's inline highlighter keeps the `.shiki` class. Cover both. */
  .dark [data-theme] span,
  .dark .shiki span {
    color: var(--shiki-dark) !important;
    font-style: var(--shiki-dark-font-style) !important;
    font-weight: var(--shiki-dark-font-weight) !important;
    text-decoration: var(--shiki-dark-text-decoration) !important;
  }
  [data-line].line--highlighted {
    background-color: color-mix(in oklch, var(--primary) 12%, transparent);
    box-shadow: inset 2px 0 0 var(--primary);
  }
  [data-highlighted-chars] {
    border-radius: 0.2rem;
    background-color: color-mix(in oklch, var(--primary) 16%, transparent);
    padding: 0.1rem 0.15rem;
  }
  /* Block-code gutter. Scoped to `pre` — rehype-pretty-code also wraps INLINE code
   * in a `<span data-line>`, so an unscoped rule leaks this 1.15rem horizontal
   * padding onto every inline chip (a `+` chip becomes a ~52px-wide pill). */
  pre [data-line] {
    padding: 0 1.15rem;
  }
}

/* House polish: dim-accent selection + a thin scrollbar tuned for dark surfaces. */
@layer base {
  ::selection {
    background: color-mix(in oklch, var(--primary) 30%, transparent);
  }
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
  }
  ::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }
  ::-webkit-scrollbar-thumb {
    background: var(--border);
    border: 3px solid transparent;
    background-clip: content-box;
    border-radius: 9999px;
  }
  ::-webkit-scrollbar-thumb:hover {
    background: var(--muted-foreground);
    background-clip: content-box;
  }
}

/*
 * Animated ForgeMark ("Forge" intro) — pairs with components/animated-forgemark.tsx.
 * Every animated part carries the settled logo as its *base* style, so under
 * prefers-reduced-motion (where all these rules are switched off) the mark renders
 * exactly as the static `ForgeMark` does. The @media block must stay last.
 */
@layer components {
  /* Stream draw. Base state is fully drawn. */
  .fm-draw {
    stroke-dashoffset: 0;
    animation: fm-draw 0.4s cubic-bezier(0.4, 0.85, 0.3, 1) both;
  }
  .fm-draw--quick {
    animation-duration: 0.34s;
    animation-timing-function: cubic-bezier(0.33, 0.9, 0.3, 1);
  }

  /* Endpoint dots. */
  .fm-pop {
    transform-box: fill-box;
    transform-origin: center;
    animation: fm-pop 0.3s cubic-bezier(0.34, 1.5, 0.64, 1) both;
  }
  .fm-pop--flare {
    animation:
      fm-pop 0.34s cubic-bezier(0.34, 1.5, 0.64, 1) both,
      fm-flare 0.7s ease-out 0.02s both;
  }

  /* Tile: outline draws, then the filled tile floods in with a heat bloom. */
  .fm-outline {
    stroke-dashoffset: 0;
    opacity: 0;
    animation: fm-outline 0.62s cubic-bezier(0.5, 0, 0.3, 1) both;
  }
  .fm-flood {
    opacity: 1;
    animation:
      fm-flood 0.3s ease-out 0.44s both,
      fm-heat 0.8s ease-out 0.58s both;
  }

  /* Spark riding the molten stream. Invisible once settled. */
  .fm-spark {
    opacity: 0;
    offset-rotate: 0deg;
    animation:
      fm-travel 0.5s cubic-bezier(0.4, 0.85, 0.3, 1) 0.7s both,
      fm-spark 0.5s linear 0.7s both;
  }
}

@keyframes fm-draw {
  from { stroke-dashoffset: 72; }
  to { stroke-dashoffset: 0; }
}
@keyframes fm-pop {
  0% { opacity: 0; transform: scale(0); }
  68% { opacity: 1; transform: scale(1.38); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes fm-outline {
  0% { stroke-dashoffset: 168; opacity: 1; }
  82% { stroke-dashoffset: 0; opacity: 1; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes fm-flood {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes fm-heat {
  0% { filter: none; }
  40% { filter: drop-shadow(0 0 9px #f59e0b); }
  100% { filter: none; }
}
@keyframes fm-travel {
  from { offset-distance: 0%; }
  to { offset-distance: 100%; }
}
@keyframes fm-spark {
  0% { opacity: 0; r: 2; }
  15% { opacity: 1; r: 4; }
  80% { opacity: 1; r: 3; }
  100% { opacity: 0; r: 2; }
}
@keyframes fm-flare {
  0% { filter: none; }
  45% { filter: drop-shadow(0 0 8px currentColor); }
  100% { filter: none; }
}

/* Reduced motion: skip straight to the settled mark. */
@media (prefers-reduced-motion: reduce) {
  .fm-draw,
  .fm-pop,
  .fm-outline,
  .fm-flood,
  .fm-spark {
    animation: none;
  }
}