Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-09-20 12:43:37 +02:00
parent a57dfe2a02
commit 60a6c908e9
16 changed files with 665 additions and 258 deletions

View File

@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.css"
referrerpolicy="no-referrer" />
<title>Factura F26200</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
color: #333;
font-size: 11pt;
line-height: 1.6;
}
header {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
margin-bottom: 0;
padding-bottom: 0;
}
.accent-color {
background-color: #F08119;
}
.company-info,
.invoice-meta {
width: 48%;
}
.invoice-meta {
text-align: right;
}
h1 {
font-size: 20px;
margin-bottom: 5px;
}
.contact {
font-size: 14px;
margin-top: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 0px;
margin-bottom: 15px;
}
table th,
table td {
border: 0px solid;
padding: 3px 10px;
text-align: left;
vertical-align: top;
}
table th {
margin-bottom: 10px;
}
.totals {
margin-top: 20px;
width: 100%;
}
.totals td {
padding: 5px 10px;
}
.totals td.label {
text-align: right;
font-weight: bold;
}
footer {
margin-top: 40px;
font-size: 10px;
}
.highlight {
background-color: #eef;
}
.accent-color {
background-color: #F08119;
}
@media print {
* {
-webkit-print-color-adjust: exact;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
}
</style>
</head>
<body>
<header>
<aside class="flex items-start mb-4 w-full">
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
<div class="w-[70%] flex flex-col items-start text-left">
<img src="https://rodax-software.com/images/logo1.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" />
<div class="flex w-full">
<div class="p-1 ">
<p>Factura nº:<strong>&nbsp;{{invoice_number}}</strong></p>
<p><span>Fecha:<strong>&nbsp;{{invoice_date}}</strong></p>
</div>
<div class="p-1 ml-9">
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
<p>{{recipient.tin}}</p>
<p>{{recipient.street}}</p>
<p>{{recipient.postal_code}}&nbsp;&nbsp;{{recipient.city}}&nbsp;&nbsp;{{recipient.province}}</p>
</div>
</div>
</div>
<!-- Bloque DERECHO: logo2 arriba y texto DEBAJO -->
<div class="ml-auto flex flex-col items-end text-right">
<img src="https://rodax-software.com/images/logo2.jpg" alt="Logo secundario"
class="block h-5 w-auto md:h-8 mb-1" />
<div class="not-italic text-xs leading-tight">
<p>Telf: 91 785 02 47 / 686 62 10 59</p>
<p><a href="mailto:info@rodax-software.com" class="hover:underline">info@rodax-software.com</a></p>
<p><a href="https://www.rodax-software.com" target="_blank" rel="noopener"
class="hover:underline">www.rodax-software.com</a></p>
</div>
</div>
</aside>
</header>
<main id="main">
<section id="details" class="border-b border-black ">
<div class="relative pt-0 border-b border-black">
<!-- Badge TOTAL decorado con imagen -->
<div class="absolute -top-9 right-0">
<div class="relative text-sm font-semibold text-black pr-2 pl-10 py-2 justify-center bg-red-900"
style="background-image: url('https://rodax-software.com/images/img-total2.jpg'); background-size: cover; background-position: left;">
<!-- Texto del total -->
<span>TOTAL: {{total_amount}}</span>
</div>
</div>
</div>
<!-- Tu tabla -->
<table class="table-header">
<thead>
<tr class="text-left">
<th class="py-2">Concepto</th>
<th class="py-2">Cantidad</th>
<th class="py-2">Precio&nbsp;unidad</th>
<th class="py-2">Importe&nbsp;total</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td class="text-right">{{#if quantity}}{{quantity}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if total_amount}}{{total_amount}}{{else}}&nbsp;{{/if}}</td>
</td>
</tr>
{{/each}}
</tbody>
</table>
</section>
<section id="resume" class="flex items-center justify-between pb-4 mb-4">
<div class="grow">
<div class="pt-4">
<p class="text-sm"><strong>Forma de pago:</strong> {{payment_method}}</p>
</div>
<div class="pt-4">
<p class="text-sm"><strong>Notas:</strong> {{notes}} </p>
</div>
</div>
<div class="relative pt-10 grow">
<table class="table-header min-w-full bg-transparent">
<tbody>
{{#if discount_percentage}}
<tr>
<td class="px-4 text-right">Importe&nbsp;neto</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{subtotal_amount}}</td>
</tr>
<tr>
<td class="px-4 text-right">Descuento&nbsp;{{discount_percentage}}</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{discount_amount.value}}</td>
</tr>
{{else}}
<!-- dto 0-->
{{/if}}
<tr>
<td class="px-4 text-right">Base&nbsp;imponible</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{taxable_amount}}</td>
</tr>
<tr>
<td class="px-4 text-right">IVA&nbsp;21%</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{taxes_amount}}</td>
</tr>
<tr class="">
<td class="px-4 text-right accent-color">
Total&nbsp;factura
</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right accent-color">
{{total_amount}}</td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
<footer id="footer" class="mt-4">
<aside>
<p class="text-center">Insc. en el Reg. Merc. de Madrid, Tomo 20.073, Libro 0, Folio 141, Sección 8, Hoja M-354212
| CIF: B83999441 -
Rodax Software S.L.</p>
</aside>
</footer>
</body>
</html>

View File

@ -1,5 +1,9 @@
{
"common": {},
"common": {
"more_details": "More details",
"back_to_list": "Back to the list",
"save": "Guardar"
},
"pages": {
"title": "Customers",
"description": "Manage your customers",
@ -19,13 +23,11 @@
},
"create": {
"title": "New customer",
"description": "Create a new customer",
"back_to_list": "Back to the list"
"description": "Create a new customer"
},
"update": {
"title": "Update customer",
"description": "Update a customer",
"back_to_list": "Back to the list"
"description": "Update a customer"
}
},
"form_fields": {

View File

@ -1,5 +1,7 @@
{
"common": {},
"common": {
"more_details": "Más detalles"
},
"pages": {
"title": "Clientes",
"description": "Gestiona tus clientes",

View File

@ -1,4 +1,4 @@
import { AppBreadcrumb, AppContent, BackHistoryButton, ButtonGroup } from "@repo/rdx-ui/components";
import { AppBreadcrumb, AppContent, ButtonGroup } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components";
import { useNavigate } from "react-router-dom";
@ -63,9 +63,12 @@ export const CustomerCreate = () => {
</p>
</div>
<ButtonGroup>
<BackHistoryButton />
<Button className='cursor-pointer' onClick={handleCancel}>
{t("common.cancel")}
</Button>
<Button type='submit' className='cursor-pointer'>
{t("pages.create.submit")}
{t("common.save")}
</Button>
</ButtonGroup>
</div>

View File

@ -36,22 +36,22 @@ export const CustomerBasicInfoFields = ({ control }: { control: any }) => {
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value ? "1" : "0"}
className='flex gap-6'
defaultValue={field.value ? "true" : "false"}
className='flex items-center gap-6'
>
<FormItem className='flex items-center space-x-2'>
<FormControl>
<RadioGroupItem value='1' />
<RadioGroupItem id='rgi-company' value='true' />
</FormControl>
<FormLabel className='font-normal'>
<FormLabel className='font-normal' htmlFor='rgi-company'>
{t("form_fields.customer_type.company")}
</FormLabel>
</FormItem>
<FormItem className='flex items-center space-x-2'>
<FormControl>
<RadioGroupItem value='0' />
<RadioGroupItem id='rgi-individual' value='false' />
</FormControl>
<FormLabel className='font-normal'>
<FormLabel className='font-normal' htmlFor='rgi-individual'>
{t("form_fields.customer_type.individual")}
</FormLabel>
</FormItem>

View File

@ -7,16 +7,10 @@ import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@repo/shadcn-ui/components";
import { TextField } from "@repo/rdx-ui/components";
import { Input } from "@repo/shadcn-ui/components";
import { ChevronDown, Phone } from "lucide-react";
import { ChevronDown, MailIcon, PhoneIcon, SmartphoneIcon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "../../i18n";
@ -30,121 +24,114 @@ export function CustomerContactFields({ control }: { control: any }) {
<CardTitle>{t("form_groups.contact_info.title")}</CardTitle>
<CardDescription>{t("form_groups.contact_info.description")}</CardDescription>
</CardHeader>
<CardContent className='grid grid-cols-1 gap-y-8 gap-x-6 @xl:grid-cols-2'>
<CardContent>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<TextField
className='lg:col-span-2'
control={control}
name='email_primary'
label={t("form_fields.email_primary.label")}
placeholder={t("form_fields.email_primary.placeholder")}
description={t("form_fields.email_primary.description")}
icon={
<MailIcon className='h-[18px] w-[18px] text-muted-foreground' strokeWidth={1.5} />
}
/>
<TextField
className='lg:col-span-2'
control={control}
name='mobile_primary'
label={t("form_fields.mobile_primary.label")}
placeholder={t("form_fields.mobile_primary.placeholder")}
description={t("form_fields.mobile_primary.description")}
icon={
<SmartphoneIcon
className='h-[18px] w-[18px] text-muted-foreground'
strokeWidth={1.5}
/>
}
/>
<TextField
className='lg:col-span-2 xl:'
control={control}
name='phone_primary'
label={t("form_fields.phone_primary.label")}
placeholder={t("form_fields.phone_primary.placeholder")}
description={t("form_fields.phone_primary.description")}
icon={
<PhoneIcon className='h-[18px] w-[18px] text-muted-foreground' strokeWidth={1.5} />
}
/>
</div>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<TextField
className='lg:col-span-2'
control={control}
name='email_secondary'
label={t("form_fields.email_secondary.label")}
placeholder={t("form_fields.email_secondary.placeholder")}
description={t("form_fields.email_secondary.description")}
icon={
<MailIcon className='h-[18px] w-[18px] text-muted-foreground' strokeWidth={1.5} />
}
/>
<TextField
className='lg:col-span-2'
control={control}
name='mobile_secondary'
label={t("form_fields.mobile_secondary.label")}
placeholder={t("form_fields.mobile_secondary.placeholder")}
description={t("form_fields.mobile_secondary.description")}
icon={
<SmartphoneIcon
className='h-[18px] w-[18px] text-muted-foreground'
strokeWidth={1.5}
/>
}
/>
<TextField
className='lg:col-span-2'
control={control}
name='phone_secondary'
label={t("form_fields.phone_secondary.label")}
placeholder={t("form_fields.phone_secondary.placeholder")}
description={t("form_fields.phone_secondary.description")}
icon={
<PhoneIcon className='h-[18px] w-[18px] text-muted-foreground' strokeWidth={1.5} />
}
/>
</div>
<Collapsible open={open} onOpenChange={setOpen} className='space-y-4'>
<CollapsibleTrigger className='inline-flex items-center gap-1 text-sm text-muted-foreground hover:underline'>
Más detalles{" "}
<CollapsibleTrigger className='inline-flex items-center gap-1 text-sm text-primary hover:underline'>
{t("common.more_details")}{" "}
<ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
</CollapsibleTrigger>
<CollapsibleContent>
<div className='grid grid-cols-1 gap-6 md:grid-cols-2'>
<FormField
control={control}
name='phone2'
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono secundario</FormLabel>
<FormControl>
<Input
icon={<Phone className='h-4 w-4 text-muted-foreground' />}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name='mobile2'
render={({ field }) => (
<FormItem>
<FormLabel>Móvil secundario</FormLabel>
<FormControl>
<Input
placeholder='+34 600 00 000'
icon={<Phone className='h-4 w-4 text-muted-foreground' />}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name='fax'
render={({ field }) => (
<FormItem className='md:col-span-2'>
<FormLabel>Fax</FormLabel>
<FormControl>
<Input placeholder='' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<div className='sm:col-span-2'>
<TextField
className='xl:col-span-2'
control={control}
name='website'
label={t("form_fields.website.label")}
placeholder={t("form_fields.website.placeholder")}
description={t("form_fields.website.description")}
/>
</div>
<div>
<TextField
control={control}
name='fax'
label={t("form_fields.fax.label")}
placeholder={t("form_fields.fax.placeholder")}
description={t("form_fields.fax.description")}
/>
</div>
</div>
</CollapsibleContent>
</Collapsible>
<TextField
control={control}
name='email_primary'
label={t("form_fields.email_primary.label")}
placeholder={t("form_fields.email_primary.placeholder")}
description={t("form_fields.email_primary.description")}
/>
<TextField
control={control}
name='email_secondary'
label={t("form_fields.email_secondary.label")}
placeholder={t("form_fields.email_secondary.placeholder")}
description={t("form_fields.email_secondary.description")}
/>
<TextField
control={control}
name='phone_primary'
label={t("form_fields.phone_primary.label")}
placeholder={t("form_fields.phone_primary.placeholder")}
description={t("form_fields.phone_primary.description")}
/>
<TextField
control={control}
name='phone_secondary'
label={t("form_fields.phone_secondary.label")}
placeholder={t("form_fields.phone_secondary.placeholder")}
description={t("form_fields.phone_secondary.description")}
/>
<TextField
control={control}
name='mobile_primary'
label={t("form_fields.mobile_primary.label")}
placeholder={t("form_fields.mobile_primary.placeholder")}
description={t("form_fields.mobile_primary.description")}
/>
<TextField
control={control}
name='mobile_secondary'
label={t("form_fields.mobile_secondary.label")}
placeholder={t("form_fields.mobile_secondary.placeholder")}
description={t("form_fields.mobile_secondary.description")}
/>
<TextField
control={control}
name='fax'
label={t("form_fields.fax.label")}
placeholder={t("form_fields.fax.placeholder")}
description={t("form_fields.fax.description")}
/>
<TextField
className='xl:col-span-2'
control={control}
name='website'
label={t("form_fields.website.label")}
placeholder={t("form_fields.website.placeholder")}
description={t("form_fields.website.description")}
/>
</CardContent>
</Card>
);

View File

@ -104,7 +104,16 @@ export const CustomerUpdate = () => {
</p>
</div>
<ButtonGroup>
<BackHistoryButton />
<Button
variant={"outline"}
className='cursor-pointer'
onClick={(e) => {
e.preventDefault();
}}
>
{t("common.cancel")}
</Button>
<Button
type='submit'
form='customer-edit-form'
@ -114,7 +123,7 @@ export const CustomerUpdate = () => {
aria-disabled={isUpdating || isLoadingCustomer}
data-state={isUpdating ? "loading" : "idle"}
>
{t("pages.update.submit")}
{t("common.save")}
</Button>
</ButtonGroup>
</div>

View File

@ -21,6 +21,9 @@ type TextFieldProps<TFormValues extends FieldValues> = {
required?: boolean;
readOnly?: boolean;
className?: string;
icon?: React.ReactNode; // Icono con tamaño: <MailIcon className="h-[18px] w-[18px]" />
iconPosition?: "left" | "right"; // 'left' por defecto
};
export function TextField<TFormValues extends FieldValues>({
@ -33,10 +36,16 @@ export function TextField<TFormValues extends FieldValues>({
required = false,
readOnly = false,
className,
icon,
iconPosition = "left",
}: TextFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled || readOnly;
const hasIcon = Boolean(icon);
const isLeft = iconPosition === "left";
const inputPadding = hasIcon ? (isLeft ? "pl-10" : "pr-10") : "";
const { getFieldState } = control;
const state = getFieldState(name);
@ -53,12 +62,26 @@ export function TextField<TFormValues extends FieldValues>({
</div>
)}
<FormControl>
<Input
disabled={isDisabled}
placeholder={placeholder}
{...field}
className='placeholder:font-normal placeholder:italic'
/>
<div className={cn("relative")}>
<Input
disabled={isDisabled}
placeholder={placeholder}
{...field}
className={cn("placeholder:font-normal placeholder:italic", inputPadding)}
/>
{hasIcon && (
<span
aria-hidden='true'
className={cn(
"pointer-events-none absolute top-1/2 -translate-y-1/2",
isLeft ? "left-3" : "right-3"
)}
>
{icon} {/* El tamaño viene indicado en el icono */}
</span>
)}
</div>
</FormControl>
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>

View File

@ -0,0 +1,11 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
export function FormContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot='form-content'
className={cn("grid grid-cols-1 gap-6 md:grid-cols-4 space-y-6", className)}
{...props}
/>
);
}

View File

@ -1,5 +1,6 @@
export * from "./DatePickerField.tsx";
export * from "./DatePickerInputField.tsx";
export * from "./form-content.tsx";
export * from "./SelectField.tsx";
export * from "./TextAreaField.tsx";
export * from "./TextField.tsx";

View File

@ -0,0 +1,30 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import * as React from "react";
/** 112 spans por breakpoint */
export type Spans = Partial<{ base: number; sm: number; md: number; lg: number; xl: number }>;
function to12(n?: number) {
if (!n) return 12;
return n < 1 ? 1 : n > 12 ? 12 : n;
}
function spansToClasses(spans?: Spans) {
const s = spans ?? { base: 12 };
const parts: string[] = [];
if (s.base) parts.push(`col-span-${to12(s.base)}`);
if (s.sm) parts.push(`sm:col-span-${to12(s.sm)}`);
if (s.md) parts.push(`md:col-span-${to12(s.md)}`);
if (s.lg) parts.push(`lg:col-span-${to12(s.lg)}`);
if (s.xl) parts.push(`xl:col-span-${to12(s.xl)}`);
return parts.join(" ");
}
export interface CellProps extends React.HTMLAttributes<HTMLDivElement> {
/** 112 por breakpoint (p.ej. { base:12, sm:6, xl:3 }) */
span?: Spans;
}
export function Cell({ span, className, ...rest }: CellProps) {
return <div className={cn(spansToClasses(span), className)} {...rest} />;
}

View File

@ -0,0 +1,64 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react";
/** 112 columnas por breakpoint */
export type Cols = Partial<{ base: number; sm: number; md: number; lg: number; xl: number }>;
const gridBase = cva("grid", {
variants: {
gap: {
0: "gap-0",
1: "gap-1",
2: "gap-2",
3: "gap-3",
4: "gap-4",
5: "gap-5",
6: "gap-6",
8: "gap-8",
10: "gap-10",
},
align: {
start: "items-start",
center: "items-center",
end: "items-end",
stretch: "items-stretch",
},
},
defaultVariants: {
gap: 6,
align: "stretch",
},
});
function to12(n?: number) {
if (!n) return 12;
return n < 1 ? 1 : n > 12 ? 12 : n;
}
function colsToClasses(cols?: Cols) {
if (!cols) return "";
const c: string[] = [];
if (cols.base) c.push(`grid-cols-${to12(cols.base)}`);
if (cols.sm) c.push(`sm:grid-cols-${to12(cols.sm)}`);
if (cols.md) c.push(`md:grid-cols-${to12(cols.md)}`);
if (cols.lg) c.push(`lg:grid-cols-${to12(cols.lg)}`);
if (cols.xl) c.push(`xl:grid-cols-${to12(cols.xl)}`);
return c.join(" ");
}
export interface GridProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">,
VariantProps<typeof gridBase> {
cols?: Cols;
children?: React.ReactNode;
}
/** Grid genérico: controla columnas visibles por breakpoint */
export function Grid({ className, cols, gap, align, children, ...rest }: GridProps) {
return (
<div className={cn(gridBase({ gap, align }), colsToClasses(cols), className)} {...rest}>
{children}
</div>
);
}

View File

@ -0,0 +1,2 @@
export * from "./cell.tsx";
export * from "./grid.tsx";

View File

@ -3,6 +3,7 @@ export * from "./custom-dialog.tsx";
export * from "./datatable/index.tsx";
export * from "./error-overlay.tsx";
export * from "./form/index.tsx";
export * from "./grid/index.ts";
export * from "./layout/index.tsx";
export * from "./loading-overlay/index.tsx";
export * from "./lookup-dialog/index.tsx";

View File

@ -8,8 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot='input'
className={cn(
"bg-input text-foreground",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className

View File

@ -4,26 +4,13 @@
@custom-variant dark (&:is(.dark *));
/**
*
* https://www.windpalette.com/app
https://themecn.dev/dashboard?theme=eyJsIjp7ImJnIjoiMCwwLDEwMCIsImZnIjoiMjI2LDEwLDQiLCJwIjoiMjI2LDEwMCw1NSIsInMiOiIyMjYsMjUsOTUiLCJhIjoiMjI2LDEwMCw4NSIsImQiOiIzNTcuMTgsMTAwLDQ1In0sImYiOlsiTGF0byIsIlBvcHBpbnMiXSwiciI6MC41fQ==
**/
*
* https://tweakcn.com/
* https://themux.vercel.app/shadcn-themes
*
**/
@theme {
--sapphire-50: #f2f5ff;
--sapphire-100: #e3eaff;
--sapphire-200: #c6d4ff;
--sapphire-300: #a1b8ff;
--sapphire-400: #7696ff;
--sapphire-500: #4b74ff;
--sapphire-600: #2152ff;
--sapphire-700: #0033e6;
--sapphire-800: #0029cc;
--sapphire-900: #001f99;
--sapphire-950: #001466;
--graphite-50: #f8f9fc;
--graphite-100: #f1f2f9;
--graphite-200: #e2e4f0;
@ -49,115 +36,153 @@
--magenta-950: #660031;
}
@theme inline {
--font-sans: Roboto, sans-serif;
--font-serif: Domine, serif;
--font-mono: "Roboto Mono", monospace;
:root {
--background: oklch(1 0 0);
--foreground: oklch(13.636% 0.02685 282.25);
--card: oklch(1.0 0 0);
--card-foreground: oklch(0.2035 0.0139 285.1021);
--popover: oklch(1.0 0 0);
--popover-foreground: oklch(0.2035 0.0139 285.1021);
--primary: oklch(0.623 0.214 259.815);
--primary-foreground: oklch(1.0 0 0);
--secondary: oklch(0.9574 0.0011 197.1383);
--secondary-foreground: oklch(0.2069 0.0098 285.5081);
--muted: oklch(0.9674 0.0013 286.3752);
--muted-foreground: oklch(0.5466 0.0216 285.664);
--accent: oklch(0.9674 0.0013 286.3752);
--accent-foreground: oklch(0.2069 0.0098 285.5081);
--destructive: oklch(0.583 0.2387 28.4765);
--destructive-foreground: oklch(1.0 0 0);
--border: oklch(0.9173 0.0067 286.2663);
--input: oklch(0.9173 0.0067 286.2663);
--ring: oklch(0.6187 0.2067 259.2316);
--chart-1: oklch(0.6471 0.2173 36.8511);
--chart-2: oklch(37.973% 0.0506 187.591);
--chart-3: oklch(0.4247 0.0852 230.5827);
--chart-4: oklch(0.829 0.1712 81.0381);
--chart-5: oklch(0.7724 0.1728 65.367);
--sidebar: oklch(0.957 0.007 272.5840410480741);
--sidebar-foreground: oklch(0.2035 0.0139 285.1021);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(1.0 0 0);
--sidebar-accent: oklch(0.9674 0.0013 286.3752);
--sidebar-accent-foreground: oklch(0.2069 0.0098 285.5081);
--sidebar-border: oklch(0.9173 0.0067 286.2663);
--sidebar-ring: oklch(0.623 0.214 259.815);
--font-sans: Roboto Flex, ui-sans-serif, sans-serif, system-ui;
--font-serif: Adamina, ui-serif, serif;
--font-mono: Roboto Mono, ui-monospace, monospace;
--radius: 0.5rem;
--shadow-2xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 1px 1px 6px 0px hsl(0 0% 0% / 0.25);
--tracking-normal: 0em;
--spacing: 0.25rem;
}
.dark {
--background: oklch(0.1448 0 0);
--foreground: oklch(0.9851 0 0);
--card: oklch(0.2046 0 0);
--card-foreground: oklch(0.9851 0 0);
--popover: oklch(0.2046 0 0);
--popover-foreground: oklch(0.9851 0 0);
--primary: oklch(0.5471 0.2506 262.8726);
--primary-foreground: oklch(0.9705 0.0142 254.6042);
--secondary: oklch(0.2707 0.0092 285.7705);
--secondary-foreground: oklch(0.9851 0 0);
--muted: oklch(0.2686 0 0);
--muted-foreground: oklch(0.709 0 0);
--accent: oklch(0.2686 0 0);
--accent-foreground: oklch(0.9851 0 0);
--destructive: oklch(0.7022 0.1892 22.2279);
--destructive-foreground: oklch(0.2558 0.0412 235.1561);
--border: oklch(1.0 0 0);
--input: oklch(1.0 0 0);
--ring: oklch(0.4915 0.2776 263.8724);
--chart-1: oklch(0.4915 0.2776 263.8724);
--chart-2: oklch(0.7019 0.1577 160.4375);
--chart-3: oklch(0.7724 0.1728 65.367);
--chart-4: oklch(0.6217 0.2589 305.309);
--chart-5: oklch(0.6435 0.2452 16.501);
--sidebar: oklch(0.2046 0 0);
--sidebar-foreground: oklch(0.9851 0 0);
--sidebar-primary: oklch(0.5471 0.2506 262.8726);
--sidebar-primary-foreground: oklch(0.9705 0.0142 254.6042);
--sidebar-accent: oklch(0.2686 0 0);
--sidebar-accent-foreground: oklch(0.9851 0 0);
--sidebar-border: oklch(1.0 0 0);
--sidebar-ring: oklch(0.4915 0.2776 263.8724);
--font-sans: Roboto Flex, ui-sans-serif, sans-serif, system-ui;
--font-serif: Lora, serif;
--font-mono: Roboto Mono, ui-monospace, monospace;
--radius: 0.4rem;
--shadow-2xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 1px 1px 6px 0px hsl(0 0% 0% / 0.1), 1px 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 1px 1px 6px 0px hsl(0 0% 0% / 0.25);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--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-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-serif: var(--font-serif);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.3rem;
--background: oklch(1.0 0.0 0);
--foreground: oklch(0.143 0.003 271.9282674829111);
--card: oklch(0.977 0.007 272.5840410480741);
--card-foreground: oklch(0.143 0.003 271.9282674829111);
--popover: oklch(1.0 0.0 0);
--popover-foreground: oklch(0.143 0.003 271.9282674829111);
--primary: oklch(0.527 0.263 264.6358829854314);
--primary-foreground: oklch(0.96 0.003 272.6281326778613);
--secondary: oklch(0.957 0.007 272.5840410480741);
--secondary-foreground: oklch(0.2 0.024 271.03952637990255);
--muted: oklch(0.957 0.007 272.5840410480741);
--muted-foreground: oklch(0.501 0.087 270.1873691459851);
--accent: oklch(0.893 0.011 270.0165724649083);
--accent-foreground: oklch(0.194 0.039 267.55460547761646);
--destructive: oklch(0.58 0.237 28.43022926835137);
--border: oklch(0.87 0.021 272.39716219522893);
--input: oklch(0.87 0.021 272.39716219522893);
--ring: oklch(0.527 0.263 264.6358829854314);
--chart-1: oklch(0.527 0.263 264.6358829854314);
--chart-2: oklch(0.587 0.17 268.089554204009);
--chart-3: oklch(0.698 0.107 270.5337309213311);
--chart-4: oklch(0.344 0.167 264.8096356890612);
--chart-5: oklch(0.265 0.121 265.17321408623843);
--sidebar: oklch(0.957 0.007 272.5840410480741);
--sidebar-foreground: oklch(0.2 0.024 271.03952637990255);
--sidebar-primary: oklch(0.527 0.263 264.6358829854314);
--sidebar-primary-foreground: oklch(0.96 0.003 272.6281326778613);
--sidebar-accent: oklch(0.893 0.011 270.0165724649083);
--sidebar-accent-foreground: oklch(0.194 0.039 267.55460547761646);
--sidebar-border: oklch(0.87 0.021 272.39716219522893);
--sidebar-ring: oklch(0.527 0.263 264.6358829854314);
}
.dark {
--background: oklch(0.18 0.02 271.2071690195347);
--foreground: oklch(0.96 0.003 272.6281326778613);
--card: oklch(0.219 0.028 270.9064636444989);
--card-foreground: oklch(0.96 0.003 272.6281326778613);
--popover: oklch(0.219 0.028 270.9064636444989);
--popover-foreground: oklch(0.96 0.003 272.6281326778613);
--primary: oklch(0.572 0.216 266.49817452939124);
--primary-foreground: oklch(0.96 0.003 272.6281326778613);
--secondary: oklch(0.297 0.037 271.01211769802813);
--secondary-foreground: oklch(0.956 0.008 272.5689802513852);
--muted: oklch(0.297 0.037 271.01211769802813);
--muted-foreground: oklch(0.703 0.031 272.154700569603);
--accent: oklch(0.645 0.061 269.1114142997618);
--accent-foreground: oklch(0.953 0.013 270.00473513692674);
--destructive: oklch(0.58 0.237 28.43022926835137);
--border: oklch(0.38 0.062 270.35456036349854);
--input: oklch(0.38 0.062 270.35456036349854);
--ring: oklch(0.572 0.216 266.49817452939124);
--chart-1: oklch(0.572 0.216 266.49817452939124);
--chart-2: oklch(0.628 0.168 268.4986758327506);
--chart-3: oklch(0.728 0.124 270.22825208762697);
--chart-4: oklch(0.388 0.15 266.35164847353985);
--chart-5: oklch(0.32 0.089 268.3276964633901);
--sidebar: oklch(0.293 0.044 270.5701620578212);
--sidebar-foreground: oklch(0.96 0.003 272.6281326778613);
--sidebar-primary: oklch(0.572 0.216 266.49817452939124);
--sidebar-primary-foreground: oklch(0.96 0.003 272.6281326778613);
--sidebar-accent: oklch(0.645 0.061 269.1114142997618);
--sidebar-accent-foreground: oklch(0.953 0.013 270.00473513692674);
--sidebar-border: oklch(0.38 0.062 270.35456036349854);
--sidebar-ring: oklch(0.572 0.216 266.49817452939124);
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
}
@layer base {