vuetiwatch 0.1.0 → 0.2.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.
Files changed (164) hide show
  1. package/README.md +200 -17
  2. package/dist/index.d.ts +4 -4
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/plugin.d.ts +77 -7
  7. package/dist/plugin.d.ts.map +1 -1
  8. package/dist/plugin.js +104 -15
  9. package/dist/plugin.js.map +1 -1
  10. package/dist/registry.d.ts +1 -1
  11. package/dist/registry.d.ts.map +1 -1
  12. package/dist/registry.js +6 -1
  13. package/dist/registry.js.map +1 -1
  14. package/dist/styles/atlas.css +99 -0
  15. package/dist/styles/atlas.min.css +1 -0
  16. package/dist/styles/atlasDark.css +99 -0
  17. package/dist/styles/atlasDark.min.css +1 -0
  18. package/dist/styles/atlasSepia.css +99 -0
  19. package/dist/styles/atlasSepia.min.css +1 -0
  20. package/dist/styles/aurora.css +97 -4
  21. package/dist/styles/aurora.min.css +1 -1
  22. package/dist/styles/brutalist.css +5 -0
  23. package/dist/styles/brutalist.min.css +1 -1
  24. package/dist/styles/calm.css +0 -4
  25. package/dist/styles/calm.min.css +1 -1
  26. package/dist/styles/candy.css +71 -0
  27. package/dist/styles/candy.min.css +1 -0
  28. package/dist/styles/clay.css +13 -0
  29. package/dist/styles/clay.min.css +1 -1
  30. package/dist/styles/core.css +239 -36
  31. package/dist/styles/core.min.css +1 -1
  32. package/dist/styles/graphite.css +29 -0
  33. package/dist/styles/graphite.min.css +1 -0
  34. package/dist/styles/morph.css +12 -0
  35. package/dist/styles/morph.min.css +1 -1
  36. package/dist/styles/neon.css +8 -0
  37. package/dist/styles/neon.min.css +1 -1
  38. package/dist/styles/sketchy.css +4 -0
  39. package/dist/styles/sketchy.min.css +1 -1
  40. package/dist/styles/soft.css +1 -12
  41. package/dist/styles/soft.min.css +1 -1
  42. package/dist/styles.css +577 -52
  43. package/dist/styles.min.css +1 -1
  44. package/dist/themes/atlas.d.ts +38 -0
  45. package/dist/themes/atlas.d.ts.map +1 -0
  46. package/dist/themes/atlas.js +203 -0
  47. package/dist/themes/atlas.js.map +1 -0
  48. package/dist/themes/atlasDark.d.ts +8 -0
  49. package/dist/themes/atlasDark.d.ts.map +1 -0
  50. package/dist/themes/atlasDark.js +64 -0
  51. package/dist/themes/atlasDark.js.map +1 -0
  52. package/dist/themes/atlasSepia.d.ts +11 -0
  53. package/dist/themes/atlasSepia.d.ts.map +1 -0
  54. package/dist/themes/atlasSepia.js +60 -0
  55. package/dist/themes/atlasSepia.js.map +1 -0
  56. package/dist/themes/aurora.d.ts.map +1 -1
  57. package/dist/themes/aurora.js +27 -2
  58. package/dist/themes/aurora.js.map +1 -1
  59. package/dist/themes/brutalist.d.ts.map +1 -1
  60. package/dist/themes/brutalist.js +26 -0
  61. package/dist/themes/brutalist.js.map +1 -1
  62. package/dist/themes/calm.d.ts.map +1 -1
  63. package/dist/themes/calm.js +22 -2
  64. package/dist/themes/calm.js.map +1 -1
  65. package/dist/themes/candy.d.ts +20 -0
  66. package/dist/themes/candy.d.ts.map +1 -0
  67. package/dist/themes/candy.js +142 -0
  68. package/dist/themes/candy.js.map +1 -0
  69. package/dist/themes/classic.d.ts.map +1 -1
  70. package/dist/themes/classic.js +2 -0
  71. package/dist/themes/classic.js.map +1 -1
  72. package/dist/themes/clay.d.ts.map +1 -1
  73. package/dist/themes/clay.js +22 -0
  74. package/dist/themes/clay.js.map +1 -1
  75. package/dist/themes/darkGlass.d.ts.map +1 -1
  76. package/dist/themes/darkGlass.js +21 -0
  77. package/dist/themes/darkGlass.js.map +1 -1
  78. package/dist/themes/graphite.d.ts +13 -0
  79. package/dist/themes/graphite.d.ts.map +1 -0
  80. package/dist/themes/graphite.js +147 -0
  81. package/dist/themes/graphite.js.map +1 -0
  82. package/dist/themes/index.d.ts +5 -0
  83. package/dist/themes/index.d.ts.map +1 -1
  84. package/dist/themes/index.js +5 -0
  85. package/dist/themes/index.js.map +1 -1
  86. package/dist/themes/liquidGlass.d.ts.map +1 -1
  87. package/dist/themes/liquidGlass.js +27 -3
  88. package/dist/themes/liquidGlass.js.map +1 -1
  89. package/dist/themes/lux.d.ts.map +1 -1
  90. package/dist/themes/lux.js +21 -2
  91. package/dist/themes/lux.js.map +1 -1
  92. package/dist/themes/morph.d.ts.map +1 -1
  93. package/dist/themes/morph.js +23 -3
  94. package/dist/themes/morph.js.map +1 -1
  95. package/dist/themes/neon.d.ts.map +1 -1
  96. package/dist/themes/neon.js +22 -0
  97. package/dist/themes/neon.js.map +1 -1
  98. package/dist/themes/paper.d.ts.map +1 -1
  99. package/dist/themes/paper.js +16 -0
  100. package/dist/themes/paper.js.map +1 -1
  101. package/dist/themes/sketchy.d.ts.map +1 -1
  102. package/dist/themes/sketchy.js +17 -0
  103. package/dist/themes/sketchy.js.map +1 -1
  104. package/dist/themes/slate.d.ts.map +1 -1
  105. package/dist/themes/slate.js +22 -1
  106. package/dist/themes/slate.js.map +1 -1
  107. package/dist/themes/soft.d.ts.map +1 -1
  108. package/dist/themes/soft.js +21 -2
  109. package/dist/themes/soft.js.map +1 -1
  110. package/dist/types.d.ts +40 -0
  111. package/dist/types.d.ts.map +1 -1
  112. package/dist/util/defaults.d.ts.map +1 -1
  113. package/dist/util/defaults.js +11 -1
  114. package/dist/util/defaults.js.map +1 -1
  115. package/dist/util/defineTheme.d.ts.map +1 -1
  116. package/dist/util/defineTheme.js +11 -5
  117. package/dist/util/defineTheme.js.map +1 -1
  118. package/package.json +2 -2
  119. package/src/index.ts +8 -2
  120. package/src/plugin.ts +214 -21
  121. package/src/registry.ts +10 -0
  122. package/src/styles/core.css +563 -35
  123. package/src/styles/index.css +4 -0
  124. package/src/styles/themes/_atlas.css +194 -0
  125. package/src/styles/themes/atlas.css +2 -0
  126. package/src/styles/themes/atlasDark.css +2 -0
  127. package/src/styles/themes/atlasSepia.css +2 -0
  128. package/src/styles/themes/aurora.css +151 -7
  129. package/src/styles/themes/brutalist.css +5 -3
  130. package/src/styles/themes/calm.css +0 -7
  131. package/src/styles/themes/candy.css +113 -0
  132. package/src/styles/themes/clay.css +24 -0
  133. package/src/styles/themes/graphite.css +63 -0
  134. package/src/styles/themes/liquidGlass.css +0 -2
  135. package/src/styles/themes/lux.css +0 -4
  136. package/src/styles/themes/morph.css +22 -0
  137. package/src/styles/themes/neon.css +8 -3
  138. package/src/styles/themes/paper.css +0 -2
  139. package/src/styles/themes/sketchy.css +9 -0
  140. package/src/styles/themes/slate.css +0 -4
  141. package/src/styles/themes/soft.css +1 -12
  142. package/src/themes/atlas.ts +211 -0
  143. package/src/themes/atlasDark.ts +64 -0
  144. package/src/themes/atlasSepia.ts +60 -0
  145. package/src/themes/aurora.ts +30 -2
  146. package/src/themes/brutalist.ts +29 -0
  147. package/src/themes/calm.ts +25 -2
  148. package/src/themes/candy.ts +144 -0
  149. package/src/themes/classic.ts +3 -0
  150. package/src/themes/clay.ts +24 -0
  151. package/src/themes/darkGlass.ts +24 -0
  152. package/src/themes/graphite.ts +150 -0
  153. package/src/themes/index.ts +5 -0
  154. package/src/themes/liquidGlass.ts +30 -3
  155. package/src/themes/lux.ts +24 -2
  156. package/src/themes/morph.ts +25 -3
  157. package/src/themes/neon.ts +25 -0
  158. package/src/themes/paper.ts +19 -0
  159. package/src/themes/sketchy.ts +20 -0
  160. package/src/themes/slate.ts +25 -1
  161. package/src/themes/soft.ts +24 -2
  162. package/src/types.ts +41 -0
  163. package/src/util/defaults.ts +12 -1
  164. package/src/util/defineTheme.ts +11 -5
@@ -7,7 +7,6 @@
7
7
  box-shadow: none;
8
8
  }
9
9
 
10
-
11
10
  & :is(.v-toolbar-title, .v-card-title, .v-list-subheader) {
12
11
  letter-spacing: 0.16em;
13
12
  text-transform: uppercase;
@@ -25,7 +24,4 @@
25
24
  border: var(--v-vw-border-width) solid rgba(var(--v-border-color), 0.14);
26
25
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.08);
27
26
  }
28
-
29
-
30
-
31
27
  }
@@ -26,6 +26,19 @@
26
26
  -3px -3px 6px rgba(255, 255, 255, 0.6);
27
27
  }
28
28
 
29
+ /**
30
+ * The relief is composed against this theme's own ground: a white
31
+ * highlight top-left, a dark shadow bottom-right. Inside a surface that
32
+ * paints its own colour — `color=` on a card, a coloured bar, a filled
33
+ * alert — the highlight has nothing to blend into and reads as a halo
34
+ * around the control, so the relief is dropped there and the control
35
+ * keeps its flat fill. Vuetify only emits `bg-*` when an author asked
36
+ * for a colour, so untouched surfaces are unaffected.
37
+ */
38
+ & [class*='bg-'] :is(.v-btn.v-btn, .v-chip.v-chip) {
39
+ box-shadow: none;
40
+ }
41
+
29
42
  /* Inputs are recesses, not tiles. */
30
43
  & :is(.v-field--variant-solo, .v-switch__track, .v-slider-track) {
31
44
  background: rgb(var(--v-theme-background));
@@ -50,4 +63,13 @@
50
63
  border-radius: 4px;
51
64
  box-shadow: var(--vw-morph-raised);
52
65
  }
66
+
67
+ /* The timeline is the same groove as the inputs, stood on end. */
68
+ & :is(.v-timeline-divider__before, .v-timeline-divider__after) {
69
+ box-shadow: var(--vw-morph-pressed);
70
+ }
71
+
72
+ & .v-timeline-divider__dot {
73
+ box-shadow: var(--vw-morph-raised);
74
+ }
53
75
  }
@@ -46,11 +46,16 @@
46
46
  box-shadow: 0 0 14px rgba(var(--v-theme-primary), 0.55);
47
47
  }
48
48
 
49
-
50
-
51
-
52
49
  & .v-expansion-panels {
53
50
  box-shadow: 0 0 0 1px rgba(var(--v-theme-primary), 0.12);
54
51
  }
55
52
 
53
+ /* The line is lit filament, and the dot is the lamp. */
54
+ & :is(.v-timeline-divider__before, .v-timeline-divider__after) {
55
+ box-shadow: 0 0 8px rgb(var(--v-theme-secondary));
56
+ }
57
+
58
+ & .v-timeline-divider__dot {
59
+ box-shadow: 0 0 12px rgba(var(--v-theme-primary), 0.9);
60
+ }
56
61
  }
@@ -16,7 +16,6 @@
16
16
  box-shadow: none;
17
17
  }
18
18
 
19
-
20
19
  /* A printed rule under the active tab, not a Material underline. */
21
20
 
22
21
  /* The switch gets an outline for the same reason cards do. */
@@ -28,5 +27,4 @@
28
27
  & :is(.v-table thead th, .v-data-table__th) {
29
28
  border-bottom: 3px double rgba(var(--v-border-color), 0.45);
30
29
  }
31
-
32
30
  }
@@ -25,6 +25,15 @@
25
25
  & .v-tab__slider {
26
26
  border-radius: 8px 3px 8px 3px / 3px 8px 3px 8px;
27
27
  }
28
+
29
+ /**
30
+ * The one thing the panel token cannot carry: ink is never even, so
31
+ * the bottom of the stroke sits heavier. A shorthand sets all four
32
+ * sides, and this is a single longhand on top of it.
33
+ */
34
+ & :is(.v-list, .v-sheet, .v-banner, .v-table):not(:is(.v-card, .v-overlay__content, .v-navigation-drawer, .v-expansion-panels, .v-list) *) {
35
+ border-bottom-width: calc(var(--v-vw-border-width) + 1px);
36
+ }
28
37
  }
29
38
 
30
39
  @media (prefers-reduced-motion: no-preference) {
@@ -19,8 +19,4 @@
19
19
  & .v-app-bar {
20
20
  box-shadow: none;
21
21
  }
22
-
23
-
24
-
25
-
26
22
  }
@@ -7,14 +7,13 @@
7
7
  box-shadow:
8
8
  0 2px 4px rgba(var(--v-theme-primary), 0.06),
9
9
  0 12px 28px rgba(var(--v-theme-primary), 0.1);
10
- transition: box-shadow 0.25s ease, transform 0.25s ease;
10
+ transition: box-shadow var(--v-vw-motion-duration) var(--v-vw-motion-ease);
11
11
  }
12
12
 
13
13
  & .v-card.v-card--variant-elevated.v-card--link:hover {
14
14
  box-shadow:
15
15
  0 4px 8px rgba(var(--v-theme-primary), 0.08),
16
16
  0 20px 40px rgba(var(--v-theme-primary), 0.14);
17
- transform: translateY(-2px);
18
17
  }
19
18
 
20
19
  & .v-btn.v-btn--variant-flat:not(.v-btn--disabled) {
@@ -51,13 +50,3 @@
51
50
  box-shadow: 0 2px 10px rgba(var(--v-theme-primary), 0.2);
52
51
  }
53
52
  }
54
-
55
- @media (prefers-reduced-motion: reduce) {
56
- .v-theme--soft .v-card.v-card--variant-elevated {
57
- transition: none;
58
- }
59
-
60
- .v-theme--soft .v-card.v-card--variant-elevated.v-card--link:hover {
61
- transform: none;
62
- }
63
- }
@@ -0,0 +1,211 @@
1
+ import type { VuetiwatchAccent, VuetiwatchDefaults } from '../types.js'
2
+ import { bars, controls, fields, icons, tables } from '../util/defaults.js'
3
+ import { defineTheme } from '../util/defineTheme.js'
4
+
5
+ /**
6
+ * The admin theme, and the first one here that comes as a family: `atlas`,
7
+ * `atlasDark` and `atlasSepia` share every measurement and differ only in
8
+ * ground, so an app can offer all three and change nothing but the name.
9
+ *
10
+ * It is built for the screen a person keeps open all day. Where `slate`
11
+ * packs a dashboard tight and `graphite` states a product's opinion, this
12
+ * one gets out of the way: comfortable density rather than compact, one
13
+ * low-chroma accent, hairlines instead of shadows, and no capitals, glow or
14
+ * texture anywhere. Nothing on the screen competes with the data on it.
15
+ *
16
+ * Exported alongside the theme are the parts the other two variants reuse —
17
+ * they are the family, not three lookalikes maintained in parallel.
18
+ */
19
+
20
+ /**
21
+ * The accents the family offers, one row per preset and one column per
22
+ * ground. A light ground needs a deep accent to carry a white label; a dark
23
+ * one needs a light accent and a dark label — the Material 3 pairing — so
24
+ * the same preset is a different hex in each variant, and the id is what
25
+ * ties them together when someone switches.
26
+ *
27
+ * Every value here is measured: filled controls clear 4.5:1 in all three.
28
+ */
29
+ const ACCENTS = [
30
+ { id: 'cobalt', title: 'Cobalt', light: ['#0059FF', '#0047CC'], dark: ['#3382FF', '#2A6BD6'], sepia: ['#1150E0', '#0E41B4'] },
31
+ { id: 'indigo', title: 'Indigo', light: ['#3F4E8C', '#323E70'], dark: ['#93A4E8', '#7688D1'], sepia: ['#46508A', '#37406E'] },
32
+ { id: 'eucalyptus', title: 'Eucalyptus', light: ['#2F6B5B', '#245448'], dark: ['#6FB39B', '#57957F'], sepia: ['#3F6B57', '#325544'] },
33
+ { id: 'plum', title: 'Plum', light: ['#6B3F63', '#55324F'], dark: ['#C596BC', '#A87A9F'], sepia: ['#6B4560', '#56374D'] },
34
+ { id: 'clay', title: 'Clay', light: ['#8A5B2B', '#6E4822'], dark: ['#D9A272', '#BC855A'], sepia: ['#8A5B34', '#6E4829'] },
35
+ ] as const
36
+
37
+ /** The accent list for one ground, in the shape `meta.accents` expects. */
38
+ export const atlasAccents = (tone: 'light' | 'dark' | 'sepia'): VuetiwatchAccent[] =>
39
+ ACCENTS.map(accent => {
40
+ const [primary, darken] = accent[tone]
41
+
42
+ return {
43
+ id: accent.id,
44
+ title: accent.title,
45
+ colors: {
46
+ primary,
47
+ 'primary-darken-1': darken,
48
+ // On a dark ground the accent is the light thing on screen, so its
49
+ // label is the ground itself rather than white.
50
+ ...(tone === 'dark' ? { 'on-primary': '#15191B' } : {}),
51
+ },
52
+ }
53
+ })
54
+
55
+ /** Geometry, type and motion. Identical across the family. */
56
+ export const atlasVariables = {
57
+ 'high-emphasis-opacity': 0.88,
58
+ 'medium-emphasis-opacity': 0.6,
59
+ 'hover-opacity': 0.04,
60
+ 'font-body': "'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif",
61
+ // Eight and ten, which is where the admin frameworks people actually
62
+ // like have settled: tight enough to read as a tool, round enough not to
63
+ // look like a spreadsheet.
64
+ 'vw-radius': '8px',
65
+ 'vw-radius-lg': '10px',
66
+ 'vw-radius-chip': '6px',
67
+ 'vw-radius-thumb': '50%',
68
+ 'vw-border-width': '1px',
69
+ // A field's outline is how you find the field: 0.5 puts it just past 3:1
70
+ // on all three grounds, where Vuetify's 0.38 leaves it at about 2.3.
71
+ 'vw-field-border-opacity': '0.5',
72
+ 'vw-btn-transform': 'none',
73
+ 'vw-btn-tracking': '0',
74
+ 'vw-btn-weight': '500',
75
+ 'vw-heading-weight': '600',
76
+ 'vw-heading-tracking': '-0.025em',
77
+ // Column headers are labels, not announcements: weight and a little
78
+ // tracking, no capitals. Three themes here already shout in their tables.
79
+ 'vw-th-weight': '500',
80
+ 'vw-th-tracking': '0.03em',
81
+ // An admin screen is mostly figures, and figures only line up in a column
82
+ // when every digit is the same width.
83
+ 'vw-numeric': 'tabular-nums',
84
+ 'vw-tab-slider-height': '2px',
85
+ 'vw-list-bar': '2px',
86
+ 'vw-list-bar-color': 'rgb(var(--v-theme-primary))',
87
+ // Quiet, quick, and no overshoot — the motion of something you look at
88
+ // for eight hours rather than something you show off.
89
+ 'vw-motion-duration': '160ms',
90
+ 'vw-motion-ease': 'cubic-bezier(0.2, 0, 0.2, 1)',
91
+ 'vw-press-scale': '0.99',
92
+ 'vw-hover-lift': '1px',
93
+ 'vw-pop-scale': '1.08',
94
+ 'vw-pop-duration': '180ms',
95
+ 'vw-theme-transition': '320ms',
96
+ } as const
97
+
98
+ /** Component defaults. Identical across the family. */
99
+ export const atlasDefaults: VuetiwatchDefaults[] = [
100
+ {
101
+ VBtn: { variant: 'flat' },
102
+ VCard: { variant: 'outlined' },
103
+ VAlert: { variant: 'tonal', density: 'compact' },
104
+ VChip: { variant: 'tonal', size: 'small' },
105
+ VNavigationDrawer: { border: 'e' },
106
+ // A row that lights under the cursor is the cheapest way to keep a
107
+ // place in a table of a hundred lines.
108
+ VDataTable: { hover: true },
109
+ /**
110
+ * The picker's 70px header repeats the date it is already showing —
111
+ * it exists for the full-screen dialog on a phone. In a form beside a
112
+ * labelled field it is a third of the control's height saying nothing.
113
+ */
114
+ VDatePicker: { hideHeader: true },
115
+ VTimePicker: { hideHeader: true },
116
+ // Vuetify draws the empty-state glyph at 96px, which is a poster on a
117
+ // panel this size. Enough to read as an illustration, not as a banner.
118
+ VEmptyState: { size: 56 },
119
+ /**
120
+ * A segmented control is one object, not a row of raised buttons: a
121
+ * hairline box with the chosen segment filled. Vuetify's default leaves
122
+ * each button floating on its own elevation.
123
+ */
124
+ VBtnToggle: { variant: 'outlined', divided: true, density: 'compact' },
125
+ },
126
+ // Compact throughout: an admin screen is a working surface, and a field
127
+ // at Vuetify's default height is a third taller than the row it sits in.
128
+ fields({ variant: 'outlined', density: 'compact' }),
129
+ controls({ density: 'compact' }),
130
+ tables('compact'),
131
+ bars({ flat: true, border: 'b', height: 56 }),
132
+ {
133
+ // Not inset: the pill is 52×32 where the plain switch is 36×14, and on
134
+ // a row of settings that difference is the whole line height.
135
+ VSwitch: { inset: false },
136
+ VSlider: { thumbSize: 14, trackSize: 3 },
137
+ },
138
+ icons({
139
+ dropdown: 'mdi-chevron-down',
140
+ collapse: 'mdi-chevron-up',
141
+ checkboxOn: 'mdi-checkbox-marked-outline',
142
+ checkboxOff: 'mdi-checkbox-blank-outline',
143
+ sortAsc: 'mdi-arrow-up',
144
+ sortDesc: 'mdi-arrow-down',
145
+ next: 'mdi-chevron-right',
146
+ prev: 'mdi-chevron-left',
147
+ first: 'mdi-page-first',
148
+ last: 'mdi-page-last',
149
+ clear: 'mdi-close',
150
+ close: 'mdi-close',
151
+ }),
152
+ ]
153
+
154
+ export const atlas = defineTheme({
155
+ name: 'atlas',
156
+ meta: {
157
+ title: 'Atlas',
158
+ description: 'Console light — a white page, hairline panels and one blue that means "do it".',
159
+ dark: false,
160
+ iconStyle: 'outline',
161
+ family: 'atlas',
162
+ variant: 'Light',
163
+ accents: atlasAccents('light'),
164
+ swatch: ['#FDFDFF', '#FFFFFF', '#0059FF', '#57647B'],
165
+ fonts: ['Inter'],
166
+ },
167
+ theme: {
168
+ dark: false,
169
+ colors: {
170
+ /**
171
+ * The ground is white, not grey. Consoles built this decade separate
172
+ * a panel from the page with a line, not with a tint — the tint is
173
+ * saved for the bands inside a panel, which is what `surface-light`
174
+ * is here: table headers, filled rows, the sidebar.
175
+ */
176
+ background: '#FDFDFF',
177
+ surface: '#FFFFFF',
178
+ 'surface-bright': '#FFFFFF',
179
+ // The reference uses this tint at a third of its strength — `bg-muted/30`
180
+ // — so the bands read as a change of paper, not as a coloured stripe.
181
+ 'surface-light': '#F5F8FD',
182
+ 'surface-variant': '#0B101C',
183
+ 'on-surface-variant': '#FDFDFF',
184
+ primary: '#0059FF',
185
+ 'primary-darken-1': '#0047CC',
186
+ secondary: '#57647B',
187
+ 'secondary-darken-1': '#45505F',
188
+ error: '#D91326',
189
+ info: '#0369A1',
190
+ success: '#15803D',
191
+ warning: '#B45309',
192
+ 'on-surface': '#0B101C',
193
+ 'on-background': '#0B101C',
194
+ },
195
+ variables: {
196
+ /**
197
+ * A cool grey line rather than ink at low alpha: ink fades to a
198
+ * neutral grey, and every line here should carry the same blue cast
199
+ * as the surfaces it separates. At 0.22 it lands on #DFE3EB, a shade
200
+ * off the panel it edges.
201
+ */
202
+ 'border-color': '#6E82A6',
203
+ 'border-opacity': 0.22,
204
+ 'shadow-color': '#0B101C',
205
+ 'vw-overlay-border': '1px solid rgba(var(--v-border-color), 0.1)',
206
+ 'vw-overlay-shadow': '0 8px 28px rgba(var(--v-shadow-color), 0.1)',
207
+ ...atlasVariables,
208
+ },
209
+ },
210
+ defaults: atlasDefaults,
211
+ })
@@ -0,0 +1,64 @@
1
+ import { atlasAccents, atlasDefaults, atlasVariables } from './atlas.js'
2
+ import { defineTheme } from '../util/defineTheme.js'
3
+
4
+ /**
5
+ * `atlas` after dark: the same measurements, the same accent hue lifted to
6
+ * where it reads on a dark ground, and a background that is grey rather
7
+ * than black — a panel someone stares at all evening should not be a hole
8
+ * with text floating in it.
9
+ */
10
+ export const atlasDark = defineTheme({
11
+ name: 'atlasDark',
12
+ meta: {
13
+ title: 'Atlas Dark',
14
+ description: 'The same console after dark — deep navy, a lit blue, lines you can find.',
15
+ dark: true,
16
+ iconStyle: 'outline',
17
+ family: 'atlas',
18
+ variant: 'Dark',
19
+ accents: atlasAccents('dark'),
20
+ swatch: ['#040817', '#070E21', '#3382FF', '#838FA8'],
21
+ fonts: ['Inter'],
22
+ },
23
+ theme: {
24
+ dark: true,
25
+ colors: {
26
+ background: '#040817',
27
+ surface: '#070E21',
28
+ 'surface-bright': '#101830',
29
+ 'surface-light': '#151E35',
30
+ 'surface-variant': '#CBD5E1',
31
+ 'on-surface-variant': '#040817',
32
+ primary: '#3382FF',
33
+ 'primary-darken-1': '#2A6BD6',
34
+ secondary: '#838FA8',
35
+ 'secondary-darken-1': '#6B778F',
36
+ error: '#F94144',
37
+ info: '#7DD3FC',
38
+ success: '#4ADE80',
39
+ warning: '#FBBF24',
40
+ 'on-surface': '#EEF2F9',
41
+ 'on-background': '#EEF2F9',
42
+ /**
43
+ * A dark theme's accents are the light thing on the screen, so their
44
+ * labels are the ground colour rather than white — the Material 3
45
+ * convention, and the only option that clears 4.5:1 here. Vuetify's
46
+ * automatic pick lands on white at about 2.5:1.
47
+ */
48
+ 'on-primary': '#040817',
49
+ 'on-secondary': '#040817',
50
+ 'on-error': '#040817',
51
+ },
52
+ variables: {
53
+ // Lands on #1F283E over the panel — the line is visible without
54
+ // becoming a grid.
55
+ 'border-color': '#8FA0C4',
56
+ 'border-opacity': 0.18,
57
+ 'shadow-color': '#000000',
58
+ 'vw-overlay-border': '1px solid rgba(var(--v-border-color), 0.16)',
59
+ 'vw-overlay-shadow': '0 12px 32px rgba(0, 0, 0, 0.5)',
60
+ ...atlasVariables,
61
+ },
62
+ },
63
+ defaults: atlasDefaults,
64
+ })
@@ -0,0 +1,60 @@
1
+ import { atlasAccents, atlasDefaults, atlasVariables } from './atlas.js'
2
+ import { defineTheme } from '../util/defineTheme.js'
3
+
4
+ /**
5
+ * The third ground: warm paper rather than white or grey.
6
+ *
7
+ * Neither light nor dark mode helps the person who finds a white panel too
8
+ * bright and a dark one too flat. Dropping the blue out of the ground and
9
+ * the ink lowers the glare without lowering the contrast — the text still
10
+ * carries better than 12:1 — and the accent turns a shade warmer to sit on
11
+ * it. It is the same panel, lit by a lamp instead of a window.
12
+ */
13
+ export const atlasSepia = defineTheme({
14
+ name: 'atlasSepia',
15
+ meta: {
16
+ title: 'Atlas Sepia',
17
+ description: 'Warm paper and brown ink — the same panel, easier on tired eyes.',
18
+ dark: false,
19
+ iconStyle: 'outline',
20
+ family: 'atlas',
21
+ variant: 'Sepia',
22
+ accents: atlasAccents('sepia'),
23
+ swatch: ['#F7F3EC', '#FFFDF9', '#1150E0', '#7A6A52'],
24
+ fonts: ['Inter'],
25
+ },
26
+ theme: {
27
+ dark: false,
28
+ colors: {
29
+ background: '#F7F3EC',
30
+ surface: '#FFFDF9',
31
+ 'surface-bright': '#FFFFFC',
32
+ 'surface-light': '#F4EEE3',
33
+ 'surface-variant': '#241E16',
34
+ 'on-surface-variant': '#F7F3EC',
35
+ // Ink-blue on warm paper: the family's accent, in the register the
36
+ // ground is written in.
37
+ primary: '#1150E0',
38
+ 'primary-darken-1': '#0E41B4',
39
+ secondary: '#7A6A52',
40
+ 'secondary-darken-1': '#615341',
41
+ error: '#A83A2C',
42
+ info: '#356B86',
43
+ success: '#3B6B4A',
44
+ warning: '#8A6118',
45
+ 'on-surface': '#241E16',
46
+ 'on-background': '#241E16',
47
+ },
48
+ variables: {
49
+ // The same idea as the light variant, in the warm register: the line
50
+ // lands on #E0D7C8 rather than on a grey.
51
+ 'border-color': '#8A7A63',
52
+ 'border-opacity': 0.28,
53
+ 'shadow-color': '#3B332A',
54
+ 'vw-overlay-border': '1px solid rgba(var(--v-border-color), 0.12)',
55
+ 'vw-overlay-shadow': '0 8px 28px rgba(var(--v-shadow-color), 0.14)',
56
+ ...atlasVariables,
57
+ },
58
+ },
59
+ defaults: atlasDefaults,
60
+ })
@@ -16,7 +16,7 @@ export const aurora = defineTheme({
16
16
  description: 'Iridescent gradients on near-black. Built to be looked at — the showcase theme.',
17
17
  dark: true,
18
18
  iconStyle: 'filled',
19
- swatch: ['#08080C', '#101018', '#7C5CFF', '#00D4FF'],
19
+ swatch: ['#08080C', '#101018', '#7857FF', '#00D4FF'],
20
20
  fonts: ['Inter'],
21
21
  },
22
22
  theme: {
@@ -28,7 +28,7 @@ export const aurora = defineTheme({
28
28
  'surface-light': '#16161F',
29
29
  'surface-variant': '#C9C6E0',
30
30
  'on-surface-variant': '#08080C',
31
- primary: '#7C5CFF',
31
+ primary: '#7857FF',
32
32
  'primary-darken-1': '#5F3FE0',
33
33
  secondary: '#00D4FF',
34
34
  'secondary-darken-1': '#00A8CC',
@@ -38,11 +38,20 @@ export const aurora = defineTheme({
38
38
  warning: '#FFB86B',
39
39
  'on-surface': '#E8E6F5',
40
40
  'on-background': '#F0EEFA',
41
+ // Vuetify picks the label colour by luminance and settles for
42
+ // about 3:1, which is the bar for large text rather than for a
43
+ // button label. Measured on the filled buttons: error 3:1 → 7.1:1.
44
+ 'on-error': '#000000',
41
45
  'on-secondary': '#04121A',
42
46
  'on-success': '#04140D',
43
47
  'on-warning': '#1A1004',
44
48
  },
45
49
  variables: {
50
+ /* A panel standing on its own — a bare list, sheet, banner or table.
51
+ Core owns the selector; this is what it draws with. */
52
+ 'vw-panel-image':
53
+ 'radial-gradient(36rem 18rem at 0% 0%, rgba(124, 92, 255, 0.14), transparent 70%)',
54
+ 'vw-panel-shadow': '0 12px 40px rgba(0, 0, 0, 0.55)',
46
55
  'border-color': '#9B8CFF',
47
56
  'border-opacity': 0.14,
48
57
  'shadow-color': '#000000',
@@ -61,6 +70,25 @@ export const aurora = defineTheme({
61
70
  // Shared by every gradient below, so a derived theme restyles the
62
71
  // whole surface language by overriding one value.
63
72
  'vw-gradient': 'linear-gradient(135deg, #7C5CFF 0%, #00D4FF 55%, #FF5CAA 100%)',
73
+ /**
74
+ * The same three hues at a depth that carries a white label: the
75
+ * showcase gradient runs through a cyan where white text reads at
76
+ * 1.8:1, which is fine behind a tab slider and unusable behind a
77
+ * word. Anything with text on it takes this one instead.
78
+ */
79
+ 'vw-gradient-btn': 'linear-gradient(135deg, #6F4CFF 0%, #007891 55%, #DA0069 100%)',
80
+
81
+ // The showcase theme, so the motion shows off too.
82
+ 'vw-motion-duration': '300ms',
83
+ 'vw-press-scale': '0.97',
84
+ 'vw-hover-lift': '3px',
85
+ 'vw-pop-scale': '1.28',
86
+ 'vw-pop-duration': '300ms',
87
+ 'vw-theme-transition': '560ms',
88
+
89
+ // The line is the theme's own gradient, top to bottom.
90
+ 'vw-timeline-line': 'var(--v-vw-gradient)',
91
+ 'vw-timeline-line-width': '2px',
64
92
  },
65
93
  },
66
94
  defaults: [
@@ -38,11 +38,21 @@ export const brutalist = defineTheme({
38
38
  warning: '#FFB300',
39
39
  'on-surface': '#0A0A0A',
40
40
  'on-background': '#0A0A0A',
41
+ // Vuetify picks the label colour by luminance and settles for
42
+ // about 3:1, which is the bar for large text rather than for a
43
+ // button label. Measured on the filled buttons: info 3.8:1 → 5.2:1, error 3.3:1 → 6:1.
44
+ 'on-info': '#0A0A0A',
45
+ 'on-error': '#0A0A0A',
41
46
  'on-secondary': '#0A0A0A',
42
47
  'on-warning': '#0A0A0A',
43
48
  'on-success': '#0A0A0A',
44
49
  },
45
50
  variables: {
51
+ /* A panel standing on its own — a bare list, sheet, banner or table.
52
+ Core owns the selector; this is what it draws with. */
53
+ 'vw-panel-border': 'var(--v-vw-border-width) solid rgb(var(--v-shadow-color))',
54
+ 'vw-panel-shadow':
55
+ 'var(--vw-brut-offset) var(--vw-brut-offset) 0 rgb(var(--v-shadow-color))',
46
56
  'border-color': '#0A0A0A',
47
57
  'border-opacity': 1,
48
58
  'shadow-color': '#0A0A0A',
@@ -54,6 +64,8 @@ export const brutalist = defineTheme({
54
64
  'vw-radius-lg': '0px',
55
65
  'vw-radius-thumb': '0px',
56
66
  'vw-radius-chip': '0px',
67
+ // Nothing here is round, the icon buttons included.
68
+ 'vw-radius-icon-btn': '0px',
57
69
  'vw-border-width': '3px',
58
70
  'vw-btn-transform': 'uppercase',
59
71
  'vw-btn-tracking': '0.04em',
@@ -65,6 +77,23 @@ export const brutalist = defineTheme({
65
77
  'vw-heading-weight': '700',
66
78
  'vw-heading-tracking': '-0.02em',
67
79
  'vw-tab-slider-height': '4px',
80
+
81
+ // Two frames, no curve. The press is the theme's own slide into its
82
+ // shadow, so the shared squash stays out of its way.
83
+ 'vw-motion-duration': '80ms',
84
+ 'vw-motion-ease': 'steps(2, end)',
85
+ 'vw-press-scale': '1',
86
+ 'vw-pop-scale': '1.3',
87
+ 'vw-pop-duration': '100ms',
88
+ 'vw-focus-ring': '3px solid rgb(var(--v-shadow-color))',
89
+ 'vw-focus-offset': '3px',
90
+ 'vw-theme-transition': '200ms',
91
+
92
+ // A structural beam, and the dot is a square like everything else.
93
+ 'vw-timeline-line': 'rgb(var(--v-shadow-color))',
94
+ 'vw-timeline-line-width': '4px',
95
+ 'vw-radius-dot': '0px',
96
+ 'vw-radius-avatar': '0px',
68
97
  },
69
98
  },
70
99
  defaults: [
@@ -17,7 +17,7 @@ export const calm = defineTheme({
17
17
  description: 'Low contrast, desaturated naturals and a lot of air. Nothing competes for attention.',
18
18
  dark: false,
19
19
  iconStyle: 'outline',
20
- swatch: ['#FAFAF7', '#FFFFFF', '#6B7A6B', '#A89684'],
20
+ swatch: ['#FAFAF7', '#FFFFFF', '#6B7A6B', '#86725F'],
21
21
  fonts: ['Inter'],
22
22
  },
23
23
  theme: {
@@ -31,7 +31,7 @@ export const calm = defineTheme({
31
31
  'on-surface-variant': '#FAFAF7',
32
32
  primary: '#6B7A6B',
33
33
  'primary-darken-1': '#556355',
34
- secondary: '#A89684',
34
+ secondary: '#86725F',
35
35
  'secondary-darken-1': '#8B7A69',
36
36
  error: '#A86B63',
37
37
  info: '#6B7A8A',
@@ -39,8 +39,19 @@ export const calm = defineTheme({
39
39
  warning: '#A89464',
40
40
  'on-surface': '#3A3A36',
41
41
  'on-background': '#3A3A36',
42
+ // Vuetify picks the label colour by luminance and settles for
43
+ // about 3:1, which is the bar for large text rather than for a
44
+ // button label. Measured on the filled buttons: success 3.8:1 → 5.5:1, info 4.4:1 → 4.8:1, warning 3:1 → 7.1:1, error 4.2:1 → 4.9:1.
45
+ 'on-success': '#000000',
46
+ 'on-info': '#000000',
47
+ 'on-warning': '#000000',
48
+ 'on-error': '#000000',
42
49
  },
43
50
  variables: {
51
+ /* A panel standing on its own — a bare list, sheet, banner or table.
52
+ Core owns the selector; this is what it draws with. */
53
+ 'vw-panel-border':
54
+ 'var(--v-vw-border-width) solid rgba(var(--v-border-color), var(--v-border-opacity))',
44
55
  'border-color': '#3A3A36',
45
56
  'border-opacity': 0.09,
46
57
  'high-emphasis-opacity': 0.78,
@@ -57,6 +68,18 @@ export const calm = defineTheme({
57
68
  'vw-heading-weight': '500',
58
69
  'vw-heading-tracking': '-0.01em',
59
70
  'vw-tab-slider-height': '2px',
71
+
72
+ // Slow enough that nothing ever catches the eye.
73
+ 'vw-motion-duration': '320ms',
74
+ 'vw-press-scale': '0.99',
75
+ 'vw-hover-lift': '2px',
76
+ 'vw-pop-scale': '1.08',
77
+ 'vw-pop-duration': '280ms',
78
+ 'vw-theme-transition': '560ms',
79
+
80
+ // Barely there, like everything else in this theme.
81
+ 'vw-timeline-line': 'rgba(var(--v-border-color), 0.5)',
82
+ 'vw-timeline-line-width': '1px',
60
83
  },
61
84
  },
62
85
  defaults: [