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.
- package/AGENTS.md +2 -0
- package/docs/filter-query-v1.md +180 -0
- package/logo.svg +7 -7
- package/package.json +7 -5
- package/public/themes/README.md +34 -2
- package/public/themes/index.json +1 -5
- package/styles/dark-theme.css +42 -186
- package/styles/picocrank-extensions.css +15 -0
- package/styles.css +1 -0
- package/supplemental-themes/README.md +9 -0
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +211 -0
- package/supplemental-themes/dracula-alucard/theme.css +193 -0
- package/supplemental-themes/gruvbox-dark-light/theme.css +199 -0
- package/supplemental-themes/index.json +17 -0
- package/{public/themes → supplemental-themes}/waffles/theme.css +69 -46
- package/vite.config.js +91 -0
- package/vue/components/Header.vue +3 -3
- package/vue/components/Login.vue +1 -3
- package/vue/components/Section.vue +45 -14
- package/vue/components/Sidebar.vue +6 -11
- package/vue/components/SidebarNavLink.vue +6 -9
- package/vue/components/Table.vue +1115 -75
- package/vue/components/TableColumnFilterPopover.vue +263 -44
- package/vue/components/TableColumnOptionsPopover.vue +1116 -0
- package/vue/components/Tabs.vue +4 -25
- package/vue/components/ThemeSwitcher.vue +158 -0
- package/vue/components/TopBar.vue +2 -2
- package/vue/composables/tableColumnPresets.js +296 -0
- package/vue/composables/tableFilterQuery.js +202 -0
- package/vue/composables/tableFilters.js +167 -3
- package/vue/composables/useCustomTheme.js +167 -24
- package/vue/data/examplePeople.js +2 -6
- package/vue/examples/App.vue +14 -1
- package/vue/examples/ExampleHeaderThemeSwitcher.vue +78 -0
- package/vue/examples/LoginExample.vue +1 -3
- package/vue/examples/PageStructure.vue +17 -0
- package/vue/examples/TableExample.vue +329 -11
- package/vue/examples/TableRemoteExample.vue +112 -4
- package/vue/examples/ThemePreview.vue +45 -38
- package/public/themes/catppuccin/theme.css +0 -195
- package/public/themes/dracula/theme.css +0 -181
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Section
|
|
3
3
|
title="Remote table"
|
|
4
|
-
|
|
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 {
|
|
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 =
|
|
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}, ${
|
|
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/<name>/theme.css</code
|
|
7
|
+
Themes live under <code>public/themes/<name>/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
|
-
<
|
|
13
|
-
|
|
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/<name>/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
|
|
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 {
|
|
124
|
-
|
|
125
|
-
const { availableThemes, themePreference, setTheme, discoverThemes } = useCustomTheme()
|
|
135
|
+
import { HEADER_THEME_SWITCHER_SELECT_ID } from '../components/ThemeSwitcher.vue'
|
|
126
136
|
|
|
127
|
-
|
|
137
|
+
const themeSwitcherEnabled = inject('themeSwitcherEnabled')
|
|
128
138
|
|
|
129
|
-
function
|
|
130
|
-
|
|
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-
|
|
136
|
-
|
|
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
|
-
}
|