@tiniestcloud/create 0.0.0-stage → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +26 -2
  2. package/index.mjs +117 -0
  3. package/package.json +33 -4
  4. package/sdk.d.ts +290 -0
  5. package/skills/tiny-design/SKILL.md +333 -0
  6. package/skills/tiny-design/app.md +72 -0
  7. package/skills/tiny-design/dashboard.md +54 -0
  8. package/skills/tiny-design/form.md +48 -0
  9. package/skills/tiny-design/presentation.md +127 -0
  10. package/skills/tiny-design/website.md +53 -0
  11. package/templates/app/AGENTS.md +42 -0
  12. package/templates/app/CLAUDE.md +1 -0
  13. package/templates/app/README.md +10 -0
  14. package/templates/app/components.json +25 -0
  15. package/templates/app/gitignore +24 -0
  16. package/templates/app/index.html +27 -0
  17. package/templates/app/package.json +32 -0
  18. package/templates/app/src/App.tsx +39 -0
  19. package/templates/app/src/components/theme-toggle.tsx +57 -0
  20. package/templates/app/src/components/ui/button.tsx +66 -0
  21. package/templates/app/src/components/ui/tooltip.tsx +54 -0
  22. package/templates/app/src/index.css +130 -0
  23. package/templates/app/src/lib/theme.ts +39 -0
  24. package/templates/app/src/lib/tiny.tsx +59 -0
  25. package/templates/app/src/lib/utils.ts +1 -0
  26. package/templates/app/src/main.tsx +20 -0
  27. package/templates/app/src/pages/home.tsx +10 -0
  28. package/templates/app/tsconfig.app.json +29 -0
  29. package/templates/app/tsconfig.json +12 -0
  30. package/templates/app/tsconfig.node.json +24 -0
  31. package/templates/app/vite.config.ts +14 -0
  32. package/templates/site/AGENTS.md +23 -0
  33. package/templates/site/CLAUDE.md +1 -0
  34. package/templates/site/README.md +10 -0
  35. package/templates/site/astro.config.mjs +11 -0
  36. package/templates/site/gitignore +24 -0
  37. package/templates/site/package.json +22 -0
  38. package/templates/site/src/layouts/Layout.astro +30 -0
  39. package/templates/site/src/pages/about.astro +10 -0
  40. package/templates/site/src/pages/index.astro +10 -0
  41. package/templates/site/src/styles/global.css +1 -0
  42. package/templates/site/tsconfig.json +5 -0
@@ -0,0 +1,53 @@
1
+ # Designing a website — pages people read
2
+
3
+ > If the person has design standards of their own — a design system, components, a brand guide, an app or site to match — follow those, and use this playbook only for what they leave open. If they gave none, follow it all.
4
+
5
+ A landing page, a portfolio, a small business, an event, docs or a blog: people arrive from a
6
+ link, decide in seconds whether it is for them, read, and maybe act once. Build it on the site
7
+ starter (Astro), so every page arrives as finished HTML that search engines and link previews
8
+ can read.
9
+
10
+ ## Shape by purpose
11
+
12
+ - **Landing page:** a hero that says what it is and for whom → proof (logos, numbers, a quote)
13
+ → how it works in three steps → what you get → questions → one clear final call to action.
14
+ - **Small business:** what, where and when first — hours, location, the menu or services, how
15
+ to book or call — then the story and the photos.
16
+ - **Portfolio:** the work first, big; a short line about the person; each project with the
17
+ problem, the result and the images.
18
+ - **Event:** name, date, place and the register button above the fold; schedule, speakers,
19
+ getting there, questions.
20
+ - **Docs or blog:** a reading layout — a narrow column (about 65 characters), a table of
21
+ contents, clear headings, code and images that fit the column.
22
+
23
+ ## The hero
24
+
25
+ Open with the most characteristic thing in the subject's world: the product in use, the dish,
26
+ the building, the work itself — not a gradient with three statistics. A headline under about
27
+ ten words that says what it is and for whom; one primary action and at most one secondary.
28
+
29
+ ## Craft
30
+
31
+ - Rhythm: generous vertical space between sections (80px or more on desktop), content width
32
+ capped (around 1100–1200px), prose narrower. Sections use that width — features side by
33
+ side, work or photos in a grid — rather than one narrow column all the way down. Vary section treatments sparingly — a band of
34
+ colour, a full-bleed image — so the page has a beat without looking like a template.
35
+ - Type can be more expressive than in an app: a display face for headlines, a calm text face
36
+ at 17–19px with a line height around 1.6; `text-wrap: balance` on headings.
37
+ - Images: real photographs or screenshots with one consistent treatment; width and height set,
38
+ lazy below the fold, compressed (WebP or AVIF), alt text that says what is shown.
39
+ - Motion: one orchestrated entrance for the hero, at most a gentle reveal as sections scroll
40
+ in. Never hijack scrolling.
41
+ - Navigation: a header that stays at the top as you scroll (it may shrink), a real menu on
42
+ a phone, a footer with contact and the essentials.
43
+ - Every page has its own title and description, one `h1`, headings in order, and an image for
44
+ link previews.
45
+ - One form (a sign-up, a booking request) follows the form playbook; the site adds the SDK tag
46
+ only for it.
47
+
48
+ ## Checks
49
+
50
+ - In five seconds a stranger can say what this is, who it is for and what to do next.
51
+ - It reads well on a phone first; tap targets are big enough and nothing scrolls sideways.
52
+ - The page loads fast: self-hosted fonts, sized images, little JavaScript.
53
+ - Then the general checklist, "Before you call it done".
@@ -0,0 +1,42 @@
1
+ # This app runs on Tiniest Cloud
2
+
3
+ React, TypeScript, Vite, Tailwind CSS and shadcn/ui, deployed to Tiniest Cloud as a static build.
4
+
5
+ - Sign-in, data, files and AI come from the global `tiny` (the `<script src="/sdk.js">` tag in
6
+ index.html; types in src/tiny.d.ts; React hooks in src/lib/tiny.tsx). Do not use localStorage,
7
+ write a login flow, or add a server of your own. The Tiniest Cloud guide has the whole SDK.
8
+ - Server logic — anything the person using the app must not be able to change, like a price or a
9
+ booking — is a backend function: `public/_functions/<name>.ts` exporting
10
+ `default async (input, tiny: Tiny.Server) => value`, called from the page with
11
+ `await tiny.call("<name>", input)`. The build copies it into `dist/_functions/`; it runs on
12
+ Tiniest Cloud and is never served.
13
+ - `npm run dev` shows a notice instead of the app: `tiny` only exists on Tiniest Cloud. To see the
14
+ app working, run `npm run build` and deploy this folder with `dist` built inside (no
15
+ node_modules, no .env files): `dist` is served, and the rest is kept as the project's source.
16
+ - Where files go, so every project reads the same way in Tiniest Cloud's Code tab:
17
+ - `src/pages/` — one file per screen (`home.tsx`, `orders.tsx`). App.tsx frames them; with
18
+ more than one, it routes to each with React Router, whose browser routes work as-is (the
19
+ platform answers every path with index.html). Keep Vite's default `base`.
20
+ Tiniest Cloud refuses a first deploy whose screens are anywhere else (all in App.tsx, say).
21
+ - `src/components/` — pieces the screens share; `src/components/ui/` for shadcn components.
22
+ - `src/hooks/` — the app's own `useSomething` hooks. The SDK's hooks stay in src/lib/tiny.tsx.
23
+ - `src/lib/` — plain code: helpers, formatting, types (`types.ts`), calculations.
24
+ - `public/` — files served as they are (images, icons), plus `_functions/`, `tiny.rules.json`
25
+ and `tiny.brand.json`.
26
+ - A file past about 300 lines is doing too much: move its parts into components and hooks.
27
+ - Components: `npx shadcn@latest add <name>` adds one to src/components/ui. They are a starting
28
+ point: restyle them to fit this app (radius, size, colour, spacing) in src/components/ui.
29
+ - Design: before the first screen, read the tiny-design skill — `.agents/skills/tiny-design/`
30
+ (the same files are in `.claude/skills/` for Claude Code): SKILL.md, then app.md. Where that
31
+ folder is missing, `tiny_guide` with topic `app` returns the same. Write what you decide in
32
+ DESIGN.md.
33
+ - Look: give the app its own palette in src/index.css (the `:root` and `.dark` blocks) that suits
34
+ what it is and who uses it — never ship the default grey. Lay each screen out for what it
35
+ holds (a sidebar, a grid of cards, a table, a list beside its detail); the frame in App.tsx and
36
+ the first screen in src/pages/home.tsx are placeholders, and a narrow centred column suits only
37
+ one focused task. Light, dark and "match
38
+ device" are chosen with `ThemeToggle` (src/components/theme-toggle.tsx); keep both themes
39
+ looking right.
40
+ - Their own components or brand: if the person points at a folder, a style guide or another app,
41
+ that is the design — reuse their components and bring their colours, fonts and logo into this
42
+ project (copied in, never linked to a path on their computer).
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,10 @@
1
+ # App
2
+
3
+ Built with React, TypeScript, Vite, Tailwind CSS and shadcn/ui, and hosted on Tiniest Cloud.
4
+
5
+ npm install
6
+ npm run build # the site is in dist/
7
+
8
+ Deploy this folder, with `dist` built inside, to Tiniest Cloud: `dist` is served, and the rest is
9
+ kept as the project's source. `npm run dev` only shows a notice: sign-in and data
10
+ come from the platform, so the app runs once it is deployed.
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/index.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,27 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>App</title>
7
+ <!-- The saved theme (src/lib/theme.ts), applied before anything draws so a dark page never flashes white. -->
8
+ <script>
9
+ (function () {
10
+ var saved = document.cookie.match(/(?:^|; )theme=(light|dark)/);
11
+ var dark = saved ? saved[1] === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches;
12
+ document.documentElement.classList.toggle("dark", dark);
13
+ document.documentElement.style.colorScheme = dark ? "dark" : "light";
14
+ })();
15
+ </script>
16
+ <link rel="manifest" href="/manifest.webmanifest" />
17
+ <link rel="apple-touch-icon" href="/_pwa/apple-touch-icon.png" />
18
+ <meta name="mobile-web-app-capable" content="yes" />
19
+ <meta name="apple-mobile-web-app-capable" content="yes" />
20
+ <!-- Tiniest Cloud's SDK: the global `tiny` (sign-in, data, AI, files). Served by the platform; vite-ignore leaves it as written. -->
21
+ <script src="/sdk.js" vite-ignore></script>
22
+ </head>
23
+ <body>
24
+ <div id="root"></div>
25
+ <script type="module" src="/src/main.tsx"></script>
26
+ </body>
27
+ </html>
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "typecheck": "tsc -b"
10
+ },
11
+ "dependencies": {
12
+ "@fontsource-variable/geist": "^5.3.0",
13
+ "@tailwindcss/vite": "^4",
14
+ "class-variance-authority": "^0.7.1",
15
+ "cn": "^0.4.0",
16
+ "lucide-react": "^1.48.0",
17
+ "radix-ui": "^1.6.7",
18
+ "react": "^19.2.8",
19
+ "react-dom": "^19.2.8",
20
+ "shadcn": "^4.21.0",
21
+ "tailwindcss": "^4",
22
+ "tw-animate-css": "^1.4.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^20",
26
+ "@types/react": "^19",
27
+ "@types/react-dom": "^19",
28
+ "@vitejs/plugin-react": "^6",
29
+ "typescript": "~6",
30
+ "vite": "^8"
31
+ }
32
+ }
@@ -0,0 +1,39 @@
1
+ import { Button } from "@/components/ui/button"
2
+ import { ThemeToggle } from "@/components/theme-toggle"
3
+ import { useMe } from "@/lib/tiny"
4
+ import Home from "@/pages/home"
5
+
6
+ export default function App() {
7
+ const me = useMe()
8
+ if (me === undefined) return null
9
+
10
+ return (
11
+ <div className="flex min-h-svh flex-col">
12
+ <header className="sticky top-0 z-40 border-b bg-background">
13
+ <div className="mx-auto flex h-14 w-full max-w-7xl items-center justify-between gap-4 px-4 sm:px-6">
14
+ <h1 className="text-base font-medium">App</h1>
15
+ <div className="flex items-center gap-3">
16
+ <ThemeToggle />
17
+ {me ? (
18
+ <div className="flex min-w-0 items-center gap-3 text-sm text-muted-foreground">
19
+ <span className="hidden truncate sm:inline">{me.email}</span>
20
+ <Button variant="outline" size="sm" onClick={() => tiny.logout()}>
21
+ Sign out
22
+ </Button>
23
+ </div>
24
+ ) : (
25
+ <Button size="sm" onClick={() => tiny.login()}>
26
+ Sign in
27
+ </Button>
28
+ )}
29
+ </div>
30
+ </div>
31
+ </header>
32
+ {/* A placeholder frame around the screens in src/pages. With more than one screen, add
33
+ React Router here: a route per page, the header shared by all of them. */}
34
+ <main className="mx-auto w-full max-w-7xl flex-1 px-4 py-6 sm:px-6">
35
+ <Home />
36
+ </main>
37
+ </div>
38
+ )
39
+ }
@@ -0,0 +1,57 @@
1
+ import { useEffect, useState } from "react"
2
+ import { Monitor, Moon, Sun } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
6
+ import { getTheme, setTheme, type Theme } from "@/lib/theme"
7
+
8
+ const CHOICES: Array<{ value: Theme; label: string; Icon: typeof Sun }> = [
9
+ { value: "light", label: "Light", Icon: Sun },
10
+ { value: "dark", label: "Dark", Icon: Moon },
11
+ { value: "system", label: "Match device", Icon: Monitor },
12
+ ]
13
+
14
+ /** Light, dark or the device's setting, as three small buttons with styled tooltips. Restyle it to fit the app. */
15
+ export function ThemeToggle({ className }: { className?: string }) {
16
+ const [theme, setCurrent] = useState<Theme>(getTheme)
17
+
18
+ useEffect(() => {
19
+ const sync = () => setCurrent(getTheme())
20
+ window.addEventListener("themechange", sync)
21
+ return () => window.removeEventListener("themechange", sync)
22
+ }, [])
23
+
24
+ return (
25
+ <TooltipProvider delayDuration={300}>
26
+ <div
27
+ role="radiogroup"
28
+ aria-label="Theme"
29
+ className={cn("inline-flex items-center gap-0.5 rounded-full border bg-muted/50 p-0.5", className)}
30
+ >
31
+ {CHOICES.map(({ value, label, Icon }) => (
32
+ <Tooltip key={value}>
33
+ <TooltipTrigger asChild>
34
+ <button
35
+ type="button"
36
+ role="radio"
37
+ aria-checked={theme === value}
38
+ aria-label={label}
39
+ onClick={() => setTheme(value)}
40
+ className={cn(
41
+ "flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors duration-200",
42
+ "hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
43
+ theme === value && "bg-background text-foreground shadow-sm"
44
+ )}
45
+ >
46
+ <Icon className="size-3.5" />
47
+ </button>
48
+ </TooltipTrigger>
49
+ <TooltipContent side="bottom" sideOffset={6}>
50
+ {label}
51
+ </TooltipContent>
52
+ </Tooltip>
53
+ ))}
54
+ </div>
55
+ </TooltipProvider>
56
+ )
57
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
4
+ import { Slot } from "radix-ui"
5
+
6
+ const buttonVariants = cva(
7
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
12
+ outline:
13
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
14
+ secondary:
15
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16
+ ghost:
17
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
18
+ destructive:
19
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default:
24
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
25
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
27
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
28
+ icon: "size-8",
29
+ "icon-xs":
30
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
31
+ "icon-sm":
32
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
33
+ "icon-lg": "size-9",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: "default",
38
+ size: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ function Button({
44
+ className,
45
+ variant = "default",
46
+ size = "default",
47
+ asChild = false,
48
+ ...props
49
+ }: React.ComponentProps<"button"> &
50
+ VariantProps<typeof buttonVariants> & {
51
+ asChild?: boolean
52
+ }) {
53
+ const Comp = asChild ? Slot.Root : "button"
54
+
55
+ return (
56
+ <Comp
57
+ data-slot="button"
58
+ data-variant={variant}
59
+ data-size={size}
60
+ className={cn(buttonVariants({ variant, size, className }))}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Button, buttonVariants }
@@ -0,0 +1,54 @@
1
+ import * as React from "react"
2
+ import { cn } from "cn"
3
+ import { Tooltip as TooltipPrimitive } from "radix-ui"
4
+
5
+ function TooltipProvider({
6
+ delayDuration = 0,
7
+ ...props
8
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
9
+ return (
10
+ <TooltipPrimitive.Provider
11
+ data-slot="tooltip-provider"
12
+ delayDuration={delayDuration}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function Tooltip({
19
+ ...props
20
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
21
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
22
+ }
23
+
24
+ function TooltipTrigger({
25
+ ...props
26
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
27
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
28
+ }
29
+
30
+ function TooltipContent({
31
+ className,
32
+ sideOffset = 0,
33
+ children,
34
+ ...props
35
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
36
+ return (
37
+ <TooltipPrimitive.Portal>
38
+ <TooltipPrimitive.Content
39
+ data-slot="tooltip-content"
40
+ sideOffset={sideOffset}
41
+ className={cn(
42
+ "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
49
+ </TooltipPrimitive.Content>
50
+ </TooltipPrimitive.Portal>
51
+ )
52
+ }
53
+
54
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
@@ -0,0 +1,130 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+ @import "@fontsource-variable/geist";
5
+
6
+ @custom-variant dark (&:is(.dark *));
7
+
8
+ @theme inline {
9
+ --font-heading: var(--font-sans);
10
+ --font-sans: 'Geist Variable', sans-serif;
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --color-foreground: var(--foreground);
41
+ --color-background: var(--background);
42
+ --radius-sm: calc(var(--radius) * 0.6);
43
+ --radius-md: calc(var(--radius) * 0.8);
44
+ --radius-lg: var(--radius);
45
+ --radius-xl: calc(var(--radius) * 1.4);
46
+ --radius-2xl: calc(var(--radius) * 1.8);
47
+ --radius-3xl: calc(var(--radius) * 2.2);
48
+ --radius-4xl: calc(var(--radius) * 2.6);
49
+ }
50
+
51
+ :root {
52
+ --background: oklch(1 0 0);
53
+ --foreground: oklch(0.145 0 0);
54
+ --card: oklch(1 0 0);
55
+ --card-foreground: oklch(0.145 0 0);
56
+ --popover: oklch(1 0 0);
57
+ --popover-foreground: oklch(0.145 0 0);
58
+ --primary: oklch(0.205 0 0);
59
+ --primary-foreground: oklch(0.985 0 0);
60
+ --secondary: oklch(0.97 0 0);
61
+ --secondary-foreground: oklch(0.205 0 0);
62
+ --muted: oklch(0.97 0 0);
63
+ --muted-foreground: oklch(0.556 0 0);
64
+ --accent: oklch(0.97 0 0);
65
+ --accent-foreground: oklch(0.205 0 0);
66
+ --destructive: oklch(0.577 0.245 27.325);
67
+ --border: oklch(0.922 0 0);
68
+ --input: oklch(0.922 0 0);
69
+ --ring: oklch(0.708 0 0);
70
+ --chart-1: oklch(0.87 0 0);
71
+ --chart-2: oklch(0.556 0 0);
72
+ --chart-3: oklch(0.439 0 0);
73
+ --chart-4: oklch(0.371 0 0);
74
+ --chart-5: oklch(0.269 0 0);
75
+ --radius: 0.625rem;
76
+ --sidebar: oklch(0.985 0 0);
77
+ --sidebar-foreground: oklch(0.145 0 0);
78
+ --sidebar-primary: oklch(0.205 0 0);
79
+ --sidebar-primary-foreground: oklch(0.985 0 0);
80
+ --sidebar-accent: oklch(0.97 0 0);
81
+ --sidebar-accent-foreground: oklch(0.205 0 0);
82
+ --sidebar-border: oklch(0.922 0 0);
83
+ --sidebar-ring: oklch(0.708 0 0);
84
+ }
85
+
86
+ .dark {
87
+ --background: oklch(0.145 0 0);
88
+ --foreground: oklch(0.985 0 0);
89
+ --card: oklch(0.205 0 0);
90
+ --card-foreground: oklch(0.985 0 0);
91
+ --popover: oklch(0.205 0 0);
92
+ --popover-foreground: oklch(0.985 0 0);
93
+ --primary: oklch(0.922 0 0);
94
+ --primary-foreground: oklch(0.205 0 0);
95
+ --secondary: oklch(0.269 0 0);
96
+ --secondary-foreground: oklch(0.985 0 0);
97
+ --muted: oklch(0.269 0 0);
98
+ --muted-foreground: oklch(0.708 0 0);
99
+ --accent: oklch(0.269 0 0);
100
+ --accent-foreground: oklch(0.985 0 0);
101
+ --destructive: oklch(0.704 0.191 22.216);
102
+ --border: oklch(1 0 0 / 10%);
103
+ --input: oklch(1 0 0 / 15%);
104
+ --ring: oklch(0.556 0 0);
105
+ --chart-1: oklch(0.87 0 0);
106
+ --chart-2: oklch(0.556 0 0);
107
+ --chart-3: oklch(0.439 0 0);
108
+ --chart-4: oklch(0.371 0 0);
109
+ --chart-5: oklch(0.269 0 0);
110
+ --sidebar: oklch(0.205 0 0);
111
+ --sidebar-foreground: oklch(0.985 0 0);
112
+ --sidebar-primary: oklch(0.488 0.243 264.376);
113
+ --sidebar-primary-foreground: oklch(0.985 0 0);
114
+ --sidebar-accent: oklch(0.269 0 0);
115
+ --sidebar-accent-foreground: oklch(0.985 0 0);
116
+ --sidebar-border: oklch(1 0 0 / 10%);
117
+ --sidebar-ring: oklch(0.556 0 0);
118
+ }
119
+
120
+ @layer base {
121
+ * {
122
+ @apply border-border outline-ring/50;
123
+ }
124
+ body {
125
+ @apply bg-background text-foreground;
126
+ }
127
+ html {
128
+ @apply font-sans;
129
+ }
130
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Light, dark, or whatever the device prefers — chosen by the person using the app and
3
+ * remembered on this device.
4
+ *
5
+ * Kept in a cookie rather than localStorage: it is a display preference, not data, and
6
+ * Tiniest Cloud flags localStorage in a deploy because that is where apps wrongly keep data.
7
+ * index.html reads the same cookie before anything draws, so a dark page never flashes white.
8
+ */
9
+ export type Theme = "light" | "dark" | "system"
10
+
11
+ const device = window.matchMedia("(prefers-color-scheme: dark)")
12
+
13
+ export function getTheme(): Theme {
14
+ const match = document.cookie.match(/(?:^|; )theme=(light|dark)/)
15
+ return match ? (match[1] as Theme) : "system"
16
+ }
17
+
18
+ function apply(theme: Theme) {
19
+ const dark = theme === "dark" || (theme === "system" && device.matches)
20
+ document.documentElement.classList.toggle("dark", dark)
21
+ document.documentElement.style.colorScheme = dark ? "dark" : "light"
22
+ }
23
+
24
+ export function setTheme(theme: Theme) {
25
+ document.cookie =
26
+ theme === "system"
27
+ ? "theme=; path=/; max-age=0; samesite=lax"
28
+ : `theme=${theme}; path=/; max-age=31536000; samesite=lax`
29
+ apply(theme)
30
+ window.dispatchEvent(new Event("themechange"))
31
+ }
32
+
33
+ /** Apply the saved choice now, and follow the device while the choice is "system". */
34
+ export function startTheme() {
35
+ apply(getTheme())
36
+ device.addEventListener("change", () => {
37
+ if (getTheme() === "system") apply("system")
38
+ })
39
+ }