picocrank 1.24.0 → 1.25.1

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.
Files changed (41) hide show
  1. package/AGENTS.md +2 -0
  2. package/docs/filter-query-v1.md +180 -0
  3. package/logo.svg +7 -7
  4. package/package.json +7 -5
  5. package/public/themes/README.md +34 -2
  6. package/public/themes/index.json +1 -5
  7. package/styles/dark-theme.css +42 -186
  8. package/styles/picocrank-extensions.css +15 -0
  9. package/styles.css +1 -0
  10. package/supplemental-themes/README.md +9 -0
  11. package/supplemental-themes/catppuccin-latte-frappe/theme.css +211 -0
  12. package/supplemental-themes/dracula-alucard/theme.css +193 -0
  13. package/supplemental-themes/gruvbox-dark-light/theme.css +199 -0
  14. package/supplemental-themes/index.json +17 -0
  15. package/{public/themes → supplemental-themes}/waffles/theme.css +69 -46
  16. package/vite.config.js +91 -0
  17. package/vue/components/Header.vue +3 -3
  18. package/vue/components/Login.vue +1 -3
  19. package/vue/components/Section.vue +45 -14
  20. package/vue/components/Sidebar.vue +6 -11
  21. package/vue/components/SidebarNavLink.vue +6 -9
  22. package/vue/components/Table.vue +1115 -75
  23. package/vue/components/TableColumnFilterPopover.vue +263 -44
  24. package/vue/components/TableColumnOptionsPopover.vue +1116 -0
  25. package/vue/components/Tabs.vue +4 -25
  26. package/vue/components/ThemeSwitcher.vue +158 -0
  27. package/vue/components/TopBar.vue +2 -2
  28. package/vue/composables/tableColumnPresets.js +296 -0
  29. package/vue/composables/tableFilterQuery.js +202 -0
  30. package/vue/composables/tableFilters.js +167 -3
  31. package/vue/composables/useCustomTheme.js +167 -24
  32. package/vue/data/examplePeople.js +2 -6
  33. package/vue/examples/App.vue +14 -1
  34. package/vue/examples/ExampleHeaderThemeSwitcher.vue +78 -0
  35. package/vue/examples/LoginExample.vue +1 -3
  36. package/vue/examples/PageStructure.vue +17 -0
  37. package/vue/examples/TableExample.vue +329 -11
  38. package/vue/examples/TableRemoteExample.vue +112 -4
  39. package/vue/examples/ThemePreview.vue +45 -38
  40. package/public/themes/catppuccin/theme.css +0 -195
  41. package/public/themes/dracula/theme.css +0 -181
@@ -1,14 +1,17 @@
1
1
  <template>
2
2
  <Section
3
3
  title="Remote table"
4
- subtitle="Server-style pagination with fetchRows. Filters, sort, and page changes are sent to the callback. Right-click or long-press a column header to filter."
4
+ :icon="CloudServerIcon"
5
+ subtitle="Server-style pagination with fetchRows. Filter queries, sort, and page changes are sent to the callback. Right-click or long-press a column header to filter. Layout save/load uses server callbacks instead of localStorage."
5
6
  :padding="false"
6
7
  >
7
8
  <p v-if="lastQuerySummary" class="subtle query-summary">{{ lastQuerySummary }}</p>
8
9
 
9
10
  <Table
11
+ row-key="name"
10
12
  :headers="headers"
11
13
  :fetch-rows="fetchRows"
14
+ :layout-presets="layoutPresets"
12
15
  :show-pagination="true"
13
16
  :remote-debounce-ms="250"
14
17
  />
@@ -17,8 +20,13 @@
17
20
 
18
21
  <script setup>
19
22
  import { ref } from 'vue'
23
+ import { CloudServerIcon } from '@hugeicons/core-free-icons'
20
24
  import { getExampleTableRows } from '../data/examplePeople.js'
21
- import { applyFilters, sortRows } from '../composables/tableFilters.js'
25
+ import {
26
+ applyFilterQuery,
27
+ countActiveFilterQuery,
28
+ } from '../composables/tableFilterQuery.js'
29
+ import { sortRows } from '../composables/tableFilters.js'
22
30
 
23
31
  const allRows = getExampleTableRows()
24
32
 
@@ -41,7 +49,107 @@ const headers = ref([
41
49
 
42
50
  const lastQuerySummary = ref('')
43
51
 
52
+ const remoteLayoutStore = ref({
53
+ defaultPresetId: null,
54
+ presets: [],
55
+ })
56
+
57
+ let remotePresetCounter = 0
58
+
59
+ function createRemotePresetId() {
60
+ remotePresetCounter += 1
61
+ return `remote-preset-${remotePresetCounter}`
62
+ }
63
+
64
+ const layoutPresets = {
65
+ async list() {
66
+ return {
67
+ presets: remoteLayoutStore.value.presets.map((preset) => ({ ...preset })),
68
+ defaultPresetId: remoteLayoutStore.value.defaultPresetId,
69
+ }
70
+ },
71
+
72
+ async getDefault() {
73
+ const { defaultPresetId, presets } = remoteLayoutStore.value
74
+ if (!defaultPresetId) {
75
+ return { ok: true, preset: null }
76
+ }
77
+
78
+ const preset = presets.find((item) => item.id === defaultPresetId) ?? null
79
+ return { ok: true, preset: preset ? { ...preset } : null }
80
+ },
81
+
82
+ async save({ name, state, setAsDefault = false }) {
83
+ const trimmedName = String(name ?? '').trim()
84
+ if (!trimmedName) {
85
+ return { ok: false, error: 'Preset name is required.' }
86
+ }
87
+
88
+ const preset = {
89
+ id: createRemotePresetId(),
90
+ name: trimmedName,
91
+ savedAt: new Date().toISOString(),
92
+ state: JSON.parse(JSON.stringify(state)),
93
+ }
94
+
95
+ remoteLayoutStore.value.presets = [...remoteLayoutStore.value.presets, preset]
96
+ if (setAsDefault) {
97
+ remoteLayoutStore.value.defaultPresetId = preset.id
98
+ }
99
+
100
+ console.info('[remote table] saved layout preset', preset)
101
+ return {
102
+ ok: true,
103
+ preset,
104
+ defaultPresetId: remoteLayoutStore.value.defaultPresetId,
105
+ }
106
+ },
107
+
108
+ async load(presetId) {
109
+ const preset = remoteLayoutStore.value.presets.find((item) => item.id === presetId)
110
+ if (!preset) {
111
+ return { ok: false, error: 'Preset not found.' }
112
+ }
113
+
114
+ console.info('[remote table] loaded layout preset', preset)
115
+ return { ok: true, preset: { ...preset } }
116
+ },
117
+
118
+ async delete(presetId) {
119
+ const nextPresets = remoteLayoutStore.value.presets.filter((item) => item.id !== presetId)
120
+ if (nextPresets.length === remoteLayoutStore.value.presets.length) {
121
+ return { ok: false, error: 'Preset not found.' }
122
+ }
123
+
124
+ remoteLayoutStore.value.presets = nextPresets
125
+ if (remoteLayoutStore.value.defaultPresetId === presetId) {
126
+ remoteLayoutStore.value.defaultPresetId = null
127
+ }
128
+
129
+ console.info('[remote table] deleted layout preset', presetId)
130
+ return { ok: true, defaultPresetId: remoteLayoutStore.value.defaultPresetId }
131
+ },
132
+
133
+ async setDefault(presetId) {
134
+ if (presetId === null) {
135
+ remoteLayoutStore.value.defaultPresetId = null
136
+ return { ok: true, defaultPresetId: null }
137
+ }
138
+
139
+ const preset = remoteLayoutStore.value.presets.find((item) => item.id === presetId)
140
+ if (!preset) {
141
+ return { ok: false, error: 'Preset not found.' }
142
+ }
143
+
144
+ remoteLayoutStore.value.defaultPresetId = presetId
145
+ console.info('[remote table] set default layout preset', presetId)
146
+ return { ok: true, defaultPresetId: presetId }
147
+ },
148
+ }
149
+
44
150
  async function fetchRows(query, { signal } = {}) {
151
+ console.log('[remote table] filterQuery', query.filterQuery)
152
+
45
153
  await new Promise((resolve, reject) => {
46
154
  const timer = window.setTimeout(resolve, 200)
47
155
  if (!signal) {
@@ -58,12 +166,12 @@ async function fetchRows(query, { signal } = {}) {
58
166
  signal.addEventListener('abort', onAbort, { once: true })
59
167
  })
60
168
 
61
- const filtered = applyFilters(allRows, headers.value, query.filters)
169
+ const filtered = applyFilterQuery(allRows, headers.value, query.filterQuery)
62
170
  const sorted = sortRows(filtered, query.sortBy, query.sortDir)
63
171
  const start = (query.page - 1) * query.pageSize
64
172
  const rows = sorted.slice(start, start + query.pageSize)
65
173
 
66
- lastQuerySummary.value = `Last query: page ${query.page}, ${Object.keys(query.filters ?? {}).length} active filter(s), ${sorted.length} matching row(s).`
174
+ lastQuerySummary.value = `Last query: page ${query.page}, ${countActiveFilterQuery(query.filterQuery, headers.value, allRows)} active filter(s), ${sorted.length} matching row(s).`
67
175
 
68
176
  return {
69
177
  rows,
@@ -4,33 +4,28 @@
4
4
  subtitle="Drop-in themes layered on Femtocrank — not part of the Vite CSS/JS bundle"
5
5
  >
6
6
  <p>
7
- Themes live under <code>public/themes/&lt;name&gt;/theme.css</code>. Drop a folder
7
+ Themes live under <code>public/themes/&lt;name&gt;/theme.css</code> (app themes) and
8
+ optionally <code>supplemental-themes/</code> (Catppuccin, Dracula / Alucard, Gruvbox, waffles). Drop a folder
8
9
  in and restart the dev server (or rebuild); PicoCrank discovers themes from
9
- <code>index.json</code> and applies them with <code>useCustomTheme()</code>.
10
+ <code>index.json</code> and applies them with <code>useCustomTheme()</code> or
11
+ <code>ThemeSwitcher</code> in the examples app header toolbar. Supplemental themes are bundled by default but only
12
+ listed when <code>include-supplemental-themes</code> is enabled.
10
13
  </p>
11
14
 
12
- <label class="theme-picker">
13
- <span>Active theme</span>
14
- <select :value="themePreference" @change="onThemeChange">
15
- <option value="">Default (Femtocrank)</option>
16
- <option
17
- v-for="name in availableThemes"
18
- :key="name"
19
- :value="name"
20
- >
21
- {{ name }}
22
- </option>
23
- </select>
24
- </label>
25
-
26
- <p v-if="availableThemes.length === 0" class="subtle">
27
- No drop-in themes found. Add <code>public/themes/&lt;name&gt;/theme.css</code>
28
- and restart Vite.
29
- </p>
30
- <p v-else class="subtle">
15
+ <p class="subtle">
16
+ Use the theme dropdown in the header (next to the light/dark toggle) to switch drop-in themes.
31
17
  Preference is stored in localStorage and applies across the whole examples site.
32
- Light/dark mode (header sun/moon) is separate from drop-in themes.
18
+ Light/dark mode switches Femtocrank defaults, or the dual-variant supplemental themes when active.
33
19
  </p>
20
+
21
+ <button
22
+ type="button"
23
+ class="neutral open-theme-switcher-button"
24
+ :disabled="!themeSwitcherEnabled"
25
+ @click="openThemeSwitcher"
26
+ >
27
+ Open theme switcher
28
+ </button>
34
29
  </Section>
35
30
 
36
31
  <Section title="Buttons" subtitle="Default and karma variants">
@@ -111,37 +106,49 @@
111
106
  <td><code>--karma-*</code></td>
112
107
  <td>Status backgrounds and accents</td>
113
108
  </tr>
109
+ <tr>
110
+ <td><code>--link-color</code></td>
111
+ <td>Links, active tabs, sort/filter column headers</td>
112
+ </tr>
113
+ <tr>
114
+ <td><code>--muted-text-color</code></td>
115
+ <td>Secondary text and inactive tab labels</td>
116
+ </tr>
117
+ <tr>
118
+ <td><code>--header-fg-color</code></td>
119
+ <td>Header chrome text and icons</td>
120
+ </tr>
121
+ <tr>
122
+ <td><code>--input-bg-color</code> / <code>--input-fg-color</code></td>
123
+ <td>Form fields</td>
124
+ </tr>
114
125
  </tbody>
115
126
  </table>
116
127
  </Section>
117
128
  </template>
118
129
 
119
130
  <script setup>
131
+ import { inject } from 'vue'
120
132
  import Section from '../components/Section.vue'
121
133
  import FormLayout from '../components/FormLayout.vue'
122
134
  import FormField from '../components/FormField.vue'
123
- import { useCustomTheme } from '../composables/useCustomTheme.js'
124
-
125
- const { availableThemes, themePreference, setTheme, discoverThemes } = useCustomTheme()
135
+ import { HEADER_THEME_SWITCHER_SELECT_ID } from '../components/ThemeSwitcher.vue'
126
136
 
127
- discoverThemes()
137
+ const themeSwitcherEnabled = inject('themeSwitcherEnabled')
128
138
 
129
- function onThemeChange(event) {
130
- setTheme(event.target.value)
139
+ function openThemeSwitcher() {
140
+ const select = document.getElementById(HEADER_THEME_SWITCHER_SELECT_ID)
141
+ if (!(select instanceof HTMLSelectElement)) {
142
+ return
143
+ }
144
+ select.focus()
145
+ select.scrollIntoView({ block: 'nearest', inline: 'nearest' })
131
146
  }
132
147
  </script>
133
148
 
134
149
  <style scoped>
135
- .theme-picker {
136
- display: flex;
137
- flex-wrap: wrap;
138
- align-items: center;
139
- gap: 0.75rem;
140
- margin: 1rem 0;
141
- }
142
-
143
- .theme-picker select {
144
- min-width: 14rem;
150
+ .open-theme-switcher-button {
151
+ margin-top: 0.75rem;
145
152
  }
146
153
 
147
154
  .preview-row {
@@ -1,195 +0,0 @@
1
- /*
2
- * Catppuccin Mocha sample theme for PicoCrank / Femtocrank.
3
- * Palette: https://catppuccin.com/palette/ (Mocha)
4
- *
5
- * Asset URLs must be absolute, e.g.:
6
- * background-image: url('/themes/catppuccin/background.png');
7
- */
8
-
9
- :root,
10
- html {
11
- color-scheme: dark;
12
-
13
- --ctp-rosewater: #f5e0dc;
14
- --ctp-flamingo: #f2cdcd;
15
- --ctp-pink: #f5c2e7;
16
- --ctp-mauve: #cba6f7;
17
- --ctp-red: #f38ba8;
18
- --ctp-maroon: #eba0ac;
19
- --ctp-peach: #fab387;
20
- --ctp-yellow: #f9e2af;
21
- --ctp-green: #a6e3a1;
22
- --ctp-teal: #94e2d5;
23
- --ctp-sky: #89dceb;
24
- --ctp-sapphire: #74c7ec;
25
- --ctp-blue: #89b4fa;
26
- --ctp-lavender: #b4befe;
27
- --ctp-text: #cdd6f4;
28
- --ctp-subtext1: #bac2de;
29
- --ctp-subtext0: #a6adc8;
30
- --ctp-overlay2: #9399b2;
31
- --ctp-overlay1: #7f849c;
32
- --ctp-overlay0: #6c7086;
33
- --ctp-surface2: #585b70;
34
- --ctp-surface1: #45475a;
35
- --ctp-surface0: #313244;
36
- --ctp-base: #1e1e2e;
37
- --ctp-mantle: #181825;
38
- --ctp-crust: #11111b;
39
-
40
- --text-color: var(--ctp-text);
41
- --border-color: var(--ctp-surface1);
42
- --standout-bg-color: var(--ctp-mantle);
43
- --hover-background-color: var(--ctp-surface0);
44
- --hover-text-color: var(--ctp-text);
45
- --header-hover-background-color: var(--ctp-surface1);
46
- --header-hover-text-color: var(--ctp-text);
47
- --disabled-background-color: var(--ctp-surface0);
48
- --disabled-text-color: var(--ctp-overlay0);
49
-
50
- --karma-good: var(--ctp-green);
51
- --karma-bad: var(--ctp-red);
52
- --karma-warning: var(--ctp-yellow);
53
- --karma-severe: var(--ctp-maroon);
54
- --karma-important: var(--ctp-peach);
55
- --karma-note: var(--ctp-sapphire);
56
- --karma-info: var(--ctp-surface1);
57
- --karma-old: var(--ctp-overlay0);
58
-
59
- --karma-good-fg: var(--ctp-green);
60
- --karma-bad-fg: var(--ctp-red);
61
- --karma-warning-fg: var(--ctp-yellow);
62
- --karma-severe-fg: var(--ctp-maroon);
63
- --karma-important-fg: var(--ctp-peach);
64
- --karma-note-fg: var(--ctp-sapphire);
65
- --karma-info-fg: var(--ctp-subtext0);
66
- --karma-old-fg: var(--ctp-overlay1);
67
- }
68
-
69
- body {
70
- background-color: var(--ctp-base);
71
- color: var(--ctp-text);
72
- }
73
-
74
- header {
75
- background-color: var(--ctp-crust);
76
- border-bottom: 1px solid var(--ctp-surface0);
77
- box-shadow: none;
78
- color: var(--ctp-text);
79
- }
80
-
81
- #sidebar-toggler-button,
82
- #sidebar-toggler-button.neutral,
83
- header .theme-toggle,
84
- header .theme-toggle.neutral,
85
- header .search-trigger,
86
- header .search-trigger.neutral {
87
- background-color: transparent;
88
- border: 0;
89
- color: var(--ctp-text);
90
- }
91
-
92
- #sidebar-toggler-button:hover,
93
- header .theme-toggle:hover,
94
- header .search-trigger:hover,
95
- header .user-info:hover {
96
- background-color: var(--header-hover-background-color);
97
- color: var(--header-hover-text-color);
98
- }
99
-
100
- aside,
101
- #sidebar {
102
- background-color: var(--ctp-mantle);
103
- color: var(--ctp-text);
104
- border-color: var(--ctp-surface0);
105
- /* Replace Femtocrank's light gray glow — it reads as a haze on Mocha */
106
- box-shadow: 0.15em 0 0.6em rgba(17, 17, 27, 0.65);
107
- }
108
-
109
- aside a,
110
- aside a:visited {
111
- color: var(--ctp-text);
112
- }
113
-
114
- aside a:hover,
115
- aside :is(ul, menu) li a:hover,
116
- aside .nav-section-header:hover {
117
- background-color: var(--ctp-surface0);
118
- color: var(--ctp-text);
119
- }
120
-
121
- section {
122
- background-color: var(--ctp-mantle);
123
- color: var(--ctp-text);
124
- box-shadow: 0 0 0.5em var(--ctp-crust);
125
- border-color: var(--ctp-surface0);
126
- }
127
-
128
- section .section-header,
129
- section .section-subheader {
130
- background-color: var(--ctp-mantle);
131
- border-bottom-color: var(--ctp-surface0);
132
- color: var(--ctp-text);
133
- }
134
-
135
- button,
136
- .button {
137
- background-color: var(--ctp-surface0);
138
- border: 1px solid var(--ctp-surface1);
139
- color: var(--ctp-text);
140
- }
141
-
142
- button:hover,
143
- .button:hover {
144
- background-color: var(--ctp-surface1);
145
- color: var(--ctp-text);
146
- }
147
-
148
- button.good,
149
- .button.good {
150
- background-color: var(--ctp-green);
151
- border-color: var(--ctp-green);
152
- color: var(--ctp-crust);
153
- }
154
-
155
- button.bad,
156
- .button.bad {
157
- background-color: var(--ctp-red);
158
- border-color: var(--ctp-red);
159
- color: var(--ctp-crust);
160
- }
161
-
162
- button.warning,
163
- .button.warning {
164
- background-color: var(--ctp-yellow);
165
- border-color: var(--ctp-yellow);
166
- color: var(--ctp-crust);
167
- }
168
-
169
- button.neutral,
170
- .button.neutral {
171
- background-color: var(--ctp-surface1);
172
- border-color: var(--ctp-surface2);
173
- color: var(--ctp-text);
174
- }
175
-
176
- input,
177
- select,
178
- textarea {
179
- background-color: var(--ctp-surface0);
180
- border: 1px solid var(--ctp-surface1);
181
- color: var(--ctp-text);
182
- }
183
-
184
- a {
185
- color: var(--ctp-blue);
186
- }
187
-
188
- footer {
189
- color: var(--ctp-subtext0);
190
- border-top-color: var(--ctp-surface0);
191
- }
192
-
193
- .subtle {
194
- color: var(--ctp-subtext0);
195
- }
@@ -1,181 +0,0 @@
1
- /*
2
- * Dracula sample theme for PicoCrank / Femtocrank.
3
- * Palette: https://draculatheme.com/ / https://spec.draculatheme.com/
4
- *
5
- * Asset URLs must be absolute, e.g.:
6
- * background-image: url('/themes/dracula/background.png');
7
- */
8
-
9
- :root,
10
- html {
11
- color-scheme: dark;
12
-
13
- --dracula-background: #282a36;
14
- --dracula-current-line: #44475a;
15
- --dracula-foreground: #f8f8f2;
16
- --dracula-comment: #6272a4;
17
- --dracula-cyan: #8be9fd;
18
- --dracula-green: #50fa7b;
19
- --dracula-orange: #ffb86c;
20
- --dracula-pink: #ff79c6;
21
- --dracula-purple: #bd93f9;
22
- --dracula-red: #ff5555;
23
- --dracula-yellow: #f1fa8c;
24
- --dracula-selection: #44475a;
25
- --dracula-ansi-black: #21222c;
26
-
27
- --text-color: var(--dracula-foreground);
28
- --border-color: var(--dracula-current-line);
29
- --standout-bg-color: var(--dracula-ansi-black);
30
- --hover-background-color: var(--dracula-selection);
31
- --hover-text-color: var(--dracula-foreground);
32
- --header-hover-background-color: var(--dracula-selection);
33
- --header-hover-text-color: var(--dracula-foreground);
34
- --disabled-background-color: var(--dracula-current-line);
35
- --disabled-text-color: var(--dracula-comment);
36
-
37
- --karma-good: var(--dracula-green);
38
- --karma-bad: var(--dracula-red);
39
- --karma-warning: var(--dracula-yellow);
40
- --karma-severe: var(--dracula-orange);
41
- --karma-important: var(--dracula-purple);
42
- --karma-note: var(--dracula-cyan);
43
- --karma-info: var(--dracula-current-line);
44
- --karma-old: var(--dracula-comment);
45
-
46
- --karma-good-fg: var(--dracula-green);
47
- --karma-bad-fg: var(--dracula-red);
48
- --karma-warning-fg: var(--dracula-yellow);
49
- --karma-severe-fg: var(--dracula-orange);
50
- --karma-important-fg: var(--dracula-purple);
51
- --karma-note-fg: var(--dracula-cyan);
52
- --karma-info-fg: var(--dracula-comment);
53
- --karma-old-fg: var(--dracula-comment);
54
- }
55
-
56
- body {
57
- background-color: var(--dracula-background);
58
- color: var(--dracula-foreground);
59
- }
60
-
61
- header {
62
- background-color: var(--dracula-ansi-black);
63
- border-bottom: 1px solid var(--dracula-current-line);
64
- box-shadow: none;
65
- color: var(--dracula-foreground);
66
- }
67
-
68
- #sidebar-toggler-button,
69
- #sidebar-toggler-button.neutral,
70
- header .theme-toggle,
71
- header .theme-toggle.neutral,
72
- header .search-trigger,
73
- header .search-trigger.neutral {
74
- background-color: transparent;
75
- border: 0;
76
- color: var(--dracula-foreground);
77
- }
78
-
79
- #sidebar-toggler-button:hover,
80
- header .theme-toggle:hover,
81
- header .search-trigger:hover,
82
- header .user-info:hover {
83
- background-color: var(--header-hover-background-color);
84
- color: var(--header-hover-text-color);
85
- }
86
-
87
- aside,
88
- #sidebar {
89
- background-color: var(--dracula-ansi-black);
90
- color: var(--dracula-foreground);
91
- border-color: var(--dracula-current-line);
92
- box-shadow: 0.15em 0 0.6em rgba(33, 34, 44, 0.85);
93
- }
94
-
95
- aside a,
96
- aside a:visited {
97
- color: var(--dracula-foreground);
98
- }
99
-
100
- aside a:hover,
101
- aside :is(ul, menu) li a:hover,
102
- aside .nav-section-header:hover {
103
- background-color: var(--dracula-selection);
104
- color: var(--dracula-foreground);
105
- }
106
-
107
- section {
108
- background-color: var(--dracula-ansi-black);
109
- color: var(--dracula-foreground);
110
- box-shadow: 0 0 0.5em rgba(33, 34, 44, 0.9);
111
- border-color: var(--dracula-current-line);
112
- }
113
-
114
- section .section-header,
115
- section .section-subheader {
116
- background-color: var(--dracula-ansi-black);
117
- border-bottom-color: var(--dracula-current-line);
118
- color: var(--dracula-foreground);
119
- }
120
-
121
- button,
122
- .button {
123
- background-color: var(--dracula-current-line);
124
- border: 1px solid var(--dracula-comment);
125
- color: var(--dracula-foreground);
126
- }
127
-
128
- button:hover,
129
- .button:hover {
130
- background-color: var(--dracula-selection);
131
- color: var(--dracula-foreground);
132
- }
133
-
134
- button.good,
135
- .button.good {
136
- background-color: var(--dracula-green);
137
- border-color: var(--dracula-green);
138
- color: var(--dracula-ansi-black);
139
- }
140
-
141
- button.bad,
142
- .button.bad {
143
- background-color: var(--dracula-red);
144
- border-color: var(--dracula-red);
145
- color: var(--dracula-foreground);
146
- }
147
-
148
- button.warning,
149
- .button.warning {
150
- background-color: var(--dracula-yellow);
151
- border-color: var(--dracula-yellow);
152
- color: var(--dracula-ansi-black);
153
- }
154
-
155
- button.neutral,
156
- .button.neutral {
157
- background-color: var(--dracula-selection);
158
- border-color: var(--dracula-comment);
159
- color: var(--dracula-foreground);
160
- }
161
-
162
- input,
163
- select,
164
- textarea {
165
- background-color: var(--dracula-background);
166
- border: 1px solid var(--dracula-current-line);
167
- color: var(--dracula-foreground);
168
- }
169
-
170
- a {
171
- color: var(--dracula-pink);
172
- }
173
-
174
- footer {
175
- color: var(--dracula-comment);
176
- border-top-color: var(--dracula-current-line);
177
- }
178
-
179
- .subtle {
180
- color: var(--dracula-comment);
181
- }