FIRE calculator
This commit is contained in:
		
							
								
								
									
										291
									
								
								src/app/components/FireCalculatorForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/app/components/FireCalculatorForm.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import * as z from "zod";
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import {
 | 
			
		||||
  Form,
 | 
			
		||||
  FormControl,
 | 
			
		||||
  FormDescription,
 | 
			
		||||
  FormField,
 | 
			
		||||
  FormItem,
 | 
			
		||||
  FormLabel,
 | 
			
		||||
  FormMessage,
 | 
			
		||||
} from "../../components/ui/form";
 | 
			
		||||
import {
 | 
			
		||||
  Card,
 | 
			
		||||
  CardContent,
 | 
			
		||||
  CardDescription,
 | 
			
		||||
  CardFooter,
 | 
			
		||||
  CardHeader,
 | 
			
		||||
  CardTitle,
 | 
			
		||||
} from "../../components/ui/card";
 | 
			
		||||
 | 
			
		||||
// Schema for form validation
 | 
			
		||||
const formSchema = z.object({
 | 
			
		||||
  startingCapital: z.coerce
 | 
			
		||||
    .number()
 | 
			
		||||
    .min(0, "Starting capital must be a non-negative number"),
 | 
			
		||||
  monthlySavings: z.coerce
 | 
			
		||||
    .number()
 | 
			
		||||
    .min(0, "Monthly savings must be a non-negative number"),
 | 
			
		||||
  currentAge: z.coerce.number().min(18, "Age must be at least 18"),
 | 
			
		||||
  cagr: z.coerce.number().min(0, "Growth rate must be a non-negative number"),
 | 
			
		||||
  desiredMonthlyAllowance: z.coerce
 | 
			
		||||
    .number()
 | 
			
		||||
    .min(0, "Monthly allowance must be a non-negative number"),
 | 
			
		||||
  swr: z.coerce.number().min(0.1, "Withdrawal rate must be at least 0.1%"),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Type for form values
 | 
			
		||||
type FormValues = z.infer<typeof formSchema>;
 | 
			
		||||
 | 
			
		||||
interface CalculationResult {
 | 
			
		||||
  fireNumber: number | null;
 | 
			
		||||
  retirementAge: number | null;
 | 
			
		||||
  error?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function FireCalculatorForm() {
 | 
			
		||||
  const [result, setResult] = useState<CalculationResult | null>(null);
 | 
			
		||||
 | 
			
		||||
  // Initialize form with default values
 | 
			
		||||
  const form = useForm<FormValues>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      startingCapital: 10000,
 | 
			
		||||
      monthlySavings: 500,
 | 
			
		||||
      currentAge: 30,
 | 
			
		||||
      cagr: 7,
 | 
			
		||||
      desiredMonthlyAllowance: 2000,
 | 
			
		||||
      swr: 4,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function onSubmit(values: FormValues) {
 | 
			
		||||
    setResult(null); // Reset previous results
 | 
			
		||||
 | 
			
		||||
    const sc = values.startingCapital;
 | 
			
		||||
    const ms = values.monthlySavings;
 | 
			
		||||
    const ca = values.currentAge;
 | 
			
		||||
    const annualRate = values.cagr / 100;
 | 
			
		||||
    const monthlyAllowance = values.desiredMonthlyAllowance;
 | 
			
		||||
    const safeWithdrawalRate = values.swr / 100;
 | 
			
		||||
 | 
			
		||||
    // Calculate FIRE number (the amount needed for retirement)
 | 
			
		||||
    const fireNumber = (monthlyAllowance * 12) / safeWithdrawalRate;
 | 
			
		||||
 | 
			
		||||
    let currentCapital = sc;
 | 
			
		||||
    let age = ca;
 | 
			
		||||
    const monthlyRate = Math.pow(1 + annualRate, 1 / 12) - 1;
 | 
			
		||||
    const maxYears = 100; // Set a limit to prevent infinite loops
 | 
			
		||||
 | 
			
		||||
    if (currentCapital >= fireNumber) {
 | 
			
		||||
      setResult({ fireNumber, retirementAge: age });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let year = 0; year < maxYears; year++) {
 | 
			
		||||
      const capitalAtYearStart = currentCapital;
 | 
			
		||||
      for (let month = 0; month < 12; month++) {
 | 
			
		||||
        currentCapital += ms;
 | 
			
		||||
        currentCapital *= 1 + monthlyRate;
 | 
			
		||||
      }
 | 
			
		||||
      age++;
 | 
			
		||||
 | 
			
		||||
      if (currentCapital >= fireNumber) {
 | 
			
		||||
        setResult({ fireNumber, retirementAge: age });
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      // Prevent infinite loop if savings don't outpace growth required
 | 
			
		||||
      if (currentCapital <= capitalAtYearStart && ms <= 0) {
 | 
			
		||||
        setResult({
 | 
			
		||||
          fireNumber: null,
 | 
			
		||||
          retirementAge: null,
 | 
			
		||||
          error: "Cannot reach FIRE goal with current savings and growth rate.",
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If loop finishes without reaching FIRE number
 | 
			
		||||
    setResult({
 | 
			
		||||
      fireNumber: null,
 | 
			
		||||
      retirementAge: null,
 | 
			
		||||
      error: `Could not reach FIRE goal within ${maxYears.toString()} years.`,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Helper function to format currency
 | 
			
		||||
  const formatCurrency = (value: number | null) => {
 | 
			
		||||
    if (value === null) return "N/A";
 | 
			
		||||
    return new Intl.NumberFormat("en", {
 | 
			
		||||
      maximumFractionDigits: 0,
 | 
			
		||||
    }).format(value);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="w-full max-w-3xl">
 | 
			
		||||
      <Card className="mb-8">
 | 
			
		||||
        <CardHeader>
 | 
			
		||||
          <CardTitle className="text-2xl">FIRE Calculator</CardTitle>
 | 
			
		||||
          <CardDescription>
 | 
			
		||||
            Calculate your path to financial independence and retirement
 | 
			
		||||
          </CardDescription>
 | 
			
		||||
        </CardHeader>
 | 
			
		||||
        <CardContent>
 | 
			
		||||
          <Form {...form}>
 | 
			
		||||
            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
 | 
			
		||||
              <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="startingCapital"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>Starting Capital</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 10000"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="monthlySavings"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>Monthly Savings</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 500"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="currentAge"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>Current Age</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 30"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="cagr"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>Expected Annual Growth Rate (%)</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 7"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          step="0.1"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="desiredMonthlyAllowance"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>
 | 
			
		||||
                        Desired Monthly Allowance in Retirement
 | 
			
		||||
                      </FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 2000"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="swr"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>Safe Withdrawal Rate (%)</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder="e.g., 4"
 | 
			
		||||
                          type="number"
 | 
			
		||||
                          step="0.1"
 | 
			
		||||
                          {...field}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <Button type="submit" className="w-full">
 | 
			
		||||
                Calculate
 | 
			
		||||
              </Button>
 | 
			
		||||
            </form>
 | 
			
		||||
          </Form>
 | 
			
		||||
        </CardContent>
 | 
			
		||||
      </Card>
 | 
			
		||||
 | 
			
		||||
      {result && (
 | 
			
		||||
        <Card>
 | 
			
		||||
          <CardHeader>
 | 
			
		||||
            <CardTitle>Results</CardTitle>
 | 
			
		||||
          </CardHeader>
 | 
			
		||||
          <CardContent>
 | 
			
		||||
            {result.error ? (
 | 
			
		||||
              <p className="text-destructive">{result.error}</p>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <div className="space-y-4">
 | 
			
		||||
                <div>
 | 
			
		||||
                  <Label>FIRE Number (Required Capital)</Label>
 | 
			
		||||
                  <p className="text-2xl font-bold">
 | 
			
		||||
                    {formatCurrency(result.fireNumber)}
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <Label>Estimated Retirement Age</Label>
 | 
			
		||||
                  <p className="text-2xl font-bold">
 | 
			
		||||
                    {result.retirementAge ?? "N/A"}
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/app/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/app/layout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import "@/styles/globals.css";
 | 
			
		||||
 | 
			
		||||
import { type Metadata } from "next";
 | 
			
		||||
import { Geist } from "next/font/google";
 | 
			
		||||
 | 
			
		||||
export const metadata: Metadata = {
 | 
			
		||||
  title: "Create T3 App",
 | 
			
		||||
  description: "Generated by create-t3-app",
 | 
			
		||||
  icons: [{ rel: "icon", url: "/favicon.ico" }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const geist = Geist({
 | 
			
		||||
  subsets: ["latin"],
 | 
			
		||||
  variable: "--font-geist-sans",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default function RootLayout({
 | 
			
		||||
  children,
 | 
			
		||||
}: Readonly<{ children: React.ReactNode }>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <html lang="en" className={`${geist.variable}`}>
 | 
			
		||||
      <body>{children}</body>
 | 
			
		||||
    </html>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/app/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/app/page.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import FireCalculatorForm from "./components/FireCalculatorForm";
 | 
			
		||||
 | 
			
		||||
export default function HomePage() {
 | 
			
		||||
  return (
 | 
			
		||||
    <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[oklch(0.49_0.1326_259.29)] to-[oklch(0.33_0.1316_336.24)] p-4 text-[oklch(0.97_0.0228_95.96)]">
 | 
			
		||||
      <div className="container mx-auto flex flex-col items-center justify-center gap-12 px-4 py-16">
 | 
			
		||||
        <h1 className="text-primary-foreground text-5xl font-extrabold tracking-tight sm:text-[5rem]">
 | 
			
		||||
          FIRE Calculator
 | 
			
		||||
        </h1>
 | 
			
		||||
        <FireCalculatorForm />
 | 
			
		||||
      </div>
 | 
			
		||||
    </main>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { clsx, type ClassValue } from "clsx"
 | 
			
		||||
import { twMerge } from "tailwind-merge"
 | 
			
		||||
import { clsx, type ClassValue } from "clsx";
 | 
			
		||||
import { twMerge } from "tailwind-merge";
 | 
			
		||||
 | 
			
		||||
export function cn(...inputs: ClassValue[]) {
 | 
			
		||||
  return twMerge(clsx(inputs))
 | 
			
		||||
  return twMerge(clsx(...inputs));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,8 @@
 | 
			
		||||
@custom-variant dark (&:is(.dark *));
 | 
			
		||||
 | 
			
		||||
@theme {
 | 
			
		||||
  --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif,
 | 
			
		||||
  --font-sans:
 | 
			
		||||
    var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif,
 | 
			
		||||
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -48,71 +49,77 @@
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --radius: 0.625rem;
 | 
			
		||||
  --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.205 0 0);
 | 
			
		||||
  --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.708 0 0);
 | 
			
		||||
  --chart-1: oklch(0.646 0.222 41.116);
 | 
			
		||||
  --chart-2: oklch(0.6 0.118 184.704);
 | 
			
		||||
  --chart-3: oklch(0.398 0.07 227.392);
 | 
			
		||||
  --chart-4: oklch(0.828 0.189 84.429);
 | 
			
		||||
  --chart-5: oklch(0.769 0.188 70.08);
 | 
			
		||||
  --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);
 | 
			
		||||
  --background: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --foreground: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --card: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --card-foreground: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --popover: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --popover-foreground: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --primary: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --primary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --secondary: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --secondary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --muted: oklch(0.67 0.0763 198.81 / 20%); /* verdigris with opacity */
 | 
			
		||||
  --muted-foreground: oklch(
 | 
			
		||||
    0.39 0.0215 96.47 / 80%
 | 
			
		||||
  ); /* black olive with opacity */
 | 
			
		||||
  --accent: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --accent-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --destructive: oklch(0.33 0.1316 336.24); /* palatinate */
 | 
			
		||||
  --border: oklch(0.67 0.0763 198.81 / 30%); /* verdigris with opacity */
 | 
			
		||||
  --input: oklch(0.67 0.0763 198.81 / 30%); /* verdigris with opacity */
 | 
			
		||||
  --ring: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --chart-1: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --chart-2: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --chart-3: oklch(0.33 0.1316 336.24); /* palatinate */
 | 
			
		||||
  --chart-4: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --chart-5: oklch(0.67 0.0763 198.81 / 70%); /* verdigris with opacity */
 | 
			
		||||
  --sidebar: oklch(0.49 0.1326 259.29 / 10%); /* denim with opacity */
 | 
			
		||||
  --sidebar-foreground: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --sidebar-primary: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --sidebar-primary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --sidebar-accent: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --sidebar-accent-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --sidebar-border: oklch(
 | 
			
		||||
    0.67 0.0763 198.81 / 20%
 | 
			
		||||
  ); /* verdigris with opacity */
 | 
			
		||||
  --sidebar-ring: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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.922 0 0);
 | 
			
		||||
  --primary-foreground: oklch(0.205 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.556 0 0);
 | 
			
		||||
  --chart-1: oklch(0.488 0.243 264.376);
 | 
			
		||||
  --chart-2: oklch(0.696 0.17 162.48);
 | 
			
		||||
  --chart-3: oklch(0.769 0.188 70.08);
 | 
			
		||||
  --chart-4: oklch(0.627 0.265 303.9);
 | 
			
		||||
  --chart-5: oklch(0.645 0.246 16.439);
 | 
			
		||||
  --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);
 | 
			
		||||
  --background: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --card: oklch(0.39 0.0215 96.47 / 80%); /* black olive with opacity */
 | 
			
		||||
  --card-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --popover: oklch(0.39 0.0215 96.47); /* black olive */
 | 
			
		||||
  --popover-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --primary: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --primary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --secondary: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --secondary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --muted: oklch(0.39 0.0215 96.47 / 70%); /* black olive with opacity */
 | 
			
		||||
  --muted-foreground: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --accent: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --accent-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --destructive: oklch(0.33 0.1316 336.24); /* palatinate */
 | 
			
		||||
  --border: oklch(0.97 0.0228 95.96 / 20%); /* cosmic latte with opacity */
 | 
			
		||||
  --input: oklch(0.97 0.0228 95.96 / 20%); /* cosmic latte with opacity */
 | 
			
		||||
  --ring: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --chart-1: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --chart-2: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --chart-3: oklch(0.33 0.1316 336.24); /* palatinate */
 | 
			
		||||
  --chart-4: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --chart-5: oklch(0.67 0.0763 198.81 / 70%); /* verdigris with opacity */
 | 
			
		||||
  --sidebar: oklch(0.39 0.0215 96.47 / 90%); /* black olive with opacity */
 | 
			
		||||
  --sidebar-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --sidebar-primary: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
  --sidebar-primary-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --sidebar-accent: oklch(0.49 0.1326 259.29); /* denim */
 | 
			
		||||
  --sidebar-accent-foreground: oklch(0.97 0.0228 95.96); /* cosmic latte */
 | 
			
		||||
  --sidebar-border: oklch(
 | 
			
		||||
    0.97 0.0228 95.96 / 10%
 | 
			
		||||
  ); /* cosmic latte with opacity */
 | 
			
		||||
  --sidebar-ring: oklch(0.67 0.0763 198.81); /* verdigris */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@layer base {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user