Files
homarr/docs/unraid-ui-project/orchis-design-system.ts
Kaloyan Danchev e881ec6cb5
Some checks failed
Master CI / yarn_install_and_build (push) Has been cancelled
Add Unraid UI project documentation and research
- Complete Unraid WebGUI inventory (~100 pages documented)
- Unraid GraphQL API research and documentation
- Homarr architecture documentation
- Orchis GTK theme design tokens (TypeScript)
- Project README with implementation plan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 07:44:52 +02:00

1330 lines
39 KiB
TypeScript

/**
* Orchis GTK Theme - Complete Design Token System
* ================================================
* Extracted from: https://github.com/vinceliuice/Orchis-theme
* Based on Google Material Design with custom Orchis adaptations
*
* This file provides a complete CSS/design token system for a Next.js
* web application, faithfully representing the Orchis GTK theme.
*/
// ============================================================================
// 1. COLOR PALETTES - ALL VARIANTS
// ============================================================================
/**
* --- MATERIAL DESIGN BASE PALETTE ---
* Orchis uses the standard Material Design 2014 color palette as its foundation.
* Each color has shades from 50 (lightest) to 900 (darkest) plus accent variants.
*/
export const materialPalette = {
red: {
50: "#FFEBEE", 100: "#FFCDD2", 200: "#EF9A9A", 300: "#E57373",
400: "#EF5350", 500: "#F44336", 600: "#E53935", 700: "#D32F2F",
800: "#C62828", 900: "#B71C1C",
a100: "#FF8A80", a200: "#FF5252", a400: "#FF1744", a700: "#D50000",
},
pink: {
50: "#FCE4EC", 100: "#F8BBD0", 200: "#F48FB1", 300: "#F06292",
400: "#EC407A", 500: "#E91E63", 600: "#D81B60", 700: "#C2185B",
800: "#AD1457", 900: "#880E4F",
a100: "#FF80AB", a200: "#FF4081", a400: "#F50057", a700: "#C51162",
},
purple: {
50: "#F3E5F5", 100: "#E1BEE7", 200: "#CE93D8", 300: "#BA68C8",
400: "#AB47BC", 500: "#9C27B0", 600: "#8E24AA", 700: "#7B1FA2",
800: "#6A1B9A", 900: "#4A148C",
a100: "#EA80FC", a200: "#E040FB", a400: "#D500F9", a700: "#AA00FF",
},
deepPurple: {
50: "#EDE7F6", 100: "#D1C4E9", 200: "#B39DDB", 300: "#9575CD",
400: "#7E57C2", 500: "#673AB7", 600: "#5E35B1", 700: "#512DA8",
800: "#4527A0", 900: "#311B92",
a100: "#B388FF", a200: "#7C4DFF", a400: "#651FFF", a700: "#6200EA",
},
indigo: {
50: "#E8EAF6", 100: "#C5CAE9", 200: "#9FA8DA", 300: "#7986CB",
400: "#5C6BC0", 500: "#3F51B5", 600: "#3949AB", 700: "#303F9F",
800: "#283593", 900: "#1A237E",
a100: "#8C9EFF", a200: "#536DFE", a400: "#3D5AFE", a700: "#304FFE",
},
blue: {
50: "#E3F2FD", 100: "#BBDEFB", 200: "#90CAF9", 300: "#64B5F6",
400: "#42A5F5", 500: "#2196F3", 600: "#1E88E5", 700: "#1976D2",
800: "#1565C0", 900: "#0D47A1",
a100: "#82B1FF", a200: "#448AFF", a400: "#2979FF", a700: "#2962FF",
},
teal: {
50: "#E0F2F1", 100: "#B2DFDB", 200: "#80CBC4", 300: "#4DB6AC",
400: "#26A69A", 500: "#009688", 600: "#00897B", 700: "#00796B",
800: "#00695C", 900: "#004D40",
a100: "#A7FFEB", a200: "#64FFDA", a400: "#1DE9B6", a700: "#00BFA5",
},
green: {
50: "#E8F5E9", 100: "#C8E6C9", 200: "#A5D6A7", 300: "#81C784",
400: "#66BB6A", 500: "#4CAF50", 600: "#43A047", 700: "#388E3C",
800: "#2E7D32", 900: "#1B5E20",
a100: "#B9F6CA", a200: "#69F0AE", a400: "#00E676", a700: "#00C853",
},
yellow: {
50: "#FFFDE7", 100: "#FFF9C4", 200: "#FFF59D", 300: "#FFF176",
400: "#FFEE58", 500: "#FFEB3B", 600: "#FDD835", 700: "#FBC02D",
800: "#F9A825", 900: "#F57F17",
a100: "#FFFF8D", a200: "#FFFF00", a400: "#FFEA00", a700: "#FFD600",
},
orange: {
50: "#FFF3E0", 100: "#FFE0B2", 200: "#FFCC80", 300: "#FFB74D",
400: "#FFA726", 500: "#FF9800", 600: "#FB8C00", 700: "#F57C00",
800: "#EF6C00", 900: "#E65100",
a100: "#FFD180", a200: "#FFAB40", a400: "#FF9100", a700: "#FF6D00",
},
grey: {
50: "#FAFAFA", 100: "#F5F5F5", 200: "#EEEEEE", 300: "#E0E0E0",
400: "#BDBDBD", 500: "#9E9E9E", 600: "#757575", 700: "#616161",
800: "#424242", 900: "#212121",
},
blueGrey: {
50: "#ECEFF1", 100: "#CFD8DC", 200: "#B0BEC5", 300: "#90A4AE",
400: "#78909C", 500: "#607D8B", 600: "#546E7A", 700: "#455A64",
800: "#37474F", 900: "#263238",
},
} as const;
/**
* --- ORCHIS CUSTOM GREY SCALE ---
* Orchis defines a more granular grey scale (050 through 950) than
* standard Material Design. These are used for backgrounds and surfaces.
*/
export const orchisGrey = {
"050": "#FAFAFA",
"100": "#F2F2F2",
"150": "#EEEEEE",
"200": "#DDDDDD",
"250": "#CCCCCC",
"300": "#BFBFBF",
"350": "#A0A0A0",
"400": "#9E9E9E",
"450": "#868686",
"500": "#727272",
"550": "#555555",
"600": "#464646",
"650": "#3C3C3C",
"700": "#2C2C2C",
"750": "#242424",
"800": "#212121",
"850": "#121212",
"900": "#0F0F0F",
"950": "#030303",
} as const;
/**
* --- ACCENT COLOR VARIANTS ---
* Each accent has a "light" variant (used on dark backgrounds)
* and a "dark" variant (used on light backgrounds).
*
* The DEFAULT theme color is Blue (default).
*/
export const accentColors = {
default: { // Blue
light: "#3281EA", // used on dark backgrounds
dark: "#1A73E8", // used on light backgrounds
},
purple: {
light: "#BA68C8",
dark: "#AB47BC",
},
pink: {
light: "#F06292",
dark: "#EC407A",
},
red: {
light: "#F44336",
dark: "#E53935",
},
orange: {
light: "#FB8C00",
dark: "#F57C00",
},
yellow: {
light: "#FBC02D",
dark: "#FFD600",
},
green: {
light: "#66BB6A",
dark: "#4CAF50",
},
teal: {
light: "#4DB6AC",
dark: "#009688",
},
grey: {
light: "#DDDDDD", // $grey-200 for dark mode
dark: "#464646", // $grey-600 for light mode
},
} as const;
/** Success/sea green used for checkmarks and success states */
export const successColors = {
light: "#81C995",
dark: "#0F9D58",
} as const;
/**
* --- THEME TOKENS: LIGHT MODE ---
* Resolved color values for the light color scheme.
* Primary defaults to Blue accent ($blue-dark = #1A73E8).
*/
export const lightTheme = {
// Primary accent (default = blue)
primary: "#1A73E8",
// Backgrounds
background: "#F2F2F2", // grey-100 = background(c)
surface: "#FFFFFF", // white = background(a) => surface
base: "#FFFFFF", // white = background(a) => base
baseAlt: "#FAFAFA", // grey-050 = background(b) => base-alt
// Text on background
text: {
primary: "rgba(0, 0, 0, 0.87)",
secondary: "rgba(0, 0, 0, 0.6)",
disabled: "rgba(0, 0, 0, 0.38)",
secondaryDisabled: "rgba(0, 0, 0, 0.26)",
},
// Text on primary (white text on blue bg)
onPrimary: {
primary: "#FFFFFF",
secondary: "rgba(255, 255, 255, 0.7)",
disabled: "rgba(255, 255, 255, 0.5)",
},
// Semantic colors
warning: "#FFD600", // $yellow-dark
error: "#E53935", // $red-dark
success: "#0F9D58", // $sea-dark
link: "#1A73E8", // same as primary
linkVisited: "#AB47BC", // $purple-dark
// Borders and dividers
border: "rgba(0, 0, 0, 0.12)",
solidBorder: "#E2E2E2", // darken($background, 8%)
divider: "rgba(0, 0, 0, 0.12)",
frame: "rgba(0, 0, 0, 0.1)",
// Overlay states (used on currentColor)
overlay: {
normal: "rgba(0, 0, 0, 0.05)", // 5% opacity
hover: "rgba(0, 0, 0, 0.08)", // 8% opacity
focus: "rgba(0, 0, 0, 0.08)", // 8% opacity
focusHover: "rgba(0, 0, 0, 0.16)", // 16% opacity
active: "rgba(0, 0, 0, 0.12)", // 12% opacity
checked: "rgba(0, 0, 0, 0.10)", // 10% opacity
selected: "rgba(0, 0, 0, 0.06)", // 6% opacity
},
// Track (for sliders, switches)
track: "rgba(0, 0, 0, 0.26)",
trackDisabled: "rgba(0, 0, 0, 0.15)",
// Fill (subtle backgrounds)
fill: "rgba(0, 0, 0, 0.04)",
secondaryFill: "rgba(0, 0, 0, 0.08)",
// Tooltip
tooltip: "rgba(0, 0, 0, 0.9)", // dark tooltip on light
// Panel/titlebar
titlebar: "#FFFFFF", // background(a) when topbar=light
titlebarBackdrop: "#FAFAFA",
// Window buttons (macOS style)
buttonClose: "#fd5f51",
buttonMaximize: "#38c76a",
buttonMinimize: "#fdbe04",
// Popover/menu
popover: "#FFFFFF",
// Sidebar
sidebar: "#FAFAFA", // $base-alt
} as const;
/**
* --- THEME TOKENS: DARK MODE ---
* Resolved color values for the dark color scheme.
* Primary defaults to Blue accent ($blue-light = #3281EA).
*/
export const darkTheme = {
// Primary accent (default = blue)
primary: "#3281EA",
// Backgrounds
background: "#212121", // grey-800 = background(e)
surface: "#3C3C3C", // grey-650 = background(h) => surface
base: "#2C2C2C", // grey-700 = background(g) => base
baseAlt: "#242424", // grey-750 = background(f) => base-alt
// Text on dark background
text: {
primary: "#FFFFFF",
secondary: "rgba(255, 255, 255, 0.7)",
disabled: "rgba(255, 255, 255, 0.5)",
secondaryDisabled: "rgba(255, 255, 255, 0.3)",
},
// Text on primary (white text on blue bg)
onPrimary: {
primary: "#FFFFFF",
secondary: "rgba(255, 255, 255, 0.7)",
disabled: "rgba(255, 255, 255, 0.5)",
},
// Semantic colors
warning: "#FBC02D", // $yellow-light
error: "#F44336", // $red-light
success: "#81C995", // $sea-light
link: "#3281EA", // same as primary
linkVisited: "#BA68C8", // $purple-light
// Borders and dividers
border: "rgba(255, 255, 255, 0.12)",
solidBorder: "#3D3D3D", // lighten($background, 12%)
divider: "rgba(255, 255, 255, 0.12)",
frame: "rgba(0, 0, 0, 0.25)",
// Overlay states
overlay: {
normal: "rgba(255, 255, 255, 0.05)",
hover: "rgba(255, 255, 255, 0.08)",
focus: "rgba(255, 255, 255, 0.08)",
focusHover: "rgba(255, 255, 255, 0.16)",
active: "rgba(255, 255, 255, 0.12)",
checked: "rgba(255, 255, 255, 0.10)",
selected: "rgba(255, 255, 255, 0.06)",
},
// Track
track: "rgba(255, 255, 255, 0.3)",
trackDisabled: "rgba(255, 255, 255, 0.15)",
// Fill
fill: "rgba(255, 255, 255, 0.04)",
secondaryFill: "rgba(255, 255, 255, 0.08)",
// Tooltip
tooltip: "rgba(0, 0, 0, 0.9)",
// Panel/titlebar (dark topbar)
titlebar: "#2C2C2C", // background(g)
titlebarBackdrop: "#3C3C3C",
// Window buttons
buttonClose: "#fd5f51",
buttonMaximize: "#38c76a",
buttonMinimize: "#fdbe04",
// Popover/menu
popover: "#212121", // background(e)
// Sidebar
sidebar: "#242424", // $base-alt
} as const;
/**
* --- BLACK VARIANT (tweaks: --black) ---
* Full black backgrounds for OLED-friendly dark mode.
*/
export const blackVariant = {
background: "#000000", // $black
surface: "#121212", // grey-850
base: "#0F0F0F", // grey-900
baseAlt: "#030303", // grey-950
} as const;
/**
* --- NORD COLOR SCHEME VARIANT ---
* Activated with: --tweaks nord
*/
export const nordPalette = {
nord0: "#2e3440", nord1: "#3b4252", nord2: "#434c5e", nord3: "#4c566a",
nord4: "#d8dee9", nord5: "#e5e9f0", nord6: "#eceff4",
nord7: "#8fbcbb", nord8: "#88c0d0", nord9: "#81a1c1", nord10: "#5e81ac",
nord11: "#bf616a", nord12: "#d08770", nord13: "#ebcb8b", nord14: "#a3be8c",
nord15: "#b48ead",
} as const;
export const nordAccents = {
default: { light: "#89a3c2", dark: "#5e81ac" },
purple: { light: "#c89dbf", dark: "#b57daa" },
pink: { light: "#dc98b1", dark: "#cd7092" },
red: { light: "#d4878f", dark: "#c35b65" },
orange: { light: "#dca493", dark: "#d08770" },
yellow: { light: "#eac985", dark: "#e4b558" },
green: { light: "#a0c082", dark: "#82ac5d" },
teal: { light: "#83b9b8", dark: "#63a6a5" },
grey: { light: "#d9dce3", dark: "#3a4150" },
} as const;
export const nordGrey = {
"050": "#f8fafc", "100": "#f0f1f4", "150": "#eaecf0", "200": "#d9dce3",
"250": "#c4c9d4", "300": "#b5bcc9", "350": "#929cb0", "400": "#8e99ae",
"450": "#74819a", "500": "#616d85", "550": "#464f62", "600": "#3a4150",
"650": "#333a47", "700": "#242932", "750": "#1e222a", "800": "#1c1f26",
"850": "#0f1115", "900": "#0d0e11", "950": "#020203",
} as const;
/**
* --- DRACULA COLOR SCHEME VARIANT ---
* Activated with: --tweaks dracula
*/
export const draculaAccents = {
default: { light: "#6272a4", dark: "#5d70ac" },
purple: { light: "#bd93f9", dark: "#a679ec" },
pink: { light: "#ff79c6", dark: "#f04cab" },
red: { light: "#ff5555", dark: "#f44d4d" },
orange: { light: "#ffb86c", dark: "#f8a854" },
yellow: { light: "#f1fa8c", dark: "#e8f467" },
green: { light: "#50fa7b", dark: "#4be772" },
teal: { light: "#50fae9", dark: "#20eed9" },
grey: { light: "#d9dae3", dark: "#3c3f51" },
} as const;
export const draculaGrey = {
"050": "#f9f9fb", "100": "#f0f1f4", "150": "#ebecf1", "200": "#d9dae3",
"250": "#c4c7d4", "300": "#b5b8c9", "350": "#9397af", "400": "#8f94ad",
"450": "#757a99", "500": "#626784", "550": "#474b61", "600": "#3c3f51",
"650": "#343746", "700": "#242632", "750": "#1f2029", "800": "#1c1e26",
"850": "#0f1015", "900": "#0d0d11", "950": "#020203",
} as const;
// ============================================================================
// 2. TYPOGRAPHY
// ============================================================================
export const typography = {
/** Primary font stack - Orchis prefers M+ 1c and Roboto */
fontFamily: '"M+ 1c", Roboto, Cantarell, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
/** For large headings, Roboto is preferred first */
largeFontFamily: 'Roboto, "M+ 1c", Cantarell, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
/** Root font size */
rootFontSize: "14px",
rootFontSizeCompact: "13px",
/** Subheading font size */
subheadingSize: "16px",
subheadingSizeCompact: "15px",
/**
* Typography scale (from GTK4 type classes)
* Maps to CSS utility classes
*/
scale: {
largeTitle: { weight: 300, size: "24pt" }, // .large-title
title1: { weight: 800, size: "20pt" }, // .title-1
title2: { weight: 800, size: "15pt" }, // .title-2
title3: { weight: 700, size: "15pt" }, // .title-3
title4: { weight: 700, size: "13pt" }, // .title-4
heading: { weight: 700, size: "11pt" }, // .heading
body: { weight: 400, size: "11pt" }, // .body
caption: { weight: 400, size: "9pt" }, // .caption
captionHeading: { weight: 700, size: "9pt" }, // .caption-heading
},
/** Button text weight */
buttonWeight: 500,
/** Tab text weight */
tabWeight: 500,
/** Sidebar selected item weight */
sidebarSelectedWeight: 500,
} as const;
// ============================================================================
// 3. BORDER RADIUS / SHAPE LANGUAGE
// ============================================================================
/**
* Orchis uses a **rounded** design language by default.
* $default_corner = 12px (configurable via --round flag from 2px to 15px).
*
* All other radii are derived from this base value.
*/
export const borderRadius = {
/** Default corner radius (12px) */
default: 12,
/** Window radius: $default_corner + $space-size = 12 + 6 = 18px */
window: 18,
/** Standard corner radius (for buttons, entries, etc.): same as default = 12px
* In compact mode: max(0, $default_corner - 2px) = 10px */
corner: 12,
cornerCompact: 10,
/** Material radius: $default_corner / 2 + 4 = 10px */
material: 10,
/** Menu/popover radius: $default_corner / 4 + $space-size + 2 = 3 + 6 + 2 = 11px */
menu: 11,
/** Popup radius: $default_corner + $space-size * 2 - 4 = 12 + 12 - 4 = 20px */
popup: 20,
/** Menu item radius: $default_corner / 4 + 2 = 5px */
menuItem: 5,
/** Card/boxed list radius: $corner-radius - 1px = 11px */
card: 11,
/** Circular/pill radius (for pills, circular buttons) */
circular: 9999,
/** Tooltip radius: $corner-radius / 2 = 6px */
tooltip: 6,
/** Toolbar button radius: $corner-radius / 2 = 6px */
toolbarButton: 6,
/** Panel corner radius (gnome-shell): 0 */
panel: 0,
} as const;
// ============================================================================
// 4. SPACING SYSTEM
// ============================================================================
/**
* Orchis uses a base spacing unit of 6px (4px in compact mode).
* Margin is 4px (2px in compact).
* Sizes scale consistently based on these units.
*/
export const spacing = {
/** Base spacing unit: 6px */
space: 6,
spaceCompact: 4,
/** Base margin: 4px */
margin: 4,
marginCompact: 2,
/** Component sizes */
small: 24, // 24px (compact: 22px) - small buttons, icons
smallCompact: 22,
medium: 36, // 36px (compact: 32px) - buttons, entries, tabs
mediumCompact: 32,
large: 48, // 48px (compact: 40px) - large buttons, headers
largeCompact: 40,
/** Menu item height: 28px (compact: 24px) */
menuItem: 28,
menuItemCompact: 24,
/** Bar size (progress bars, level bars): 6px */
bar: 6,
/** Icon sizes */
iconBase: 16,
iconMedium: 24, // 16 * 1.5
iconLarge: 32, // 16 * 2
/** Derived spacing values frequently used */
xs: 2, // half margin
sm: 4, // margin
md: 6, // space
lg: 12, // space * 2
xl: 18, // space * 3
xxl: 24, // space * 4
} as const;
// ============================================================================
// 5. COMPONENT STYLING PATTERNS
// ============================================================================
// --- 5.1 BUTTONS ---
export const button = {
/** Standard button */
standard: {
minHeight: 24,
minWidth: 24,
// padding: (medium-size - 24) / 2 = (36-24)/2 = 6px
padding: "6px",
borderRadius: 12, // $corner-radius
fontWeight: 500,
transition: "all 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
},
/** States for standard button (light mode) */
states: {
normal: {
background: "rgba(0, 0, 0, 0.05)", // currentColor at 5%
color: "rgba(0, 0, 0, 0.87)", // $text primary
boxShadow: "inset 0 0 0 2px transparent",
},
hover: {
background: "rgba(0, 0, 0, 0.08)",
},
focus: {
boxShadow: "inset 0 0 0 2px rgba(0, 0, 0, 0.08)",
},
active: {
// Ripple animation plays
background: "rgba(0, 0, 0, 0.08)",
},
disabled: {
background: "rgba(0, 0, 0, 0.05)",
color: "rgba(0, 0, 0, 0.38)",
},
checked: {
// PRIMARY colored button
background: "#1A73E8", // $primary
color: "#FFFFFF", // $primary-text
},
},
/** Flat/text button */
flat: {
normal: {
background: "transparent",
color: "rgba(0, 0, 0, 0.6)", // $text-secondary
},
hover: {
background: "rgba(0, 0, 0, 0.08)",
color: "rgba(0, 0, 0, 0.87)",
},
active: {
background: "rgba(0, 0, 0, 0.08)",
color: "rgba(0, 0, 0, 0.87)",
},
disabled: {
background: "transparent",
color: "rgba(0, 0, 0, 0.26)",
},
checked: {
background: "rgba(0, 0, 0, 0.10)",
color: "rgba(0, 0, 0, 0.87)",
},
},
/** Suggested action (primary CTA) - uses primary color */
suggested: {
background: "#1A73E8",
color: "#FFFFFF",
},
/** Destructive action */
destructive: {
background: "#E53935",
color: "#FFFFFF",
},
/** Pill button (for dialog actions) */
pill: {
borderRadius: 9999,
padding: "6px 32px",
},
/** Circular button */
circular: {
borderRadius: 9999,
minWidth: 24,
minHeight: 24,
},
} as const;
// --- 5.2 CARDS / PANELS ---
export const card = {
borderRadius: 12, // $corner-radius (same as default)
border: "1px solid", // $divider color
// light: "rgba(0,0,0,0.12)", dark: "rgba(255,255,255,0.12)"
boxShadow: "none",
backgroundClip: "padding-box",
// light bg: $base = #FFFFFF, dark bg: $base = #2C2C2C
// color: $text-secondary
/** Boxed list (grouped card rows) */
boxedList: {
borderRadius: 11, // $corner-radius - 1px
rowBorderBottom: "1px solid", // $divider
transition: "200ms cubic-bezier(0.0, 0.0, 0.2, 1)",
},
} as const;
// --- 5.3 NAVIGATION / SIDEBAR ---
export const sidebar = {
// background: $base-alt (light: #FAFAFA, dark: #242424)
borderRight: "1px solid", // $divider
padding: "6px 0", // $space-size vertical only
row: {
minHeight: 36, // $medium-size
padding: "0 9px", // 0 $space-size * 1.5
// Navigation sidebar rows have special pill-shaped right side
borderRadius: "0 9999px 9999px 0", // 0 $circular 0 $circular
margin: "0 6px 0 0",
selected: {
// background: overlay-checked = 10% currentColor
color: "#1A73E8", // $primary
},
},
stackSidebar: {
rowPadding: "6px 9px",
rowRadius: 6, // $corner-radius / 2
rowSelectedWeight: 500,
},
} as const;
// --- 5.4 HEADERBAR / NAVIGATION BAR ---
export const headerbar = {
// Light mode: background = #FFFFFF, dark mode: background = #2C2C2C
// Topbar can be independently dark even in light mode
minHeight: 48, // $large-size
padding: "0 6px",
borderSpacing: 6,
/** Compact headerbar */
compact: {
minHeight: 40,
padding: "0 4px",
borderSpacing: 4,
},
buttonStyle: {
// headerbar buttons are flat by default
color: "inherit", // uses titlebar text colors
fontWeight: 500,
},
} as const;
// --- 5.5 INPUT FIELDS ---
export const input = {
minHeight: 36, // $medium-size
padding: "0 8px", // 0 ($space-size + 2px)
borderRadius: 12, // $corner-radius
borderSpacing: 6,
states: {
normal: {
background: "rgba(0, 0, 0, 0.05)", // currentColor 5%
outline: "0 solid transparent",
outlineOffset: "4px",
},
hover: {
background: "rgba(0, 0, 0, 0.08)",
outline: "2px solid rgba(0, 0, 0, 0.08)",
outlineOffset: "-2px",
},
focus: {
background: "rgba(0, 0, 0, 0.08)",
outline: "2px solid rgba(0, 0, 0, 0.26)", // $track color
outlineOffset: "-2px",
},
checked: {
// When actively typing (focus + content)
outline: "2px solid #1A73E8", // $primary
outlineOffset: "-2px",
},
disabled: {
background: "rgba(0, 0, 0, 0.05)",
color: "rgba(0, 0, 0, 0.38)",
},
error: {
outline: "2px solid #E53935", // $error
},
warning: {
outline: "2px solid #FFD600", // $warning
},
success: {
outline: "2px solid #0F9D58", // $success
},
},
/** Flat entry (no background) */
flat: {
minHeight: "auto",
padding: "2px",
borderRadius: 0,
background: "transparent",
},
} as const;
// --- 5.6 DROPDOWNS / COMBOBOX ---
// Orchis treats dropdowns as button + popover combinations
export const dropdown = {
// Inherits button styling for the trigger
trigger: {
minHeight: 24,
borderRadius: 12,
fontWeight: 500,
},
// Menu uses popover styling
menu: {
borderRadius: 11, // $menu-radius
boxShadow: "0 3px 3px -1px rgba(0,0,0,0.2), 0 6px 6px 0 rgba(0,0,0,0.14), 0 1px 11px 0 rgba(0,0,0,0.12)", // $shadow-z6
padding: 6,
},
menuItem: {
minHeight: 22, // $menuitem-size - $space-size
minWidth: 56, // $menuitem-size * 2
padding: "3px 9px", // $space-size/2 $space-size*1.5
borderRadius: 5, // $menuitem-radius
},
} as const;
// --- 5.7 TOGGLES / SWITCHES ---
export const toggle = {
// GTK switch is a sliding toggle
margin: "6px 0",
padding: "0 2px",
border: "5px solid transparent",
borderRadius: 9999, // $circular-radius
transition: "all 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
// Track
track: {
unchecked: "rgba(0, 0, 0, 0.26)", // $track (light mode)
checked: "rgba(26, 115, 232, 0.5)", // color-mix($primary 50%)
disabled: { opacity: 0.5 },
},
// Slider/thumb
slider: {
width: 20,
height: 20,
margin: "-3px -2px",
borderRadius: 9999,
backgroundColor: "#FFFFFF", // $surface (light)
boxShadow: "0 2px 2px -2px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.24), 0 1px 2px -1px rgba(0,0,0,0.17)", // $shadow-z1
},
// Checked slider
sliderChecked: {
backgroundColor: "#1A73E8", // $primary
},
// Hover ring
hoverRing: "0 0 0 10px rgba(0, 0, 0, 0.08)",
focusRing: "0 0 0 10px rgba(0, 0, 0, 0.08)",
} as const;
// --- 5.8 TABLES / TREE VIEWS ---
export const table = {
background: "#FFFFFF", // $base
headerBackground: "#FFFFFF", // $base
headerColor: "rgba(0, 0, 0, 0.6)", // $text-secondary
headerPadding: "2px 6px",
headerBorder: "1px solid rgba(0, 0, 0, 0.12)", // $divider
headerFontWeight: "normal",
row: {
padding: "2px",
color: "rgba(0, 0, 0, 0.6)",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
/** Row hover (activatable rows) */
rowHover: {
background: "rgba(0, 0, 0, 0.08)",
color: "rgba(0, 0, 0, 0.87)",
},
/** Row active (with ripple animation) */
rowActive: {
background: "rgba(0, 0, 0, 0.08)",
},
/** Row selected */
rowSelected: {
background: "rgba(0, 0, 0, 0.06)", // $overlay-selected
},
/** Progress bar in treeview */
progressBar: "6px solid #1A73E8", // $bar-size solid $primary
progressTrough: "6px solid rgba(0, 0, 0, 0.15)",
} as const;
// --- 5.9 TABS / NOTEBOOK ---
export const tabs = {
tab: {
minHeight: 24, // $small-size
minWidth: 24,
padding: "6px 12px", // $space-size $space-size*2
fontWeight: 500,
transition: "all 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
},
states: {
normal: {
borderColor: "transparent",
background: "transparent",
color: "rgba(0, 0, 0, 0.6)", // $text-secondary
},
hover: {
background: "#E2E2E2", // $solid-border (light)
color: "rgba(0, 0, 0, 0.87)",
},
disabled: {
color: "rgba(0, 0, 0, 0.26)",
},
checked: {
background: "#FFFFFF", // $base
borderColor: "#E2E2E2", // $solid-border
color: "rgba(0, 0, 0, 0.87)",
},
},
/** Notebook container */
notebook: {
background: "#FFFFFF", // $base
frameRadius: 12, // $corner-radius
},
/** Stack switcher (segmented control style) */
stackSwitcher: {
borderRadius: 9999, // $circular-radius
background: "rgba(0, 0, 0, 0.05)", // $overlay-normal
buttonRadius: 9999,
// Checked: background = $primary, color = white
},
} as const;
// --- 5.10 DIALOGS / MODALS ---
export const dialog = {
background: "#3C3C3C", // $surface (or $surface in current mode)
borderRadius: 18, // $window-radius
boxShadow: "0 8px 6px -5px rgba(0,0,0,0.2), 0 16px 16px 2px rgba(0,0,0,0.14), 0 6px 18px 5px rgba(0,0,0,0.12)", // $shadow-z16
borderSpacing: 10,
/** Message dialog specific */
messageDialog: {
actionPadding: 6,
actionButtonRadius: 9999, // $circular-radius (pill shaped)
actionButtonStyle: "flat", // flat buttons in dialogs
// Non-disabled buttons colored with $primary
// Destructive buttons colored with $destructive
},
/** About dialog */
aboutDialog: {
iconSize: 128,
scrolledWindowRadius: 12, // $window-radius - $space-size
},
} as const;
// --- 5.11 PROGRESS BARS ---
export const progressBar = {
/** Bar height: $bar-size = 6px */
height: 6,
borderRadius: 12, // $corner-radius
/** Trough (background track) */
trough: {
background: "rgba(0, 0, 0, 0.15)", // $track-disabled (light)
},
/** Progress (filled portion) */
progress: {
background: "#1A73E8", // $primary
},
/** Level bar */
levelBar: {
low: "#FFD600", // $warning
high: "#1A73E8", // $primary
full: "#0F9D58", // $success
empty: "rgba(0, 0, 0, 0.15)",
},
} as const;
// --- 5.12 TOOLTIPS ---
export const tooltipStyle = {
// Light mode: dark tooltip (darken(background(h), 3%) at 0.9 opacity)
// Dark mode: dark tooltip
background: "rgba(0, 0, 0, 0.9)", // $tooltip (light mode default)
color: "#FFFFFF",
borderRadius: 6, // $corner-radius / 2
padding: "6px 12px", // $space-size $space-size*2
boxShadow: "0 2px 2px -1px rgba(0,0,0,0.2), 0 4px 4px 0 rgba(0,0,0,0.14), 0 1px 6px 0 rgba(0,0,0,0.12)", // $shadow-z4
margin: "2px 6px 8px 6px",
} as const;
// --- 5.13 SCROLLBARS ---
export const scrollbar = {
background: "#FFFFFF", // $base
borderWidth: 1,
borderColor: "rgba(0, 0, 0, 0.12)",
slider: {
minWidth: 8,
minHeight: 8,
border: "4px solid transparent",
borderRadius: 9999,
backgroundClip: "padding-box",
normal: "rgba(0, 0, 0, 0.26)", // $text-secondary-disabled
hover: "rgba(0, 0, 0, 0.38)", // $text-disabled
active: "rgba(0, 0, 0, 0.6)", // $text-secondary
disabled: "rgba(0, 0, 0, 0.26)",
},
/** Overlay scrollbar (thin) */
overlay: {
sliderWidth: 4,
sliderHeight: 4,
margin: 3,
},
} as const;
// --- 5.14 POPOVERS / MENUS ---
export const popover = {
borderRadius: 11, // $menu-radius
boxShadow: "0 3px 3px -1px rgba(0,0,0,0.2), 0 6px 6px 0 rgba(0,0,0,0.14), 0 1px 11px 0 rgba(0,0,0,0.12)", // $shadow-z6
padding: 6, // $space-size
border: "none",
// Light: background = $surface = #FFFFFF
// Dark: background = $surface = #3C3C3C
/** Menu item styling */
menuItem: {
minHeight: 22, // $menuitem-size - $space-size
minWidth: 56,
padding: "3px 9px",
borderRadius: 5, // $menuitem-radius
transition: "background-color 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
},
/** Separator */
separator: {
margin: "3px 0", // $space-size/2 0
background: "rgba(0, 0, 0, 0.12)",
},
/** Backdrop state */
backdrop: {
boxShadow: "0 3px 2px -3px rgba(0,0,0,0.3), 0 2px 2px -1px rgba(0,0,0,0.24), 0 1px 3px 0 rgba(0,0,0,0.12)", // $shadow-z2
},
} as const;
// --- 5.15 CALENDAR ---
export const calendar = {
borderRadius: 12,
border: "1px solid rgba(0, 0, 0, 0.12)",
headerBorderBottom: "1px solid rgba(0, 0, 0, 0.12)",
headerPadding: 3,
gridMargin: 3,
dayPadding: 9, // $space-size * 1.5
todayHighlight: "#1A73E8", // primary color
selectedBorder: 9999, // circular for selected days
} as const;
// ============================================================================
// 6. SHADOW / ELEVATION SYSTEM
// ============================================================================
/**
* Based on Material Design elevation system.
* Values from material-components-web with Orchis adjustments.
*/
export const shadows = {
/** z1 - Subtle elevation (cards at rest, buttons) */
z1: "0 2px 2px -2px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.24), 0 1px 2px -1px rgba(0,0,0,0.17)",
/** z2 - Low elevation (search bars, app bars) */
z2: "0 3px 2px -3px rgba(0,0,0,0.3), 0 2px 2px -1px rgba(0,0,0,0.24), 0 1px 3px 0 rgba(0,0,0,0.12)",
/** z3 - Medium elevation (cards on hover, FAB at rest) */
z3: "0 3px 3px -2px rgba(0,0,0,0.2), 0 3px 3px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12)",
/** z4 - Medium-high elevation (menus, tooltips, backdrops) */
z4: "0 2px 2px -1px rgba(0,0,0,0.2), 0 4px 4px 0 rgba(0,0,0,0.14), 0 1px 6px 0 rgba(0,0,0,0.12)",
/** z6 - High elevation (popovers, dropdown menus) */
z6: "0 3px 3px -1px rgba(0,0,0,0.2), 0 6px 6px 0 rgba(0,0,0,0.14), 0 1px 11px 0 rgba(0,0,0,0.12)",
/** z8 - Higher elevation (bottom sheets, side sheets) */
z8: "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 8px 1px rgba(0,0,0,0.14), 0 3px 9px 2px rgba(0,0,0,0.12)",
/** z12 - Very high elevation (FAB pressed) */
z12: "0 7px 7px -4px rgba(0,0,0,0.2), 0 12px 12px 2px rgba(0,0,0,0.14), 0 5px 13px 4px rgba(0,0,0,0.12)",
/** z16 - Window elevation (dialogs, modal windows) */
z16: "0 8px 6px -5px rgba(0,0,0,0.2), 0 16px 16px 2px rgba(0,0,0,0.14), 0 6px 18px 5px rgba(0,0,0,0.12)",
/** z24 - Maximum elevation (full-screen dialogs) */
z24: "0 11px 11px -7px rgba(0,0,0,0.2), 0 24px 24px 3px rgba(0,0,0,0.14), 0 9px 28px 8px rgba(0,0,0,0.12)",
/** Text shadow */
text: "0 1px 1px rgba(0,0,0,0.2), 0 1px 2px rgba(0,0,0,0.14), 0 1px 3px rgba(0,0,0,0.12)",
/** Gnome Shell simplified shadows */
shell: {
z1: "0 1px 2px rgba(0,0,0,0.25)",
z2: "0 3px 3px rgba(0,0,0,0.32)",
z3: "0 3px 8px rgba(0,0,0,0.2)",
z4: "0 6px 10px rgba(0,0,0,0.32)",
z5: "0 10px 15px rgba(0,0,0,0.45)",
z6: "0 12px 20px rgba(0,0,0,0.6)",
},
} as const;
// ============================================================================
// 7. ANIMATION / TRANSITION PATTERNS
// ============================================================================
export const animation = {
/** Durations */
duration: {
shortest: "75ms", // $duration - micro interactions
shorter: "100ms", // $shorter-duration
short: "150ms", // $longer-duration
rippleFadeIn: "225ms", // $ripple-fade-in-duration
rippleFadeOut: "300ms", // $ripple-fade-out-duration
rippleOpacity: "1200ms", // $ripple-fade-out-opacity-duration
},
/** Timing functions (Material Design standard) */
easing: {
/** Standard easing - for most transitions */
standard: "cubic-bezier(0.4, 0.0, 0.2, 1)",
/** Decelerate - for elements entering the screen */
decelerate: "cubic-bezier(0.0, 0.0, 0.2, 1)",
/** Accelerate - for elements leaving the screen */
accelerate: "cubic-bezier(0.4, 0.0, 1, 1)",
/** Sharp - for elements that may return to screen */
sharp: "cubic-bezier(0.4, 0.0, 0.6, 1)",
},
/** Predefined transition shorthands */
transition: {
default: "all 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
shadow: "box-shadow 75ms cubic-bezier(0.0, 0.0, 0.2, 1)",
},
/** Ripple effect (Material Design signature interaction) */
ripple: {
/**
* CSS keyframes for ripple effect:
* @keyframes ripple {
* from { background-image: radial-gradient(circle farthest-corner at center, rgba(0,0,0,0.10) 30%, transparent 0%); }
* to { background-image: radial-gradient(circle farthest-corner at center, rgba(0,0,0,0.10) 100%, transparent 0%); }
* }
*/
duration: "225ms",
easing: "cubic-bezier(0.0, 0.0, 0.2, 1)",
// The ripple circle starts at 30% and expands to 100%
overlayColor: "rgba(0, 0, 0, 0.10)", // $overlay-checked
},
/** Spinner animation: rotate 1 full turn, linear, infinite */
spinner: {
animation: "spin 1s linear infinite",
},
/** Needs-attention pulse */
needsAttention: {
// Radial gradient pulse from 0% to 95% with primary color
},
} as const;
// ============================================================================
// 8. ICON STYLE PREFERENCES
// ============================================================================
export const icons = {
/** Orchis uses symbolic (monochrome outline) icons in the GNOME style */
style: "symbolic", // Monochrome, outline-based icons
/** Base icon size */
baseSize: 16, // $base-icon-size
mediumSize: 24, // $base-icon-size * 1.5
largeSize: 32, // $base-icon-size * 2
/** Icon color follows text color (currentColor) */
color: "currentColor",
/** Check/radio icon size */
checkRadioSize: 24,
/** The Orchis icon set uses:
* - Papirus icons (recommended companion)
* - Symbolic SVG icons for UI elements
* - Material Design inspired icon shapes
* - Rounded/soft stroke style
*/
recommendation: "Use Material Symbols (Rounded variant) or Phosphor Icons for web",
} as const;
// ============================================================================
// CSS CUSTOM PROPERTIES (ready for :root injection)
// ============================================================================
/**
* Generate CSS custom properties for a Next.js application.
* Usage: inject these into your global CSS or a ThemeProvider.
*/
export function generateCSSVariables(mode: "light" | "dark" = "light", accent: keyof typeof accentColors = "default"): string {
const theme = mode === "light" ? lightTheme : darkTheme;
const accentColor = mode === "light" ? accentColors[accent].dark : accentColors[accent].light;
return `
:root {
/* ---- Primary / Accent ---- */
--orchis-primary: ${accentColor};
--orchis-on-primary: ${theme.onPrimary.primary};
--orchis-on-primary-secondary: ${theme.onPrimary.secondary};
/* ---- Backgrounds ---- */
--orchis-background: ${theme.background};
--orchis-surface: ${theme.surface};
--orchis-base: ${theme.base};
--orchis-base-alt: ${theme.baseAlt};
/* ---- Text ---- */
--orchis-text: ${theme.text.primary};
--orchis-text-secondary: ${theme.text.secondary};
--orchis-text-disabled: ${theme.text.disabled};
/* ---- Semantic ---- */
--orchis-error: ${theme.error};
--orchis-warning: ${theme.warning};
--orchis-success: ${theme.success};
--orchis-link: ${accentColor};
--orchis-link-visited: ${theme.linkVisited};
--orchis-destructive: ${theme.error};
/* ---- Borders ---- */
--orchis-border: ${theme.border};
--orchis-border-solid: ${theme.solidBorder};
--orchis-divider: ${theme.divider};
/* ---- Overlay States ---- */
--orchis-overlay-hover: ${theme.overlay.hover};
--orchis-overlay-focus: ${theme.overlay.focus};
--orchis-overlay-active: ${theme.overlay.active};
--orchis-overlay-checked: ${theme.overlay.checked};
--orchis-overlay-selected: ${theme.overlay.selected};
/* ---- Track / Fill ---- */
--orchis-track: ${theme.track};
--orchis-track-disabled: ${theme.trackDisabled};
--orchis-fill: ${theme.fill};
--orchis-secondary-fill: ${theme.secondaryFill};
/* ---- Tooltip ---- */
--orchis-tooltip-bg: ${theme.tooltip};
/* ---- Titlebar ---- */
--orchis-titlebar: ${theme.titlebar};
--orchis-titlebar-backdrop: ${theme.titlebarBackdrop};
--orchis-sidebar: ${theme.sidebar};
/* ---- Window Buttons ---- */
--orchis-btn-close: ${theme.buttonClose};
--orchis-btn-maximize: ${theme.buttonMaximize};
--orchis-btn-minimize: ${theme.buttonMinimize};
/* ---- Typography ---- */
--orchis-font-family: ${typography.fontFamily};
--orchis-font-size: ${typography.rootFontSize};
--orchis-font-weight-button: ${typography.buttonWeight};
/* ---- Spacing ---- */
--orchis-space: ${spacing.space}px;
--orchis-space-xs: ${spacing.xs}px;
--orchis-space-sm: ${spacing.sm}px;
--orchis-space-md: ${spacing.md}px;
--orchis-space-lg: ${spacing.lg}px;
--orchis-space-xl: ${spacing.xl}px;
--orchis-space-xxl: ${spacing.xxl}px;
/* ---- Border Radius ---- */
--orchis-radius: ${borderRadius.default}px;
--orchis-radius-window: ${borderRadius.window}px;
--orchis-radius-corner: ${borderRadius.corner}px;
--orchis-radius-menu: ${borderRadius.menu}px;
--orchis-radius-card: ${borderRadius.card}px;
--orchis-radius-tooltip: ${borderRadius.tooltip}px;
--orchis-radius-menuitem: ${borderRadius.menuItem}px;
--orchis-radius-circular: ${borderRadius.circular}px;
/* ---- Shadows ---- */
--orchis-shadow-z1: ${shadows.z1};
--orchis-shadow-z2: ${shadows.z2};
--orchis-shadow-z3: ${shadows.z3};
--orchis-shadow-z4: ${shadows.z4};
--orchis-shadow-z6: ${shadows.z6};
--orchis-shadow-z8: ${shadows.z8};
--orchis-shadow-z12: ${shadows.z12};
--orchis-shadow-z16: ${shadows.z16};
--orchis-shadow-z24: ${shadows.z24};
/* ---- Transitions ---- */
--orchis-duration: ${animation.duration.shortest};
--orchis-duration-short: ${animation.duration.short};
--orchis-duration-ripple: ${animation.duration.rippleFadeIn};
--orchis-ease: ${animation.easing.standard};
--orchis-ease-out: ${animation.easing.decelerate};
--orchis-ease-in: ${animation.easing.accelerate};
--orchis-transition: ${animation.transition.default};
/* ---- Component Sizes ---- */
--orchis-size-small: ${spacing.small}px;
--orchis-size-medium: ${spacing.medium}px;
--orchis-size-large: ${spacing.large}px;
--orchis-icon-size: ${icons.baseSize}px;
--orchis-icon-size-md: ${icons.mediumSize}px;
--orchis-icon-size-lg: ${icons.largeSize}px;
--orchis-bar-size: ${spacing.bar}px;
}
`.trim();
}