Ctrl K
ComunidadGuías…

app/tokens.css

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/* ============================================================================
   Minddy design tokens — radix-nova style, OKLch color space.

   Imported by app/globals.css right after `@import "tailwindcss";`. The
   primitives of components/ui/ and the application read these variables.

   Fonts (--font-inter, --font-space-grotesk, --font-instrument-serif) are
   provided by next/font in app/layout.tsx. If absent, the browser default
   sans-serif is used.

   ── Brand color ────────────────────────────────────────────────────────────
   minddy's color is lavender (MIN-840): `#7059C4` in light mode, `#B6A5F0` in
   dark mode. It is both the default accent (`--primary`, `--ring` and the
   sidebar's: the primary button, segmented controls, switches, the focus
   ring) and the identity color (`--brand` and the surfaces derived from it).
   The account palettes of app/theme-presets.css override the accent per mode
   (MIN-696); the "Noir" / "Blanc" presets bring back ink actions. `--brand`
   stays lavender under every accent preset.
   ============================================================================ */

@import "tw-animate-css";

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

/* ── State variants ──────────────────────────────────────────────────────────
   Restored from the shadcn base (lost on extraction). They map short names to
   the data attributes Radix/cmdk actually emit, so utilities like
   `data-horizontal:h-1`, `data-active:shadow-sm` or `data-selected:bg-muted`
   target the right element instead of matching every node (or none). */
@custom-variant data-open {
  &:where([data-state="open"]),
  &:where([data-open]:not([data-open="false"])) {
    @slot;
  }
}
@custom-variant data-closed {
  &:where([data-state="closed"]),
  &:where([data-closed]:not([data-closed="false"])) {
    @slot;
  }
}
@custom-variant data-checked {
  &:where([data-state="checked"]),
  &:where([data-checked]:not([data-checked="false"])) {
    @slot;
  }
}
@custom-variant data-unchecked {
  &:where([data-state="unchecked"]),
  &:where([data-unchecked]:not([data-unchecked="false"])) {
    @slot;
  }
}
@custom-variant data-indeterminate {
  &:where([data-state="indeterminate"]),
  &:where([data-indeterminate]:not([data-indeterminate="false"])) {
    @slot;
  }
}
@custom-variant data-selected {
  &:where([data-selected="true"]) {
    @slot;
  }
}
@custom-variant data-disabled {
  &:where([data-disabled="true"]),
  &:where([data-disabled]:not([data-disabled="false"])) {
    @slot;
  }
}
@custom-variant data-active {
  &:where([data-state="active"]),
  &:where([data-active]:not([data-active="false"])) {
    @slot;
  }
}
@custom-variant data-horizontal {
  &:where([data-orientation="horizontal"]) {
    @slot;
  }
}
@custom-variant data-vertical {
  &:where([data-orientation="vertical"]) {
    @slot;
  }
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}

/**
 * NAVIGATION columns (primary sidebar, secondary sidebar): the thumb only shows
 * while the column is hovered. A navigation column is read at a glance; a
 * vertical bar parked alongside the list weighs more than it informs — and
 * becomes useful again the moment you come to scroll it.
 *
 * A 10px track for a comfortable pointer target, and a thumb that only fills 4
 * of it: the 3px transparent border thins it without shrinking what you can grab
 * (`background-clip: content-box`). It is painted from `--scrollbar-thumb`,
 * defined below in ALPHA — black in light theme, white in dark — so it sits as
 * well on a card as on the sidebar, without a tone per surface.
 *
 * WORTH KNOWING: styling a scrollbar makes it lose its OVERLAY mode. On macOS,
 * where it floated above the content taking nothing from it, it now reserves its
 * 10px as soon as there is something to scroll. `no-scrollbar` above stays there
 * for the places that would rather have nothing at all.
 */
@utility scrollbar-quiet {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
  &::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }
  &::-webkit-scrollbar-track,
  &::-webkit-scrollbar-corner {
    background: transparent;
  }
  &::-webkit-scrollbar-thumb {
    background-color: transparent;
    background-clip: content-box;
    border: 3px solid transparent;
    border-radius: 9999px;
  }
  &:hover {
    scrollbar-color: var(--scrollbar-thumb) transparent;
  }
  &:hover::-webkit-scrollbar-thumb {
    background-color: var(--scrollbar-thumb);
  }
  &:hover::-webkit-scrollbar-thumb:hover {
    background-color: var(--scrollbar-thumb-hover);
  }
}

/* Elevation. Only what sits ABOVE the page casts a shadow: menus, popovers,
   dialogs, sheets, toasts, a dragged item. Tailwind's defaults pair every
   shadow with a tight 1-2px band right against the edge, which read as a thin
   dark outline around each surface rather than as depth; these are wide and
   soft, with no band at the edge. The small steps stay for the few raised
   controls (a switch thumb) and are nearly flat. */
@theme {
  --shadow-2xs: 0 1px 2px rgb(0 0 0 / 0.03);
  --shadow-xs: 0 1px 3px rgb(0 0 0 / 0.04);
  --shadow-sm: 0 1px 4px rgb(0 0 0 / 0.06), 0 2px 8px -4px rgb(0 0 0 / 0.04);
  --shadow: 0 1px 4px rgb(0 0 0 / 0.06), 0 2px 8px -4px rgb(0 0 0 / 0.04);
  --shadow-md: 0 8px 24px -6px rgb(0 0 0 / 0.1), 0 2px 10px -4px rgb(0 0 0 / 0.04);
  --shadow-lg: 0 14px 36px -10px rgb(0 0 0 / 0.14), 0 4px 14px -6px rgb(0 0 0 / 0.05);
  --shadow-xl: 0 22px 48px -14px rgb(0 0 0 / 0.18), 0 6px 18px -8px rgb(0 0 0 / 0.06);
  --shadow-2xl: 0 30px 70px -18px rgb(0 0 0 / 0.24);
}

@theme inline {
  /* Menu separators: the general border, light enough to divide a list
     without drawing a line across it. Its own name keeps menus apart should
     the two ever differ again. */
  --color-menu-separator: var(--border);
  /* The scrim behind every modal layer (MIN-931): dialogs, wizards, alert
     dialogs, sheets and side panels. Menus, popovers and the Numo shell
     float without one. */
  --color-scrim: var(--overlay-scrim);
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-inter);
  --font-mono: var(--font-inter);
  --font-serif: var(--font-instrument-serif);
  /* Headings use Inter too: Space Grotesk is not loaded. */
  --font-display: var(--font-inter);
  --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);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --radius-2xl: calc(var(--radius) + 8px);
  --radius-3xl: calc(var(--radius) + 12px);
  --radius-4xl: calc(var(--radius) + 16px);

  /* Semantic color mappings */
  --color-control: var(--control);
  --color-control-hover: var(--control-hover);
  --color-primary-hover: var(--primary-hover);
  --color-foreground-hover: var(--foreground-hover);
  --color-destructive-surface: var(--destructive-surface);
  --color-destructive-surface-hover: var(--destructive-surface-hover);
  --color-destructive-highlight: var(--destructive-highlight);
  --color-destructive-button: var(--destructive-button);
  --color-destructive-button-foreground: var(--destructive-button-foreground);
  --color-destructive-border: var(--destructive-border);
  --color-destructive-border-hover: var(--destructive-border-hover);
  --color-tabs-hover: var(--tabs-hover);
  --color-slider-thumb: var(--slider-thumb);
  --color-surface-raised: var(--surface-raised);
  --color-surface-sunken: var(--surface-sunken);
  --color-surface-glass: var(--surface-glass);
  --color-surface-brand: var(--surface-brand);
  --color-surface-brand-hover: var(--surface-brand-hover);
  --color-surface-info: var(--surface-info);
  --color-surface-success: var(--surface-success);
  --color-surface-warning: var(--surface-warning);
  --color-surface-danger: var(--surface-danger);
  --color-surface-type-video: var(--surface-type-video);
  --color-surface-type-clip: var(--surface-type-clip);
  --color-surface-type-screenshot: var(--surface-type-screenshot);
  --color-surface-type-locked: var(--surface-type-locked);
  --color-border-subtle: var(--border-subtle);
  --color-accent-glow: var(--accent-glow);
  --color-accent-glow-foreground: var(--accent-glow-foreground);
  --color-accent-glow-muted: var(--accent-glow-muted);
  --color-success: var(--success);
  --color-success-foreground: var(--success-foreground);
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
  --color-warning-strong: var(--warning-strong);
  --color-text-tertiary: var(--text-tertiary);

  /* Brand accent — identity color (badges, indicators). Derived from primary. */
  --color-brand: var(--brand);
  --color-brand-foreground: var(--brand-foreground);

  /* Typography scale extensions (UI coherence tokens) */
  --text-2xs: 10px;
  --text-3xs: 11px;
  --text-md: 13px;

  /* Layout spacing */
  --spacing-header: 60px;
  --spacing-sidebar: 256px;
  --spacing-sidebar-sm: 56px;

  /* Component sizing */
  --spacing-icon: 16px;
  --spacing-icon-sm: 14px;
  --spacing-favicon: 20px;
  --radius-favicon: 4px;

  /* App shell max size (ultrawide monitors) */
  --spacing-app-max-w: 1800px;
  --spacing-app-max-h: 1100px;
  --breakpoint-3xl: 2200px;

  /* Default only: app/globals.css redefines `desktop` as 1024px and adds
     `wide` at 1200px. App chrome follows the `app-mobile:` / `app-desktop:`
     variants (lib/app-layout.ts), not these width breakpoints; see DESIGN.md. */
  --breakpoint-desktop: 1200px;
}

:root {
  /* Dialog max size — 90% of app shell on ultrawide, 90% of viewport below. */
  --spacing-dialog-w: min(90vw, calc(var(--spacing-app-max-w) * 0.9));
  --spacing-dialog-h: min(90vh, calc(var(--spacing-app-max-h) * 0.9));

  --radius: 1rem;
  --background: oklch(0.975 0.006 266);
  --foreground: oklch(0.145 0 0);
  --card: oklch(0.995 0.002 266);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(0.995 0.002 266);
  --popover-foreground: oklch(0.145 0 0);

  /* Lavender, with white text on top (5.4:1). */
  --primary: #7059c4;
  --primary-foreground: #ffffff;

  --secondary: oklch(0.920 0.020 268);
  --secondary-foreground: oklch(0.256 0.102 268);
  --muted: oklch(0.935 0.008 268);
  --muted-foreground: oklch(0.500 0.010 266);
  --accent: oklch(0.945 0.010 268);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.58 0.22 27);
  /* Borders are a light veil of the text color rather than a fixed gray, so
     an edge reads the same on the page, a card or a menu: the weight of the
     menus' separators, carried to every border (scratchpad, 2026-10-30). */
  --border: color-mix(in oklab, var(--foreground) 6%, transparent);
  --input: oklch(0.910 0.012 268);
  --ring: #7059c4;
  /* Scrollbar thumb, read by `scrollbar-quiet`. Repeat that utility's rules on
     `*` in your app's CSS to make it the scrollbar of every surface. */
  --scrollbar-thumb: oklch(0 0 0 / 18%);
  --scrollbar-thumb-hover: oklch(0 0 0 / 30%);
  --chart-1: oklch(0.85 0.12 266);
  --chart-2: oklch(0.754 0.120 270);
  --chart-3: oklch(0.65 0.15 266);
  --chart-4: oklch(0.498 0.282 266);
  --chart-5: oklch(0.45 0.12 266);
  --sidebar: oklch(0.975 0.006 266);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-primary: #7059c4;
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: oklch(0.940 0.012 268);
  --sidebar-accent-foreground: oklch(0.205 0 0);
  --sidebar-border: oklch(0.900 0.012 268);
  --sidebar-ring: #7059c4;

  /* Semantic tokens */
  /* Shared control fill — inputs, selects, outline buttons and neutral chips use
     the same opaque gray. It stays visible on both background and card without
     relying on a border. */
  --control: oklch(0.935 0.008 268);
  --control-hover: oklch(0.900 0.010 268);
  --primary-hover: var(--primary);
  --foreground-hover: var(--foreground);
  --destructive-button: var(--destructive);
  --destructive-button-foreground: oklch(0.985 0 0);
  --destructive-surface: oklch(0.925 0.040 27);
  --destructive-surface-hover: oklch(0.895 0.060 27);
  /* A destructive menu row's highlight: a tint of the red over the menu's
     own surface (translucent menus included), not an opaque pink block. */
  --destructive-highlight: oklch(0.58 0.22 27 / 0.10);
  --destructive-border: oklch(0.820 0.080 27);
  --destructive-border-hover: oklch(0.750 0.100 27);
  --tabs-hover: var(--control-hover);
  --slider-thumb: oklch(1 0 0);
  --surface-raised: oklch(0.995 0.002 266);
  --surface-sunken: oklch(0.940 0.008 268);
  --surface-glass: oklch(0.965 0.006 268 / 60%);
  --surface-brand: #efebf8;
  --surface-brand-hover: #e4ddf4;
  --surface-info: oklch(0.950 0.022 265);
  --surface-success: oklch(0.920 0.032 155);
  --surface-warning: oklch(0.937 0.021 75);
  --surface-danger: var(--destructive-surface);
  --surface-type-video: oklch(0.930 0.030 303);
  --surface-type-clip: oklch(0.930 0.030 163);
  --surface-type-screenshot: oklch(0.930 0.030 230);
  --surface-type-locked: oklch(0.930 0.030 55);
  --border-subtle: color-mix(in oklab, var(--foreground) 4%, transparent);
  --accent-glow: #7059c4;
  --accent-glow-foreground: #ffffff;
  --accent-glow-muted: oklch(0.541 0.160 289.9 / 10%);
  --success: oklch(0.68 0.16 155);
  --success-foreground: oklch(1 0 0);
  --warning: oklch(0.78 0.14 75);
  --warning-foreground: oklch(0.25 0 0);
  --warning-strong: oklch(0.53 0.17 52);
  --text-tertiary: oklch(0.500 0.010 266);

  /* Brand — lavender, the identity color, under every accent preset. */
  --brand: #7059c4;
  --brand-foreground: #ffffff;

  /* Modal scrim (MIN-931): dims the whole app behind a dialog so the layer
     reads as separate from it. A cool near-black, the page's own hue. */
  --overlay-scrim: oklch(0.2 0.02 266 / 32%);

  /* Ultrawide canvas — backdrop behind the centered app shell on ≥3xl screens */
  --canvas: oklch(0.945 0.025 266);
  --canvas-glow-1: oklch(0.80 0.10 290 / 0.55);
  --canvas-glow-2: oklch(0.80 0.09 320 / 0.45);
}

.dark {
  /* Linear-style dark — true black/gray with an imperceptible cool cast. */
  --background: oklch(0.135 0.003 250);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.170 0.003 250);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.170 0.003 250);
  --popover-foreground: oklch(0.985 0 0);

  /* Lavender, with deep-lavender text on top (8.1:1). */
  --primary: #b6a5f0;
  --primary-foreground: #1b1530;

  --secondary: oklch(0.220 0.003 250);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.200 0.003 250);
  --muted-foreground: oklch(0.620 0.003 250);
  --accent: oklch(0.230 0.003 250);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 6%);
  --input: oklch(1 0 0 / 12%);
  --ring: #b6a5f0;
  --scrollbar-thumb: oklch(1 0 0 / 16%);
  --scrollbar-thumb-hover: oklch(1 0 0 / 28%);
  --chart-1: oklch(0.85 0 0);
  --chart-2: oklch(0.70 0 0);
  --chart-3: oklch(0.55 0 0);
  --chart-4: oklch(0.65 0.18 265);
  --chart-5: oklch(0.40 0 0);
  --sidebar: oklch(0.110 0.003 250);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: #b6a5f0;
  --sidebar-primary-foreground: #1b1530;
  --sidebar-accent: oklch(0.200 0.003 250);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 6%);
  --sidebar-ring: #b6a5f0;

  /* Semantic tokens dark */
  /* Opaque on purpose: controls must stay visible without relying on a border. */
  --control: oklch(0.245 0.006 250);
  --control-hover: oklch(0.285 0.006 250);
  --primary-hover: var(--primary);
  --foreground-hover: var(--foreground);
  --destructive-button: oklch(0.50 0.20 22.216);
  --destructive-button-foreground: oklch(0.985 0 0);
  --destructive-surface: oklch(0.250 0.050 22.216);
  --destructive-surface-hover: oklch(0.310 0.070 22.216);
  --destructive-highlight: oklch(0.704 0.191 22.216 / 0.16);
  --destructive-border: oklch(0.360 0.080 22.216);
  --destructive-border-hover: oklch(0.420 0.100 22.216);
  --tabs-hover: var(--control-hover);
  --slider-thumb: oklch(0.985 0 0);
  --surface-raised: oklch(0.170 0.003 250);
  --surface-sunken: oklch(0.090 0.003 250);
  --surface-glass: oklch(0.170 0.003 250 / 60%);
  --surface-brand: #29233d;
  --surface-brand-hover: #332b4b;
  --surface-info: oklch(0.210 0.033 265);
  --surface-success: oklch(0.210 0.032 155);
  --surface-warning: oklch(0.240 0.028 75);
  --surface-danger: var(--destructive-surface);
  --surface-type-video: oklch(0.230 0.030 303);
  --surface-type-clip: oklch(0.230 0.030 163);
  --surface-type-screenshot: oklch(0.230 0.030 230);
  --surface-type-locked: oklch(0.230 0.030 55);
  --border-subtle: oklch(1 0 0 / 4%);
  --accent-glow: #b6a5f0;
  --accent-glow-foreground: #1b1530;
  --accent-glow-muted: oklch(0.764 0.107 294.1 / 12%);
  --success: oklch(0.72 0.16 155);
  --success-foreground: oklch(0.95 0 0);
  --warning: oklch(0.82 0.14 75);
  --warning-foreground: oklch(0.95 0 0);
  --warning-strong: oklch(0.82 0.14 75);
  --text-tertiary: oklch(0.555 0 0);

  /* Brand dark */
  --brand: #b6a5f0;
  --brand-foreground: #1b1530;

  /* Modal scrim dark: the page is already near black, so the scrim needs
     more weight to push it back behind the raised card surface. */
  --overlay-scrim: oklch(0 0 0 / 56%);

  /* Ultrawide canvas dark */
  --canvas: oklch(0.060 0.003 250);
  --canvas-glow-1: oklch(0.28 0.08 292 / 0.30);
  --canvas-glow-2: oklch(0.20 0.05 292 / 0.25);
}

@supports (color: color-mix(in oklab, red, blue)) {
  :root {
    --primary-hover: color-mix(in oklab, var(--primary) 90%, var(--background));
    --foreground-hover: color-mix(in oklab, var(--foreground) 90%, var(--background));
    --destructive-surface: color-mix(in oklab, var(--destructive) 10%, var(--background));
    --destructive-surface-hover: color-mix(in oklab, var(--destructive) 20%, var(--background));
    --destructive-border: color-mix(in oklab, var(--destructive) 30%, var(--background));
    --destructive-border-hover: color-mix(in oklab, var(--destructive) 40%, var(--background));
    --tabs-hover: color-mix(in oklab, var(--foreground) 5%, var(--muted));
    /* Lavender tint over the page: about #efebf8 / #e4ddf4 on white. */
    --surface-brand: color-mix(in oklab, var(--brand) 10%, var(--background));
    --surface-brand-hover: color-mix(in oklab, var(--brand) 20%, var(--background));
    --surface-info: color-mix(in oklab, var(--brand) 10%, var(--background));
    --surface-success: color-mix(in oklab, var(--success) 10%, var(--background));
    --surface-warning: color-mix(in oklab, var(--warning) 15%, var(--background));
    --surface-type-video: color-mix(in oklab, oklch(0.62 0.21 303) 10%, var(--background));
    --surface-type-clip: color-mix(in oklab, oklch(0.62 0.17 163) 10%, var(--background));
    --surface-type-screenshot: color-mix(in oklab, oklch(0.62 0.16 230) 10%, var(--background));
    --surface-type-locked: color-mix(in oklab, oklch(0.66 0.17 55) 10%, var(--background));
  }

  .dark {
    --primary-hover: color-mix(in oklab, var(--primary) 90%, var(--background));
    --foreground-hover: color-mix(in oklab, var(--foreground) 90%, var(--background));
    --destructive-surface: color-mix(in oklab, var(--destructive) 20%, var(--background));
    --destructive-surface-hover: color-mix(in oklab, var(--destructive) 30%, var(--background));
    --destructive-border: color-mix(in oklab, var(--destructive) 40%, var(--background));
    --destructive-border-hover: color-mix(in oklab, var(--destructive) 50%, var(--background));
    --tabs-hover: color-mix(in oklab, var(--foreground) 5%, var(--muted));
    --surface-brand: color-mix(in oklab, var(--brand) 20%, var(--background));
    --surface-brand-hover: color-mix(in oklab, var(--brand) 30%, var(--background));
    --surface-info: color-mix(in oklab, var(--brand) 20%, var(--background));
    --surface-success: color-mix(in oklab, var(--success) 20%, var(--background));
    --surface-warning: color-mix(in oklab, var(--warning) 25%, var(--background));
    --surface-type-video: color-mix(in oklab, oklch(0.68 0.19 303) 20%, var(--background));
    --surface-type-clip: color-mix(in oklab, oklch(0.68 0.15 163) 20%, var(--background));
    --surface-type-screenshot: color-mix(in oklab, oklch(0.68 0.14 230) 20%, var(--background));
    --surface-type-locked: color-mix(in oklab, oklch(0.72 0.15 55) 20%, var(--background));
  }
}

html,
body {
  font-family:
    var(--font-inter),
    ui-sans-serif,
    system-ui,
    sans-serif,
    "Apple Color Emoji",
    "Segoe UI Emoji",
    "Segoe UI Symbol",
    "Noto Color Emoji";
}

html {
  scroll-behavior: smooth;
}

/* Keyframes used by primitives (skeleton shimmer, accordion, collapsible) */
@keyframes shimmer {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
@keyframes accordion-down {
  from { height: 0; opacity: 0; }
  to { height: var(--radix-accordion-content-height); opacity: 1; }
}
@keyframes accordion-up {
  from { height: var(--radix-accordion-content-height); opacity: 1; }
  to { height: 0; opacity: 0; }
}
@keyframes collapsible-down {
  from { height: 0; opacity: 0; }
  to { height: var(--radix-collapsible-content-height); opacity: 1; }
}
@keyframes collapsible-up {
  from { height: var(--radix-collapsible-content-height); opacity: 1; }
  to { height: 0; opacity: 0; }
}
/* The drawn caret of the one-time-code boxes (components/otp-input.tsx). */
@keyframes caret-blink {
  0%, 70%, 100% { opacity: 1; }
  20%, 50% { opacity: 0; }
}

/* Text shimmer — the "this is running" signal of the AI surfaces (ToolCallList's
   in-flight action, WorkAccordion's header). A bright band sweeps across dimmed
   text, IN READING ORDER — left to right.

   That direction is a sign, not a duration, and it is easy to get backwards.
   With `background-size: 300%`, a `background-position` of X% offsets the image
   by (width − 3×width)·X — i.e. LEFTWARDS as X grows. So the bright band (at
   50% of the gradient) sits 1.5×width to the RIGHT of the text at 0%, and
   0.5×width to the left at 100%. Sweeping with the text therefore means going
   from 100% to 0%, not the other way round: 0%→100% enters from the right and
   reads against the text, which is instantly noticeable.

   The gradient is THREE times wider than the text (300%) with no-repeat, and
   that is not cosmetic either — it is what makes the loop seamless. At 200% the
   band lands on one edge at each extreme: half-visible at BOTH ends, so every
   lap it teleported across. At 300% it is off the text at both ends, so the two
   extremes look IDENTICAL (uniformly dimmed text) and the seam is invisible.
   The short breath between passes is intended. At any position the image still
   covers the whole text — no transparent gap. */
@keyframes text-shimmer {
  from { background-position: 100% center; }
  to { background-position: 0% center; }
}

@utility text-shimmer {
  /* NB: `background-clip: text` requires `color: transparent`, which makes
     `currentColor` transparent too (it RESOLVES to `color`) — so this uses the
     theme tokens directly rather than currentColor. Base = muted-foreground
     (the normal label color), sweeping band = foreground (brighter). */
  background-image: linear-gradient(
    90deg,
    var(--muted-foreground) 0%,
    var(--muted-foreground) 35%,
    var(--foreground) 50%,
    var(--muted-foreground) 65%,
    var(--muted-foreground) 100%
  );
  background-size: 300% 100%;
  background-repeat: no-repeat;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: text-shimmer 2.4s linear infinite;

  @media (prefers-reduced-motion: reduce) {
    animation: none;
    background: none;
    color: var(--muted-foreground);
    -webkit-text-fill-color: var(--muted-foreground);
  }
}

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

  /* The containing block of the coarse-pointer hit areas
     (components/ui/touch-target.ts). In the base layer on purpose: a control
     positioned by a utility (`absolute`, `fixed`, `sticky` — the "back to
     bottom" arrow of a thread, the close cross of a dialog) keeps its own
     positioning, which already makes it a containing block. */
  [data-slot="button"] {
    position: relative;
  }
  @media (pointer: coarse) {
    :is([data-slot="select-trigger"], [data-slot="dialog-close"]) {
      position: relative;
    }
  }
}

/* Card hover-lift — subtle rise + shadow for a fine pointer that hovers. */
.lift {
  transition:
    transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.3s ease,
    border-color 0.3s ease;
}
@media (hover: hover) and (pointer: fine) {
  .lift:hover {
    transform: translateY(-4px);
    box-shadow: 0 20px 40px -20px rgba(0, 0, 0, 0.28);
  }
}

/* Reduced motion — kill CSS animations globally */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Selected text takes the accent: a light tint of `--primary` behind text
   that keeps its own color, so contrast stays the text's own on every theme
   and accent (a black or white accent gives a neutral gray tint).
   `--selection-block` is that same tint, for what an editor selects as a
   whole (selected blocks, an image, an equation: app/globals.css). Declared
   again under `.dark`, like `--primary-hover`, so it follows the theme's
   accent wherever the class sits. */
:root,
.dark {
  --selection-block: color-mix(in oklab, var(--primary) 28%, transparent);
}
::selection {
  background-color: var(--selection-block);
  color: inherit;
}

/* Global user-select: none (outside @layer for higher priority) */
*:not(input):not(textarea):not([contenteditable="true"]):not(pre):not(code) {
  user-select: none;
}
input,
textarea,
[contenteditable="true"],
pre,
code,
.select-text {
  user-select: text;
}
/* The text INSIDE an editable surface too — its paragraphs, list items and
   marks are elements the reset above reached. A desktop browser selects in
   an editing host anyway; a touch screen honors the reset, and long-press
   selection did nothing in every editor. Atomic pieces the editor marks
   non-editable (a mention pill, a task's checkbox) keep the reset. */
[contenteditable="true"] :not([contenteditable="false"], [contenteditable="false"] *) {
  user-select: text;
}
/* A READ-ONLY editor too — a reader's view of a description, a page or a
   plan — so whoever cannot edit the text can still select and copy it. */
.ProseMirror[contenteditable="false"],
.ProseMirror[contenteditable="false"] * {
  user-select: text;
}

/* iOS Safari zooms a focused control whose font-size < 16px. Enforce 16px floor
   on small screens. Excludes checkbox/radio/buttons. */
@media (max-width: 767px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="button"]):not([type="submit"]),
  /* A title field is already 18 px or more: the floor would SHRINK it, and a
     page title read 16 px on a phone. */
  textarea:not(.title-field),
  select,
  [contenteditable="true"] {
    font-size: 16px;
  }
}

/* react-colorful — match the radix-nova look (rounded, soft shadows). */
.color-input-picker .react-colorful {
  width: 220px;
  height: 220px;
  gap: 10px;
}
.color-input-picker .react-colorful__saturation {
  border-radius: calc(var(--radius) - 2px);
  border-bottom: none;
  box-shadow: inset 0 0 0 1px var(--border);
}
.color-input-picker .react-colorful__last-control {
  border-radius: 999px;
}
.color-input-picker .react-colorful__hue,
.color-input-picker .react-colorful__alpha {
  height: 12px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--border);
}
.color-input-picker .react-colorful__pointer {
  width: 18px;
  height: 18px;
  border-width: 2px;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.12),
    0 2px 4px rgba(0, 0, 0, 0.2);
}
.color-input-picker .react-colorful__interactive:focus .react-colorful__pointer {
  transform: translate(-50%, -50%) scale(1.15);
}

/* ── Ultrawide canvas ─────────────────────────────────────────────────────────
   On ≥3xl screens the <AppShell> becomes a centered card (via its `3xl:*` classes
   and the body flex/height it applies). This paints the aurora backdrop revealed
   around the card, and re-anchors floating panels to the app zone edges. */
.ultrawide-canvas {
  background-color: var(--canvas);
  background-image:
    radial-gradient(ellipse 70% 55% at 12% 18%, var(--canvas-glow-1), transparent 62%),
    radial-gradient(ellipse 75% 55% at 88% 82%, var(--canvas-glow-2), transparent 62%);
  background-attachment: fixed;
}

@layer utilities {
  /* The floating <SidePanel> is fixed to the viewport. On ultrawide the app is a
     centered card, so re-anchor the panel to the app-zone edges (+12px) instead
     of the viewport — top AND bottom, so it never exceeds the app height. */
  .ultrawide-canvas [data-slot="side-panel-content"][data-side="right"] {
    right: calc((100dvw - var(--spacing-app-max-w)) / 2 + 12px) !important;
    top: calc(max(0px, (100dvh - var(--spacing-app-max-h)) / 2) + 12px) !important;
    bottom: calc(max(0px, (100dvh - var(--spacing-app-max-h)) / 2) + 12px) !important;
  }
  .ultrawide-canvas [data-slot="side-panel-content"][data-side="left"] {
    left: calc((100dvw - var(--spacing-app-max-w)) / 2 + 12px) !important;
    top: calc(max(0px, (100dvh - var(--spacing-app-max-h)) / 2) + 12px) !important;
    bottom: calc(max(0px, (100dvh - var(--spacing-app-max-h)) / 2) + 12px) !important;
  }
}

/* ─── Settings: naming one card ────────────────────────────────────────────
   `SettingsLayout` takes you to the right tab, but a settings tab is a column of
   cards that look alike: arriving does not yet say WHICH one you came for. The
   ring names it for the length of a glance, then the layout removes the
   attribute.

   `box-shadow` rather than `outline`: it follows the card's radius and fades out
   in a single property, and it pushes nothing — the column does not move by a
   pixel for the two seconds it lasts.

   No `fill-mode`: the landing frame is already transparent, and a frozen shadow
   would stay a composited layer for nothing. */
@keyframes settings-section-focus {
  0%,
  55% {
    box-shadow:
      0 0 0 2px var(--ring),
      0 0 0 7px color-mix(in oklab, var(--ring) 22%, transparent);
  }
  100% {
    box-shadow:
      0 0 0 2px transparent,
      0 0 0 7px transparent;
  }
}
[data-settings-focus] {
  animation: settings-section-focus 2s cubic-bezier(0.16, 1, 0.3, 1);
}

/* "Less motion": we do not shorten it, we remove it. The ring itself STAYS — it
   does not decorate, it points. */
@media (prefers-reduced-motion: reduce) {
  [data-settings-focus] {
    animation: none;
    box-shadow:
      0 0 0 2px var(--ring),
      0 0 0 7px color-mix(in oklab, var(--ring) 22%, transparent);
  }
}