@saasicat/ui-vue 0.22.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-LY52T4N4.js → chunk-TQQMKWN6.js} +58 -1
- package/dist/{chunk-XMMFN2WJ.js → chunk-VYMTWCTT.js} +51 -1
- package/dist/client/index.cjs +57 -0
- package/dist/client/index.d.cts +58 -1
- package/dist/client/index.d.ts +58 -1
- package/dist/client/index.js +15 -1
- package/dist/index.cjs +321 -159
- package/dist/index.d.cts +67 -4
- package/dist/index.d.ts +67 -4
- package/dist/index.js +72 -2
- package/dist/quasar/index.cjs +116 -20
- package/dist/quasar/index.d.cts +48 -3
- package/dist/quasar/index.d.ts +48 -3
- package/dist/quasar/index.js +58 -3
- package/dist/{use-super-admin-i18n-CgTuNi8h.d.cts → use-sa-theme-B5t7oXph.d.cts} +51 -1
- package/dist/{use-super-admin-i18n-CHNdsSlm.d.ts → use-sa-theme-OUBlaqgl.d.ts} +51 -1
- package/package.json +5 -2
- package/src/client/identity-accents.ts +129 -0
- package/src/client/index.ts +1 -0
- package/src/components/BundleVersionPublishDialog.vue +17 -17
- package/src/components/KvBlock.vue +6 -6
- package/src/components/MarketingPromotionsTab.vue +84 -80
- package/src/components/MfaPromptDialog.vue +2 -2
- package/src/components/TenantActionConfirmDialog.vue +5 -5
- package/src/components/VersionDiffPreview.vue +18 -18
- package/src/components/bundle-editor/BundleCreatePanel.vue +52 -52
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +20 -20
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +33 -33
- package/src/components/bundle-editor/BundleQuotasEditor.vue +26 -26
- package/src/components/bundle-editor/BundleStatusBanner.vue +21 -21
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +49 -49
- package/src/components/bundle-editor/BundleVersionStrip.vue +30 -30
- package/src/components/dialogs/PilotCreateDialog.vue +20 -20
- package/src/components/dialogs/PilotEditDialog.vue +6 -6
- package/src/components/dialogs/PromoCodeCreateDialog.vue +9 -9
- package/src/components/dialogs/PromoCodeDialogFields.vue +64 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +11 -11
- package/src/components/dialogs/pilot-dialog.css +48 -48
- package/src/components/plan/PlanCycleToggle.vue +9 -16
- package/src/components/plan/PlanGrid.vue +30 -34
- package/src/components/plan/PriceSummary.vue +35 -35
- package/src/components/plan/PromoCodeInput.vue +26 -26
- package/src/components/plan/PublicBundleGrid.vue +32 -28
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +50 -50
- package/src/components/plan-detail/PlanDetail.vue +178 -157
- package/src/components/plan-detail/PlanTitleEdit.vue +11 -11
- package/src/components/plan-list/PlanList.vue +116 -100
- package/src/components/plan-matrix/PlanMatrix.vue +97 -114
- package/src/components/plan-review/PlanReview.vue +61 -57
- package/src/components/plan-version-editor/PlanVersionEditor.vue +246 -203
- package/src/components/wizard-stepper/WizardStepper.vue +17 -17
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +58 -37
- package/src/pages-standard/AdminManifestErrorPage.vue +6 -6
- package/src/pages-standard/AuditPage.vue +3 -3
- package/src/pages-standard/BundlesPage.vue +37 -37
- package/src/pages-standard/DashboardPage.vue +22 -19
- package/src/pages-standard/DiscoveryPage.vue +55 -49
- package/src/pages-standard/EmailHistoryPage.vue +53 -71
- package/src/pages-standard/MarketingCatalogPage.vue +245 -221
- package/src/pages-standard/PilotsPage.vue +7 -6
- package/src/pages-standard/PlatformEmailPage.vue +18 -18
- package/src/pages-standard/PromoCodeDetailPage.vue +3 -3
- package/src/pages-standard/PromoCodesPage.vue +10 -7
- package/src/pages-standard/SuperAdminLoginPage.vue +39 -25
- package/src/pages-standard/SuperAdminSetupWizard.vue +44 -33
- package/src/pages-standard/TenantDetailPage.vue +7 -7
- package/src/pages-standard/TenantsPage.vue +16 -50
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +13 -13
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +24 -24
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +22 -22
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +39 -39
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +26 -26
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +1 -1
- package/src/pages-standard/discovery-page/discovery-ui.ts +10 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +2 -5
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +27 -20
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +24 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +45 -45
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +21 -15
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +4 -4
- package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
- package/src/pages-standard/sa-theme.css +9 -687
- package/src/pages-standard/tenants/StatusPill.vue +13 -13
- package/src/pages-standard/tenants/format.ts +11 -12
- package/src/pages-tenant/LimitsRow.vue +4 -4
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +70 -61
- package/src/pages-tenant/OnboardingConfigurator.vue +25 -25
- package/src/pages-tenant/PackageSnapshotPanel.vue +10 -16
- package/src/pages-tenant/PendingVersionBanner.vue +12 -12
- package/src/pages-tenant/PlanChangeWizard.vue +22 -76
- package/src/pages-tenant/TenantPlanSection.vue +19 -69
- package/src/pages-tenant/UsageBar.vue +7 -7
- package/src/pages-tenant/default-i18n.ts +113 -0
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +14 -14
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +10 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +2 -2
- package/src/quasar/create-super-admin-app.ts +96 -1
- package/src/quasar/dark-bridge.ts +66 -0
- package/src/quasar/index.ts +1 -0
- package/src/ui/theme/_breakpoints.scss +34 -0
- package/src/ui/theme/base.css +50 -0
- package/src/ui/theme/components/body.css +23 -0
- package/src/ui/theme/components/button.css +158 -0
- package/src/ui/theme/components/card.css +74 -0
- package/src/ui/theme/components/field.css +38 -0
- package/src/ui/theme/components/hero.css +46 -0
- package/src/ui/theme/components/paginator.css +63 -0
- package/src/ui/theme/components/section.css +67 -0
- package/src/ui/theme/components/statistics.css +189 -0
- package/src/ui/theme/components/table.css +60 -0
- package/src/ui/theme/index.css +42 -0
- package/src/ui/theme/tokens.legacy.css +147 -0
- package/src/ui/theme/tokens.primitive.css +112 -0
- package/src/ui/theme/tokens.scale.css +103 -0
- package/src/ui/theme/tokens.semantic.dark.css +185 -0
- package/src/ui/theme/tokens.semantic.light.css +242 -0
- package/src/vue/use-sa-theme.ts +154 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* The pager below a list (see AdminPaginator). Right-aligned, one rule, so a
|
|
2
|
+
* list contributes its counts and nothing about where paging sits.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.sa-paginator {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: flex-end;
|
|
9
|
+
gap: 8px;
|
|
10
|
+
padding: 12px 0 0;
|
|
11
|
+
font-size: var(--sa-text-md);
|
|
12
|
+
color: var(--sa-color-fg-muted);
|
|
13
|
+
}
|
|
14
|
+
.sa-paginator__size-label {
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
}
|
|
17
|
+
.sa-paginator__select {
|
|
18
|
+
min-width: 92px;
|
|
19
|
+
}
|
|
20
|
+
.sa-paginator__step,
|
|
21
|
+
.sa-paginator__page {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
min-height: 30px;
|
|
26
|
+
padding: 0 8px;
|
|
27
|
+
border: 1px solid var(--sa-color-border);
|
|
28
|
+
border-radius: var(--sa-radius-control);
|
|
29
|
+
background: var(--sa-color-bg-surface);
|
|
30
|
+
color: var(--sa-color-fg-body);
|
|
31
|
+
font: inherit;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition:
|
|
34
|
+
background 0.12s,
|
|
35
|
+
border-color 0.12s;
|
|
36
|
+
}
|
|
37
|
+
.sa-paginator__page {
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: var(--sa-color-fg-heading);
|
|
40
|
+
min-width: 96px;
|
|
41
|
+
}
|
|
42
|
+
.sa-paginator__step:hover:not(:disabled),
|
|
43
|
+
.sa-paginator__page:hover:not(:disabled) {
|
|
44
|
+
background: var(--sa-color-bg-sunken);
|
|
45
|
+
border-color: var(--sa-color-border-strong);
|
|
46
|
+
}
|
|
47
|
+
.sa-paginator__step:disabled,
|
|
48
|
+
.sa-paginator__page:disabled {
|
|
49
|
+
opacity: 0.45;
|
|
50
|
+
cursor: default;
|
|
51
|
+
}
|
|
52
|
+
.sa-paginator__input {
|
|
53
|
+
min-width: 96px;
|
|
54
|
+
min-height: 30px;
|
|
55
|
+
padding: 0 8px;
|
|
56
|
+
border: 1px solid var(--sa-color-accent);
|
|
57
|
+
border-radius: var(--sa-radius-control);
|
|
58
|
+
background: var(--sa-color-bg-surface);
|
|
59
|
+
color: var(--sa-color-fg-heading);
|
|
60
|
+
font: inherit;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Section — a labelled block within a page (see AdminSection). It carries the
|
|
2
|
+
* surface: the page frame is transparent, so what a reader sees as "a block of
|
|
3
|
+
* this page" is exactly one section. A section that needs an inner card still
|
|
4
|
+
* adds `.sa-card`; a section IS one, so the two do not stack.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Rhythm only for sections sitting directly in the page flow. Deliberately a
|
|
8
|
+
* child selector: sections placed inside a grid or flex container get their
|
|
9
|
+
* spacing from that container, and a blanket margin would fight it. */
|
|
10
|
+
.sa-page > .sa-section,
|
|
11
|
+
.sa-page-body > .sa-section {
|
|
12
|
+
margin-bottom: 16px;
|
|
13
|
+
}
|
|
14
|
+
.sa-page > .sa-section:last-child,
|
|
15
|
+
.sa-page-body > .sa-section:last-child {
|
|
16
|
+
margin-bottom: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sa-section {
|
|
20
|
+
background: var(--sa-color-bg-surface);
|
|
21
|
+
border: 1px solid var(--sa-color-border);
|
|
22
|
+
border-radius: var(--sa-radius-section);
|
|
23
|
+
}
|
|
24
|
+
.sa-section__body {
|
|
25
|
+
padding: var(--sa-pad-section);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* The head is the hero's block one level down: same typography, same 22px
|
|
29
|
+
* inset — so a section title sits exactly under the page title — but no tinted
|
|
30
|
+
* box of its own. The section already is a surface; a box inside it would read
|
|
31
|
+
* as a panel within a panel.
|
|
32
|
+
*
|
|
33
|
+
* The size steps down because the level does. Everything else is the hero's. */
|
|
34
|
+
.sa-section__head {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
gap: 20px;
|
|
38
|
+
padding: 18px 22px 0;
|
|
39
|
+
}
|
|
40
|
+
.sa-section__title {
|
|
41
|
+
margin: 0;
|
|
42
|
+
font-family: var(--sa-font-head);
|
|
43
|
+
font-size: var(--sa-text-xl);
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
letter-spacing: -0.02em;
|
|
46
|
+
line-height: 1.25;
|
|
47
|
+
color: var(--sa-color-fg-heading);
|
|
48
|
+
}
|
|
49
|
+
.sa-section__sub {
|
|
50
|
+
margin: 1px 0 0;
|
|
51
|
+
font-size: var(--sa-text-md);
|
|
52
|
+
line-height: 1.35;
|
|
53
|
+
color: var(--sa-color-fg-muted);
|
|
54
|
+
}
|
|
55
|
+
.sa-section__actions {
|
|
56
|
+
margin-left: auto;
|
|
57
|
+
display: flex;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
align-items: center;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Key-value grid — the layout `KvBlock` is meant to sit in. */
|
|
63
|
+
.sa-kv-grid {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
66
|
+
gap: 12px;
|
|
67
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* Statistics — one tile look for the whole admin surface (see AdminStatistics
|
|
2
|
+
* and AdminKpi). Seven near-copies preceded this; two of them were byte
|
|
3
|
+
* identical. Every radius here resolves to `--sa-radius-tile`, which is the
|
|
4
|
+
* single place a tile's corner is decided.
|
|
5
|
+
*
|
|
6
|
+
* Two arrangements, because the tiles serve two jobs: `grid` for informational
|
|
7
|
+
* rows that should share a baseline, `strip` for the clickable filter pills
|
|
8
|
+
* that wrap along a toolbar.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.sa-stats {
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
14
|
+
/* Kept in step with GAP_PX in AdminStatistics, which subtracts the gap
|
|
15
|
+
* total when it caps the track count. */
|
|
16
|
+
gap: 10px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sa-kpi {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: stretch;
|
|
22
|
+
gap: 12px;
|
|
23
|
+
background: var(--sa-color-bg-surface);
|
|
24
|
+
border: 1px solid var(--sa-color-border);
|
|
25
|
+
border-radius: var(--sa-radius-tile);
|
|
26
|
+
padding: 10px 14px 9px;
|
|
27
|
+
font: inherit;
|
|
28
|
+
color: inherit;
|
|
29
|
+
text-align: left;
|
|
30
|
+
}
|
|
31
|
+
/* The figure column. It carries the stacking so the tile itself can put an
|
|
32
|
+
* icon beside it without a second set of rules. */
|
|
33
|
+
.sa-kpi__text {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
gap: 2px;
|
|
38
|
+
flex: 1;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
/* Subs line up across a row rather than floating under uneven values. */
|
|
42
|
+
.sa-stats .sa-kpi__sub {
|
|
43
|
+
margin-top: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sa-kpi__label {
|
|
47
|
+
font-size: var(--sa-text-sm);
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
color: var(--sa-color-fg-muted);
|
|
50
|
+
order: 2;
|
|
51
|
+
}
|
|
52
|
+
.sa-kpi__value {
|
|
53
|
+
font-size: var(--sa-text-2xl);
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
letter-spacing: -0.02em;
|
|
56
|
+
line-height: 1.05;
|
|
57
|
+
color: var(--sa-color-fg-heading);
|
|
58
|
+
order: 1;
|
|
59
|
+
}
|
|
60
|
+
.sa-kpi__sub {
|
|
61
|
+
font: 500 var(--sa-text-2xs) var(--sa-font-mono);
|
|
62
|
+
letter-spacing: 0.04em;
|
|
63
|
+
color: var(--sa-color-fg-muted);
|
|
64
|
+
padding-top: 1px;
|
|
65
|
+
order: 3;
|
|
66
|
+
}
|
|
67
|
+
.sa-kpi__icon {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
color: var(--sa-color-fg-muted);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Inline tiles: the icon leads in a tinted square and the label sits above the
|
|
75
|
+
* figure, for rows where the icon carries the identity of the number. */
|
|
76
|
+
.sa-kpi--inline {
|
|
77
|
+
align-items: center;
|
|
78
|
+
padding: 12px 14px;
|
|
79
|
+
}
|
|
80
|
+
.sa-kpi--inline .sa-kpi__icon {
|
|
81
|
+
width: 34px;
|
|
82
|
+
height: 34px;
|
|
83
|
+
flex: none;
|
|
84
|
+
border-radius: var(--sa-radius-tile);
|
|
85
|
+
background: var(--sa-color-accent-surface-strong);
|
|
86
|
+
color: var(--sa-color-accent);
|
|
87
|
+
}
|
|
88
|
+
.sa-kpi--inline .sa-kpi__label {
|
|
89
|
+
order: 1;
|
|
90
|
+
text-transform: uppercase;
|
|
91
|
+
letter-spacing: 0.06em;
|
|
92
|
+
}
|
|
93
|
+
.sa-kpi--inline .sa-kpi__value {
|
|
94
|
+
order: 2;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Interactive tiles — rendered as <button> by AdminKpi when it has an action. */
|
|
98
|
+
.sa-kpi--action {
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition:
|
|
101
|
+
border-color 0.12s,
|
|
102
|
+
background 0.12s,
|
|
103
|
+
box-shadow 0.12s;
|
|
104
|
+
}
|
|
105
|
+
.sa-kpi--action:hover {
|
|
106
|
+
background: var(--sa-color-bg-sunken);
|
|
107
|
+
border-color: var(--sa-color-border-strong);
|
|
108
|
+
}
|
|
109
|
+
.sa-kpi--selected {
|
|
110
|
+
border-color: var(--sa-color-fg-heading);
|
|
111
|
+
box-shadow:
|
|
112
|
+
0 0 0 1px var(--sa-color-fg-heading) inset,
|
|
113
|
+
var(--sa-shadow-2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Tone. By default only the value takes the colour; `--surface` tints the
|
|
117
|
+
* whole tile, which is what the discovery and draft tiles need to read as a
|
|
118
|
+
* state rather than as a number. */
|
|
119
|
+
.sa-kpi--positive .sa-kpi__value {
|
|
120
|
+
color: var(--sa-color-positive);
|
|
121
|
+
}
|
|
122
|
+
.sa-kpi--warn .sa-kpi__value {
|
|
123
|
+
color: var(--sa-color-warning);
|
|
124
|
+
}
|
|
125
|
+
.sa-kpi--danger .sa-kpi__value {
|
|
126
|
+
color: var(--sa-color-negative);
|
|
127
|
+
}
|
|
128
|
+
.sa-kpi--info .sa-kpi__value {
|
|
129
|
+
color: var(--sa-color-info-fg);
|
|
130
|
+
}
|
|
131
|
+
.sa-kpi--muted .sa-kpi__value {
|
|
132
|
+
color: var(--sa-color-fg-secondary);
|
|
133
|
+
}
|
|
134
|
+
.sa-kpi--purple .sa-kpi__value {
|
|
135
|
+
color: var(--sa-color-feature-fg);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.sa-kpi--surface.sa-kpi--positive {
|
|
139
|
+
border-color: var(--sa-color-positive-border);
|
|
140
|
+
background: var(--sa-color-positive-surface);
|
|
141
|
+
}
|
|
142
|
+
.sa-kpi--surface.sa-kpi--warn {
|
|
143
|
+
border-color: var(--sa-color-warning-border);
|
|
144
|
+
background: var(--sa-color-warning-surface);
|
|
145
|
+
}
|
|
146
|
+
.sa-kpi--surface.sa-kpi--danger {
|
|
147
|
+
border-color: var(--sa-color-negative-border);
|
|
148
|
+
background: var(--sa-color-negative-surface);
|
|
149
|
+
}
|
|
150
|
+
.sa-kpi--surface.sa-kpi--positive .sa-kpi__label,
|
|
151
|
+
.sa-kpi--surface.sa-kpi--warn .sa-kpi__label,
|
|
152
|
+
.sa-kpi--surface.sa-kpi--danger .sa-kpi__label {
|
|
153
|
+
color: inherit;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Selected tiles keep the tone in the outline and take the neutral elevation.
|
|
157
|
+
* Five tinted shadows preceded this — one per tone, each a different rgba() —
|
|
158
|
+
* and at 6% alpha under a 1px outline none of them was distinguishable from
|
|
159
|
+
* the others. The outline is what carries the state. */
|
|
160
|
+
.sa-kpi--selected.sa-kpi--positive {
|
|
161
|
+
border-color: var(--sa-color-positive-strong);
|
|
162
|
+
box-shadow:
|
|
163
|
+
0 0 0 1px var(--sa-color-positive-strong) inset,
|
|
164
|
+
var(--sa-shadow-2);
|
|
165
|
+
}
|
|
166
|
+
.sa-kpi--selected.sa-kpi--warn {
|
|
167
|
+
border-color: var(--sa-color-warning-strong);
|
|
168
|
+
box-shadow:
|
|
169
|
+
0 0 0 1px var(--sa-color-warning-strong) inset,
|
|
170
|
+
var(--sa-shadow-2);
|
|
171
|
+
}
|
|
172
|
+
.sa-kpi--selected.sa-kpi--danger {
|
|
173
|
+
border-color: var(--sa-color-negative);
|
|
174
|
+
box-shadow:
|
|
175
|
+
0 0 0 1px var(--sa-color-negative) inset,
|
|
176
|
+
var(--sa-shadow-2);
|
|
177
|
+
}
|
|
178
|
+
.sa-kpi--selected.sa-kpi--info {
|
|
179
|
+
border-color: var(--sa-color-info-strong);
|
|
180
|
+
box-shadow:
|
|
181
|
+
0 0 0 1px var(--sa-color-info-strong) inset,
|
|
182
|
+
var(--sa-shadow-2);
|
|
183
|
+
}
|
|
184
|
+
.sa-kpi--selected.sa-kpi--purple {
|
|
185
|
+
border-color: var(--sa-color-feature);
|
|
186
|
+
box-shadow:
|
|
187
|
+
0 0 0 1px var(--sa-color-feature) inset,
|
|
188
|
+
var(--sa-shadow-2);
|
|
189
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* Lists (see AdminTable). The package shipped no table rules at all until
|
|
2
|
+
* this, which is why nine pages each looked slightly different.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* The surface under the rows. Measured on the promo-codes page in dark mode:
|
|
6
|
+
* the `.q-table__container.q-table__card.q-dark` element resolved to
|
|
7
|
+
* rgb(29, 29, 29) while the section around it was rgb(15, 23, 42) — Quasar's
|
|
8
|
+
* neutral grey inside our slate. Only `thead th` and `tbody td` were ours, and
|
|
9
|
+
* neither of them is the table's background. */
|
|
10
|
+
.sa-page .q-table__container,
|
|
11
|
+
.sa-portal .q-table__container {
|
|
12
|
+
background: var(--sa-color-bg-surface);
|
|
13
|
+
color: var(--sa-color-fg-body);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.sa-page .q-table thead th {
|
|
17
|
+
font-size: var(--sa-text-xs);
|
|
18
|
+
font-weight: 700;
|
|
19
|
+
letter-spacing: 0.04em;
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
color: var(--sa-color-fg-muted);
|
|
22
|
+
background: var(--sa-color-bg-sunken);
|
|
23
|
+
border-bottom: 1px solid var(--sa-color-border);
|
|
24
|
+
padding: 10px 14px;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
}
|
|
27
|
+
.sa-page .q-table tbody td {
|
|
28
|
+
font-size: var(--sa-text-md);
|
|
29
|
+
color: var(--sa-color-fg-body);
|
|
30
|
+
border-bottom: 1px solid var(--sa-color-border-soft);
|
|
31
|
+
padding: 10px 14px;
|
|
32
|
+
}
|
|
33
|
+
.sa-page .q-table tbody tr:last-child td {
|
|
34
|
+
border-bottom: 0;
|
|
35
|
+
}
|
|
36
|
+
.sa-page .q-table tbody tr:hover td {
|
|
37
|
+
background: var(--sa-color-bg-sunken);
|
|
38
|
+
}
|
|
39
|
+
/* Sorted column: the header carries the state, not just the arrow. */
|
|
40
|
+
.sa-page .q-table thead th.sortable:hover,
|
|
41
|
+
.sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
|
|
42
|
+
color: var(--sa-color-fg-heading);
|
|
43
|
+
}
|
|
44
|
+
.sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
|
|
45
|
+
background: var(--sa-color-accent-surface-strong);
|
|
46
|
+
}
|
|
47
|
+
.sa-table__actions {
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
.sa-table__empty {
|
|
51
|
+
color: var(--sa-color-fg-muted);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The pager sits below the list, separated by a line that runs the full width
|
|
55
|
+
* of the surface — hence the pull-out by the section's own padding token. */
|
|
56
|
+
.sa-table__foot {
|
|
57
|
+
margin: 0 calc(-1 * var(--sa-pad-section)) calc(-1 * var(--sa-pad-section));
|
|
58
|
+
padding: 0 var(--sa-pad-section) 10px;
|
|
59
|
+
border-top: 1px solid var(--sa-color-border);
|
|
60
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* The SaaSiCat admin theme. One import: `@saasicat/ui-vue/theme.css`.
|
|
2
|
+
*
|
|
3
|
+
* ── The three layers, and why there are three ──────────────────────────────
|
|
4
|
+
*
|
|
5
|
+
* L1 tokens.primitive.css the palette references nothing
|
|
6
|
+
* tokens.scale.css space/type/… references nothing
|
|
7
|
+
* L2 tokens.semantic.*.css roles references L1 and --q-*
|
|
8
|
+
* L3 components/*.css the shell chrome references L2
|
|
9
|
+
*
|
|
10
|
+
* A component asks for a ROLE (`--sa-color-fg-muted`), never for a value
|
|
11
|
+
* (`--sa-neutral-500`), and never for a literal. That one rule is what makes
|
|
12
|
+
* the dark theme a file instead of a project: only L2 knows the difference
|
|
13
|
+
* between light and dark, so only L2 has two versions.
|
|
14
|
+
*
|
|
15
|
+
* The direction is enforced by `tests/theme-layer-discipline.test.js`
|
|
16
|
+
* (rule 22) and the key parity of the two L2 files by
|
|
17
|
+
* `tests/theme-token-parity.test.js` (rule 21).
|
|
18
|
+
*
|
|
19
|
+
* ── Order is the cascade ───────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* Tokens first, because everything below reads them; dark after light, because
|
|
22
|
+
* it overrides. Within `components/`, the order is the order the old single
|
|
23
|
+
* stylesheet had — several rules there share a specificity and settle it by
|
|
24
|
+
* position, so this list is not alphabetical and must not be sorted.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
@import './tokens.primitive.css';
|
|
28
|
+
@import './tokens.scale.css';
|
|
29
|
+
@import './tokens.semantic.light.css';
|
|
30
|
+
@import './tokens.semantic.dark.css';
|
|
31
|
+
@import './tokens.legacy.css';
|
|
32
|
+
|
|
33
|
+
@import './base.css';
|
|
34
|
+
@import './components/hero.css';
|
|
35
|
+
@import './components/button.css';
|
|
36
|
+
@import './components/field.css';
|
|
37
|
+
@import './components/statistics.css';
|
|
38
|
+
@import './components/body.css';
|
|
39
|
+
@import './components/section.css';
|
|
40
|
+
@import './components/card.css';
|
|
41
|
+
@import './components/paginator.css';
|
|
42
|
+
@import './components/table.css';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/* The pre-role token names, kept alive as aliases for one release.
|
|
2
|
+
*
|
|
3
|
+
* Every name below is what `sa-theme.css` published before the role layer
|
|
4
|
+
* existed. They are aliases, not values: each resolves to the role it turned
|
|
5
|
+
* out to mean, so a consumer stylesheet that still says `var(--sa-muted)` keeps
|
|
6
|
+
* working — and starts following the dark theme for free.
|
|
7
|
+
*
|
|
8
|
+
* ── They are aliases in ONE direction ──────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* Reading an old name still works. OVERRIDING one no longer does: setting
|
|
11
|
+
* `--sa-primary` in your own `:root` used to re-tint the admin, and now the
|
|
12
|
+
* role it feeds is `--sa-color-accent`, which reads Quasar's `--q-primary`.
|
|
13
|
+
* The replacement is better than what it removes — set `$primary` and the
|
|
14
|
+
* admin, Quasar's own components and the tenant-facing pages all follow one
|
|
15
|
+
* value instead of three. At runtime, use
|
|
16
|
+
* `setCssVar('primary', value, document.documentElement)`; the default target
|
|
17
|
+
* is `<body>`, which the roles on `:root` cannot see.
|
|
18
|
+
*
|
|
19
|
+
* To override a ROLE rather than the brand, write both of its values, on the
|
|
20
|
+
* same selectors the theme uses — `:root` for light, and
|
|
21
|
+
* `[data-sa-theme='dark'], body.body--dark` for dark. A role has two values, so
|
|
22
|
+
* an override has two, and `:root` alone silently loses in the Quasar-triggered
|
|
23
|
+
* dark path. `docs/design-guide.md#overriding-a-role` has the measurements.
|
|
24
|
+
*
|
|
25
|
+
* Nothing in this repository or in the scaffolder template overrode an `--sa-*`
|
|
26
|
+
* name; that was checked before choosing this direction.
|
|
27
|
+
*
|
|
28
|
+
* ── Consolidations, so a moved colour is not a surprise ────────────────────
|
|
29
|
+
*
|
|
30
|
+
* Five names had no role of their own and now share one, which is the point of
|
|
31
|
+
* the exercise rather than a side effect:
|
|
32
|
+
*
|
|
33
|
+
* --sa-border-strong #d1d5db → --sa-color-border-strong (#cbd5e1, the slate
|
|
34
|
+
* ramp — the grey ramp had exactly this one member)
|
|
35
|
+
* --sa-positive-soft, --sa-warning-soft translucent tints → the opaque
|
|
36
|
+
* `-surface` roles, so they stack predictably
|
|
37
|
+
* --sa-amber-light #fbbf24 → --sa-color-warning-strong (#f59e0b)
|
|
38
|
+
* --sa-primary-50 #eff6ff → --sa-color-accent-surface-strong, which now
|
|
39
|
+
* follows the host's brand instead of staying blue
|
|
40
|
+
*
|
|
41
|
+
* This file is deleted in 1.0. Its contents are the migration checklist.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
:root {
|
|
45
|
+
--sa-bg-app: var(--sa-color-bg-app);
|
|
46
|
+
--sa-bg-surface: var(--sa-color-bg-surface);
|
|
47
|
+
--sa-bg-surface-2: var(--sa-color-bg-sunken);
|
|
48
|
+
|
|
49
|
+
--sa-primary: var(--sa-color-accent);
|
|
50
|
+
--sa-primary-strong: var(--sa-color-accent-strong);
|
|
51
|
+
--sa-primary-soft: var(--sa-color-accent-surface);
|
|
52
|
+
--sa-primary-softer: var(--sa-color-accent-surface-soft);
|
|
53
|
+
--sa-primary-border: var(--sa-color-accent-border);
|
|
54
|
+
--sa-primary-50: var(--sa-color-accent-surface-strong);
|
|
55
|
+
|
|
56
|
+
--sa-positive: var(--sa-color-positive);
|
|
57
|
+
--sa-positive-light: var(--sa-color-positive-strong);
|
|
58
|
+
--sa-positive-soft: var(--sa-color-positive-surface);
|
|
59
|
+
--sa-positive-bg: var(--sa-color-positive-surface);
|
|
60
|
+
|
|
61
|
+
--sa-negative: var(--sa-color-negative);
|
|
62
|
+
--sa-negative-soft: var(--sa-color-negative-surface);
|
|
63
|
+
|
|
64
|
+
--sa-warning: var(--sa-color-warning);
|
|
65
|
+
--sa-warning-soft: var(--sa-color-warning-surface);
|
|
66
|
+
--sa-warning-bg: var(--sa-color-warning-surface);
|
|
67
|
+
--sa-amber: var(--sa-color-warning-strong);
|
|
68
|
+
--sa-amber-light: var(--sa-color-warning-strong);
|
|
69
|
+
--sa-amber-border: var(--sa-color-warning-border);
|
|
70
|
+
|
|
71
|
+
/* Read by `TenantsPage` as `var(--sa-accent, #7c3aed)` and never declared
|
|
72
|
+
* anywhere — the fallback was the only value it ever had. It marked the
|
|
73
|
+
* catalogue-feature action, which is now a role. */
|
|
74
|
+
--sa-accent: var(--sa-color-feature);
|
|
75
|
+
|
|
76
|
+
--sa-heading: var(--sa-color-fg-heading);
|
|
77
|
+
--sa-body: var(--sa-color-fg-body);
|
|
78
|
+
--sa-muted: var(--sa-color-fg-muted);
|
|
79
|
+
--sa-muted-dark: var(--sa-color-fg-secondary);
|
|
80
|
+
--sa-muted-light: var(--sa-color-fg-subtle);
|
|
81
|
+
|
|
82
|
+
--sa-border: var(--sa-color-border);
|
|
83
|
+
--sa-border-soft: var(--sa-color-border-soft);
|
|
84
|
+
--sa-border-strong: var(--sa-color-border-strong);
|
|
85
|
+
|
|
86
|
+
--sa-section-pad: var(--sa-pad-section);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* The same declarations again, under the dark triggers.
|
|
90
|
+
*
|
|
91
|
+
* Not redundancy — an alias is computed WHERE it is declared. `--sa-heading` on
|
|
92
|
+
* `:root` substitutes whatever `--sa-color-fg-heading` resolves to ON `:root`,
|
|
93
|
+
* and descendants inherit that finished value. Quasar's trigger redeclares the
|
|
94
|
+
* roles on `<body>`, which never re-evaluates an alias computed one level up:
|
|
95
|
+
* a consumer still reading `var(--sa-heading)` kept light text while everything
|
|
96
|
+
* around it went dark, and this file promises the opposite for one release.
|
|
97
|
+
*
|
|
98
|
+
* The attribute trigger sits on `:root` itself and would not need this, but the
|
|
99
|
+
* pair is written the way the design guide tells consumers to write an override
|
|
100
|
+
* — both values, on the selectors the theme uses. A file that documents a rule
|
|
101
|
+
* and then exempts itself from it teaches the exemption.
|
|
102
|
+
*/
|
|
103
|
+
[data-sa-theme='dark'],
|
|
104
|
+
body.body--dark {
|
|
105
|
+
--sa-bg-app: var(--sa-color-bg-app);
|
|
106
|
+
--sa-bg-surface: var(--sa-color-bg-surface);
|
|
107
|
+
--sa-bg-surface-2: var(--sa-color-bg-sunken);
|
|
108
|
+
|
|
109
|
+
--sa-primary: var(--sa-color-accent);
|
|
110
|
+
--sa-primary-strong: var(--sa-color-accent-strong);
|
|
111
|
+
--sa-primary-soft: var(--sa-color-accent-surface);
|
|
112
|
+
--sa-primary-softer: var(--sa-color-accent-surface-soft);
|
|
113
|
+
--sa-primary-border: var(--sa-color-accent-border);
|
|
114
|
+
--sa-primary-50: var(--sa-color-accent-surface-strong);
|
|
115
|
+
|
|
116
|
+
--sa-positive: var(--sa-color-positive);
|
|
117
|
+
--sa-positive-light: var(--sa-color-positive-strong);
|
|
118
|
+
--sa-positive-soft: var(--sa-color-positive-surface);
|
|
119
|
+
--sa-positive-bg: var(--sa-color-positive-surface);
|
|
120
|
+
|
|
121
|
+
--sa-negative: var(--sa-color-negative);
|
|
122
|
+
--sa-negative-soft: var(--sa-color-negative-surface);
|
|
123
|
+
|
|
124
|
+
--sa-warning: var(--sa-color-warning);
|
|
125
|
+
--sa-warning-soft: var(--sa-color-warning-surface);
|
|
126
|
+
--sa-warning-bg: var(--sa-color-warning-surface);
|
|
127
|
+
--sa-amber: var(--sa-color-warning-strong);
|
|
128
|
+
--sa-amber-light: var(--sa-color-warning-strong);
|
|
129
|
+
--sa-amber-border: var(--sa-color-warning-border);
|
|
130
|
+
|
|
131
|
+
/* Read by `TenantsPage` as `var(--sa-accent, #7c3aed)` and never declared
|
|
132
|
+
* anywhere — the fallback was the only value it ever had. It marked the
|
|
133
|
+
* catalogue-feature action, which is now a role. */
|
|
134
|
+
--sa-accent: var(--sa-color-feature);
|
|
135
|
+
|
|
136
|
+
--sa-heading: var(--sa-color-fg-heading);
|
|
137
|
+
--sa-body: var(--sa-color-fg-body);
|
|
138
|
+
--sa-muted: var(--sa-color-fg-muted);
|
|
139
|
+
--sa-muted-dark: var(--sa-color-fg-secondary);
|
|
140
|
+
--sa-muted-light: var(--sa-color-fg-subtle);
|
|
141
|
+
|
|
142
|
+
--sa-border: var(--sa-color-border);
|
|
143
|
+
--sa-border-soft: var(--sa-color-border-soft);
|
|
144
|
+
--sa-border-strong: var(--sa-color-border-strong);
|
|
145
|
+
|
|
146
|
+
--sa-section-pad: var(--sa-pad-section);
|
|
147
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* Layer 1 — primitives. The raw palette, and nothing else.
|
|
2
|
+
*
|
|
3
|
+
* A primitive says WHAT a colour is (`--sa-red-600` is a particular red). It
|
|
4
|
+
* never says what the colour is FOR — that is layer 2's job, and it is the only
|
|
5
|
+
* layer that changes between light and dark. Consequently:
|
|
6
|
+
*
|
|
7
|
+
* - nothing in this file may reference another variable;
|
|
8
|
+
* - nothing outside `tokens.semantic.*.css` may reference this file.
|
|
9
|
+
*
|
|
10
|
+
* A component reaching for `--sa-red-600` instead of `--sa-color-negative` is
|
|
11
|
+
* the bug this split exists to prevent: it is invisible in review, and it is
|
|
12
|
+
* the reason a dark theme costs 62 files instead of one. Rule 22 in the
|
|
13
|
+
* layer-discipline test enforces both directions.
|
|
14
|
+
*
|
|
15
|
+
* The ramps are the ones the admin surface already used. Steps that no role
|
|
16
|
+
* consumes are absent on purpose: a palette with unused rungs invites people to
|
|
17
|
+
* pick one, and then the role layer is no longer the single answer.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
:root {
|
|
21
|
+
/* ── Neutral (slate). Carries text, surfaces and lines. ─────────────── */
|
|
22
|
+
--sa-white: #ffffff;
|
|
23
|
+
--sa-black: #000000;
|
|
24
|
+
--sa-neutral-25: #fbfbfd;
|
|
25
|
+
--sa-neutral-50: #f8fafc;
|
|
26
|
+
--sa-neutral-100: #f1f5f9;
|
|
27
|
+
--sa-neutral-200: #e2e8f0;
|
|
28
|
+
--sa-neutral-300: #cbd5e1;
|
|
29
|
+
--sa-neutral-400: #94a3b8;
|
|
30
|
+
/* Between 400 and 500, and it exists for a measured reason: `fg-subtle` at
|
|
31
|
+
* slate-400 reads 2.56:1 on a white card and 2.34:1 on the page canvas —
|
|
32
|
+
* under the 3:1 floor, i.e. captions and price units that are decoration
|
|
33
|
+
* rather than information. Slate-500 is the next rung and too dark to stay
|
|
34
|
+
* quiet. This one clears 3:1 on both. */
|
|
35
|
+
--sa-neutral-450: #7c8aa0;
|
|
36
|
+
--sa-neutral-500: #64748b;
|
|
37
|
+
--sa-neutral-600: #475569;
|
|
38
|
+
--sa-neutral-700: #334155;
|
|
39
|
+
--sa-neutral-800: #1e293b;
|
|
40
|
+
--sa-neutral-900: #0f172a;
|
|
41
|
+
--sa-neutral-950: #0b1220;
|
|
42
|
+
--sa-neutral-975: #070d18;
|
|
43
|
+
|
|
44
|
+
/* ── Blue. The `info` tone. ─────────────────────────────────────────── */
|
|
45
|
+
--sa-blue-50: #eff6ff;
|
|
46
|
+
--sa-blue-100: #dbeafe;
|
|
47
|
+
--sa-blue-200: #bfdbfe;
|
|
48
|
+
--sa-blue-300: #93c5fd;
|
|
49
|
+
--sa-blue-500: #3b82f6;
|
|
50
|
+
--sa-blue-600: #2563eb;
|
|
51
|
+
--sa-blue-700: #1d4ed8;
|
|
52
|
+
--sa-blue-800: #1e40af;
|
|
53
|
+
|
|
54
|
+
/* The platform's own brand blue. Kept out of the blue ramp because it is
|
|
55
|
+
* not a step in it: it is the value the accent role falls back to when the
|
|
56
|
+
* host application declares no Quasar `$primary` of its own. */
|
|
57
|
+
--sa-brand-blue: #3f6bff;
|
|
58
|
+
|
|
59
|
+
/* ── Green (emerald). `positive`. ───────────────────────────────────── */
|
|
60
|
+
--sa-green-50: #ecfdf5;
|
|
61
|
+
--sa-green-100: #d1fae5;
|
|
62
|
+
--sa-green-200: #a7f3d0;
|
|
63
|
+
--sa-green-500: #10b981;
|
|
64
|
+
--sa-green-700: #047857;
|
|
65
|
+
--sa-green-800: #065f46;
|
|
66
|
+
|
|
67
|
+
/* ── Amber. `warning`, and every "draft / scheduled / pending" state. ─ */
|
|
68
|
+
--sa-amber-50: #fffbeb;
|
|
69
|
+
--sa-amber-100: #fef3c7;
|
|
70
|
+
--sa-amber-200: #fde68a;
|
|
71
|
+
--sa-amber-300: #fcd34d;
|
|
72
|
+
--sa-amber-400: #fbbf24;
|
|
73
|
+
--sa-amber-500: #f59e0b;
|
|
74
|
+
--sa-amber-600: #d97706;
|
|
75
|
+
--sa-amber-700: #b45309;
|
|
76
|
+
--sa-amber-800: #92400e;
|
|
77
|
+
|
|
78
|
+
/* ── Red. `negative`. ───────────────────────────────────────────────── */
|
|
79
|
+
--sa-red-50: #fef2f2;
|
|
80
|
+
--sa-red-100: #fee2e2;
|
|
81
|
+
--sa-red-200: #fecaca;
|
|
82
|
+
--sa-red-300: #fca5a5;
|
|
83
|
+
--sa-red-500: #ef4444;
|
|
84
|
+
--sa-red-600: #dc2626;
|
|
85
|
+
--sa-red-700: #b91c1c;
|
|
86
|
+
--sa-red-800: #991b1b;
|
|
87
|
+
|
|
88
|
+
/* ── Indigo. `scheduled` — a version that is planned but not yet live. */
|
|
89
|
+
--sa-indigo-50: #eef2ff;
|
|
90
|
+
--sa-indigo-100: #e0e7ff;
|
|
91
|
+
--sa-indigo-200: #c7d2fe;
|
|
92
|
+
--sa-indigo-400: #818cf8;
|
|
93
|
+
--sa-indigo-700: #4338ca;
|
|
94
|
+
|
|
95
|
+
/* ── Violet and sky. The two catalogue entities, feature and quota. ── */
|
|
96
|
+
--sa-violet-50: #ede9fe;
|
|
97
|
+
--sa-violet-200: #ddd6fe;
|
|
98
|
+
--sa-violet-500: #8b5cf6;
|
|
99
|
+
--sa-violet-700: #6d28d9;
|
|
100
|
+
--sa-sky-50: #f0f9ff;
|
|
101
|
+
--sa-sky-200: #bae6fd;
|
|
102
|
+
--sa-sky-500: #0ea5e9;
|
|
103
|
+
/* Sky-500 is 2.6:1 on a white card — fine as a dot or a border, too pale to
|
|
104
|
+
* carry a label. The identity ramp needs a readable sky, so here is the
|
|
105
|
+
* rung below it. */
|
|
106
|
+
--sa-sky-700: #0369a1;
|
|
107
|
+
|
|
108
|
+
/* ── Type families. ─────────────────────────────────────────────────── */
|
|
109
|
+
--sa-font-head: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
|
|
110
|
+
--sa-font-body: 'Inter', system-ui, sans-serif;
|
|
111
|
+
--sa-font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
112
|
+
}
|