Wardrobe / Nodesign / Install

Install in
30 seconds.

Pick your AI tool. Hit copy. Paste it into a fresh chat. Strip everything until only one element remains on screen.

Install the Wardrobe Nodesign theme in my project. I'm using Claude Code / Cursor. Apply every step below as written.

Step 1 — Add this to globals.css:

@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap");

/* =========================================================
   Wardrobe — NODESIGN tokens
   Almost-invisible minimalism. The absence is the design.
   White canvas, Inter at 11-14px, every line earned.
   Activate via data-system="nodesign".
   ========================================================= */

:where([data-system="nodesign"]) {
    /* ---- Surface ---- */
    --color-bg: #ffffff;
    --color-fg: #000000;
    --color-fg-soft: rgba(0, 0, 0, 0.4);
    --color-fg-faint: rgba(0, 0, 0, 0.1);
    --color-fg-trace: rgba(0, 0, 0, 0.05);
    --color-image-placeholder: #f5f5f5;

    /* Aliases used in reference designs */
    --bg: #ffffff;
    --fg: #000000;

    /* ---- Typography ---- */
    --font-display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-main: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-body: var(--font-main);
    --font-mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;

    --weight-regular: 400;
    --weight-medium: 500;

    --label-tracking: 0.1em;
    --peripheral-tracking: 0.02em;
    --focal-tracking: 0.15em;
    --tight-tracking: -0.01em;
    --tighter-tracking: -0.02em;

    /* ---- Layout ---- */
    --pad: 2.5vw;
    --pad-mobile: 30px;

    /* ---- Easing ---- */
    --ease-soft: cubic-bezier(0.16, 1, 0.3, 1);
}


Step 2 — Create components/ui/wardrobe/cn.ts:

export function cn(...classes: Array<string | false | null | undefined>): string {
    return classes.filter(Boolean).join(" ");
}


Step 3 — Create the following 10 component files in components/ui/wardrobe/:

--- components/ui/wardrobe/Button.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ButtonProps = {
    variant?: "primary" | "secondary" | "destructive" | "ghost";
    size?: "sm" | "md" | "lg";
    children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export function Button({
    variant = "primary",
    size = "md",
    className,
    children,
    ...props
}: ButtonProps) {
    return (
        <button
            className={cn("nds-btn", `nds-btn--${variant}`, size !== "md" && `nds-btn--${size}`, className)}
            {...props}
        >
            {children}
        </button>
    );
}


--- components/ui/wardrobe/Input.tsx ---

import * as React from "react";
import { cn } from "./cn";

type InputProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.InputHTMLAttributes<HTMLInputElement>;

export function Input({
    label,
    hint,
    className,
    id,
    ...props
}: InputProps) {
    const inputId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && (
                <label htmlFor={inputId}>{label}</label>
            )}
            <input id={inputId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Textarea.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TextareaProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

export function Textarea({
    label,
    hint,
    className,
    id,
    ...props
}: TextareaProps) {
    const textareaId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={textareaId}>{label}</label>}
            <textarea id={textareaId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Select.tsx ---

import * as React from "react";
import { cn } from "./cn";

type Option = { value: string; label: string };

type SelectProps = {
    options: Option[];
    value: string;
    onValueChange: (value: string) => void;
    placeholder?: string;
    label?: string;
} & Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "value" | "onChange">;

export function Select({
    options,
    value,
    onValueChange,
    placeholder,
    label,
    className,
    id,
    ...props
}: SelectProps) {
    const selectId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={selectId}>{label}</label>}
            <select
                id={selectId}
                className={cn(className)}
                value={value}
                onChange={(e) => onValueChange(e.target.value)}
                {...props}
            >
                {placeholder && <option value="" disabled>{placeholder}</option>}
                {options.map((opt) => (
                    <option key={opt.value} value={opt.value}>
                        {opt.label}
                    </option>
                ))}
            </select>
        </div>
    );
}


--- components/ui/wardrobe/Card.tsx ---

import * as React from "react";
import { cn } from "./cn";

type CardProps = {
    variant?: "default" | "ghost" | "solid";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export function Card({
    variant = "default",
    className,
    children,
    ...props
}: CardProps) {
    return (
        <div
            className={cn("nds-card", variant !== "default" && `nds-card--${variant}`, className)}
            {...props}
        >
            {children}
        </div>
    );
}


--- components/ui/wardrobe/Badge.tsx ---

import * as React from "react";
import { cn } from "./cn";

type BadgeProps = {
    variant?: "default" | "solid" | "soft";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLSpanElement>;

export function Badge({
    variant = "default",
    className,
    children,
    ...props
}: BadgeProps) {
    return (
        <span
            className={cn("nds-badge", variant !== "default" && `nds-badge--${variant}`, className)}
            {...props}
        >
            {children}
        </span>
    );
}


--- components/ui/wardrobe/Dialog.tsx ---

import * as React from "react";

type DialogProps = {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    title: string;
    children: React.ReactNode;
};

export function Dialog({ open, onOpenChange, title, children }: DialogProps) {
    React.useEffect(() => {
        if (!open) return;
        function onKey(e: KeyboardEvent) {
            if (e.key === "Escape") onOpenChange(false);
        }
        document.addEventListener("keydown", onKey);
        return () => document.removeEventListener("keydown", onKey);
    }, [open, onOpenChange]);

    if (!open) return null;
    return (
        <div
            className="nds-dialog-backdrop"
            role="dialog"
            aria-modal="true"
            aria-label={title}
            onClick={(e) => {
                if (e.target === e.currentTarget) onOpenChange(false);
            }}
        >
            <div className="nds-dialog">
                <h2 className="nds-dialog__title">{title}</h2>
                {children}
            </div>
        </div>
    );
}


--- components/ui/wardrobe/Tabs.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TabsProps = {
    tabs: Array<{ id: string; label: string }>;
    activeId: string;
    onTabChange: (id: string) => void;
};

export function Tabs({ tabs, activeId, onTabChange }: TabsProps) {
    return (
        <div className="nds-tabs" role="tablist">
            {tabs.map((tab) => (
                <button
                    key={tab.id}
                    type="button"
                    role="tab"
                    aria-selected={tab.id === activeId}
                    className={cn("nds-tab", tab.id === activeId && "is-active")}
                    onClick={() => onTabChange(tab.id)}
                >
                    {tab.label}
                </button>
            ))}
        </div>
    );
}


--- components/ui/wardrobe/Switch.tsx ---

import * as React from "react";

type SwitchProps = {
    checked: boolean;
    onCheckedChange: (checked: boolean) => void;
    label?: string;
};

export function Switch({ checked, onCheckedChange, label }: SwitchProps) {
    return (
        <label className="nds-switch" data-checked={checked}>
            <span className="nds-switch__track">
                <span className="nds-switch__thumb" />
            </span>
            <input
                type="checkbox"
                checked={checked}
                onChange={(e) => onCheckedChange(e.target.checked)}
                style={{ position: "absolute", opacity: 0, width: 0, height: 0 }}
            />
            {label && <span>{label}</span>}
        </label>
    );
}


--- components/ui/wardrobe/Toast.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ToastProps = {
    title: string;
    description?: string;
    variant?: "default" | "success" | "error";
};

export function Toast({ title, description, variant = "default" }: ToastProps) {
    return (
        <div
            className={cn("nds-toast", variant !== "default" && `nds-toast--${variant}`)}
            role="status"
            aria-live="polite"
        >
            <span className="nds-toast__title">{title}</span>
            {description && <span className="nds-toast__desc">{description}</span>}
        </div>
    );
}


Step 4 — Add globals.css component styles. Append the following to globals.css below the tokens:

/* =========================================================
   Wardrobe — NODESIGN globals
   Loads Inter, applies decorations only inside
   [data-system="nodesign"]. Other pages stay untouched.
   ========================================================= */



[data-system="nodesign"] *,
[data-system="nodesign"] *::before,
[data-system="nodesign"] *::after {
    box-sizing: border-box;
}

[data-system="nodesign"] {
    background-color: var(--color-bg);
    color: var(--color-fg);
    font-family: var(--font-main);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    min-height: 100vh;
}

/* Body fade-in on entering Nodesign pages. We tag <body data-system="nodesign">
   in the layout, so the animation runs once on first paint. */
body[data-system="nodesign"] {
    opacity: 0;
    animation: nds-fade-in 1.6s var(--ease-soft) forwards;
    /* Many Nodesign habitat pages place all visible content with
       position: absolute (V O I D, inquiry form). Without a flex
       column on the body, the in-flow footer collapses up against
       the top of the viewport. Force the body to fill the screen
       and let the footer drop to the bottom. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
body[data-system="nodesign"] > .foot {
    margin-top: auto;
}
@keyframes nds-fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

/* ===========================
   PERIPHERAL NAV
   (top-left / top-center / top-right text-elements)
   =========================== */
.nds-text {
    position: absolute;
    white-space: nowrap;
    will-change: transform;
    backface-visibility: hidden;
    display: inline-block;
    text-decoration: none;
    color: inherit;
}
.nds-peripheral {
    font-size: 11px;
    font-weight: var(--weight-regular);
    letter-spacing: var(--peripheral-tracking);
    text-transform: capitalize;
    /* padding-trick for larger hit area without visual change */
    padding: 20px;
    margin: -20px;
    z-index: 5;
}
.nds-peripheral.is-active span {
    text-decoration: underline;
    text-underline-offset: 4px;
}
.nds-top-left { top: var(--pad); left: var(--pad); }
.nds-top-center { top: var(--pad); left: 50%; transform: translateX(-50%); }
.nds-top-right { top: var(--pad); right: var(--pad); }

/* magnetic inner span — JS adjusts transform on mousemove */
.nds-magnetic span {
    display: inline-block;
    transition: transform 0.4s var(--ease-soft);
    pointer-events: none;
}

/* ===========================
   FOCAL POINT
   (V O I D — letter-spaced display text in the middle)
   =========================== */
.nds-focal {
    font-size: 14px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--focal-tracking);
    padding: 40px;
    margin: -40px;
}
.nds-center-absolute {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
.nds-focal--display {
    /* Used on overview hero — bigger than habitat focal points. */
    font-size: 64px;
    letter-spacing: 0.08em;
    font-weight: var(--weight-medium);
}

/* ===========================
   BLUEPRINT BACKGROUND SVG
   (faint geometric guide-lines behind content)
   =========================== */
.nds-blueprint {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    opacity: 0.04;
    z-index: 0;
}
.nds-blueprint svg { width: 100%; height: 100%; }

/* ===========================
   PROJECT GRID (work page)
   =========================== */
.nds-work {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4vw;
    position: relative;
    z-index: 2;
}
.nds-project {
    position: relative;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-project:nth-child(2n) { margin-top: 80px; }
.nds-project:nth-child(2) { animation-delay: 0.15s; }
.nds-project:nth-child(3) { animation-delay: 0.3s; }
.nds-project:nth-child(4) { animation-delay: 0.45s; }
@keyframes nds-slide-up {
    to { opacity: 1; transform: translateY(0); }
}
.nds-project-image {
    width: 100%;
    aspect-ratio: 16 / 10;
    background: var(--color-image-placeholder);
    overflow: hidden;
    margin-bottom: 20px;
    transition: transform 1.2s var(--ease-soft);
}
.nds-project:hover .nds-project-image { transform: scale(1.02); }
.nds-project-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}
.nds-project-title {
    font-size: 13px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--tight-tracking);
    text-transform: uppercase;
}
.nds-project-year {
    font-size: 11px;
    color: var(--color-fg-soft);
}

/* ===========================
   ABOUT — manifesto + team + awards
   =========================== */
.nds-about {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    position: relative;
    z-index: 2;
}
.nds-manifesto {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4vw;
    margin-bottom: 120px;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-editorial-image {
    width: 100%;
    aspect-ratio: 4 / 5;
    background: var(--color-image-placeholder);
    overflow: hidden;
}
.nds-manifesto-text {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-bottom: 40px;
}
.nds-section-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 40px;
}
.nds-manifesto-content {
    font-size: 24px;
    line-height: 1.4;
    letter-spacing: var(--tighter-tracking);
    font-weight: var(--weight-regular);
    max-width: 90%;
}
.nds-section-header {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 60px;
    border-top: 1px solid var(--color-fg-faint);
    padding-top: 20px;
}

.nds-team {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 2vw;
    margin-bottom: 120px;
}
.nds-team-photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--color-image-placeholder);
    margin-bottom: 15px;
    overflow: hidden;
}
.nds-team-name {
    font-size: 13px;
    font-weight: var(--weight-medium);
    display: block;
}
.nds-team-role {
    font-size: 11px;
    color: var(--color-fg-soft);
}

.nds-awards {
    max-width: 100%;
}
.nds-award-row {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    padding: 20px 0;
    border-bottom: 1px solid var(--color-fg-trace);
    font-size: 13px;
    transition: opacity 0.3s ease;
}
.nds-award-row:hover { opacity: 0.5; }
.nds-award-year { color: var(--color-fg-soft); }
.nds-award-status {
    text-align: right;
    color: var(--color-fg-soft);
}

/* ===========================
   INQUIRY FORM
   =========================== */
.nds-form {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(420px, 90vw);
    display: flex;
    flex-direction: column;
    gap: 36px;
    z-index: 3;
}
.nds-field { position: relative; width: 100%; }
.nds-field label {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin-bottom: 8px;
    color: var(--color-fg-soft);
}
.nds-field input,
.nds-field textarea,
.nds-field select {
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--color-fg-faint);
    background: transparent;
    padding: 8px 0;
    font-family: var(--font-main);
    font-size: 13px;
    color: var(--color-fg);
    outline: none;
    transition: border-color 0.4s ease;
    border-radius: 0;
}
.nds-field input:focus,
.nds-field textarea:focus,
.nds-field select:focus {
    border-bottom-color: rgba(0, 0, 0, 0.8);
}
.nds-field textarea {
    resize: none;
    height: 70px;
    font-family: var(--font-main);
}
.nds-send {
    align-self: flex-start;
    font-size: 12px;
    font-weight: var(--weight-medium);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    margin-top: 10px;
    color: inherit;
    transition: opacity 0.3s ease;
}
.nds-send:hover { opacity: 0.6; }

/* ===========================
   STANDARD COMPONENT CLASSES
   (Button / Badge / Card / Tabs / Switch / Toast / Dialog —
   minimal Nodesign treatment for the install contract)
   =========================== */
.nds-btn {
    font-family: var(--font-main);
    font-weight: var(--weight-medium);
    font-size: 12px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    color: var(--color-fg);
    transition: opacity 0.3s ease;
    border-bottom: 1px solid var(--color-fg-faint);
    border-radius: 0;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
}
.nds-btn:hover { opacity: 0.55; border-bottom-color: var(--color-fg); }
.nds-btn:disabled { opacity: 0.25; }
.nds-btn--primary {
    border-bottom-color: var(--color-fg);
}
.nds-btn--secondary {
    border-bottom-color: var(--color-fg-faint);
}
.nds-btn--ghost {
    border-bottom-color: transparent;
}
.nds-btn--destructive {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-btn--sm { font-size: 10px; padding: 6px 0; }
.nds-btn--lg { font-size: 14px; padding: 14px 0; }

.nds-card {
    background: transparent;
    border: 1px solid var(--color-fg-faint);
    padding: 32px;
    color: var(--color-fg);
}
.nds-card--ghost { border-color: var(--color-fg-trace); }
.nds-card--solid { background: var(--color-image-placeholder); border-color: transparent; }

.nds-badge {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    padding: 4px 10px;
    border: 1px solid var(--color-fg-faint);
    color: var(--color-fg);
    line-height: 1.2;
    background: transparent;
}
.nds-badge--solid {
    background: var(--color-fg);
    color: var(--color-bg);
    border-color: var(--color-fg);
}
.nds-badge--soft { color: var(--color-fg-soft); }

.nds-tabs {
    display: flex;
    gap: 28px;
    border-bottom: 1px solid var(--color-fg-faint);
}
.nds-tab {
    background: none;
    border: none;
    padding: 12px 0;
    font-family: var(--font-main);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: -1px;
    border-bottom: 1px solid transparent;
    transition: color 200ms ease, border-color 200ms ease;
}
.nds-tab.is-active {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-tab:hover { color: var(--color-fg); }

.nds-switch {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
}
.nds-switch__track {
    width: 36px;
    height: 1px;
    background: var(--color-fg-faint);
    position: relative;
}
.nds-switch__thumb {
    position: absolute;
    top: 50%;
    left: 0;
    width: 8px;
    height: 8px;
    background: var(--color-fg);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    transition: left 240ms var(--ease-soft);
}
.nds-switch[data-checked="true"] .nds-switch__thumb { left: 36px; }
.nds-switch[data-checked="true"] { color: var(--color-fg); }

.nds-toast {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 14px 18px;
    color: var(--color-fg);
    font-family: var(--font-main);
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 240px;
}
.nds-toast__title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
}
.nds-toast__desc {
    font-size: 12px;
    color: var(--color-fg-soft);
}
.nds-toast--success { border-left: 1px solid var(--color-fg); }
.nds-toast--error { border-left: 1px solid var(--color-fg); }

.nds-dialog-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.nds-dialog {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 40px;
    max-width: 480px;
    width: 100%;
    color: var(--color-fg);
}
.nds-dialog__title {
    font-size: 14px;
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin: 0 0 18px;
}

/* ===========================
   RESPONSIVE
   =========================== */
@media (max-width: 720px) {
    [data-system="nodesign"] { --pad: var(--pad-mobile); }
    .nds-work { grid-template-columns: 1fr; gap: 30px; }
    .nds-project:nth-child(2n) { margin-top: 0; }
    .nds-manifesto { grid-template-columns: 1fr; gap: 30px; }
    .nds-team { grid-template-columns: 1fr 1fr; gap: 20px; }
    .nds-focal--display { font-size: 36px; }
    .nds-form { width: calc(100vw - 60px); }
}

Step 5 — Wrap the section/page you want themed with <div data-system="nodesign">:

  <div data-system="nodesign">
    <Button>HELLO NODESIGN</Button>
    <Card>...</Card>
  </div>

Done. Now <Button>, <Input>, <Card>, <Badge>, <Dialog>, <Tabs>, <Switch>, <Toast>, <Select>, <Textarea> render in Nodesign.
Install the Wardrobe Nodesign theme in this v0 project. Treat each step below as a concrete file or edit you must make.

Step 1 — Add this to globals.css:

@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap");

/* =========================================================
   Wardrobe — NODESIGN tokens
   Almost-invisible minimalism. The absence is the design.
   White canvas, Inter at 11-14px, every line earned.
   Activate via data-system="nodesign".
   ========================================================= */

:where([data-system="nodesign"]) {
    /* ---- Surface ---- */
    --color-bg: #ffffff;
    --color-fg: #000000;
    --color-fg-soft: rgba(0, 0, 0, 0.4);
    --color-fg-faint: rgba(0, 0, 0, 0.1);
    --color-fg-trace: rgba(0, 0, 0, 0.05);
    --color-image-placeholder: #f5f5f5;

    /* Aliases used in reference designs */
    --bg: #ffffff;
    --fg: #000000;

    /* ---- Typography ---- */
    --font-display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-main: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-body: var(--font-main);
    --font-mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;

    --weight-regular: 400;
    --weight-medium: 500;

    --label-tracking: 0.1em;
    --peripheral-tracking: 0.02em;
    --focal-tracking: 0.15em;
    --tight-tracking: -0.01em;
    --tighter-tracking: -0.02em;

    /* ---- Layout ---- */
    --pad: 2.5vw;
    --pad-mobile: 30px;

    /* ---- Easing ---- */
    --ease-soft: cubic-bezier(0.16, 1, 0.3, 1);
}


Step 2 — Create components/ui/wardrobe/cn.ts:

export function cn(...classes: Array<string | false | null | undefined>): string {
    return classes.filter(Boolean).join(" ");
}


Step 3 — Create the following 10 component files in components/ui/wardrobe/:

--- components/ui/wardrobe/Button.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ButtonProps = {
    variant?: "primary" | "secondary" | "destructive" | "ghost";
    size?: "sm" | "md" | "lg";
    children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export function Button({
    variant = "primary",
    size = "md",
    className,
    children,
    ...props
}: ButtonProps) {
    return (
        <button
            className={cn("nds-btn", `nds-btn--${variant}`, size !== "md" && `nds-btn--${size}`, className)}
            {...props}
        >
            {children}
        </button>
    );
}


--- components/ui/wardrobe/Input.tsx ---

import * as React from "react";
import { cn } from "./cn";

type InputProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.InputHTMLAttributes<HTMLInputElement>;

export function Input({
    label,
    hint,
    className,
    id,
    ...props
}: InputProps) {
    const inputId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && (
                <label htmlFor={inputId}>{label}</label>
            )}
            <input id={inputId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Textarea.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TextareaProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

export function Textarea({
    label,
    hint,
    className,
    id,
    ...props
}: TextareaProps) {
    const textareaId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={textareaId}>{label}</label>}
            <textarea id={textareaId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Select.tsx ---

import * as React from "react";
import { cn } from "./cn";

type Option = { value: string; label: string };

type SelectProps = {
    options: Option[];
    value: string;
    onValueChange: (value: string) => void;
    placeholder?: string;
    label?: string;
} & Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "value" | "onChange">;

export function Select({
    options,
    value,
    onValueChange,
    placeholder,
    label,
    className,
    id,
    ...props
}: SelectProps) {
    const selectId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={selectId}>{label}</label>}
            <select
                id={selectId}
                className={cn(className)}
                value={value}
                onChange={(e) => onValueChange(e.target.value)}
                {...props}
            >
                {placeholder && <option value="" disabled>{placeholder}</option>}
                {options.map((opt) => (
                    <option key={opt.value} value={opt.value}>
                        {opt.label}
                    </option>
                ))}
            </select>
        </div>
    );
}


--- components/ui/wardrobe/Card.tsx ---

import * as React from "react";
import { cn } from "./cn";

type CardProps = {
    variant?: "default" | "ghost" | "solid";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export function Card({
    variant = "default",
    className,
    children,
    ...props
}: CardProps) {
    return (
        <div
            className={cn("nds-card", variant !== "default" && `nds-card--${variant}`, className)}
            {...props}
        >
            {children}
        </div>
    );
}


--- components/ui/wardrobe/Badge.tsx ---

import * as React from "react";
import { cn } from "./cn";

type BadgeProps = {
    variant?: "default" | "solid" | "soft";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLSpanElement>;

export function Badge({
    variant = "default",
    className,
    children,
    ...props
}: BadgeProps) {
    return (
        <span
            className={cn("nds-badge", variant !== "default" && `nds-badge--${variant}`, className)}
            {...props}
        >
            {children}
        </span>
    );
}


--- components/ui/wardrobe/Dialog.tsx ---

import * as React from "react";

type DialogProps = {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    title: string;
    children: React.ReactNode;
};

export function Dialog({ open, onOpenChange, title, children }: DialogProps) {
    React.useEffect(() => {
        if (!open) return;
        function onKey(e: KeyboardEvent) {
            if (e.key === "Escape") onOpenChange(false);
        }
        document.addEventListener("keydown", onKey);
        return () => document.removeEventListener("keydown", onKey);
    }, [open, onOpenChange]);

    if (!open) return null;
    return (
        <div
            className="nds-dialog-backdrop"
            role="dialog"
            aria-modal="true"
            aria-label={title}
            onClick={(e) => {
                if (e.target === e.currentTarget) onOpenChange(false);
            }}
        >
            <div className="nds-dialog">
                <h2 className="nds-dialog__title">{title}</h2>
                {children}
            </div>
        </div>
    );
}


--- components/ui/wardrobe/Tabs.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TabsProps = {
    tabs: Array<{ id: string; label: string }>;
    activeId: string;
    onTabChange: (id: string) => void;
};

export function Tabs({ tabs, activeId, onTabChange }: TabsProps) {
    return (
        <div className="nds-tabs" role="tablist">
            {tabs.map((tab) => (
                <button
                    key={tab.id}
                    type="button"
                    role="tab"
                    aria-selected={tab.id === activeId}
                    className={cn("nds-tab", tab.id === activeId && "is-active")}
                    onClick={() => onTabChange(tab.id)}
                >
                    {tab.label}
                </button>
            ))}
        </div>
    );
}


--- components/ui/wardrobe/Switch.tsx ---

import * as React from "react";

type SwitchProps = {
    checked: boolean;
    onCheckedChange: (checked: boolean) => void;
    label?: string;
};

export function Switch({ checked, onCheckedChange, label }: SwitchProps) {
    return (
        <label className="nds-switch" data-checked={checked}>
            <span className="nds-switch__track">
                <span className="nds-switch__thumb" />
            </span>
            <input
                type="checkbox"
                checked={checked}
                onChange={(e) => onCheckedChange(e.target.checked)}
                style={{ position: "absolute", opacity: 0, width: 0, height: 0 }}
            />
            {label && <span>{label}</span>}
        </label>
    );
}


--- components/ui/wardrobe/Toast.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ToastProps = {
    title: string;
    description?: string;
    variant?: "default" | "success" | "error";
};

export function Toast({ title, description, variant = "default" }: ToastProps) {
    return (
        <div
            className={cn("nds-toast", variant !== "default" && `nds-toast--${variant}`)}
            role="status"
            aria-live="polite"
        >
            <span className="nds-toast__title">{title}</span>
            {description && <span className="nds-toast__desc">{description}</span>}
        </div>
    );
}


Step 4 — Add globals.css component styles. Append the following to globals.css below the tokens:

/* =========================================================
   Wardrobe — NODESIGN globals
   Loads Inter, applies decorations only inside
   [data-system="nodesign"]. Other pages stay untouched.
   ========================================================= */



[data-system="nodesign"] *,
[data-system="nodesign"] *::before,
[data-system="nodesign"] *::after {
    box-sizing: border-box;
}

[data-system="nodesign"] {
    background-color: var(--color-bg);
    color: var(--color-fg);
    font-family: var(--font-main);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    min-height: 100vh;
}

/* Body fade-in on entering Nodesign pages. We tag <body data-system="nodesign">
   in the layout, so the animation runs once on first paint. */
body[data-system="nodesign"] {
    opacity: 0;
    animation: nds-fade-in 1.6s var(--ease-soft) forwards;
    /* Many Nodesign habitat pages place all visible content with
       position: absolute (V O I D, inquiry form). Without a flex
       column on the body, the in-flow footer collapses up against
       the top of the viewport. Force the body to fill the screen
       and let the footer drop to the bottom. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
body[data-system="nodesign"] > .foot {
    margin-top: auto;
}
@keyframes nds-fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

/* ===========================
   PERIPHERAL NAV
   (top-left / top-center / top-right text-elements)
   =========================== */
.nds-text {
    position: absolute;
    white-space: nowrap;
    will-change: transform;
    backface-visibility: hidden;
    display: inline-block;
    text-decoration: none;
    color: inherit;
}
.nds-peripheral {
    font-size: 11px;
    font-weight: var(--weight-regular);
    letter-spacing: var(--peripheral-tracking);
    text-transform: capitalize;
    /* padding-trick for larger hit area without visual change */
    padding: 20px;
    margin: -20px;
    z-index: 5;
}
.nds-peripheral.is-active span {
    text-decoration: underline;
    text-underline-offset: 4px;
}
.nds-top-left { top: var(--pad); left: var(--pad); }
.nds-top-center { top: var(--pad); left: 50%; transform: translateX(-50%); }
.nds-top-right { top: var(--pad); right: var(--pad); }

/* magnetic inner span — JS adjusts transform on mousemove */
.nds-magnetic span {
    display: inline-block;
    transition: transform 0.4s var(--ease-soft);
    pointer-events: none;
}

/* ===========================
   FOCAL POINT
   (V O I D — letter-spaced display text in the middle)
   =========================== */
.nds-focal {
    font-size: 14px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--focal-tracking);
    padding: 40px;
    margin: -40px;
}
.nds-center-absolute {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
.nds-focal--display {
    /* Used on overview hero — bigger than habitat focal points. */
    font-size: 64px;
    letter-spacing: 0.08em;
    font-weight: var(--weight-medium);
}

/* ===========================
   BLUEPRINT BACKGROUND SVG
   (faint geometric guide-lines behind content)
   =========================== */
.nds-blueprint {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    opacity: 0.04;
    z-index: 0;
}
.nds-blueprint svg { width: 100%; height: 100%; }

/* ===========================
   PROJECT GRID (work page)
   =========================== */
.nds-work {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4vw;
    position: relative;
    z-index: 2;
}
.nds-project {
    position: relative;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-project:nth-child(2n) { margin-top: 80px; }
.nds-project:nth-child(2) { animation-delay: 0.15s; }
.nds-project:nth-child(3) { animation-delay: 0.3s; }
.nds-project:nth-child(4) { animation-delay: 0.45s; }
@keyframes nds-slide-up {
    to { opacity: 1; transform: translateY(0); }
}
.nds-project-image {
    width: 100%;
    aspect-ratio: 16 / 10;
    background: var(--color-image-placeholder);
    overflow: hidden;
    margin-bottom: 20px;
    transition: transform 1.2s var(--ease-soft);
}
.nds-project:hover .nds-project-image { transform: scale(1.02); }
.nds-project-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}
.nds-project-title {
    font-size: 13px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--tight-tracking);
    text-transform: uppercase;
}
.nds-project-year {
    font-size: 11px;
    color: var(--color-fg-soft);
}

/* ===========================
   ABOUT — manifesto + team + awards
   =========================== */
.nds-about {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    position: relative;
    z-index: 2;
}
.nds-manifesto {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4vw;
    margin-bottom: 120px;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-editorial-image {
    width: 100%;
    aspect-ratio: 4 / 5;
    background: var(--color-image-placeholder);
    overflow: hidden;
}
.nds-manifesto-text {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-bottom: 40px;
}
.nds-section-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 40px;
}
.nds-manifesto-content {
    font-size: 24px;
    line-height: 1.4;
    letter-spacing: var(--tighter-tracking);
    font-weight: var(--weight-regular);
    max-width: 90%;
}
.nds-section-header {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 60px;
    border-top: 1px solid var(--color-fg-faint);
    padding-top: 20px;
}

.nds-team {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 2vw;
    margin-bottom: 120px;
}
.nds-team-photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--color-image-placeholder);
    margin-bottom: 15px;
    overflow: hidden;
}
.nds-team-name {
    font-size: 13px;
    font-weight: var(--weight-medium);
    display: block;
}
.nds-team-role {
    font-size: 11px;
    color: var(--color-fg-soft);
}

.nds-awards {
    max-width: 100%;
}
.nds-award-row {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    padding: 20px 0;
    border-bottom: 1px solid var(--color-fg-trace);
    font-size: 13px;
    transition: opacity 0.3s ease;
}
.nds-award-row:hover { opacity: 0.5; }
.nds-award-year { color: var(--color-fg-soft); }
.nds-award-status {
    text-align: right;
    color: var(--color-fg-soft);
}

/* ===========================
   INQUIRY FORM
   =========================== */
.nds-form {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(420px, 90vw);
    display: flex;
    flex-direction: column;
    gap: 36px;
    z-index: 3;
}
.nds-field { position: relative; width: 100%; }
.nds-field label {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin-bottom: 8px;
    color: var(--color-fg-soft);
}
.nds-field input,
.nds-field textarea,
.nds-field select {
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--color-fg-faint);
    background: transparent;
    padding: 8px 0;
    font-family: var(--font-main);
    font-size: 13px;
    color: var(--color-fg);
    outline: none;
    transition: border-color 0.4s ease;
    border-radius: 0;
}
.nds-field input:focus,
.nds-field textarea:focus,
.nds-field select:focus {
    border-bottom-color: rgba(0, 0, 0, 0.8);
}
.nds-field textarea {
    resize: none;
    height: 70px;
    font-family: var(--font-main);
}
.nds-send {
    align-self: flex-start;
    font-size: 12px;
    font-weight: var(--weight-medium);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    margin-top: 10px;
    color: inherit;
    transition: opacity 0.3s ease;
}
.nds-send:hover { opacity: 0.6; }

/* ===========================
   STANDARD COMPONENT CLASSES
   (Button / Badge / Card / Tabs / Switch / Toast / Dialog —
   minimal Nodesign treatment for the install contract)
   =========================== */
.nds-btn {
    font-family: var(--font-main);
    font-weight: var(--weight-medium);
    font-size: 12px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    color: var(--color-fg);
    transition: opacity 0.3s ease;
    border-bottom: 1px solid var(--color-fg-faint);
    border-radius: 0;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
}
.nds-btn:hover { opacity: 0.55; border-bottom-color: var(--color-fg); }
.nds-btn:disabled { opacity: 0.25; }
.nds-btn--primary {
    border-bottom-color: var(--color-fg);
}
.nds-btn--secondary {
    border-bottom-color: var(--color-fg-faint);
}
.nds-btn--ghost {
    border-bottom-color: transparent;
}
.nds-btn--destructive {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-btn--sm { font-size: 10px; padding: 6px 0; }
.nds-btn--lg { font-size: 14px; padding: 14px 0; }

.nds-card {
    background: transparent;
    border: 1px solid var(--color-fg-faint);
    padding: 32px;
    color: var(--color-fg);
}
.nds-card--ghost { border-color: var(--color-fg-trace); }
.nds-card--solid { background: var(--color-image-placeholder); border-color: transparent; }

.nds-badge {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    padding: 4px 10px;
    border: 1px solid var(--color-fg-faint);
    color: var(--color-fg);
    line-height: 1.2;
    background: transparent;
}
.nds-badge--solid {
    background: var(--color-fg);
    color: var(--color-bg);
    border-color: var(--color-fg);
}
.nds-badge--soft { color: var(--color-fg-soft); }

.nds-tabs {
    display: flex;
    gap: 28px;
    border-bottom: 1px solid var(--color-fg-faint);
}
.nds-tab {
    background: none;
    border: none;
    padding: 12px 0;
    font-family: var(--font-main);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: -1px;
    border-bottom: 1px solid transparent;
    transition: color 200ms ease, border-color 200ms ease;
}
.nds-tab.is-active {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-tab:hover { color: var(--color-fg); }

.nds-switch {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
}
.nds-switch__track {
    width: 36px;
    height: 1px;
    background: var(--color-fg-faint);
    position: relative;
}
.nds-switch__thumb {
    position: absolute;
    top: 50%;
    left: 0;
    width: 8px;
    height: 8px;
    background: var(--color-fg);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    transition: left 240ms var(--ease-soft);
}
.nds-switch[data-checked="true"] .nds-switch__thumb { left: 36px; }
.nds-switch[data-checked="true"] { color: var(--color-fg); }

.nds-toast {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 14px 18px;
    color: var(--color-fg);
    font-family: var(--font-main);
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 240px;
}
.nds-toast__title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
}
.nds-toast__desc {
    font-size: 12px;
    color: var(--color-fg-soft);
}
.nds-toast--success { border-left: 1px solid var(--color-fg); }
.nds-toast--error { border-left: 1px solid var(--color-fg); }

.nds-dialog-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.nds-dialog {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 40px;
    max-width: 480px;
    width: 100%;
    color: var(--color-fg);
}
.nds-dialog__title {
    font-size: 14px;
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin: 0 0 18px;
}

/* ===========================
   RESPONSIVE
   =========================== */
@media (max-width: 720px) {
    [data-system="nodesign"] { --pad: var(--pad-mobile); }
    .nds-work { grid-template-columns: 1fr; gap: 30px; }
    .nds-project:nth-child(2n) { margin-top: 0; }
    .nds-manifesto { grid-template-columns: 1fr; gap: 30px; }
    .nds-team { grid-template-columns: 1fr 1fr; gap: 20px; }
    .nds-focal--display { font-size: 36px; }
    .nds-form { width: calc(100vw - 60px); }
}

Step 5 — Wrap the section/page you want themed with <div data-system="nodesign">:

  <div data-system="nodesign">
    <Button>HELLO NODESIGN</Button>
    <Card>...</Card>
  </div>

Done. Now <Button>, <Input>, <Card>, <Badge>, <Dialog>, <Tabs>, <Switch>, <Toast>, <Select>, <Textarea> render in Nodesign.
Install the Wardrobe Nodesign theme in my Lovable project. Each step below maps to a specific file or change.

Step 1 — Add this to globals.css:

@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap");

/* =========================================================
   Wardrobe — NODESIGN tokens
   Almost-invisible minimalism. The absence is the design.
   White canvas, Inter at 11-14px, every line earned.
   Activate via data-system="nodesign".
   ========================================================= */

:where([data-system="nodesign"]) {
    /* ---- Surface ---- */
    --color-bg: #ffffff;
    --color-fg: #000000;
    --color-fg-soft: rgba(0, 0, 0, 0.4);
    --color-fg-faint: rgba(0, 0, 0, 0.1);
    --color-fg-trace: rgba(0, 0, 0, 0.05);
    --color-image-placeholder: #f5f5f5;

    /* Aliases used in reference designs */
    --bg: #ffffff;
    --fg: #000000;

    /* ---- Typography ---- */
    --font-display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-main: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, Helvetica, Arial, sans-serif;
    --font-body: var(--font-main);
    --font-mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;

    --weight-regular: 400;
    --weight-medium: 500;

    --label-tracking: 0.1em;
    --peripheral-tracking: 0.02em;
    --focal-tracking: 0.15em;
    --tight-tracking: -0.01em;
    --tighter-tracking: -0.02em;

    /* ---- Layout ---- */
    --pad: 2.5vw;
    --pad-mobile: 30px;

    /* ---- Easing ---- */
    --ease-soft: cubic-bezier(0.16, 1, 0.3, 1);
}


Step 2 — Create components/ui/wardrobe/cn.ts:

export function cn(...classes: Array<string | false | null | undefined>): string {
    return classes.filter(Boolean).join(" ");
}


Step 3 — Create the following 10 component files in components/ui/wardrobe/:

--- components/ui/wardrobe/Button.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ButtonProps = {
    variant?: "primary" | "secondary" | "destructive" | "ghost";
    size?: "sm" | "md" | "lg";
    children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export function Button({
    variant = "primary",
    size = "md",
    className,
    children,
    ...props
}: ButtonProps) {
    return (
        <button
            className={cn("nds-btn", `nds-btn--${variant}`, size !== "md" && `nds-btn--${size}`, className)}
            {...props}
        >
            {children}
        </button>
    );
}


--- components/ui/wardrobe/Input.tsx ---

import * as React from "react";
import { cn } from "./cn";

type InputProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.InputHTMLAttributes<HTMLInputElement>;

export function Input({
    label,
    hint,
    className,
    id,
    ...props
}: InputProps) {
    const inputId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && (
                <label htmlFor={inputId}>{label}</label>
            )}
            <input id={inputId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Textarea.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TextareaProps = {
    size?: "sm" | "md" | "lg";
    variant?: "default" | "error";
    label?: string;
    hint?: string;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

export function Textarea({
    label,
    hint,
    className,
    id,
    ...props
}: TextareaProps) {
    const textareaId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={textareaId}>{label}</label>}
            <textarea id={textareaId} className={cn(className)} {...props} />
            {hint && (
                <span style={{ display: "block", marginTop: 6, fontSize: 10, color: "rgba(0,0,0,0.4)", textTransform: "uppercase", letterSpacing: "0.1em" }}>
                    {hint}
                </span>
            )}
        </div>
    );
}


--- components/ui/wardrobe/Select.tsx ---

import * as React from "react";
import { cn } from "./cn";

type Option = { value: string; label: string };

type SelectProps = {
    options: Option[];
    value: string;
    onValueChange: (value: string) => void;
    placeholder?: string;
    label?: string;
} & Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "value" | "onChange">;

export function Select({
    options,
    value,
    onValueChange,
    placeholder,
    label,
    className,
    id,
    ...props
}: SelectProps) {
    const selectId = id ?? React.useId();
    return (
        <div className="nds-field">
            {label && <label htmlFor={selectId}>{label}</label>}
            <select
                id={selectId}
                className={cn(className)}
                value={value}
                onChange={(e) => onValueChange(e.target.value)}
                {...props}
            >
                {placeholder && <option value="" disabled>{placeholder}</option>}
                {options.map((opt) => (
                    <option key={opt.value} value={opt.value}>
                        {opt.label}
                    </option>
                ))}
            </select>
        </div>
    );
}


--- components/ui/wardrobe/Card.tsx ---

import * as React from "react";
import { cn } from "./cn";

type CardProps = {
    variant?: "default" | "ghost" | "solid";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export function Card({
    variant = "default",
    className,
    children,
    ...props
}: CardProps) {
    return (
        <div
            className={cn("nds-card", variant !== "default" && `nds-card--${variant}`, className)}
            {...props}
        >
            {children}
        </div>
    );
}


--- components/ui/wardrobe/Badge.tsx ---

import * as React from "react";
import { cn } from "./cn";

type BadgeProps = {
    variant?: "default" | "solid" | "soft";
    children: React.ReactNode;
} & React.HTMLAttributes<HTMLSpanElement>;

export function Badge({
    variant = "default",
    className,
    children,
    ...props
}: BadgeProps) {
    return (
        <span
            className={cn("nds-badge", variant !== "default" && `nds-badge--${variant}`, className)}
            {...props}
        >
            {children}
        </span>
    );
}


--- components/ui/wardrobe/Dialog.tsx ---

import * as React from "react";

type DialogProps = {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    title: string;
    children: React.ReactNode;
};

export function Dialog({ open, onOpenChange, title, children }: DialogProps) {
    React.useEffect(() => {
        if (!open) return;
        function onKey(e: KeyboardEvent) {
            if (e.key === "Escape") onOpenChange(false);
        }
        document.addEventListener("keydown", onKey);
        return () => document.removeEventListener("keydown", onKey);
    }, [open, onOpenChange]);

    if (!open) return null;
    return (
        <div
            className="nds-dialog-backdrop"
            role="dialog"
            aria-modal="true"
            aria-label={title}
            onClick={(e) => {
                if (e.target === e.currentTarget) onOpenChange(false);
            }}
        >
            <div className="nds-dialog">
                <h2 className="nds-dialog__title">{title}</h2>
                {children}
            </div>
        </div>
    );
}


--- components/ui/wardrobe/Tabs.tsx ---

import * as React from "react";
import { cn } from "./cn";

type TabsProps = {
    tabs: Array<{ id: string; label: string }>;
    activeId: string;
    onTabChange: (id: string) => void;
};

export function Tabs({ tabs, activeId, onTabChange }: TabsProps) {
    return (
        <div className="nds-tabs" role="tablist">
            {tabs.map((tab) => (
                <button
                    key={tab.id}
                    type="button"
                    role="tab"
                    aria-selected={tab.id === activeId}
                    className={cn("nds-tab", tab.id === activeId && "is-active")}
                    onClick={() => onTabChange(tab.id)}
                >
                    {tab.label}
                </button>
            ))}
        </div>
    );
}


--- components/ui/wardrobe/Switch.tsx ---

import * as React from "react";

type SwitchProps = {
    checked: boolean;
    onCheckedChange: (checked: boolean) => void;
    label?: string;
};

export function Switch({ checked, onCheckedChange, label }: SwitchProps) {
    return (
        <label className="nds-switch" data-checked={checked}>
            <span className="nds-switch__track">
                <span className="nds-switch__thumb" />
            </span>
            <input
                type="checkbox"
                checked={checked}
                onChange={(e) => onCheckedChange(e.target.checked)}
                style={{ position: "absolute", opacity: 0, width: 0, height: 0 }}
            />
            {label && <span>{label}</span>}
        </label>
    );
}


--- components/ui/wardrobe/Toast.tsx ---

import * as React from "react";
import { cn } from "./cn";

type ToastProps = {
    title: string;
    description?: string;
    variant?: "default" | "success" | "error";
};

export function Toast({ title, description, variant = "default" }: ToastProps) {
    return (
        <div
            className={cn("nds-toast", variant !== "default" && `nds-toast--${variant}`)}
            role="status"
            aria-live="polite"
        >
            <span className="nds-toast__title">{title}</span>
            {description && <span className="nds-toast__desc">{description}</span>}
        </div>
    );
}


Step 4 — Add globals.css component styles. Append the following to globals.css below the tokens:

/* =========================================================
   Wardrobe — NODESIGN globals
   Loads Inter, applies decorations only inside
   [data-system="nodesign"]. Other pages stay untouched.
   ========================================================= */



[data-system="nodesign"] *,
[data-system="nodesign"] *::before,
[data-system="nodesign"] *::after {
    box-sizing: border-box;
}

[data-system="nodesign"] {
    background-color: var(--color-bg);
    color: var(--color-fg);
    font-family: var(--font-main);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    min-height: 100vh;
}

/* Body fade-in on entering Nodesign pages. We tag <body data-system="nodesign">
   in the layout, so the animation runs once on first paint. */
body[data-system="nodesign"] {
    opacity: 0;
    animation: nds-fade-in 1.6s var(--ease-soft) forwards;
    /* Many Nodesign habitat pages place all visible content with
       position: absolute (V O I D, inquiry form). Without a flex
       column on the body, the in-flow footer collapses up against
       the top of the viewport. Force the body to fill the screen
       and let the footer drop to the bottom. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
body[data-system="nodesign"] > .foot {
    margin-top: auto;
}
@keyframes nds-fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

/* ===========================
   PERIPHERAL NAV
   (top-left / top-center / top-right text-elements)
   =========================== */
.nds-text {
    position: absolute;
    white-space: nowrap;
    will-change: transform;
    backface-visibility: hidden;
    display: inline-block;
    text-decoration: none;
    color: inherit;
}
.nds-peripheral {
    font-size: 11px;
    font-weight: var(--weight-regular);
    letter-spacing: var(--peripheral-tracking);
    text-transform: capitalize;
    /* padding-trick for larger hit area without visual change */
    padding: 20px;
    margin: -20px;
    z-index: 5;
}
.nds-peripheral.is-active span {
    text-decoration: underline;
    text-underline-offset: 4px;
}
.nds-top-left { top: var(--pad); left: var(--pad); }
.nds-top-center { top: var(--pad); left: 50%; transform: translateX(-50%); }
.nds-top-right { top: var(--pad); right: var(--pad); }

/* magnetic inner span — JS adjusts transform on mousemove */
.nds-magnetic span {
    display: inline-block;
    transition: transform 0.4s var(--ease-soft);
    pointer-events: none;
}

/* ===========================
   FOCAL POINT
   (V O I D — letter-spaced display text in the middle)
   =========================== */
.nds-focal {
    font-size: 14px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--focal-tracking);
    padding: 40px;
    margin: -40px;
}
.nds-center-absolute {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
.nds-focal--display {
    /* Used on overview hero — bigger than habitat focal points. */
    font-size: 64px;
    letter-spacing: 0.08em;
    font-weight: var(--weight-medium);
}

/* ===========================
   BLUEPRINT BACKGROUND SVG
   (faint geometric guide-lines behind content)
   =========================== */
.nds-blueprint {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    opacity: 0.04;
    z-index: 0;
}
.nds-blueprint svg { width: 100%; height: 100%; }

/* ===========================
   PROJECT GRID (work page)
   =========================== */
.nds-work {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 4vw;
    position: relative;
    z-index: 2;
}
.nds-project {
    position: relative;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-project:nth-child(2n) { margin-top: 80px; }
.nds-project:nth-child(2) { animation-delay: 0.15s; }
.nds-project:nth-child(3) { animation-delay: 0.3s; }
.nds-project:nth-child(4) { animation-delay: 0.45s; }
@keyframes nds-slide-up {
    to { opacity: 1; transform: translateY(0); }
}
.nds-project-image {
    width: 100%;
    aspect-ratio: 16 / 10;
    background: var(--color-image-placeholder);
    overflow: hidden;
    margin-bottom: 20px;
    transition: transform 1.2s var(--ease-soft);
}
.nds-project:hover .nds-project-image { transform: scale(1.02); }
.nds-project-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}
.nds-project-title {
    font-size: 13px;
    font-weight: var(--weight-medium);
    letter-spacing: var(--tight-tracking);
    text-transform: uppercase;
}
.nds-project-year {
    font-size: 11px;
    color: var(--color-fg-soft);
}

/* ===========================
   ABOUT — manifesto + team + awards
   =========================== */
.nds-about {
    margin-top: 150px;
    padding: 0 var(--pad) 100px;
    position: relative;
    z-index: 2;
}
.nds-manifesto {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4vw;
    margin-bottom: 120px;
    opacity: 0;
    transform: translateY(20px);
    animation: nds-slide-up 1.2s var(--ease-soft) forwards;
}
.nds-editorial-image {
    width: 100%;
    aspect-ratio: 4 / 5;
    background: var(--color-image-placeholder);
    overflow: hidden;
}
.nds-manifesto-text {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-bottom: 40px;
}
.nds-section-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 40px;
}
.nds-manifesto-content {
    font-size: 24px;
    line-height: 1.4;
    letter-spacing: var(--tighter-tracking);
    font-weight: var(--weight-regular);
    max-width: 90%;
}
.nds-section-header {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: 60px;
    border-top: 1px solid var(--color-fg-faint);
    padding-top: 20px;
}

.nds-team {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 2vw;
    margin-bottom: 120px;
}
.nds-team-photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--color-image-placeholder);
    margin-bottom: 15px;
    overflow: hidden;
}
.nds-team-name {
    font-size: 13px;
    font-weight: var(--weight-medium);
    display: block;
}
.nds-team-role {
    font-size: 11px;
    color: var(--color-fg-soft);
}

.nds-awards {
    max-width: 100%;
}
.nds-award-row {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    padding: 20px 0;
    border-bottom: 1px solid var(--color-fg-trace);
    font-size: 13px;
    transition: opacity 0.3s ease;
}
.nds-award-row:hover { opacity: 0.5; }
.nds-award-year { color: var(--color-fg-soft); }
.nds-award-status {
    text-align: right;
    color: var(--color-fg-soft);
}

/* ===========================
   INQUIRY FORM
   =========================== */
.nds-form {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(420px, 90vw);
    display: flex;
    flex-direction: column;
    gap: 36px;
    z-index: 3;
}
.nds-field { position: relative; width: 100%; }
.nds-field label {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin-bottom: 8px;
    color: var(--color-fg-soft);
}
.nds-field input,
.nds-field textarea,
.nds-field select {
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--color-fg-faint);
    background: transparent;
    padding: 8px 0;
    font-family: var(--font-main);
    font-size: 13px;
    color: var(--color-fg);
    outline: none;
    transition: border-color 0.4s ease;
    border-radius: 0;
}
.nds-field input:focus,
.nds-field textarea:focus,
.nds-field select:focus {
    border-bottom-color: rgba(0, 0, 0, 0.8);
}
.nds-field textarea {
    resize: none;
    height: 70px;
    font-family: var(--font-main);
}
.nds-send {
    align-self: flex-start;
    font-size: 12px;
    font-weight: var(--weight-medium);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    margin-top: 10px;
    color: inherit;
    transition: opacity 0.3s ease;
}
.nds-send:hover { opacity: 0.6; }

/* ===========================
   STANDARD COMPONENT CLASSES
   (Button / Badge / Card / Tabs / Switch / Toast / Dialog —
   minimal Nodesign treatment for the install contract)
   =========================== */
.nds-btn {
    font-family: var(--font-main);
    font-weight: var(--weight-medium);
    font-size: 12px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 10px 0;
    color: var(--color-fg);
    transition: opacity 0.3s ease;
    border-bottom: 1px solid var(--color-fg-faint);
    border-radius: 0;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
}
.nds-btn:hover { opacity: 0.55; border-bottom-color: var(--color-fg); }
.nds-btn:disabled { opacity: 0.25; }
.nds-btn--primary {
    border-bottom-color: var(--color-fg);
}
.nds-btn--secondary {
    border-bottom-color: var(--color-fg-faint);
}
.nds-btn--ghost {
    border-bottom-color: transparent;
}
.nds-btn--destructive {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-btn--sm { font-size: 10px; padding: 6px 0; }
.nds-btn--lg { font-size: 14px; padding: 14px 0; }

.nds-card {
    background: transparent;
    border: 1px solid var(--color-fg-faint);
    padding: 32px;
    color: var(--color-fg);
}
.nds-card--ghost { border-color: var(--color-fg-trace); }
.nds-card--solid { background: var(--color-image-placeholder); border-color: transparent; }

.nds-badge {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    padding: 4px 10px;
    border: 1px solid var(--color-fg-faint);
    color: var(--color-fg);
    line-height: 1.2;
    background: transparent;
}
.nds-badge--solid {
    background: var(--color-fg);
    color: var(--color-bg);
    border-color: var(--color-fg);
}
.nds-badge--soft { color: var(--color-fg-soft); }

.nds-tabs {
    display: flex;
    gap: 28px;
    border-bottom: 1px solid var(--color-fg-faint);
}
.nds-tab {
    background: none;
    border: none;
    padding: 12px 0;
    font-family: var(--font-main);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
    margin-bottom: -1px;
    border-bottom: 1px solid transparent;
    transition: color 200ms ease, border-color 200ms ease;
}
.nds-tab.is-active {
    color: var(--color-fg);
    border-bottom-color: var(--color-fg);
}
.nds-tab:hover { color: var(--color-fg); }

.nds-switch {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    color: var(--color-fg-soft);
}
.nds-switch__track {
    width: 36px;
    height: 1px;
    background: var(--color-fg-faint);
    position: relative;
}
.nds-switch__thumb {
    position: absolute;
    top: 50%;
    left: 0;
    width: 8px;
    height: 8px;
    background: var(--color-fg);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    transition: left 240ms var(--ease-soft);
}
.nds-switch[data-checked="true"] .nds-switch__thumb { left: 36px; }
.nds-switch[data-checked="true"] { color: var(--color-fg); }

.nds-toast {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 14px 18px;
    color: var(--color-fg);
    font-family: var(--font-main);
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 240px;
}
.nds-toast__title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
}
.nds-toast__desc {
    font-size: 12px;
    color: var(--color-fg-soft);
}
.nds-toast--success { border-left: 1px solid var(--color-fg); }
.nds-toast--error { border-left: 1px solid var(--color-fg); }

.nds-dialog-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.nds-dialog {
    background: var(--color-bg);
    border: 1px solid var(--color-fg-faint);
    padding: 40px;
    max-width: 480px;
    width: 100%;
    color: var(--color-fg);
}
.nds-dialog__title {
    font-size: 14px;
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: var(--label-tracking);
    margin: 0 0 18px;
}

/* ===========================
   RESPONSIVE
   =========================== */
@media (max-width: 720px) {
    [data-system="nodesign"] { --pad: var(--pad-mobile); }
    .nds-work { grid-template-columns: 1fr; gap: 30px; }
    .nds-project:nth-child(2n) { margin-top: 0; }
    .nds-manifesto { grid-template-columns: 1fr; gap: 30px; }
    .nds-team { grid-template-columns: 1fr 1fr; gap: 20px; }
    .nds-focal--display { font-size: 36px; }
    .nds-form { width: calc(100vw - 60px); }
}

Step 5 — Wrap the section/page you want themed with <div data-system="nodesign">:

  <div data-system="nodesign">
    <Button>HELLO NODESIGN</Button>
    <Card>...</Card>
  </div>

Done. Now <Button>, <Input>, <Card>, <Badge>, <Dialog>, <Tabs>, <Switch>, <Toast>, <Select>, <Textarea> render in Nodesign.

Once installed, wrap whichever section you want themed:

<div data-system="nodesign">
    <Button variant="primary">Send →</Button>
    <Card>Almost nothing</Card>
</div>

See it in the wild → Essence →