diff --git a/app/app.vue b/app/app.vue
index 7f4f1244d5..b8582a1719 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -63,427 +63,3 @@ if (import.meta.client) {
-
-
diff --git a/app/assets/main.css b/app/assets/main.css
new file mode 100644
index 0000000000..54f6683817
--- /dev/null
+++ b/app/assets/main.css
@@ -0,0 +1,470 @@
+/* Base reset and defaults */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+:root[data-theme='dark'] {
+ /* background colors */
+ --bg: oklch(0.145 0 0);
+ --bg-subtle: oklch(0.178 0 0);
+ --bg-muted: oklch(0.218 0 0);
+ --bg-elevated: oklch(0.252 0 0);
+
+ /* text colors */
+ --fg: oklch(0.985 0 0);
+ --fg-muted: oklch(0.709 0 0);
+ --fg-subtle: oklch(0.633 0 0);
+
+ /* border, seperator colors */
+ --border: oklch(0.269 0 0);
+ --border-subtle: oklch(0.239 0 0);
+ --border-hover: oklch(0.371 0 0);
+
+ --accent: var(--accent-color, oklch(1 0 0));
+ --accent-muted: var(--accent-color, oklch(0.922 0 0));
+
+ --syntax-fn: oklch(0.727 0.137 299.149);
+ --syntax-str: oklch(0.829 0.088 252.458);
+ --syntax-kw: oklch(0.721 0.162 15.494);
+ --syntax-comment: oklch(0.551 0.019 250.976);
+}
+
+:root[data-theme='light'] {
+ --bg: oklch(1 0 0);
+ --bg-subtle: oklch(0.979 0.001 286.375);
+ --bg-muted: oklch(0.979 0.001 286.375 / 90%);
+ --bg-elevated: oklch(0.955 0 0);
+
+ --fg: oklch(0.145 0 0);
+ --fg-muted: oklch(0.439 0 0);
+ --fg-subtle: oklch(0.52 0 0);
+
+ --border: oklch(0.8514 0 0);
+ --border-subtle: oklch(0.922 0 0);
+ --border-hover: oklch(0.715 0 0);
+
+ --accent: var(--accent-color, oklch(0.145 0 0));
+ --accent-muted: var(--accent-color, oklch(0.205 0 0));
+
+ --syntax-fn: oklch(0.502 0.188 294.988);
+ --syntax-str: oklch(0.54 0.191 257.481);
+ --syntax-kw: oklch(0.588 0.193 20.469);
+ --syntax-comment: oklch(0.551 0.019 250.976);
+}
+
+html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+}
+
+/*
+ * Enable CSS scroll-state container queries for the document
+ * This allows the footer to query the scroll state using pure CSS
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@container#scroll-state_container_descriptors
+ */
+@supports (container-type: scroll-state) {
+ html {
+ container-type: scroll-state;
+ }
+}
+
+body {
+ margin: 0;
+ background-color: var(--bg);
+ color: var(--fg);
+ line-height: 1.6;
+ padding-bottom: var(--footer-height, 0);
+}
+
+/* Default link styling for accessibility on dark background */
+a {
+ color: var(--fg);
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ text-decoration-color: var(--fg-subtle);
+ transition:
+ color 0.2s ease,
+ text-decoration-color 0.2s ease;
+}
+
+a:hover {
+ color: var(--accent);
+ text-decoration-color: var(--accent);
+}
+
+a:focus-visible {
+ outline: 2px solid var(--border);
+ outline-offset: 2px;
+ border-radius: 2px;
+}
+
+/* Reset dd margin (browser default is margin-left: 40px) */
+dd {
+ margin: 0;
+}
+
+/* Reset button styles */
+button {
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font: inherit;
+ padding: 0;
+}
+
+/* Selection */
+::selection {
+ background-color: var(--fg-muted);
+ color: var(--bg-subtle);
+}
+
+/* Scrollbar styling */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: #404040;
+}
+
+/* Skip link */
+.skip-link {
+ position: absolute;
+ top: -100%;
+ left: 0;
+ padding: 0.5rem 1rem;
+ background: var(--fg);
+ color: var(--bg);
+ font-size: 0.875rem;
+ z-index: 100;
+ transition: top 0.2s ease;
+}
+
+.skip-link:focus {
+ top: 0;
+}
+
+/* README prose styling */
+.readme-content {
+ color: var(--fg-muted);
+ line-height: 1.75;
+ /* Prevent horizontal overflow on mobile */
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+ /* Contain all children */
+ overflow: hidden;
+ min-width: 0;
+}
+
+/* README headings - styled by visual level (data-level), not semantic level */
+.readme-content h3,
+.readme-content h4,
+.readme-content h5,
+.readme-content h6 {
+ color: var(--fg);
+ @apply font-mono;
+ font-weight: 500;
+ margin-top: 2rem;
+ margin-bottom: 1rem;
+ line-height: 1.3;
+
+ a {
+ text-decoration: none;
+ }
+}
+
+/* Visual styling based on original README heading level */
+.readme-content [data-level='1'] {
+ font-size: 1.5rem;
+}
+.readme-content [data-level='2'] {
+ font-size: 1.25rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid var(--border);
+}
+.readme-content [data-level='3'] {
+ font-size: 1.125rem;
+}
+.readme-content [data-level='4'] {
+ font-size: 1rem;
+}
+.readme-content [data-level='5'] {
+ font-size: 0.925rem;
+}
+.readme-content [data-level='6'] {
+ font-size: 0.875rem;
+}
+
+.readme-content p {
+ margin-bottom: 1rem;
+}
+
+.readme-content a {
+ color: var(--fg);
+ text-decoration: underline;
+ text-underline-offset: 4px;
+ text-decoration-color: var(--fg-subtle);
+ transition: text-decoration-color 0.2s ease;
+}
+
+.readme-content a:hover {
+ text-decoration-color: var(--accent);
+}
+
+.readme-content code {
+ @apply font-mono;
+ font-size: 0.875em;
+ background: var(--bg-muted);
+ padding: 0.2em 0.4em;
+ border-radius: 4px;
+ border: 1px solid var(--border);
+}
+
+/* Code blocks - including Shiki output */
+.readme-content pre,
+.readme-content .shiki {
+ background: oklch(0.145 0 0) !important;
+ border: 1px solid oklch(0.2686 0 0);
+ border-radius: 8px;
+ padding: 1rem;
+ overflow-x: auto;
+ margin: 1.5rem 0;
+ /* Fix horizontal overflow */
+ max-width: 100%;
+ box-sizing: border-box;
+}
+
+.readme-content pre code,
+.readme-content .shiki code {
+ background: transparent !important;
+ border: none;
+ padding: 0;
+ @apply font-mono;
+ font-size: 0.875rem;
+ color: var(--fg);
+ /* Prevent code from forcing width */
+ white-space: pre;
+ word-break: normal;
+ overflow-wrap: normal;
+}
+
+.readme-content ul,
+.readme-content ol {
+ margin: 1rem 0;
+ padding-left: 1.5rem;
+}
+
+.readme-content ul {
+ list-style-type: disc;
+}
+
+.readme-content ol {
+ list-style-type: decimal;
+}
+
+.readme-content li {
+ margin-bottom: 0.5rem;
+ display: list-item;
+}
+
+.readme-content li::marker {
+ color: var(--border-hover);
+}
+
+.readme-content blockquote {
+ border-left: 2px solid var(--border);
+ padding-left: 1rem;
+ margin: 1.5rem 0;
+ color: var(--fg-subtle);
+ font-style: italic;
+}
+
+/* GitHub-style callouts/alerts */
+.readme-content blockquote[data-callout] {
+ border-left-width: 3px;
+ padding: 1rem 1rem 1rem 1.25rem;
+ background: var(--bg-subtle);
+ font-style: normal;
+ color: var(--fg-subtle);
+}
+
+.readme-content blockquote[data-callout]::before {
+ display: block;
+ @apply font-mono;
+ font-size: 0.75rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: 0.5rem;
+}
+
+.readme-content blockquote[data-callout] > p:first-child {
+ margin-top: 0;
+}
+
+.readme-content blockquote[data-callout] > p:last-child {
+ margin-bottom: 0;
+}
+
+/* Note - blue */
+.readme-content blockquote[data-callout='note'] {
+ border-left-color: var(--syntax-str);
+ background: rgba(59, 130, 246, 0.05);
+}
+.readme-content blockquote[data-callout='note']::before {
+ content: 'Note';
+ color: #3b82f6;
+}
+
+/* Tip - green */
+.readme-content blockquote[data-callout='tip'] {
+ border-left-color: #22c55e;
+ background: rgba(34, 197, 94, 0.05);
+}
+.readme-content blockquote[data-callout='tip']::before {
+ content: 'Tip';
+ color: #22c55e;
+}
+
+/* Important - purple */
+.readme-content blockquote[data-callout='important'] {
+ border-left-color: var(--syntax-fn);
+ background: rgba(168, 85, 247, 0.05);
+}
+.readme-content blockquote[data-callout='important']::before {
+ content: 'Important';
+ color: var(--syntax-fn);
+}
+
+/* Warning - yellow/orange */
+.readme-content blockquote[data-callout='warning'] {
+ border-left-color: #eab308;
+ background: rgba(234, 179, 8, 0.05);
+}
+.readme-content blockquote[data-callout='warning']::before {
+ content: 'Warning';
+ color: #eab308;
+}
+
+/* Caution - red */
+.readme-content blockquote[data-callout='caution'] {
+ border-left-color: #ef4444;
+ background: rgba(239, 68, 68, 0.05);
+}
+.readme-content blockquote[data-callout='caution']::before {
+ content: 'Caution';
+ color: #ef4444;
+}
+
+/* Table wrapper for horizontal scroll on mobile */
+.readme-content table {
+ display: block;
+ width: 100%;
+ overflow-x: auto;
+ border-collapse: collapse;
+ margin: 1.5rem 0;
+ font-size: 0.875rem;
+}
+
+.readme-content th,
+.readme-content td {
+ border: 1px solid var(--border);
+ padding: 0.75rem 1rem;
+ text-align: left;
+}
+
+.readme-content th {
+ background: var(--bg-subtle);
+ color: var(--fg);
+ font-weight: 500;
+}
+
+.readme-content tr:hover {
+ background: var(--bg-subtle);
+}
+
+.readme-content img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 8px;
+ margin: 1rem 0;
+}
+
+.readme-content hr {
+ border: none;
+ border-top: 1px solid var(--border);
+ margin: 2rem 0;
+}
+
+/* Badge images inline */
+.readme-content p > a > img,
+.readme-content p > img {
+ display: inline-block;
+ margin: 0 0.25rem 0.25rem 0;
+ border-radius: 4px;
+}
+
+/* Inline code in package descriptions */
+p > span > code,
+.line-clamp-2 code {
+ @apply font-mono;
+ font-size: 0.85em;
+ background: var(--bg-muted);
+ padding: 0.1em 0.3em;
+ border-radius: 3px;
+ border: 1px solid var(--border);
+}
+
+/* View transition for search box (includes / and input) */
+.search-box {
+ view-transition-name: search-box;
+}
+
+/* Safari search input fixes */
+input[type='search'] {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+input[type='search']::-webkit-search-decoration,
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-results-button,
+input[type='search']::-webkit-search-results-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+/* View transition for logo (hero -> header) */
+.hero-logo,
+.header-logo {
+ view-transition-name: site-logo;
+}
+
+/* Disable the default fade transition on page navigation */
+::view-transition-old(root),
+::view-transition-new(root) {
+ animation: none;
+}
+
+/* Customize the view transition animations for specific elements */
+::view-transition-old(search-box),
+::view-transition-new(search-box),
+::view-transition-old(site-logo),
+::view-transition-new(site-logo) {
+ animation-duration: 0.3s;
+ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
+}
diff --git a/app/components/PackageWeeklyDownloadStats.vue b/app/components/PackageWeeklyDownloadStats.vue
index 3ee5ec3306..dc8fb11e84 100644
--- a/app/components/PackageWeeklyDownloadStats.vue
+++ b/app/components/PackageWeeklyDownloadStats.vue
@@ -56,22 +56,45 @@ const config = computed(() => ({
style: {
backgroundColor: 'transparent',
animation: { show: false },
- area: { color: '#6A6A6A', useGradient: false, opacity: 10 },
- dataLabel: { offsetX: -10, fontSize: 28, bold: false, color: '#FAFAFA' },
+ area: {
+ color: 'oklch(0.5243 0 0)', // css variable doesn't seem to work here
+ useGradient: false,
+ opacity: 10,
+ },
+ dataLabel: {
+ offsetX: -10,
+ fontSize: 28,
+ bold: false,
+ color: 'var(--fg)',
+ },
line: {
- color: '#6A6A6A',
+ color: 'var(--fg-subtle)',
pulse: {
show: true,
- loop: true,
+ loop: true, // runs only once if false
radius: 2,
- color: '#8A8A8A',
+ color: 'var(--fg-muted)',
easing: 'ease-in-out',
- trail: { show: true, length: 6 },
+ trail: {
+ show: true,
+ length: 6,
+ },
},
},
- plot: { radius: 6, stroke: '#FAFAFA' },
- title: { text: lastDatapoint.value, fontSize: 12, color: '#8A8A8A', bold: false },
- verticalIndicator: { strokeDasharray: 0, color: '#FAFAFA' },
+ plot: {
+ radius: 6,
+ stroke: 'var(--fg)',
+ },
+ title: {
+ text: lastDatapoint.value,
+ fontSize: 12,
+ color: 'var(--fg)',
+ bold: false,
+ },
+ verticalIndicator: {
+ strokeDasharray: 0,
+ color: 'var(--fg-muted)',
+ },
},
}))
diff --git a/app/components/SettingsMenu.vue b/app/components/SettingsMenu.vue
index 575884a38b..e864d342cd 100644
--- a/app/components/SettingsMenu.vue
+++ b/app/components/SettingsMenu.vue
@@ -3,6 +3,7 @@ import { onKeyStroke, onClickOutside } from '@vueuse/core'
const { settings } = useSettings()
const { locale, locales, setLocale } = useI18n()
+const colorMode = useColorMode()
const availableLocales = computed(() =>
locales.value.map(l => (typeof l === 'string' ? { code: l, name: l } : l)),
@@ -96,14 +97,16 @@ onKeyStroke(',', e => {
>
{{ $t('settings.relative_dates') }}
@@ -121,21 +124,47 @@ onKeyStroke(',', e => {
$t('settings.include_types')
}}
+
+
+
+
+
+
+
+
+
+
diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts
index ac615a9fa7..3302851456 100644
--- a/app/composables/useSettings.ts
+++ b/app/composables/useSettings.ts
@@ -90,12 +90,12 @@ export function initAccentOnPrehydrate() {
// Colors must be hardcoded since ACCENT_COLORS can't be referenced.
onPrehydrate(() => {
const colors: Record
= {
- rose: '#e9aeba',
- amber: '#fbbf24',
- emerald: '#34d399',
- sky: '#38bdf8',
- violet: '#a78bfa',
- coral: '#fb7185',
+ rose: 'oklch(0.797 0.084 11.056)',
+ amber: 'oklch(0.828 0.165 84.429)',
+ emerald: 'oklch(0.792 0.153 166.95)',
+ sky: 'oklch(0.787 0.128 230.318)',
+ violet: 'oklch(0.714 0.148 286.067)',
+ coral: 'oklch(0.704 0.177 14.75)',
}
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
const color = settings.accentColorId ? colors[settings.accentColorId as AccentColorId] : null
diff --git a/app/pages/[...package].vue b/app/pages/[...package].vue
index c5d171bda0..265be053bd 100644
--- a/app/pages/[...package].vue
+++ b/app/pages/[...package].vue
@@ -762,7 +762,7 @@ defineOgImageComponent('Package', {
@@ -772,11 +772,11 @@ defineOgImageComponent('Package', {
:key="pm.id"
role="tab"
:aria-selected="selectedPM === pm.id"
- class="px-2 py-1 font-mono text-xs rounded transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
+ class="px-2 py-1 font-mono text-xs rounded transition-colors duration-150 border border-solid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
:class="
selectedPM === pm.id
- ? 'bg-bg-elevated text-fg'
- : 'text-fg-subtle hover:text-fg-muted'
+ ? 'bg-bg shadow text-fg border-border'
+ : 'text-fg-subtle hover:text-fg border-transparent'
"
@click="selectedPM = pm.id"
>
@@ -797,11 +797,11 @@ defineOgImageComponent('Package', {
-
+
-
-
-
+
+
+
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 037c8f662c..94f8ccda82 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -43,6 +43,10 @@
"settings": {
"relative_dates": "Relative dates",
"include_types": "Include {'@'}types in install",
+ "theme": "Theme",
+ "theme_light": "Light",
+ "theme_dark": "Dark",
+ "theme_system": "System",
"language": "Language",
"help_translate": "Help translate npmx"
},
diff --git a/nuxt.config.ts b/nuxt.config.ts
index f19046ba35..b50775f58f 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -31,9 +31,17 @@ export default defineNuxtConfig({
'@vite-pwa/nuxt',
'@vueuse/nuxt',
'@nuxtjs/i18n',
+ '@nuxtjs/color-mode',
],
- css: ['vue-data-ui/style.css'],
+ colorMode: {
+ preference: 'system',
+ fallback: 'dark',
+ dataValue: 'theme',
+ storageKey: 'npmx-color-mode',
+ },
+
+ css: ['~/assets/main.css', 'vue-data-ui/style.css'],
devtools: { enabled: true },
diff --git a/package.json b/package.json
index fdd3800b1c..16d65f6257 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@nuxt/a11y": "1.0.0-alpha.1",
"@nuxt/fonts": "^0.13.0",
"@nuxt/scripts": "^0.13.2",
+ "@nuxtjs/color-mode": "^4.0.0",
"@nuxtjs/html-validator": "^2.1.0",
"@nuxtjs/i18n": "10.2.1",
"@shikijs/langs": "^3.21.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8081a8b91e..f9ca4084e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,6 +30,9 @@ importers:
'@nuxt/scripts':
specifier: ^0.13.2
version: 0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.5.0))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
+ '@nuxtjs/color-mode':
+ specifier: ^4.0.0
+ version: 4.0.0(magicast@0.5.1)
'@nuxtjs/html-validator':
specifier: ^2.1.0
version: 2.1.0(@voidzero-dev/vite-plus-test@0.0.0-ffb4d08a8edafe855c59736c0a38ee85a2373ebb(@types/node@24.10.9)(esbuild@0.27.2)(happy-dom@20.3.5)(jiti@2.6.1)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))(magicast@0.5.1)
@@ -1807,6 +1810,9 @@ packages:
'@nuxtjs/color-mode@3.5.2':
resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==}
+ '@nuxtjs/color-mode@4.0.0':
+ resolution: {integrity: sha512-xyaVR/TPLdMuRa2VOgH6b75jvmFEsn9QKL6ISldaAw38ooFJfWY1ts2F3ye43wcT/goCbcuvPuskF2f8yUZhlw==}
+
'@nuxtjs/html-validator@2.1.0':
resolution: {integrity: sha512-ldo8ioSsH3OEumtgwDMokTxlhjgO9FxjJWViAxisq5l/wjvaVX8SYTQ02wjtQcQQPSvS6BwgypAp400RlyFHng==}
@@ -7903,6 +7909,7 @@ packages:
tar@7.5.6:
resolution: {integrity: sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==}
engines: {node: '>=18'}
+ deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
temp-dir@2.0.0:
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
@@ -9566,7 +9573,7 @@ snapshots:
dependencies:
'@babel/code-frame': 7.28.6
'@babel/generator': 7.28.6
- '@babel/parser': 7.27.7
+ '@babel/parser': 7.28.6
'@babel/template': 7.28.6
'@babel/types': 7.28.6
debug: 4.4.3
@@ -11012,6 +11019,16 @@ snapshots:
transitivePeerDependencies:
- magicast
+ '@nuxtjs/color-mode@4.0.0(magicast@0.5.1)':
+ dependencies:
+ '@nuxt/kit': 4.3.0(magicast@0.5.1)
+ exsolve: 1.0.8
+ pathe: 2.0.3
+ pkg-types: 2.3.0
+ semver: 7.7.3
+ transitivePeerDependencies:
+ - magicast
+
'@nuxtjs/html-validator@2.1.0(@voidzero-dev/vite-plus-test@0.0.0-ffb4d08a8edafe855c59736c0a38ee85a2373ebb(@types/node@24.10.9)(esbuild@0.27.2)(happy-dom@20.3.5)(jiti@2.6.1)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))(magicast@0.5.1)':
dependencies:
'@nuxt/kit': 3.21.0(magicast@0.5.1)
diff --git a/shared/utils/constants.ts b/shared/utils/constants.ts
index f4ec66d1f1..b992e947af 100644
--- a/shared/utils/constants.ts
+++ b/shared/utils/constants.ts
@@ -19,10 +19,10 @@ export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'
// Theming
export const ACCENT_COLORS = {
- rose: '#e9aeba',
- amber: '#fbbf24',
- emerald: '#34d399',
- sky: '#38bdf8',
- violet: '#a78bfa',
- coral: '#fb7185',
+ rose: 'oklch(0.797 0.084 11.056)',
+ amber: 'oklch(0.828 0.165 84.429)',
+ emerald: 'oklch(0.792 0.153 166.95)',
+ sky: 'oklch(0.787 0.128 230.318)',
+ violet: 'oklch(0.714 0.148 286.067)',
+ coral: 'oklch(0.704 0.177 14.75)',
} as const
diff --git a/uno.config.ts b/uno.config.ts
index 2985f9c069..a6aa70badd 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -31,31 +31,31 @@ export default defineConfig({
colors: {
// Minimal black & white palette with subtle grays
bg: {
- DEFAULT: '#0a0a0a',
- subtle: '#111111',
- muted: '#1a1a1a',
- elevated: '#222222',
+ DEFAULT: 'var(--bg)',
+ subtle: 'var(--bg-subtle)',
+ muted: 'var(--bg-muted)',
+ elevated: 'var(--bg-elevated)',
},
fg: {
- DEFAULT: '#fafafa',
- muted: '#a1a1a1',
- subtle: '#8A8A8A',
+ DEFAULT: 'var(--fg)',
+ muted: 'var(--fg-muted)',
+ subtle: 'var(--fg-subtle)',
},
border: {
- DEFAULT: '#262626',
- subtle: '#1f1f1f',
- hover: '#404040',
+ DEFAULT: 'var(--border)',
+ subtle: 'var(--border-subtle)',
+ hover: 'var(--border-hover)',
},
accent: {
- DEFAULT: 'var(--accent-color, #666666)',
- fallback: '#666666',
+ DEFAULT: 'var(--accent)',
+ fallback: 'var(--accent-muted)',
},
// Syntax highlighting colors (inspired by GitHub Dark)
syntax: {
- fn: '#b392f0', // function/command - purple
- str: '#9ecbff', // string/argument - light blue
- kw: '#f97583', // keyword - red/pink
- comment: '#6a737d', // comment - gray
+ fn: 'var(--syntax-fn)',
+ str: 'var(--syntax-str)',
+ kw: 'var(--syntax-kw)',
+ comment: 'var(--syntax-comment)',
},
// Playground provider brand colors
provider: {
@@ -101,7 +101,7 @@ export default defineConfig({
['container', 'max-w-4xl mx-auto px-4 sm:px-6'],
// Focus states - subtle but accessible
- ['focus-ring', 'outline-none focus-visible:(ring-2 ring-fg/20 ring-offset-2 ring-offset-bg)'],
+ ['focus-ring', 'outline-none focus-visible:(ring-2 ring-fg/10 ring-offset-2)'],
// Buttons
[