@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,103 @@
|
|
|
1
|
+
/* Layer 1 — scales. Sizes, not colours, and identical in every theme.
|
|
2
|
+
*
|
|
3
|
+
* The point of a scale is that the choice is already made. You do not decide
|
|
4
|
+
* between 11px and 11.5px; you decide whether a piece of text is a caption or a
|
|
5
|
+
* body line, and the scale answers with a number. That is why the admin surface
|
|
6
|
+
* had 23 font sizes and 80 pixel values: with no scale, every author decided
|
|
7
|
+
* again, and nobody could tell a decision from a leftover.
|
|
8
|
+
*
|
|
9
|
+
* Breakpoints are the one scale that CANNOT live here — custom properties are
|
|
10
|
+
* not valid in a `@media` condition. They are in `_breakpoints.scss` for SCSS
|
|
11
|
+
* blocks, and for plain CSS the five values are an allow-list the lint enforces.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
:root {
|
|
15
|
+
/* ── Spacing. A 4px base with two half-steps below it, because hairline
|
|
16
|
+
* gaps (2px) and icon insets (4px) are real and rounding them up moves
|
|
17
|
+
* every dense row in the product. ───────────────────────────────────── */
|
|
18
|
+
--sa-space-0: 0;
|
|
19
|
+
--sa-space-1: 2px;
|
|
20
|
+
--sa-space-2: 4px;
|
|
21
|
+
--sa-space-3: 8px;
|
|
22
|
+
--sa-space-4: 12px;
|
|
23
|
+
--sa-space-5: 16px;
|
|
24
|
+
--sa-space-6: 20px;
|
|
25
|
+
--sa-space-7: 24px;
|
|
26
|
+
--sa-space-8: 32px;
|
|
27
|
+
--sa-space-9: 40px;
|
|
28
|
+
--sa-space-10: 48px;
|
|
29
|
+
--sa-space-11: 64px;
|
|
30
|
+
|
|
31
|
+
/* Named spacings, so a rule can say what the gap is for rather than how
|
|
32
|
+
* big it is. These are the six the admin surface actually distinguishes. */
|
|
33
|
+
--sa-gap-inline: var(--sa-space-3);
|
|
34
|
+
--sa-gap-stack: var(--sa-space-5);
|
|
35
|
+
--sa-pad-page: var(--sa-space-6);
|
|
36
|
+
--sa-pad-section: var(--sa-space-6);
|
|
37
|
+
--sa-pad-control: var(--sa-space-3);
|
|
38
|
+
--sa-pad-cell: var(--sa-space-4);
|
|
39
|
+
|
|
40
|
+
/* ── Type. Eight steps, each with the line-height it was drawn for.
|
|
41
|
+
*
|
|
42
|
+
* The ladder is dense at the bottom because that is where the admin lives:
|
|
43
|
+
* table cells, chips and captions are three quarters of every page, and one
|
|
44
|
+
* pixel there is the difference between a dense list and a cramped one. It
|
|
45
|
+
* spreads out at the top, where the steps are titles and there are few. */
|
|
46
|
+
--sa-text-2xs: 10px;
|
|
47
|
+
--sa-text-xs: 11px;
|
|
48
|
+
--sa-text-sm: 12px;
|
|
49
|
+
--sa-text-md: 13px;
|
|
50
|
+
--sa-text-lg: 15px;
|
|
51
|
+
--sa-text-xl: 18px;
|
|
52
|
+
--sa-text-2xl: 22px;
|
|
53
|
+
--sa-text-3xl: 28px;
|
|
54
|
+
/* A ninth rung the plan did not foresee, added for a measured reason: the
|
|
55
|
+
* price totals and the marketing headline sit at 32–38px, and collapsing
|
|
56
|
+
* them into 28px would have shrunk the biggest number in the product to
|
|
57
|
+
* hit a target of eight. A step the product uses for a distinct job is a
|
|
58
|
+
* step. */
|
|
59
|
+
--sa-text-4xl: 32px;
|
|
60
|
+
|
|
61
|
+
--sa-leading-2xs: 1.4;
|
|
62
|
+
--sa-leading-xs: 1.45;
|
|
63
|
+
--sa-leading-sm: 1.5;
|
|
64
|
+
--sa-leading-md: 1.55;
|
|
65
|
+
--sa-leading-lg: 1.5;
|
|
66
|
+
--sa-leading-xl: 1.4;
|
|
67
|
+
--sa-leading-2xl: 1.3;
|
|
68
|
+
--sa-leading-3xl: 1.25;
|
|
69
|
+
--sa-leading-4xl: 1.15;
|
|
70
|
+
|
|
71
|
+
--sa-weight-regular: 400;
|
|
72
|
+
--sa-weight-medium: 500;
|
|
73
|
+
--sa-weight-semibold: 600;
|
|
74
|
+
--sa-weight-bold: 700;
|
|
75
|
+
|
|
76
|
+
--sa-tracking-tight: -0.02em;
|
|
77
|
+
--sa-tracking-normal: 0;
|
|
78
|
+
--sa-tracking-wide: 0.04em;
|
|
79
|
+
--sa-tracking-wider: 0.06em;
|
|
80
|
+
|
|
81
|
+
/* ── Corner radii, largest to smallest, so the nesting reads as hierarchy:
|
|
82
|
+
* a section is a bigger block than the cards inside it, a card bigger than
|
|
83
|
+
* a tile. `--sa-radius-tile` is the single declaration every statistic tile
|
|
84
|
+
* in the admin surface resolves to. ─────────────────────────────────── */
|
|
85
|
+
--sa-radius-hero: 16px;
|
|
86
|
+
--sa-radius-section: 14px;
|
|
87
|
+
--sa-radius-card: 12px;
|
|
88
|
+
--sa-radius-head: 10px;
|
|
89
|
+
--sa-radius-tile: 10px;
|
|
90
|
+
--sa-radius-field: 8px;
|
|
91
|
+
--sa-radius-control: 7px;
|
|
92
|
+
--sa-radius-pill: 999px;
|
|
93
|
+
|
|
94
|
+
/* ── Stacking. Aligned with Quasar's own layers rather than invented above
|
|
95
|
+
* them: a platform overlay that outranks Quasar's dialog would cover the
|
|
96
|
+
* dialog a consumer opened from it. ─────────────────────────────────── */
|
|
97
|
+
--sa-z-base: 0;
|
|
98
|
+
--sa-z-sticky: 100;
|
|
99
|
+
--sa-z-drawer: 3000;
|
|
100
|
+
--sa-z-overlay: 5900;
|
|
101
|
+
--sa-z-dialog: 6000;
|
|
102
|
+
--sa-z-toast: 9500;
|
|
103
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* Layer 2 — roles, dark theme. The same keys as `tokens.semantic.light.css`,
|
|
2
|
+
* with other values. That sentence is the whole design: nothing else in the
|
|
3
|
+
* package knows a dark theme exists.
|
|
4
|
+
*
|
|
5
|
+
* ── Two ways in, and why there is no third ─────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* [data-sa-theme='dark'] the app decided — `handle.theme.scheme.value`
|
|
8
|
+
* body.body--dark Quasar decided — `$q.dark.set(true)`
|
|
9
|
+
*
|
|
10
|
+
* Both mean "the application decided", and that is the point: the application
|
|
11
|
+
* is the only party that can decide for BOTH halves of the screen. Quasar
|
|
12
|
+
* paints its own cards, dialogs, steppers and separators, and it does so only
|
|
13
|
+
* under `body--dark` — it has no `prefers-color-scheme` support of its own.
|
|
14
|
+
*
|
|
15
|
+
* ── Why this file does NOT follow the operating system ─────────────────────
|
|
16
|
+
*
|
|
17
|
+
* It used to, via `@media (prefers-color-scheme: dark)`, and that was wrong in
|
|
18
|
+
* the one case it was supposed to serve. An app that loads this stylesheet
|
|
19
|
+
* without running the bridge — every consumer embedding `pages-tenant/*` into
|
|
20
|
+
* its own frontend — would have had the platform's surfaces go dark from the
|
|
21
|
+
* operating system while Quasar's stayed light. Measured, on an embedded page
|
|
22
|
+
* with the OS set to dark:
|
|
23
|
+
*
|
|
24
|
+
* .sa-section rgb(15, 23, 42) ← dark
|
|
25
|
+
* .q-card rgb(255, 255, 255) ← white, with rgb(226, 232, 240) text
|
|
26
|
+
* .q-separator rgba(0, 0, 0, 0.12) ← a black hairline on near-black
|
|
27
|
+
*
|
|
28
|
+
* That is not "light cards beside dark surfaces", it is white-on-white at
|
|
29
|
+
* about 1.15:1. A stylesheet must not overrule the application that loaded it,
|
|
30
|
+
* because it cannot see the half of the screen it does not paint.
|
|
31
|
+
*
|
|
32
|
+
* Following the operating system is not lost — it moved to where both halves
|
|
33
|
+
* can be moved together. `createSaTheme({ scheme: 'system' })` is the default,
|
|
34
|
+
* it subscribes to `prefers-color-scheme` live, and `bindSaThemeToDocument`
|
|
35
|
+
* sets the attribute AND Quasar's `Dark`. `createSuperAdminApp` wires that up,
|
|
36
|
+
* so the admin shell follows the OS out of the box. An app that wants the same
|
|
37
|
+
* for an embedded surface calls those two functions; that is the one line that
|
|
38
|
+
* makes it actually work.
|
|
39
|
+
*
|
|
40
|
+
* `tests-e2e/theme-contrast.spec.ts` holds the counter-proof: with the OS set
|
|
41
|
+
* to dark and no bridge, the platform stays in step with Quasar.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
[data-sa-theme='dark'],
|
|
45
|
+
body.body--dark {
|
|
46
|
+
--sa-color-bg-app: var(--sa-neutral-975);
|
|
47
|
+
--sa-color-bg-surface: var(--sa-neutral-900);
|
|
48
|
+
--sa-color-bg-surface-raised: var(--sa-neutral-800);
|
|
49
|
+
--sa-color-bg-sunken: var(--sa-neutral-950);
|
|
50
|
+
--sa-color-bg-overlay: color-mix(in srgb, var(--sa-black) 70%, transparent);
|
|
51
|
+
|
|
52
|
+
--sa-color-fg-heading: var(--sa-neutral-50);
|
|
53
|
+
--sa-color-fg-body: var(--sa-neutral-200);
|
|
54
|
+
--sa-color-fg-secondary: var(--sa-neutral-300);
|
|
55
|
+
--sa-color-fg-muted: var(--sa-neutral-400);
|
|
56
|
+
--sa-color-fg-subtle: var(--sa-neutral-500);
|
|
57
|
+
--sa-color-fg-disabled: var(--sa-neutral-600);
|
|
58
|
+
--sa-color-fg-on-accent: var(--sa-white);
|
|
59
|
+
|
|
60
|
+
--sa-color-border: var(--sa-neutral-700);
|
|
61
|
+
--sa-color-border-soft: var(--sa-neutral-800);
|
|
62
|
+
--sa-color-border-strong: var(--sa-neutral-600);
|
|
63
|
+
--sa-color-border-focus: var(--sa-color-accent);
|
|
64
|
+
|
|
65
|
+
/* The brand does not flip — a green product stays green in the dark. Only
|
|
66
|
+
* the derivations change: `-strong` lightens instead of darkening, because
|
|
67
|
+
* "more emphatic" means "closer to the paper" here, and every tint needs
|
|
68
|
+
* more of the accent to register against a dark surface. */
|
|
69
|
+
--sa-color-accent: var(--q-primary, var(--sa-brand-blue));
|
|
70
|
+
--sa-color-accent-strong: color-mix(in srgb, var(--sa-color-accent) 82%, var(--sa-white));
|
|
71
|
+
--sa-color-accent-surface: color-mix(in srgb, var(--sa-color-accent) 16%, transparent);
|
|
72
|
+
--sa-color-accent-surface-soft: color-mix(in srgb, var(--sa-color-accent) 8%, transparent);
|
|
73
|
+
--sa-color-accent-surface-strong: color-mix(
|
|
74
|
+
in srgb,
|
|
75
|
+
var(--sa-color-accent) 22%,
|
|
76
|
+
var(--sa-color-bg-surface)
|
|
77
|
+
);
|
|
78
|
+
--sa-color-accent-border: color-mix(in srgb, var(--sa-color-accent) 32%, transparent);
|
|
79
|
+
|
|
80
|
+
/* Each tone rises one or two steps up its ramp, because the dark shades
|
|
81
|
+
* that read as "deep red" on white read as "almost black" on slate. The
|
|
82
|
+
* surfaces become translucent so that a chip on a raised card and a chip on
|
|
83
|
+
* the page canvas both keep their contrast. */
|
|
84
|
+
--sa-color-positive: var(--sa-green-500);
|
|
85
|
+
--sa-color-positive-strong: var(--sa-green-500);
|
|
86
|
+
--sa-color-positive-fg: var(--sa-green-200);
|
|
87
|
+
--sa-color-positive-surface: color-mix(in srgb, var(--sa-green-500) 14%, transparent);
|
|
88
|
+
--sa-color-positive-surface-strong: color-mix(in srgb, var(--sa-green-500) 24%, transparent);
|
|
89
|
+
--sa-color-positive-border: color-mix(in srgb, var(--sa-green-500) 36%, transparent);
|
|
90
|
+
|
|
91
|
+
--sa-color-warning: var(--sa-amber-400);
|
|
92
|
+
--sa-color-warning-strong: var(--sa-amber-400);
|
|
93
|
+
--sa-color-warning-fg: var(--sa-amber-200);
|
|
94
|
+
--sa-color-warning-surface: color-mix(in srgb, var(--sa-amber-500) 14%, transparent);
|
|
95
|
+
--sa-color-warning-surface-strong: color-mix(in srgb, var(--sa-amber-500) 24%, transparent);
|
|
96
|
+
--sa-color-warning-border: color-mix(in srgb, var(--sa-amber-500) 36%, transparent);
|
|
97
|
+
|
|
98
|
+
--sa-color-negative: var(--sa-red-500);
|
|
99
|
+
--sa-color-negative-strong: var(--sa-red-500);
|
|
100
|
+
--sa-color-negative-fg: var(--sa-red-300);
|
|
101
|
+
--sa-color-negative-surface: color-mix(in srgb, var(--sa-red-500) 14%, transparent);
|
|
102
|
+
--sa-color-negative-surface-strong: color-mix(in srgb, var(--sa-red-500) 24%, transparent);
|
|
103
|
+
--sa-color-negative-border: color-mix(in srgb, var(--sa-red-500) 36%, transparent);
|
|
104
|
+
|
|
105
|
+
--sa-color-info: var(--sa-blue-500);
|
|
106
|
+
--sa-color-info-strong: var(--sa-blue-500);
|
|
107
|
+
--sa-color-info-fg: var(--sa-blue-200);
|
|
108
|
+
--sa-color-info-surface: color-mix(in srgb, var(--sa-blue-500) 14%, transparent);
|
|
109
|
+
--sa-color-info-surface-strong: color-mix(in srgb, var(--sa-blue-500) 24%, transparent);
|
|
110
|
+
--sa-color-info-border: color-mix(in srgb, var(--sa-blue-500) 36%, transparent);
|
|
111
|
+
|
|
112
|
+
--sa-color-scheduled: var(--sa-indigo-400);
|
|
113
|
+
--sa-color-scheduled-strong: var(--sa-indigo-400);
|
|
114
|
+
--sa-color-scheduled-fg: var(--sa-indigo-200);
|
|
115
|
+
--sa-color-scheduled-surface: color-mix(in srgb, var(--sa-indigo-400) 14%, transparent);
|
|
116
|
+
--sa-color-scheduled-surface-strong: color-mix(in srgb, var(--sa-indigo-400) 24%, transparent);
|
|
117
|
+
--sa-color-scheduled-border: color-mix(in srgb, var(--sa-indigo-400) 36%, transparent);
|
|
118
|
+
|
|
119
|
+
--sa-color-feature: var(--sa-violet-500);
|
|
120
|
+
--sa-color-feature-fg: var(--sa-violet-200);
|
|
121
|
+
--sa-color-feature-surface: color-mix(in srgb, var(--sa-violet-500) 16%, transparent);
|
|
122
|
+
--sa-color-quota: var(--sa-sky-500);
|
|
123
|
+
--sa-color-quota-surface: color-mix(in srgb, var(--sa-sky-500) 16%, transparent);
|
|
124
|
+
--sa-color-quota-border: color-mix(in srgb, var(--sa-sky-500) 36%, transparent);
|
|
125
|
+
--sa-color-bundle: var(--sa-amber-500);
|
|
126
|
+
--sa-color-bundle-surface: color-mix(in srgb, var(--sa-amber-500) 16%, transparent);
|
|
127
|
+
|
|
128
|
+
/* The identity ramp, one rung lighter throughout — see the light file for
|
|
129
|
+
* what it is for. This is the half that did not exist while the ramps lived
|
|
130
|
+
* as hex literals in TypeScript, and its absence is the whole reason the
|
|
131
|
+
* plan mark sat at 2.96:1 here. */
|
|
132
|
+
--sa-color-identity-1: var(--sa-blue-300);
|
|
133
|
+
--sa-color-identity-2: var(--sa-violet-200);
|
|
134
|
+
--sa-color-identity-3: var(--sa-green-200);
|
|
135
|
+
--sa-color-identity-4: var(--sa-amber-300);
|
|
136
|
+
--sa-color-identity-5: var(--sa-sky-200);
|
|
137
|
+
--sa-color-identity-6: var(--sa-red-300);
|
|
138
|
+
--sa-color-identity-neutral: var(--sa-neutral-400);
|
|
139
|
+
|
|
140
|
+
/* Identical to the light theme, and that is the point — see there. */
|
|
141
|
+
--sa-color-inverse-bg: var(--sa-neutral-950);
|
|
142
|
+
--sa-color-inverse-surface: var(--sa-neutral-800);
|
|
143
|
+
--sa-color-inverse-surface-soft: var(--sa-neutral-600);
|
|
144
|
+
--sa-color-inverse-fg: var(--sa-white);
|
|
145
|
+
--sa-color-inverse-fg-muted: var(--sa-neutral-300);
|
|
146
|
+
--sa-color-inverse-accent: var(--sa-amber-400);
|
|
147
|
+
--sa-color-inverse-accent-strong: var(--sa-amber-600);
|
|
148
|
+
--sa-color-inverse-accent-surface: color-mix(in srgb, var(--sa-amber-500) 15%, transparent);
|
|
149
|
+
--sa-color-inverse-accent-surface-strong: color-mix(
|
|
150
|
+
in srgb,
|
|
151
|
+
var(--sa-amber-500) 22%,
|
|
152
|
+
transparent
|
|
153
|
+
);
|
|
154
|
+
--sa-color-inverse-accent-fg: var(--sa-neutral-900);
|
|
155
|
+
--sa-color-inverse-danger: var(--sa-red-700);
|
|
156
|
+
--sa-color-inverse-danger-strong: var(--sa-red-600);
|
|
157
|
+
--sa-color-inverse-notice: var(--sa-amber-600);
|
|
158
|
+
--sa-color-inverse-notice-strong: var(--sa-amber-700);
|
|
159
|
+
--sa-color-inverse-fg-subtle: color-mix(in srgb, var(--sa-white) 55%, transparent);
|
|
160
|
+
--sa-color-inverse-border: color-mix(in srgb, var(--sa-white) 8%, transparent);
|
|
161
|
+
--sa-color-inverse-border-strong: color-mix(in srgb, var(--sa-white) 16%, transparent);
|
|
162
|
+
|
|
163
|
+
--sa-color-focus-ring: color-mix(in srgb, var(--sa-color-accent) 45%, transparent);
|
|
164
|
+
--sa-color-selection: color-mix(in srgb, var(--sa-color-accent) 30%, transparent);
|
|
165
|
+
|
|
166
|
+
/* Black ink and roughly double the alpha: a slate shadow at 6% is invisible
|
|
167
|
+
* on a slate surface, so the depth cue has to come from real darkness. */
|
|
168
|
+
--sa-shadow-ink: var(--sa-black);
|
|
169
|
+
--sa-shadow-tint-1: color-mix(in srgb, var(--sa-shadow-ink) 24%, transparent);
|
|
170
|
+
--sa-shadow-tint-2: color-mix(in srgb, var(--sa-shadow-ink) 32%, transparent);
|
|
171
|
+
--sa-shadow-tint-3: color-mix(in srgb, var(--sa-shadow-ink) 44%, transparent);
|
|
172
|
+
--sa-shadow-tint-4: color-mix(in srgb, var(--sa-shadow-ink) 60%, transparent);
|
|
173
|
+
|
|
174
|
+
--sa-shadow-0: none;
|
|
175
|
+
--sa-shadow-1: 0 1px 2px var(--sa-shadow-tint-1);
|
|
176
|
+
--sa-shadow-2: 0 2px 6px var(--sa-shadow-tint-2);
|
|
177
|
+
--sa-shadow-3: 0 8px 20px var(--sa-shadow-tint-3);
|
|
178
|
+
--sa-shadow-4: 0 20px 48px var(--sa-shadow-tint-4);
|
|
179
|
+
--sa-shadow-focus: 0 0 0 3px var(--sa-color-focus-ring);
|
|
180
|
+
|
|
181
|
+
--sa-elevation-tile: var(--sa-shadow-1);
|
|
182
|
+
--sa-elevation-card: var(--sa-shadow-2);
|
|
183
|
+
--sa-elevation-menu: var(--sa-shadow-3);
|
|
184
|
+
--sa-elevation-dialog: var(--sa-shadow-4);
|
|
185
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/* Layer 2 — roles, light theme. What a colour is FOR.
|
|
2
|
+
*
|
|
3
|
+
* This is the only layer that changes between light and dark, and that is the
|
|
4
|
+
* whole reason the split exists: a dark theme is this file with other values,
|
|
5
|
+
* not 62 stylesheets with other values.
|
|
6
|
+
*
|
|
7
|
+
* Two rules, both enforced by the layer-discipline test (rule 22):
|
|
8
|
+
*
|
|
9
|
+
* 1. No colour literal may appear here. Every value is a primitive, a
|
|
10
|
+
* `--q-*` brand variable from the host, or a `color-mix()` of those.
|
|
11
|
+
* A literal here is a value nobody can find from the outside.
|
|
12
|
+
* 2. `tokens.semantic.dark.css` declares exactly the same keys. A role that
|
|
13
|
+
* exists in one theme and not the other is a page that goes unreadable the
|
|
14
|
+
* moment somebody flips the switch (rule 21).
|
|
15
|
+
*
|
|
16
|
+
* ── Where the accent comes from ────────────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* `--sa-color-accent` reads Quasar's `--q-primary`, which Quasar publishes from
|
|
19
|
+
* the host application's `$primary`. That is the fix for the worst finding in
|
|
20
|
+
* the audit: the platform painted `#3f6bff` while the scaffolded app painted
|
|
21
|
+
* `#1e40af`, in the same surface, because each had its own source of truth.
|
|
22
|
+
* Now there is one — the host's — and `--sa-brand-blue` is what it falls back
|
|
23
|
+
* to when the host declares nothing. Setting `$primary` re-tints the admin with
|
|
24
|
+
* it, and there is no second switch to remember.
|
|
25
|
+
*
|
|
26
|
+
* To change the brand AT RUNTIME, write to the root:
|
|
27
|
+
*
|
|
28
|
+
* setCssVar('primary', value, document.documentElement)
|
|
29
|
+
*
|
|
30
|
+
* The third argument is not optional here, and the reason is the same one that
|
|
31
|
+
* shapes the rest of this file: Quasar's `setCssVar` defaults to `<body>`, and
|
|
32
|
+
* this role is computed on `:root`, one level above it. A custom property is
|
|
33
|
+
* resolved where it is DECLARED, so the root substitution never sees a value
|
|
34
|
+
* written below it — Quasar's own components would recolour and the admin would
|
|
35
|
+
* not. Measured, and pinned by a test.
|
|
36
|
+
*
|
|
37
|
+
* Tints derive from the accent with `color-mix()` for the same reason: a fixed
|
|
38
|
+
* `rgba(63, 107, 255, 0.08)` would stay platform-blue on a host that is green.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
:root {
|
|
42
|
+
/* ── Surfaces, from the page canvas up to what floats above it. ─────── */
|
|
43
|
+
--sa-color-bg-app: var(--sa-neutral-100);
|
|
44
|
+
--sa-color-bg-surface: var(--sa-white);
|
|
45
|
+
--sa-color-bg-surface-raised: var(--sa-neutral-25);
|
|
46
|
+
--sa-color-bg-sunken: var(--sa-neutral-50);
|
|
47
|
+
--sa-color-bg-overlay: color-mix(in srgb, var(--sa-neutral-900) 55%, transparent);
|
|
48
|
+
|
|
49
|
+
/* ── Text, from the loudest to the quietest.
|
|
50
|
+
*
|
|
51
|
+
* Six rungs, not five, because the surface uses six. `-secondary` was very
|
|
52
|
+
* nearly folded into `-muted` on the argument that two greys one step apart
|
|
53
|
+
* are a duplicate — the baseline said otherwise: it carries the figures and
|
|
54
|
+
* meta lines of over a hundred elements, and reading it one step lighter
|
|
55
|
+
* made them quieter than the captions beside them. A rung the product uses
|
|
56
|
+
* for a distinct job is not a duplicate, it is a rung. ───────────────── */
|
|
57
|
+
--sa-color-fg-heading: var(--sa-neutral-900);
|
|
58
|
+
--sa-color-fg-body: var(--sa-neutral-800);
|
|
59
|
+
--sa-color-fg-secondary: var(--sa-neutral-600);
|
|
60
|
+
--sa-color-fg-muted: var(--sa-neutral-500);
|
|
61
|
+
--sa-color-fg-subtle: var(--sa-neutral-450);
|
|
62
|
+
--sa-color-fg-disabled: var(--sa-neutral-300);
|
|
63
|
+
/* Text lying on the accent. White, and that is an ASSUMPTION about your
|
|
64
|
+
* brand rather than a derivation from it: CSS cannot branch on a colour's
|
|
65
|
+
* luminance, so nothing here can notice that `$primary` is a light amber
|
|
66
|
+
* and white on it reads 2.15:1. `contrast-color()` will answer this without
|
|
67
|
+
* JavaScript once it is safe to ship; until then it is a knob.
|
|
68
|
+
*
|
|
69
|
+
* If your brand is light, set this — in BOTH themes, like any role
|
|
70
|
+
* (docs/design-guide.md#overriding-a-role):
|
|
71
|
+
*
|
|
72
|
+
* :root, [data-sa-theme='dark'], body.body--dark {
|
|
73
|
+
* --sa-color-fg-on-accent: #1a1300;
|
|
74
|
+
* }
|
|
75
|
+
*/
|
|
76
|
+
--sa-color-fg-on-accent: var(--sa-white);
|
|
77
|
+
|
|
78
|
+
/* ── Lines. ─────────────────────────────────────────────────────────── */
|
|
79
|
+
--sa-color-border: var(--sa-neutral-200);
|
|
80
|
+
--sa-color-border-soft: var(--sa-neutral-100);
|
|
81
|
+
--sa-color-border-strong: var(--sa-neutral-300);
|
|
82
|
+
--sa-color-border-focus: var(--sa-color-accent);
|
|
83
|
+
|
|
84
|
+
/* ── Accent — the host's brand, and the surfaces derived from it. ───── */
|
|
85
|
+
--sa-color-accent: var(--q-primary, var(--sa-brand-blue));
|
|
86
|
+
--sa-color-accent-strong: color-mix(in srgb, var(--sa-color-accent) 82%, var(--sa-black));
|
|
87
|
+
--sa-color-accent-surface: color-mix(in srgb, var(--sa-color-accent) 8%, transparent);
|
|
88
|
+
--sa-color-accent-surface-soft: color-mix(in srgb, var(--sa-color-accent) 4%, transparent);
|
|
89
|
+
--sa-color-accent-surface-strong: color-mix(
|
|
90
|
+
in srgb,
|
|
91
|
+
var(--sa-color-accent) 10%,
|
|
92
|
+
var(--sa-color-bg-surface)
|
|
93
|
+
);
|
|
94
|
+
--sa-color-accent-border: color-mix(in srgb, var(--sa-color-accent) 18%, transparent);
|
|
95
|
+
|
|
96
|
+
/* ── Status tones. Five, and all five have the same six slots, so a page
|
|
97
|
+
* that knows one knows the others:
|
|
98
|
+
*
|
|
99
|
+
* <tone> the solid graphic colour — an icon, a bar, a dot
|
|
100
|
+
* <tone>-strong the brighter variant, for a selected outline
|
|
101
|
+
* <tone>-fg text ON the tinted surface, contrast-checked
|
|
102
|
+
* <tone>-surface the quiet tint — a banner across a section
|
|
103
|
+
* <tone>-surface-strong the loud tint — a chip, a pill, a single cell
|
|
104
|
+
* <tone>-border the tinted line that closes either surface
|
|
105
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
106
|
+
--sa-color-positive: var(--sa-green-700);
|
|
107
|
+
--sa-color-positive-strong: var(--sa-green-500);
|
|
108
|
+
--sa-color-positive-fg: var(--sa-green-700);
|
|
109
|
+
--sa-color-positive-surface: var(--sa-green-50);
|
|
110
|
+
--sa-color-positive-surface-strong: var(--sa-green-100);
|
|
111
|
+
--sa-color-positive-border: var(--sa-green-200);
|
|
112
|
+
|
|
113
|
+
--sa-color-warning: var(--sa-amber-700);
|
|
114
|
+
--sa-color-warning-strong: var(--sa-amber-500);
|
|
115
|
+
--sa-color-warning-fg: var(--sa-amber-700);
|
|
116
|
+
--sa-color-warning-surface: var(--sa-amber-50);
|
|
117
|
+
--sa-color-warning-surface-strong: var(--sa-amber-100);
|
|
118
|
+
--sa-color-warning-border: var(--sa-amber-200);
|
|
119
|
+
|
|
120
|
+
--sa-color-negative: var(--sa-red-600);
|
|
121
|
+
--sa-color-negative-strong: var(--sa-red-500);
|
|
122
|
+
--sa-color-negative-fg: var(--sa-red-700);
|
|
123
|
+
--sa-color-negative-surface: var(--sa-red-50);
|
|
124
|
+
--sa-color-negative-surface-strong: var(--sa-red-100);
|
|
125
|
+
--sa-color-negative-border: var(--sa-red-200);
|
|
126
|
+
|
|
127
|
+
--sa-color-info: var(--sa-blue-600);
|
|
128
|
+
--sa-color-info-strong: var(--sa-blue-500);
|
|
129
|
+
--sa-color-info-fg: var(--sa-blue-700);
|
|
130
|
+
--sa-color-info-surface: var(--sa-blue-50);
|
|
131
|
+
--sa-color-info-surface-strong: var(--sa-blue-100);
|
|
132
|
+
--sa-color-info-border: var(--sa-blue-200);
|
|
133
|
+
|
|
134
|
+
/* `scheduled` is a tone rather than a status colour reused: a version that
|
|
135
|
+
* is planned but not yet live is neither a warning nor an achievement, and
|
|
136
|
+
* six surfaces had each picked their own blue-ish colour to say so. */
|
|
137
|
+
--sa-color-scheduled: var(--sa-indigo-700);
|
|
138
|
+
--sa-color-scheduled-strong: var(--sa-indigo-400);
|
|
139
|
+
--sa-color-scheduled-fg: var(--sa-indigo-700);
|
|
140
|
+
--sa-color-scheduled-surface: var(--sa-indigo-50);
|
|
141
|
+
--sa-color-scheduled-surface-strong: var(--sa-indigo-100);
|
|
142
|
+
--sa-color-scheduled-border: var(--sa-indigo-200);
|
|
143
|
+
|
|
144
|
+
/* ── Catalogue entities. Not statuses: these two say WHAT a row is, and
|
|
145
|
+
* the plan editor, the matrix, the review and the discovery page all need
|
|
146
|
+
* to agree, or the same dot means two things on two screens. ────────── */
|
|
147
|
+
--sa-color-feature: var(--sa-violet-500);
|
|
148
|
+
--sa-color-feature-fg: var(--sa-violet-700);
|
|
149
|
+
--sa-color-feature-surface: var(--sa-violet-50);
|
|
150
|
+
--sa-color-quota: var(--sa-sky-500);
|
|
151
|
+
--sa-color-quota-surface: var(--sa-sky-50);
|
|
152
|
+
--sa-color-quota-border: var(--sa-sky-200);
|
|
153
|
+
--sa-color-bundle: var(--sa-amber-500);
|
|
154
|
+
--sa-color-bundle-surface: var(--sa-amber-100);
|
|
155
|
+
|
|
156
|
+
/* ── Identity. A CATEGORICAL ramp: the only job of these seven is to be
|
|
157
|
+
* told apart.
|
|
158
|
+
*
|
|
159
|
+
* Use them when a colour distinguishes one row from the next and means
|
|
160
|
+
* nothing beyond that — a plan's mark, a promotion badge, a capability
|
|
161
|
+
* kind. Use `feature`/`quota`/`bundle`/`scheduled` above when the category
|
|
162
|
+
* carries meaning; a reader should never have to guess which of the two a
|
|
163
|
+
* colour is doing.
|
|
164
|
+
*
|
|
165
|
+
* They exist because the package had FIVE of these ramps, all six colours
|
|
166
|
+
* long, none agreeing: `PlanList` and `PlanMatrix` shared one byte for
|
|
167
|
+
* byte, `tenants/format.ts` had a third that gave the same plan a
|
|
168
|
+
* different colour on the tenants page, and `PromoCodesPage`,
|
|
169
|
+
* `MarketingPromotionsTab` and `discovery-ui.ts` each invented their own.
|
|
170
|
+
* Every one of them was a hex literal in TypeScript, so none followed the
|
|
171
|
+
* theme: the plan mark measured 2.96:1 on a dark surface and was carried
|
|
172
|
+
* as a named contrast exception because there was nowhere to fix it.
|
|
173
|
+
*
|
|
174
|
+
* Each rung is chosen to clear 3:1 as TEXT on this theme's card surface,
|
|
175
|
+
* because that is how they are used — as the label, not only as a dot. */
|
|
176
|
+
--sa-color-identity-1: var(--sa-blue-700);
|
|
177
|
+
--sa-color-identity-2: var(--sa-violet-700);
|
|
178
|
+
--sa-color-identity-3: var(--sa-green-700);
|
|
179
|
+
--sa-color-identity-4: var(--sa-amber-700);
|
|
180
|
+
--sa-color-identity-5: var(--sa-sky-700);
|
|
181
|
+
--sa-color-identity-6: var(--sa-red-700);
|
|
182
|
+
/* The entry tier, and the answer for "no identity yet". Quiet on purpose:
|
|
183
|
+
* a starter plan reading as loud as an enterprise one is a wrong signal. */
|
|
184
|
+
--sa-color-identity-neutral: var(--sa-neutral-500);
|
|
185
|
+
|
|
186
|
+
/* ── Inverse chrome — the dark header, drawer and login backdrop.
|
|
187
|
+
*
|
|
188
|
+
* Declared identically in both themes ON PURPOSE. This surface is dark in
|
|
189
|
+
* a light theme too, so a role that flipped would turn the drawer's labels
|
|
190
|
+
* black on black the moment somebody enabled dark mode. Same reasoning as
|
|
191
|
+
* for the brand accent: what is dark by design does not follow the theme.
|
|
192
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
193
|
+
--sa-color-inverse-bg: var(--sa-neutral-950);
|
|
194
|
+
--sa-color-inverse-surface: var(--sa-neutral-800);
|
|
195
|
+
--sa-color-inverse-surface-soft: var(--sa-neutral-600);
|
|
196
|
+
--sa-color-inverse-fg: var(--sa-white);
|
|
197
|
+
--sa-color-inverse-fg-muted: var(--sa-neutral-300);
|
|
198
|
+
--sa-color-inverse-accent: var(--sa-amber-400);
|
|
199
|
+
--sa-color-inverse-accent-strong: var(--sa-amber-600);
|
|
200
|
+
--sa-color-inverse-accent-surface: color-mix(in srgb, var(--sa-amber-500) 15%, transparent);
|
|
201
|
+
--sa-color-inverse-accent-surface-strong: color-mix(
|
|
202
|
+
in srgb,
|
|
203
|
+
var(--sa-amber-500) 22%,
|
|
204
|
+
transparent
|
|
205
|
+
);
|
|
206
|
+
--sa-color-inverse-accent-fg: var(--sa-neutral-900);
|
|
207
|
+
--sa-color-inverse-danger: var(--sa-red-700);
|
|
208
|
+
--sa-color-inverse-danger-strong: var(--sa-red-600);
|
|
209
|
+
--sa-color-inverse-notice: var(--sa-amber-600);
|
|
210
|
+
--sa-color-inverse-notice-strong: var(--sa-amber-700);
|
|
211
|
+
--sa-color-inverse-fg-subtle: color-mix(in srgb, var(--sa-white) 55%, transparent);
|
|
212
|
+
--sa-color-inverse-border: color-mix(in srgb, var(--sa-white) 8%, transparent);
|
|
213
|
+
--sa-color-inverse-border-strong: color-mix(in srgb, var(--sa-white) 16%, transparent);
|
|
214
|
+
|
|
215
|
+
/* ── Focus and selection. ───────────────────────────────────────────── */
|
|
216
|
+
--sa-color-focus-ring: color-mix(in srgb, var(--sa-color-accent) 35%, transparent);
|
|
217
|
+
--sa-color-selection: color-mix(in srgb, var(--sa-color-accent) 22%, transparent);
|
|
218
|
+
|
|
219
|
+
/* ── Shadow. One ink, four tints, five elevations.
|
|
220
|
+
*
|
|
221
|
+
* The ink is a token rather than black because a slate-tinted shadow reads
|
|
222
|
+
* as depth on a slate-tinted page, while pure black reads as dirt. In the
|
|
223
|
+
* dark theme it becomes black and the alphas rise, because a light shadow
|
|
224
|
+
* on a dark surface is invisible at 6%. ─────────────────────────────── */
|
|
225
|
+
--sa-shadow-ink: var(--sa-neutral-900);
|
|
226
|
+
--sa-shadow-tint-1: color-mix(in srgb, var(--sa-shadow-ink) 6%, transparent);
|
|
227
|
+
--sa-shadow-tint-2: color-mix(in srgb, var(--sa-shadow-ink) 8%, transparent);
|
|
228
|
+
--sa-shadow-tint-3: color-mix(in srgb, var(--sa-shadow-ink) 12%, transparent);
|
|
229
|
+
--sa-shadow-tint-4: color-mix(in srgb, var(--sa-shadow-ink) 20%, transparent);
|
|
230
|
+
|
|
231
|
+
--sa-shadow-0: none;
|
|
232
|
+
--sa-shadow-1: 0 1px 2px var(--sa-shadow-tint-1);
|
|
233
|
+
--sa-shadow-2: 0 2px 6px var(--sa-shadow-tint-2);
|
|
234
|
+
--sa-shadow-3: 0 8px 20px var(--sa-shadow-tint-3);
|
|
235
|
+
--sa-shadow-4: 0 20px 48px var(--sa-shadow-tint-4);
|
|
236
|
+
--sa-shadow-focus: 0 0 0 3px var(--sa-color-focus-ring);
|
|
237
|
+
|
|
238
|
+
--sa-elevation-tile: var(--sa-shadow-1);
|
|
239
|
+
--sa-elevation-card: var(--sa-shadow-2);
|
|
240
|
+
--sa-elevation-menu: var(--sa-shadow-3);
|
|
241
|
+
--sa-elevation-dialog: var(--sa-shadow-4);
|
|
242
|
+
}
|