@reinvented/design 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/package.json +7 -3
- package/src/eslint/index.js +192 -0
- package/src/eslint/recommended.js +64 -0
- package/src/styles/index.css +6 -0
- package/src/styles/tokens.css +8 -12
package/README.md
CHANGED
|
@@ -21,6 +21,110 @@ import { Button, Card, Input, Badge, Dialog } from '@reinvented/design'
|
|
|
21
21
|
- **Styling**: [Tailwind CSS](https://tailwindcss.com/) with design tokens
|
|
22
22
|
- **Icons**: [Lucide Vue Next](https://lucide.dev/) (no emojis, ever)
|
|
23
23
|
- **Typography**: Inter (via Google Fonts)
|
|
24
|
+
- **Linting**: Built-in ESLint plugin enforcing design rules
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Consumer Setup
|
|
29
|
+
|
|
30
|
+
### 1. Install dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# The design system (includes all component + ESLint deps)
|
|
34
|
+
npm install @reinvented/design
|
|
35
|
+
|
|
36
|
+
# Required dev dependencies for Tailwind CSS processing
|
|
37
|
+
npm install -D tailwindcss@^3.4 postcss@^8.4 autoprefixer@^10.4 tailwindcss-animate@^1.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Tailwind CSS config
|
|
41
|
+
|
|
42
|
+
Create `tailwind.config.js` in your app root. **Extend** the DS config — this gives you all design tokens, theme colors, animations, and font family:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import baseConfig from '@reinvented/design/tailwind.config.js'
|
|
46
|
+
|
|
47
|
+
export default {
|
|
48
|
+
...baseConfig,
|
|
49
|
+
content: [
|
|
50
|
+
'./index.html',
|
|
51
|
+
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
|
52
|
+
// Include DS source so Tailwind picks up classes used inside components
|
|
53
|
+
'./node_modules/@reinvented/design/src/**/*.{vue,ts}',
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. PostCSS config
|
|
59
|
+
|
|
60
|
+
Create `postcss.config.js`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
export default {
|
|
64
|
+
plugins: {
|
|
65
|
+
tailwindcss: {},
|
|
66
|
+
autoprefixer: {},
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. CSS entry point
|
|
72
|
+
|
|
73
|
+
Create `src/assets/main.css` (or similar) and import the DS styles. This loads the design tokens (CSS variables) and Tailwind directives:
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
@import '@reinvented/design/styles';
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then import it in your app entry (`main.js` or `main.ts`):
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import './assets/main.css'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 5. Dark mode
|
|
86
|
+
|
|
87
|
+
The DS uses the `class` strategy for dark mode. Add `class="dark"` to `<html>` for dark theme:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<html lang="en" class="dark">
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
All color tokens automatically switch between light and dark values.
|
|
94
|
+
|
|
95
|
+
### 6. ESLint (recommended)
|
|
96
|
+
|
|
97
|
+
The DS ships an ESLint plugin that enforces design system rules. Create `eslint.config.js`:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
import recommended from '@reinvented/design/eslint/recommended'
|
|
101
|
+
|
|
102
|
+
export default [
|
|
103
|
+
...recommended,
|
|
104
|
+
// Your custom overrides (optional)
|
|
105
|
+
]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This enables:
|
|
109
|
+
|
|
110
|
+
| Rule | Severity | What it catches |
|
|
111
|
+
|------|----------|-----------------|
|
|
112
|
+
| `no-raw-button` | error | Raw `<button>` → use `<Button>` |
|
|
113
|
+
| `no-raw-input` | error | Raw `<input>` → use `<Input>` |
|
|
114
|
+
| `no-raw-select` | error | Raw `<select>` → use `<Select>` |
|
|
115
|
+
| `no-raw-textarea` | error | Raw `<textarea>` → use `<Textarea>` |
|
|
116
|
+
| `no-browser-dialogs` | error | `alert()`, `confirm()`, `prompt()` → use Dialog/Toast |
|
|
117
|
+
| `no-emoji` | error | Emoji characters → use Lucide icons |
|
|
118
|
+
| `no-hardcoded-colors` | warn | Inline hex/rgb colors → use token variables |
|
|
119
|
+
| `no-arbitrary-tailwind` | warn | `text-[#333]` → use token classes |
|
|
120
|
+
|
|
121
|
+
Install ESLint if not already present:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install -D eslint
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
24
128
|
|
|
25
129
|
## Structure
|
|
26
130
|
|
|
@@ -31,6 +135,7 @@ src/
|
|
|
31
135
|
layouts/ Page-level layout components with slots
|
|
32
136
|
lib/ Utilities (cn, etc.)
|
|
33
137
|
styles/ Tokens, CSS variables
|
|
138
|
+
eslint/ ESLint plugin + recommended config
|
|
34
139
|
docs/
|
|
35
140
|
rules.md Hard design rules
|
|
36
141
|
conventions.md Opinionated UX decisions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reinvented/design",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"./src/styles/*": "./src/styles/*",
|
|
11
11
|
"./src/*": "./src/*",
|
|
12
12
|
"./tailwind.config.js": "./tailwind.config.js",
|
|
13
|
-
"./patterns/*": "./src/patterns/*"
|
|
13
|
+
"./patterns/*": "./src/patterns/*",
|
|
14
|
+
"./eslint": "./src/eslint/index.js",
|
|
15
|
+
"./eslint/recommended": "./src/eslint/recommended.js"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
18
|
"src/",
|
|
@@ -40,7 +42,9 @@
|
|
|
40
42
|
"zod": "^3.23.0",
|
|
41
43
|
"vue-sonner": "^1.2.0",
|
|
42
44
|
"v-calendar": "^3.1.0",
|
|
43
|
-
"@tanstack/vue-table": "^8.21.0"
|
|
45
|
+
"@tanstack/vue-table": "^8.21.0",
|
|
46
|
+
"eslint-plugin-vue": "^9.28.0",
|
|
47
|
+
"vue-eslint-parser": "^9.4.0"
|
|
44
48
|
},
|
|
45
49
|
"devDependencies": {
|
|
46
50
|
"vue": "^3.5.0",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @reinvented/design ESLint Plugin
|
|
3
|
+
*
|
|
4
|
+
* Enforces the Reinvented design system rules across all consumer apps.
|
|
5
|
+
* See docs/rules.md for the full list.
|
|
6
|
+
*
|
|
7
|
+
* Usage (flat config):
|
|
8
|
+
* import recommended from '@reinvented/design/eslint/recommended'
|
|
9
|
+
* export default [...recommended]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** Matches emoji characters (Emoji_Presentation + common ranges) */
|
|
15
|
+
const EMOJI_REGEX = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}\u{231A}-\u{231B}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2614}-\u{2615}\u{2648}-\u{2653}\u{267F}\u{2693}\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}]/u
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a rule that bans a specific raw HTML element in Vue templates.
|
|
19
|
+
* @param {string} element - The raw element name to ban (e.g. 'button')
|
|
20
|
+
* @param {string} component - The DS component to use instead (e.g. 'Button')
|
|
21
|
+
*/
|
|
22
|
+
function createNoRawElementRule(element, component) {
|
|
23
|
+
return {
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'suggestion',
|
|
26
|
+
docs: {
|
|
27
|
+
description: `Disallow raw <${element}> — use <${component}> from @reinvented/design`,
|
|
28
|
+
recommended: true,
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
noRawElement: `Use <${component}> from @reinvented/design instead of raw <${element}>. See docs/rules.md.`,
|
|
32
|
+
},
|
|
33
|
+
schema: [],
|
|
34
|
+
},
|
|
35
|
+
create(context) {
|
|
36
|
+
// Works with eslint-plugin-vue's template AST
|
|
37
|
+
return context.parserServices?.defineTemplateBodyVisitor
|
|
38
|
+
? context.parserServices.defineTemplateBodyVisitor({
|
|
39
|
+
VElement(node) {
|
|
40
|
+
if (node.rawName === element) {
|
|
41
|
+
context.report({ node, messageId: 'noRawElement' })
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
: {}
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Rules ──────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const rules = {
|
|
53
|
+
// Rule 1-4: No raw form elements
|
|
54
|
+
'no-raw-button': createNoRawElementRule('button', 'Button'),
|
|
55
|
+
'no-raw-input': createNoRawElementRule('input', 'Input'),
|
|
56
|
+
'no-raw-select': createNoRawElementRule('select', 'Select'),
|
|
57
|
+
'no-raw-textarea': createNoRawElementRule('textarea', 'Textarea'),
|
|
58
|
+
|
|
59
|
+
// Rule 5: No browser dialog APIs
|
|
60
|
+
'no-browser-dialogs': {
|
|
61
|
+
meta: {
|
|
62
|
+
type: 'problem',
|
|
63
|
+
docs: {
|
|
64
|
+
description: 'Disallow alert(), confirm(), prompt() — use Dialog/AlertDialog/Toast from @reinvented/design',
|
|
65
|
+
recommended: true,
|
|
66
|
+
},
|
|
67
|
+
messages: {
|
|
68
|
+
noBrowserDialog: 'Use Dialog, AlertDialog, or Toast from @reinvented/design instead of {{ name }}(). See docs/rules.md.',
|
|
69
|
+
},
|
|
70
|
+
schema: [],
|
|
71
|
+
},
|
|
72
|
+
create(context) {
|
|
73
|
+
const banned = new Set(['alert', 'confirm', 'prompt'])
|
|
74
|
+
return {
|
|
75
|
+
CallExpression(node) {
|
|
76
|
+
// Direct calls: alert(), confirm(), prompt()
|
|
77
|
+
if (node.callee.type === 'Identifier' && banned.has(node.callee.name)) {
|
|
78
|
+
context.report({ node, messageId: 'noBrowserDialog', data: { name: node.callee.name } })
|
|
79
|
+
}
|
|
80
|
+
// window.alert(), window.confirm(), window.prompt()
|
|
81
|
+
if (
|
|
82
|
+
node.callee.type === 'MemberExpression' &&
|
|
83
|
+
node.callee.object.type === 'Identifier' &&
|
|
84
|
+
node.callee.object.name === 'window' &&
|
|
85
|
+
node.callee.property.type === 'Identifier' &&
|
|
86
|
+
banned.has(node.callee.property.name)
|
|
87
|
+
) {
|
|
88
|
+
context.report({ node, messageId: 'noBrowserDialog', data: { name: node.callee.property.name } })
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Rule 10: No emojis in templates
|
|
96
|
+
'no-emoji': {
|
|
97
|
+
meta: {
|
|
98
|
+
type: 'suggestion',
|
|
99
|
+
docs: {
|
|
100
|
+
description: 'Disallow emoji characters in Vue templates — use Lucide icons from lucide-vue-next',
|
|
101
|
+
recommended: true,
|
|
102
|
+
},
|
|
103
|
+
messages: {
|
|
104
|
+
noEmoji: 'Use a Lucide icon from lucide-vue-next instead of emoji characters. See docs/rules.md.',
|
|
105
|
+
},
|
|
106
|
+
schema: [],
|
|
107
|
+
},
|
|
108
|
+
create(context) {
|
|
109
|
+
return context.parserServices?.defineTemplateBodyVisitor
|
|
110
|
+
? context.parserServices.defineTemplateBodyVisitor({
|
|
111
|
+
'VText'(node) {
|
|
112
|
+
if (EMOJI_REGEX.test(node.value)) {
|
|
113
|
+
context.report({ node, messageId: 'noEmoji' })
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
: {}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Rule 6: No hardcoded colors
|
|
122
|
+
'no-hardcoded-colors': {
|
|
123
|
+
meta: {
|
|
124
|
+
type: 'suggestion',
|
|
125
|
+
docs: {
|
|
126
|
+
description: 'Disallow hardcoded color values in inline styles — use CSS variables via Tailwind tokens',
|
|
127
|
+
recommended: true,
|
|
128
|
+
},
|
|
129
|
+
messages: {
|
|
130
|
+
noHardcodedColor: 'Use design token CSS variables (e.g. bg-background, text-foreground) instead of hardcoded colors. See docs/rules.md.',
|
|
131
|
+
},
|
|
132
|
+
schema: [],
|
|
133
|
+
},
|
|
134
|
+
create(context) {
|
|
135
|
+
const COLOR_REGEX = /#[0-9a-fA-F]{3,8}\b|rgba?\s*\(|hsla?\s*\(/
|
|
136
|
+
return context.parserServices?.defineTemplateBodyVisitor
|
|
137
|
+
? context.parserServices.defineTemplateBodyVisitor({
|
|
138
|
+
'VAttribute[key.name="style"]'(node) {
|
|
139
|
+
const value = node.value?.value
|
|
140
|
+
if (value && COLOR_REGEX.test(value)) {
|
|
141
|
+
context.report({ node, messageId: 'noHardcodedColor' })
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
: {}
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Rule 7: No arbitrary Tailwind values
|
|
150
|
+
'no-arbitrary-tailwind': {
|
|
151
|
+
meta: {
|
|
152
|
+
type: 'suggestion',
|
|
153
|
+
docs: {
|
|
154
|
+
description: 'Disallow arbitrary Tailwind values like text-[#333] or p-[13px] — use token-based utilities',
|
|
155
|
+
recommended: true,
|
|
156
|
+
},
|
|
157
|
+
messages: {
|
|
158
|
+
noArbitrary: 'Use token-based Tailwind utilities instead of arbitrary values (e.g. {{ value }}). See docs/rules.md.',
|
|
159
|
+
},
|
|
160
|
+
schema: [],
|
|
161
|
+
},
|
|
162
|
+
create(context) {
|
|
163
|
+
// Matches Tailwind arbitrary value syntax: word-[value]
|
|
164
|
+
const ARBITRARY_REGEX = /\b(?:text|bg|border|ring|shadow|p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|w|h|gap|space|rounded|top|right|bottom|left|inset|min-w|min-h|max-w|max-h)-\[.+?\]/
|
|
165
|
+
return context.parserServices?.defineTemplateBodyVisitor
|
|
166
|
+
? context.parserServices.defineTemplateBodyVisitor({
|
|
167
|
+
'VAttribute[key.name="class"]'(node) {
|
|
168
|
+
const value = node.value?.value
|
|
169
|
+
if (value) {
|
|
170
|
+
const match = value.match(ARBITRARY_REGEX)
|
|
171
|
+
if (match) {
|
|
172
|
+
context.report({ node, messageId: 'noArbitrary', data: { value: match[0] } })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
: {}
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Plugin Export ──────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
const plugin = {
|
|
185
|
+
meta: {
|
|
186
|
+
name: '@reinvented/design',
|
|
187
|
+
version: '0.3.0',
|
|
188
|
+
},
|
|
189
|
+
rules,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default plugin
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @reinvented/design — Recommended ESLint Flat Config
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { recommended } from '@reinvented/design/eslint/recommended'
|
|
6
|
+
* export default [...recommended]
|
|
7
|
+
*
|
|
8
|
+
* Or combine with your own config:
|
|
9
|
+
* export default [...recommended, { rules: { /* overrides */ } }]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import vuePlugin from 'eslint-plugin-vue'
|
|
13
|
+
import vueParser from 'vue-eslint-parser'
|
|
14
|
+
import designPlugin from './index.js'
|
|
15
|
+
|
|
16
|
+
const PREFIX = 'reinvented-design'
|
|
17
|
+
|
|
18
|
+
export const recommended = [
|
|
19
|
+
// ── Base Vue support (essential rules) ──────────────────────────
|
|
20
|
+
...vuePlugin.configs['flat/essential'],
|
|
21
|
+
|
|
22
|
+
// ── Design system enforcement ───────────────────────────────────
|
|
23
|
+
{
|
|
24
|
+
name: '@reinvented/design/recommended',
|
|
25
|
+
files: ['**/*.vue'],
|
|
26
|
+
languageOptions: {
|
|
27
|
+
parser: vueParser,
|
|
28
|
+
},
|
|
29
|
+
plugins: {
|
|
30
|
+
[PREFIX]: designPlugin,
|
|
31
|
+
},
|
|
32
|
+
rules: {
|
|
33
|
+
// Component usage — error on raw HTML elements
|
|
34
|
+
[`${PREFIX}/no-raw-button`]: 'error',
|
|
35
|
+
[`${PREFIX}/no-raw-input`]: 'error',
|
|
36
|
+
[`${PREFIX}/no-raw-select`]: 'error',
|
|
37
|
+
[`${PREFIX}/no-raw-textarea`]: 'error',
|
|
38
|
+
|
|
39
|
+
// No browser dialogs
|
|
40
|
+
[`${PREFIX}/no-browser-dialogs`]: 'error',
|
|
41
|
+
|
|
42
|
+
// No emojis — use Lucide icons
|
|
43
|
+
[`${PREFIX}/no-emoji`]: 'error',
|
|
44
|
+
|
|
45
|
+
// Styling hygiene
|
|
46
|
+
[`${PREFIX}/no-hardcoded-colors`]: 'warn',
|
|
47
|
+
[`${PREFIX}/no-arbitrary-tailwind`]: 'warn',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// ── JS/TS files: ban browser dialogs everywhere ─────────────────
|
|
52
|
+
{
|
|
53
|
+
name: '@reinvented/design/recommended-scripts',
|
|
54
|
+
files: ['**/*.{js,ts,jsx,tsx,vue}'],
|
|
55
|
+
plugins: {
|
|
56
|
+
[PREFIX]: designPlugin,
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
[`${PREFIX}/no-browser-dialogs`]: 'error',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export default recommended
|
package/src/styles/index.css
CHANGED
package/src/styles/tokens.css
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
--card-foreground: 240 10% 3.9%;
|
|
16
16
|
--popover: 0 0% 100%;
|
|
17
17
|
--popover-foreground: 240 10% 3.9%;
|
|
18
|
-
--primary:
|
|
18
|
+
--primary: 160 84% 39%; /* Emerald — Reinvented brand */
|
|
19
19
|
--primary-foreground: 0 0% 100%;
|
|
20
20
|
--secondary: 240 4.8% 95.9%;
|
|
21
21
|
--secondary-foreground: 240 5.9% 10%;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
--destructive-foreground: 0 0% 98%;
|
|
28
28
|
--border: 240 5.9% 90%;
|
|
29
29
|
--input: 240 5.9% 90%;
|
|
30
|
-
--ring:
|
|
30
|
+
--ring: 160 84% 39%;
|
|
31
31
|
--radius: 0.625rem;
|
|
32
32
|
|
|
33
33
|
/* ── Success / Warning / Info ──────────────────────────────── */
|
|
@@ -86,12 +86,12 @@
|
|
|
86
86
|
/* ── Sidebar (for ShadCN sidebar component) ────────────────── */
|
|
87
87
|
--sidebar-background: 0 0% 98%;
|
|
88
88
|
--sidebar-foreground: 240 5.3% 26.1%;
|
|
89
|
-
--sidebar-primary:
|
|
89
|
+
--sidebar-primary: 160 84% 39%;
|
|
90
90
|
--sidebar-primary-foreground: 0 0% 100%;
|
|
91
91
|
--sidebar-accent: 240 4.8% 95.9%;
|
|
92
92
|
--sidebar-accent-foreground: 240 5.9% 10%;
|
|
93
93
|
--sidebar-border: 220 13% 91%;
|
|
94
|
-
--sidebar-ring:
|
|
94
|
+
--sidebar-ring: 160 84% 39%;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/* ── Dark Mode ───────────────────────────────────────────────── */
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
--card-foreground: 0 0% 98%;
|
|
103
103
|
--popover: 240 10% 3.9%;
|
|
104
104
|
--popover-foreground: 0 0% 98%;
|
|
105
|
-
--primary:
|
|
105
|
+
--primary: 160 84% 45%;
|
|
106
106
|
--primary-foreground: 0 0% 100%;
|
|
107
107
|
--secondary: 240 3.7% 15.9%;
|
|
108
108
|
--secondary-foreground: 0 0% 98%;
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
--destructive-foreground: 0 0% 98%;
|
|
115
115
|
--border: 240 3.7% 15.9%;
|
|
116
116
|
--input: 240 3.7% 15.9%;
|
|
117
|
-
--ring:
|
|
117
|
+
--ring: 160 84% 45%;
|
|
118
118
|
|
|
119
119
|
--success: 142 76% 45%;
|
|
120
120
|
--warning: 38 92% 55%;
|
|
@@ -122,19 +122,15 @@
|
|
|
122
122
|
|
|
123
123
|
--sidebar-background: 240 5.9% 10%;
|
|
124
124
|
--sidebar-foreground: 240 4.8% 95.9%;
|
|
125
|
-
--sidebar-primary:
|
|
125
|
+
--sidebar-primary: 160 84% 45%;
|
|
126
126
|
--sidebar-primary-foreground: 0 0% 100%;
|
|
127
127
|
--sidebar-accent: 240 3.7% 15.9%;
|
|
128
128
|
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
129
129
|
--sidebar-border: 240 3.7% 15.9%;
|
|
130
|
-
--sidebar-ring:
|
|
130
|
+
--sidebar-ring: 160 84% 45%;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/* ── Base Styles ─────────────────────────────────────────────── */
|
|
134
|
-
* {
|
|
135
|
-
border-color: hsl(var(--border));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
134
|
body {
|
|
139
135
|
font-family: var(--font-sans);
|
|
140
136
|
background-color: hsl(var(--background));
|