@cavulsqa/create 2.9.2 → 2.10.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 +12 -1
- package/bin/create.mjs +18 -25
- package/lib/templates.mjs +56 -0
- package/package.json +13 -5
- package/templates/f7-app/CLAUDE.md +3 -0
- package/templates/f7-app/auto-imports.d.ts +15 -0
- package/templates/f7-app/package.json +6 -3
- package/templates/f7-app/src/App.vue +1 -0
- package/templates/f7-app/src/env.d.ts +1 -0
- package/templates/f7-app/src/main.ts +3 -0
- package/templates/f7-app/src/modules/demo/router/routes/demo.routes.ts +4 -16
- package/templates/f7-app/src/modules/home/router/routes/home.routes.ts +3 -11
- package/templates/f7-app/src/modules/settings/router/routes/settings.routes.ts +2 -6
- package/templates/f7-app/src/plugins/bootstrapError.ts +2 -0
- package/templates/f7-app/src/plugins/recorder.plugin.ts +155 -0
- package/templates/f7-app/src/router/global/global.routes.ts +2 -6
- package/templates/f7-app/src/shared/composables/useNavigationGuard.ts +48 -0
- package/templates/f7-app/src/shared/recorder/capacitorSink.ts +124 -0
- package/templates/f7-app/src/shared/utils/lazyRoute.ts +25 -0
- package/templates/f7-app/tests/lazyRoute.test.ts +30 -0
- package/templates/f7-app/tests/navigationGuard.test.ts +82 -0
- package/templates/f7-app/vite.config.ts +1 -0
- package/templates/m3e-app/.claude/rules/data-fetching.md +68 -0
- package/templates/m3e-app/.claude/rules/database.md +106 -0
- package/templates/m3e-app/.claude/rules/m3e-ui.md +48 -0
- package/templates/m3e-app/.claude/rules/modules.md +43 -0
- package/templates/m3e-app/.claude/rules/native.md +60 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/SKILL.md +68 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/color.md +44 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/components.md +325 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/layout.md +42 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/motion.md +51 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/shapes-type.md +48 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/sources.md +53 -0
- package/templates/m3e-app/.claude/skills/module-architecture/SKILL.md +65 -0
- package/templates/m3e-app/.claude/skills/module-architecture/file-templates.md +178 -0
- package/templates/m3e-app/.claude/skills/reactive-data/SKILL.md +90 -0
- package/templates/m3e-app/.claude/skills/reactive-data/testing.md +55 -0
- package/templates/m3e-app/.env.example +32 -0
- package/templates/m3e-app/CLAUDE.md +93 -0
- package/templates/m3e-app/auto-imports.d.ts +851 -0
- package/templates/m3e-app/capacitor.config.ts +44 -0
- package/templates/m3e-app/components.d.ts +252 -0
- package/templates/m3e-app/index.html +16 -0
- package/templates/m3e-app/package.json +107 -0
- package/templates/m3e-app/src/App.vue +129 -0
- package/templates/m3e-app/src/app/pragmas.config.ts +35 -0
- package/templates/m3e-app/src/app/scroll.config.ts +20 -0
- package/templates/m3e-app/src/app/storage.config.ts +65 -0
- package/templates/m3e-app/src/app/tabs.ts +33 -0
- package/templates/m3e-app/src/app/theme.config.ts +51 -0
- package/templates/m3e-app/src/assets/css/app.css +18 -0
- package/templates/m3e-app/src/assets/css/base.css +53 -0
- package/templates/m3e-app/src/assets/css/layout/container-transform.css +113 -0
- package/templates/m3e-app/src/assets/css/layout/shell.css +69 -0
- package/templates/m3e-app/src/assets/css/layout/transitions.css +121 -0
- package/templates/m3e-app/src/assets/css/m3e.css +6 -0
- package/templates/m3e-app/src/assets/css/theme/framework7.css +18 -0
- package/templates/m3e-app/src/assets/css/theme/tailwind.css +365 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.dataset.ts +209 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.suite.ts +662 -0
- package/templates/m3e-app/src/domains/sales/sales.repository.ts +458 -0
- package/templates/m3e-app/src/env.d.ts +40 -0
- package/templates/m3e-app/src/locales/ar.json +1260 -0
- package/templates/m3e-app/src/locales/en.json +1260 -0
- package/templates/m3e-app/src/locales/fr.json +1260 -0
- package/templates/m3e-app/src/main.ts +41 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBenchmark.vue +119 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBusLog.vue +63 -0
- package/templates/m3e-app/src/modules/demo/components/DemoCreateOrderSheet.vue +203 -0
- package/templates/m3e-app/src/modules/demo/components/DemoMetricsSheet.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoOrderList.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoPipelineBenchmark.vue +65 -0
- package/templates/m3e-app/src/modules/demo/components/DemoStatCards.vue +71 -0
- package/templates/m3e-app/src/modules/demo/composables/useBenchmark.ts +139 -0
- package/templates/m3e-app/src/modules/demo/composables/useOrderStatus.ts +64 -0
- package/templates/m3e-app/src/modules/demo/composables/useReactiveDemo.ts +171 -0
- package/templates/m3e-app/src/modules/demo/router/routes/demo.routes.ts +22 -0
- package/templates/m3e-app/src/modules/demo/views/DemoView.vue +210 -0
- package/templates/m3e-app/src/modules/demo/views/OrderDetailView.vue +136 -0
- package/templates/m3e-app/src/modules/demo/views/OrderSearchView.vue +74 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryBlock.vue +16 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCarouselTile.vue +71 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCustomerForm.vue +76 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryProofOfDelivery.vue +69 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaDay.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaMonth.vue +40 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaVisitList.vue +45 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatAttachSheet.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatContactPicker.vue +85 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatInviteComposer.vue +131 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatPollComposer.vue +148 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatRecentPhotos.vue +62 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsAccount.vue +79 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsTags.vue +53 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsVerification.vue +73 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/PasswordStrength.vue +37 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryButtons.vue +128 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCarousels.vue +236 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCharts.vue +84 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryFabs.vue +70 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryInputs.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryNavigation.vue +141 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryOverlays.vue +368 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryPickers.vue +175 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryProgress.vue +118 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryScale.vue +103 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySelection.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryShapes.vue +48 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySurfaces.vue +213 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryTables.vue +185 -0
- package/templates/m3e-app/src/modules/gallery/components/surfaces/SurfacesContainerTransform.vue +46 -0
- package/templates/m3e-app/src/modules/gallery/composables/chatTeam.ts +18 -0
- package/templates/m3e-app/src/modules/gallery/composables/composeKind.ts +2 -0
- package/templates/m3e-app/src/modules/gallery/composables/currentPosition.ts +35 -0
- package/templates/m3e-app/src/modules/gallery/composables/demoOrders.ts +29 -0
- package/templates/m3e-app/src/modules/gallery/composables/featuredStories.ts +73 -0
- package/templates/m3e-app/src/modules/gallery/composables/fieldFormats.ts +36 -0
- package/templates/m3e-app/src/modules/gallery/composables/openExternal.ts +11 -0
- package/templates/m3e-app/src/modules/gallery/composables/routePlan.ts +90 -0
- package/templates/m3e-app/src/modules/gallery/composables/useAgenda.ts +61 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatCustomers.ts +31 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatDemo.ts +299 -0
- package/templates/m3e-app/src/modules/gallery/composables/useGallerySections.ts +230 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocalAttachments.ts +71 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocationShare.ts +38 -0
- package/templates/m3e-app/src/modules/gallery/composables/usePhotoScenes.ts +82 -0
- package/templates/m3e-app/src/modules/gallery/composables/useTreeDemo.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/composables/wilayas.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/router/routes/gallery.routes.ts +52 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryAgendaView.vue +117 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryChatView.vue +292 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryContactsView.vue +77 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryFeaturedView.vue +60 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryLoginView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryOnboardingView.vue +116 -0
- package/templates/m3e-app/src/modules/gallery/views/GallerySectionView.vue +27 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryTabsView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryView.vue +50 -0
- package/templates/m3e-app/src/modules/home/components/HomeHero.vue +57 -0
- package/templates/m3e-app/src/modules/home/composables/useHomeFeatures.ts +128 -0
- package/templates/m3e-app/src/modules/home/composables/useOpenFeature.ts +22 -0
- package/templates/m3e-app/src/modules/home/router/routes/home.routes.ts +21 -0
- package/templates/m3e-app/src/modules/home/views/FeatureDetailView.vue +66 -0
- package/templates/m3e-app/src/modules/home/views/HomeView.vue +47 -0
- package/templates/m3e-app/src/modules/settings/components/RolePalette.vue +38 -0
- package/templates/m3e-app/src/modules/settings/components/SeedSwatches.vue +41 -0
- package/templates/m3e-app/src/modules/settings/components/SettingsChoice.vue +43 -0
- package/templates/m3e-app/src/modules/settings/components/StudioPreview.vue +52 -0
- package/templates/m3e-app/src/modules/settings/router/routes/settings.routes.ts +17 -0
- package/templates/m3e-app/src/modules/settings/types.ts +7 -0
- package/templates/m3e-app/src/modules/settings/views/ColorStudioView.vue +166 -0
- package/templates/m3e-app/src/modules/settings/views/SettingsView.vue +157 -0
- package/templates/m3e-app/src/plugins/bootstrapError.ts +62 -0
- package/templates/m3e-app/src/plugins/capacitor/index.ts +14 -0
- package/templates/m3e-app/src/plugins/capacitor/useAndroidBackButton.ts +36 -0
- package/templates/m3e-app/src/plugins/capacitor/useKeyboard.ts +48 -0
- package/templates/m3e-app/src/plugins/capacitor/useSplashScreen.ts +43 -0
- package/templates/m3e-app/src/plugins/capacitor/useStatusBar.ts +16 -0
- package/templates/m3e-app/src/plugins/framework7.plugin.ts +36 -0
- package/templates/m3e-app/src/plugins/i18n.plugin.ts +87 -0
- package/templates/m3e-app/src/plugins/m3e.plugin.ts +22 -0
- package/templates/m3e-app/src/plugins/seed.plugin.ts +7 -0
- package/templates/m3e-app/src/plugins/sqlite.plugin.ts +32 -0
- package/templates/m3e-app/src/router/global/global.routes.ts +12 -0
- package/templates/m3e-app/src/router/index.ts +20 -0
- package/templates/m3e-app/src/shared/components/error/404.vue +13 -0
- package/templates/m3e-app/src/shared/components/layout/EmptyState.vue +30 -0
- package/templates/m3e-app/src/shared/components/layout/SectionHeader.vue +10 -0
- package/templates/m3e-app/src/shared/components/page/AppPage.vue +104 -0
- package/templates/m3e-app/src/shared/composables/navigation/useActiveTab.ts +39 -0
- package/templates/m3e-app/src/shared/composables/navigation/useContainerTransform.ts +117 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationGuard.ts +48 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationVisibility.ts +64 -0
- package/templates/m3e-app/src/shared/composables/navigation/useViewRouter.ts +25 -0
- package/templates/m3e-app/src/shared/composables/navigation/useWindowClass.ts +22 -0
- package/templates/m3e-app/src/shared/composables/theme/useThemeSettings.ts +155 -0
- package/templates/m3e-app/src/shared/database/candidates/capacitorSqlite.ts +36 -0
- package/templates/m3e-app/src/shared/database/candidates/index.ts +4 -0
- package/templates/m3e-app/src/shared/database/candidates/opfsSahPool.ts +25 -0
- package/templates/m3e-app/src/shared/database/candidates/types.ts +56 -0
- package/templates/m3e-app/src/shared/database/candidates/waSqlite.ts +56 -0
- package/templates/m3e-app/src/shared/database/database.ts +219 -0
- package/templates/m3e-app/src/shared/database/index.ts +3 -0
- package/templates/m3e-app/src/shared/database/migrations.ts +223 -0
- package/templates/m3e-app/src/shared/database/opfs.worker.ts +5 -0
- package/templates/m3e-app/src/shared/database/queries.ts +26 -0
- package/templates/m3e-app/src/shared/database/schema.ts +153 -0
- package/templates/m3e-app/src/shared/database/storage.ts +64 -0
- package/templates/m3e-app/src/shared/database/wa.worker.ts +5 -0
- package/templates/m3e-app/src/shared/utils/lazyRoute.ts +41 -0
- package/templates/m3e-app/src/shared/utils/resolvers/resolvers.ts +42 -0
- package/templates/m3e-app/src/shared/utils/textDirection.ts +22 -0
- package/templates/m3e-app/src/shared/utils/theme/themeSettings.ts +59 -0
- package/templates/m3e-app/src/shared/utils/tone.ts +8 -0
- package/templates/m3e-app/tests/benchmark.suite.test.ts +104 -0
- package/templates/m3e-app/tests/containerTransform.test.ts +58 -0
- package/templates/m3e-app/tests/fieldFormats.test.ts +25 -0
- package/templates/m3e-app/tests/lazyRoute.test.ts +30 -0
- package/templates/m3e-app/tests/locales.test.ts +73 -0
- package/templates/m3e-app/tests/migrations.test.ts +74 -0
- package/templates/m3e-app/tests/navigationGuard.test.ts +82 -0
- package/templates/m3e-app/tests/openDatabase.test.ts +54 -0
- package/templates/m3e-app/tests/sales.repository.test.ts +329 -0
- package/templates/m3e-app/tests/storage.test.ts +117 -0
- package/templates/m3e-app/tests/textDirection.test.ts +11 -0
- package/templates/m3e-app/tooling/linkedPackages.ts +112 -0
- package/templates/m3e-app/tsconfig.json +21 -0
- package/templates/m3e-app/vite.config.ts +144 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
capacitorSqlite,
|
|
3
|
+
isStorageId,
|
|
4
|
+
opfsSahPool,
|
|
5
|
+
waAccessHandlePool,
|
|
6
|
+
waIdbBatchAtomic,
|
|
7
|
+
waOriginPrivateFileSystem,
|
|
8
|
+
STORAGE_IDS,
|
|
9
|
+
type StorageCandidate,
|
|
10
|
+
} from "@/shared/database/candidates";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The knob. Walked in order, first candidate that opens wins, and the rest are never imported - so a
|
|
14
|
+
* chain that stops at the first entry never downloads the others' wasm.
|
|
15
|
+
*
|
|
16
|
+
* Reorder freely to trade speed for reach: the entries lower down reach older WebViews, and each
|
|
17
|
+
* one states its own `tradeoff` and whether its `evidence` came from a device or a vendor's README.
|
|
18
|
+
* Read the list rather than this comment - `@cavulsqa/create` removes the engines an app did not
|
|
19
|
+
* ask for, so what is below is what this app actually has.
|
|
20
|
+
*
|
|
21
|
+
* No in-memory entry on purpose: a chain that ends somewhere data is not kept is worse than one that
|
|
22
|
+
* fails and names every attempt.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_ORDER: StorageCandidate[] = [
|
|
25
|
+
opfsSahPool,
|
|
26
|
+
waAccessHandlePool,
|
|
27
|
+
waOriginPrivateFileSystem,
|
|
28
|
+
waIdbBatchAtomic,
|
|
29
|
+
capacitorSqlite,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `VITE_STORAGE_ENGINE` promotes one candidate to the front and leaves the rest as fallback, so the
|
|
34
|
+
* env var reorders the chain rather than replacing it - a device that cannot open the preferred
|
|
35
|
+
* engine still gets a working app.
|
|
36
|
+
*
|
|
37
|
+
* A typo is reported and ignored rather than fatal: it should not brick the build's output, but a
|
|
38
|
+
* setting that was silently dropped looks exactly like one that was applied and did nothing.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* What `VITE_STORAGE_ENGINE` was at build time, or undefined.
|
|
42
|
+
*
|
|
43
|
+
* Exported so the open can report it. Vite inlines this when it bundles, so an empty value means
|
|
44
|
+
* the variable never reached the build - a `.env` written as UTF-16 by PowerShell's `>` parses as
|
|
45
|
+
* garbage and is dropped without a word.
|
|
46
|
+
*/
|
|
47
|
+
export const preferredEngine = import.meta.env.VITE_STORAGE_ENGINE;
|
|
48
|
+
|
|
49
|
+
function preferredFirst(candidates: StorageCandidate[]): StorageCandidate[] {
|
|
50
|
+
const preferred = preferredEngine;
|
|
51
|
+
if (!preferred) return candidates;
|
|
52
|
+
|
|
53
|
+
if (!isStorageId(preferred)) {
|
|
54
|
+
console.warn(
|
|
55
|
+
`[storage] VITE_STORAGE_ENGINE="${preferred}" is not an engine this app has. ` +
|
|
56
|
+
`Expected one of: ${STORAGE_IDS.join(", ")}. Falling back to the default order.`,
|
|
57
|
+
);
|
|
58
|
+
return candidates;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const match = candidates.find((candidate) => candidate.id === preferred);
|
|
62
|
+
return match ? [match, ...candidates.filter((candidate) => candidate !== match)] : candidates;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const storageChain: StorageCandidate[] = preferredFirst(DEFAULT_ORDER);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Component } from "vue";
|
|
2
|
+
import HomeOutline from "~icons/material-symbols/home-outline-rounded";
|
|
3
|
+
import HomeFilled from "~icons/material-symbols/home-rounded";
|
|
4
|
+
import OrdersOutline from "~icons/material-symbols/receipt-long-outline-rounded";
|
|
5
|
+
import OrdersFilled from "~icons/material-symbols/receipt-long-rounded";
|
|
6
|
+
import GalleryOutline from "~icons/material-symbols/widgets-outline-rounded";
|
|
7
|
+
import GalleryFilled from "~icons/material-symbols/widgets-rounded";
|
|
8
|
+
import SettingsOutline from "~icons/material-symbols/settings-outline-rounded";
|
|
9
|
+
import SettingsFilled from "~icons/material-symbols/settings-rounded";
|
|
10
|
+
|
|
11
|
+
export interface TabDefinition {
|
|
12
|
+
/** Also the tab's DOM id (`view-<id>`) and the first segment of its route. */
|
|
13
|
+
id: string;
|
|
14
|
+
labelKey: string;
|
|
15
|
+
/** M3 marks the selected destination with a filled glyph as well as the indicator. */
|
|
16
|
+
icon: Component;
|
|
17
|
+
iconSelected: Component;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Add an entry plus a route file in the matching module; the bar and the rail follow. */
|
|
21
|
+
export const tabs: readonly TabDefinition[] = [
|
|
22
|
+
{ id: "home", labelKey: "tabs.home", icon: HomeOutline, iconSelected: HomeFilled },
|
|
23
|
+
{ id: "demo", labelKey: "tabs.demo", icon: OrdersOutline, iconSelected: OrdersFilled },
|
|
24
|
+
{ id: "gallery", labelKey: "tabs.gallery", icon: GalleryOutline, iconSelected: GalleryFilled },
|
|
25
|
+
{
|
|
26
|
+
id: "settings",
|
|
27
|
+
labelKey: "tabs.settings",
|
|
28
|
+
icon: SettingsOutline,
|
|
29
|
+
iconSelected: SettingsFilled,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const START_TAB = tabs[0]!.id;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SchemeVariant } from "@cavulsqa/m3e";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The brand, as data. Every colour on screen is generated from `seed` by Material's 2025 colour
|
|
5
|
+
* system; nothing else in the app is allowed to name a hex value. Change the seed here and the
|
|
6
|
+
* whole app - light, dark, every contrast level - follows.
|
|
7
|
+
*/
|
|
8
|
+
export const BRAND_SEED = "#c96442";
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_VARIANT: SchemeVariant = "tonalSpot";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Custom colour groups beyond the M3 roles, each published as `X`, `on-X`, `X-container` and
|
|
14
|
+
* `on-X-container`. Harmonised toward the seed so they belong to the same palette.
|
|
15
|
+
*/
|
|
16
|
+
export const EXTRA_COLORS = {
|
|
17
|
+
success: "#1c7a4a",
|
|
18
|
+
warning: "#c08a2e",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export interface SeedPreset {
|
|
22
|
+
id: string;
|
|
23
|
+
seed: string;
|
|
24
|
+
labelKey: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The colour studio's swatches, spread around the hue wheel. The first is the brand. */
|
|
28
|
+
export const SEED_PRESETS: readonly SeedPreset[] = [
|
|
29
|
+
{ id: "brand", seed: BRAND_SEED, labelKey: "studio.presets.brand" },
|
|
30
|
+
{ id: "red", seed: "#d32f2f", labelKey: "studio.presets.red" },
|
|
31
|
+
{ id: "amber", seed: "#c08a2e", labelKey: "studio.presets.amber" },
|
|
32
|
+
{ id: "green", seed: "#2e7d32", labelKey: "studio.presets.green" },
|
|
33
|
+
{ id: "teal", seed: "#00796b", labelKey: "studio.presets.teal" },
|
|
34
|
+
{ id: "blue", seed: "#2f6fd6", labelKey: "studio.presets.blue" },
|
|
35
|
+
{ id: "indigo", seed: "#4f5bd5", labelKey: "studio.presets.indigo" },
|
|
36
|
+
{ id: "violet", seed: "#7b4fd0", labelKey: "studio.presets.violet" },
|
|
37
|
+
{ id: "pink", seed: "#c2185b", labelKey: "studio.presets.pink" },
|
|
38
|
+
{ id: "slate", seed: "#546e7a", labelKey: "studio.presets.slate" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** The variants the studio offers, in the order M3 recommends them. */
|
|
42
|
+
export const STUDIO_VARIANTS: readonly SchemeVariant[] = [
|
|
43
|
+
"tonalSpot",
|
|
44
|
+
"expressive",
|
|
45
|
+
"vibrant",
|
|
46
|
+
"brand",
|
|
47
|
+
"neutral",
|
|
48
|
+
"monochrome",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export const TYPEFACE = `"Google Sans Flex Variable", "Google Sans Flex", Roboto, "Noto Sans", "Noto Sans Arabic", system-ui, sans-serif`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cascade layers decide every conflict here, not specificity and not `!important`. Framework7's
|
|
3
|
+
* stylesheet is the lowest layer, so nothing it ships can override a component or a utility; the
|
|
4
|
+
* M3 components sit in `components` (imported from `m3e.css`), below Tailwind's utilities, so a
|
|
5
|
+
* utility on a component wins.
|
|
6
|
+
*/
|
|
7
|
+
@layer framework7, theme, base, components, utilities;
|
|
8
|
+
|
|
9
|
+
@import "framework7/css" layer(framework7);
|
|
10
|
+
@import "framework7/components/tabs/css" layer(framework7);
|
|
11
|
+
@import "tailwindcss";
|
|
12
|
+
|
|
13
|
+
@import "./theme/tailwind.css";
|
|
14
|
+
@import "./theme/framework7.css";
|
|
15
|
+
@import "./base.css";
|
|
16
|
+
@import "./layout/shell.css";
|
|
17
|
+
@import "./layout/transitions.css";
|
|
18
|
+
@import "./layout/container-transform.css";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@layer base {
|
|
2
|
+
html,
|
|
3
|
+
body {
|
|
4
|
+
background: var(--md-sys-color-surface);
|
|
5
|
+
color: var(--md-sys-color-on-surface);
|
|
6
|
+
font-family: var(--md-ref-typeface-plain);
|
|
7
|
+
font-optical-sizing: auto;
|
|
8
|
+
-webkit-font-smoothing: antialiased;
|
|
9
|
+
-webkit-tap-highlight-color: transparent;
|
|
10
|
+
-webkit-text-size-adjust: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
user-select: none;
|
|
15
|
+
overscroll-behavior: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
input,
|
|
19
|
+
textarea,
|
|
20
|
+
[contenteditable],
|
|
21
|
+
.selectable {
|
|
22
|
+
user-select: text;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
::selection {
|
|
26
|
+
background: var(--md-sys-color-primary-container);
|
|
27
|
+
color: var(--md-sys-color-on-primary-container);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:focus-visible {
|
|
31
|
+
outline: 3px solid var(--md-sys-color-secondary);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* The page under a pushed one stays in the DOM; its looping animations - a loading indicator, a
|
|
37
|
+
* wave - would keep the compositor busy for a page nobody can see.
|
|
38
|
+
*/
|
|
39
|
+
.page-previous *,
|
|
40
|
+
.page-previous *::before,
|
|
41
|
+
.page-previous *::after {
|
|
42
|
+
animation-play-state: paused !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (prefers-reduced-motion: reduce) {
|
|
46
|
+
*,
|
|
47
|
+
*::before,
|
|
48
|
+
*::after {
|
|
49
|
+
animation-iteration-count: 1 !important;
|
|
50
|
+
scroll-behavior: auto !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `m3e-container`: Material's container transform, driven by `useContainerTransform`. The opened
|
|
3
|
+
* page starts clipped to the tapped card - its box, its corner radius, its colour, written on the
|
|
4
|
+
* view as `--m3e-ct-*` - and the clip opens to the full screen while the page's own content fades
|
|
5
|
+
* in over the card's colour. Back runs it in reverse into the card, re-measured just before. The
|
|
6
|
+
* page underneath holds still: only the container moves.
|
|
7
|
+
*
|
|
8
|
+
* The clip animates on the main thread, so it stays one element for one emphasized half-second.
|
|
9
|
+
* Under reduced motion it is a cross-fade.
|
|
10
|
+
*
|
|
11
|
+
* @see https://m3.material.io/styles/motion/transitions/transition-patterns
|
|
12
|
+
*/
|
|
13
|
+
@layer base {
|
|
14
|
+
:root {
|
|
15
|
+
--m3e-ct-duration: var(--md-sys-motion-duration-long2, 500ms);
|
|
16
|
+
--m3e-ct-ease: var(--md-sys-motion-easing-emphasized, cubic-bezier(0.2, 0, 0, 1));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.router-transition-m3e-container-forward .page-next {
|
|
20
|
+
z-index: 1;
|
|
21
|
+
animation: m3e-ct-open var(--m3e-ct-duration) var(--m3e-ct-ease) both;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.router-transition-m3e-container-forward .page-next > * {
|
|
25
|
+
animation: m3e-ct-content-in var(--m3e-ct-duration) linear both;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.router-transition-m3e-container-backward .page-current {
|
|
29
|
+
z-index: 1;
|
|
30
|
+
animation: m3e-ct-close var(--m3e-ct-duration) var(--m3e-ct-ease) both;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.router-transition-m3e-container-backward .page-current > * {
|
|
34
|
+
animation: m3e-ct-content-out var(--m3e-ct-duration) linear both;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.router-transition-m3e-container-forward .page-current,
|
|
38
|
+
.router-transition-m3e-container-backward .page-previous {
|
|
39
|
+
animation: m3e-ct-hold var(--m3e-ct-duration) linear both;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes m3e-ct-open {
|
|
43
|
+
from {
|
|
44
|
+
clip-path: inset(
|
|
45
|
+
var(--m3e-ct-top) var(--m3e-ct-right) var(--m3e-ct-bottom) var(--m3e-ct-left) round
|
|
46
|
+
var(--m3e-ct-radius)
|
|
47
|
+
);
|
|
48
|
+
background-color: var(--m3e-ct-color);
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
clip-path: inset(0 0 0 0 round 0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes m3e-ct-close {
|
|
56
|
+
from {
|
|
57
|
+
clip-path: inset(0 0 0 0 round 0);
|
|
58
|
+
}
|
|
59
|
+
to {
|
|
60
|
+
clip-path: inset(
|
|
61
|
+
var(--m3e-ct-top) var(--m3e-ct-right) var(--m3e-ct-bottom) var(--m3e-ct-left) round
|
|
62
|
+
var(--m3e-ct-radius)
|
|
63
|
+
);
|
|
64
|
+
background-color: var(--m3e-ct-color);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* The card's own content is gone at once; the page's arrives once the container has room. */
|
|
69
|
+
@keyframes m3e-ct-content-in {
|
|
70
|
+
0%,
|
|
71
|
+
35% {
|
|
72
|
+
opacity: 0;
|
|
73
|
+
}
|
|
74
|
+
100% {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes m3e-ct-content-out {
|
|
80
|
+
0% {
|
|
81
|
+
opacity: 1;
|
|
82
|
+
}
|
|
83
|
+
25%,
|
|
84
|
+
100% {
|
|
85
|
+
opacity: 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes m3e-ct-hold {
|
|
90
|
+
from,
|
|
91
|
+
to {
|
|
92
|
+
transform: none;
|
|
93
|
+
opacity: 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@media (prefers-reduced-motion: reduce) {
|
|
98
|
+
.router-transition-m3e-container-forward .page-next,
|
|
99
|
+
.router-transition-m3e-container-backward .page-previous {
|
|
100
|
+
animation: m3e-fade-in 200ms linear both;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.router-transition-m3e-container-forward .page-current,
|
|
104
|
+
.router-transition-m3e-container-backward .page-current {
|
|
105
|
+
animation: m3e-fade-out 200ms linear both;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.router-transition-m3e-container-forward .page-next > *,
|
|
109
|
+
.router-transition-m3e-container-backward .page-current > * {
|
|
110
|
+
animation: none;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The shell: the views fill the window; the compact navigation bar floats over their bottom edge,
|
|
3
|
+
* or, from 600dp, the navigation rail sits beside them. Pages never measure either - they read
|
|
4
|
+
* `--app-nav-offset`, which the shell sets to the bar's height while it is on screen.
|
|
5
|
+
*/
|
|
6
|
+
@layer base {
|
|
7
|
+
:root {
|
|
8
|
+
--app-nav-offset: 0px;
|
|
9
|
+
--app-bottom-inset: calc(var(--app-nav-offset) + env(safe-area-inset-bottom));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* The root is Framework7's own element: `F7App` renders `.framework7-root` and passes no class
|
|
14
|
+
* through, so a class on `<F7App>` styles nothing. Without the row, the rail stacks above the
|
|
15
|
+
* views and pushes every page below a landscape screen.
|
|
16
|
+
*/
|
|
17
|
+
.framework7-root {
|
|
18
|
+
display: flex;
|
|
19
|
+
background: var(--md-sys-color-surface);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.app-shell__rail {
|
|
23
|
+
position: relative;
|
|
24
|
+
z-index: 5001;
|
|
25
|
+
flex: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.app-shell__views {
|
|
29
|
+
position: relative;
|
|
30
|
+
flex: 1 1 auto;
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
padding-right: env(safe-area-inset-right);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Material's hide-on-scroll timing: the bar enters in 225ms decelerating and leaves in 175ms
|
|
38
|
+
* accelerating, so it gets out of the way faster than it comes back.
|
|
39
|
+
*/
|
|
40
|
+
.app-shell__bar {
|
|
41
|
+
position: fixed;
|
|
42
|
+
inset: auto 0 0;
|
|
43
|
+
z-index: 5001;
|
|
44
|
+
transition: transform 225ms var(--md-sys-motion-easing-emphasized-decelerate);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.app-shell__bar--hidden {
|
|
48
|
+
transform: translateY(calc(100% + 1px));
|
|
49
|
+
transition: transform 175ms var(--md-sys-motion-easing-emphasized-accelerate);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.page-content {
|
|
53
|
+
padding-bottom: calc(var(--app-bottom-inset) + 24px);
|
|
54
|
+
scroll-padding-bottom: calc(var(--app-bottom-inset) + 24px);
|
|
55
|
+
overscroll-behavior-y: contain;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.app-page--has-fab .page-content {
|
|
59
|
+
padding-bottom: calc(var(--app-bottom-inset) + 112px);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* Tabs pinned under a page's app bar take the bar's scrolled container with it, so the two read
|
|
64
|
+
* as one surface once content slides beneath them.
|
|
65
|
+
*/
|
|
66
|
+
.m3-app-bar[data-scrolled] ~ .app-sticky-tabs {
|
|
67
|
+
--m3-tabs-container: var(--md-sys-color-surface-container);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `m3e-axis`: Material's shared-axis X transition with an Expressive spring. Forward, the new page
|
|
3
|
+
* slides in from the end edge while the old one drifts toward the start; the two fade through each
|
|
4
|
+
* other rather than stacking, so a page never reads as a card sliding over another. Back plays the
|
|
5
|
+
* same movement mirrored. Directions flip under `dir="rtl"`.
|
|
6
|
+
*
|
|
7
|
+
* Travel runs on the slow spatial spring (full-screen moves), the fade on linear keyframes timed for
|
|
8
|
+
* a fade-through. Only transform and opacity move, both on the compositor. Under reduced motion it
|
|
9
|
+
* is a plain cross-fade: the meaning (you went somewhere) stays, the travel goes.
|
|
10
|
+
*
|
|
11
|
+
* @see https://m3.material.io/styles/motion/transitions/transition-patterns
|
|
12
|
+
*/
|
|
13
|
+
@layer base {
|
|
14
|
+
:root {
|
|
15
|
+
--m3e-axis-distance: 64px;
|
|
16
|
+
--m3e-axis-sign: 1;
|
|
17
|
+
--m3e-axis-duration: var(--md-sys-motion-spring-slow-spatial-duration, 650ms);
|
|
18
|
+
--m3e-axis-ease: var(--md-sys-motion-spring-slow-spatial, cubic-bezier(0.2, 0, 0, 1));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
[dir="rtl"] {
|
|
22
|
+
--m3e-axis-sign: -1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.router-transition-m3e-axis-forward .page,
|
|
26
|
+
.router-transition-m3e-axis-backward .page {
|
|
27
|
+
animation-fill-mode: both;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.router-transition-m3e-axis-forward .page-next {
|
|
31
|
+
animation:
|
|
32
|
+
m3e-axis-from-end var(--m3e-axis-duration) var(--m3e-axis-ease) both,
|
|
33
|
+
m3e-fade-in var(--m3e-axis-duration) linear both;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.router-transition-m3e-axis-forward .page-current {
|
|
37
|
+
animation:
|
|
38
|
+
m3e-axis-to-start var(--m3e-axis-duration) var(--m3e-axis-ease) both,
|
|
39
|
+
m3e-fade-out var(--m3e-axis-duration) linear both;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.router-transition-m3e-axis-backward .page-current {
|
|
43
|
+
animation:
|
|
44
|
+
m3e-axis-to-end var(--m3e-axis-duration) var(--m3e-axis-ease) both,
|
|
45
|
+
m3e-fade-out var(--m3e-axis-duration) linear both;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.router-transition-m3e-axis-backward .page-previous {
|
|
49
|
+
animation:
|
|
50
|
+
m3e-axis-from-start var(--m3e-axis-duration) var(--m3e-axis-ease) both,
|
|
51
|
+
m3e-fade-in var(--m3e-axis-duration) linear both;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@keyframes m3e-axis-from-end {
|
|
55
|
+
from {
|
|
56
|
+
transform: translateX(calc(var(--m3e-axis-distance) * var(--m3e-axis-sign)));
|
|
57
|
+
}
|
|
58
|
+
to {
|
|
59
|
+
transform: none;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes m3e-axis-to-start {
|
|
64
|
+
from {
|
|
65
|
+
transform: none;
|
|
66
|
+
}
|
|
67
|
+
to {
|
|
68
|
+
transform: translateX(calc(var(--m3e-axis-distance) * var(--m3e-axis-sign) * -1));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@keyframes m3e-axis-to-end {
|
|
73
|
+
from {
|
|
74
|
+
transform: none;
|
|
75
|
+
}
|
|
76
|
+
to {
|
|
77
|
+
transform: translateX(calc(var(--m3e-axis-distance) * var(--m3e-axis-sign)));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@keyframes m3e-axis-from-start {
|
|
82
|
+
from {
|
|
83
|
+
transform: translateX(calc(var(--m3e-axis-distance) * var(--m3e-axis-sign) * -1));
|
|
84
|
+
}
|
|
85
|
+
to {
|
|
86
|
+
transform: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@keyframes m3e-fade-in {
|
|
91
|
+
0%,
|
|
92
|
+
30% {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
}
|
|
95
|
+
100% {
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes m3e-fade-out {
|
|
101
|
+
0% {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
}
|
|
104
|
+
30%,
|
|
105
|
+
100% {
|
|
106
|
+
opacity: 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (prefers-reduced-motion: reduce) {
|
|
111
|
+
.router-transition-m3e-axis-forward .page-next,
|
|
112
|
+
.router-transition-m3e-axis-backward .page-previous {
|
|
113
|
+
animation: m3e-fade-in 200ms linear both;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.router-transition-m3e-axis-forward .page-current,
|
|
117
|
+
.router-transition-m3e-axis-backward .page-current {
|
|
118
|
+
animation: m3e-fade-out 200ms linear both;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The component stylesheet, in the `components` layer `app.css` declared. It is imported on its own,
|
|
3
|
+
* after `app.css`, so Vite's CSS pipeline - not Tailwind's - owns it: Tailwind caches its compile and
|
|
4
|
+
* never noticed a linked package rebuilding this file.
|
|
5
|
+
*/
|
|
6
|
+
@import "@cavulsqa/m3e-vue/style.css" layer(components);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Framework7 still draws the page and view containers, so its variables point at the M3 roles. It
|
|
3
|
+
* draws nothing else: no navbar, no toolbar, no list. If something here grows past the page
|
|
4
|
+
* surface, a Framework7 component has crept back in.
|
|
5
|
+
*/
|
|
6
|
+
@layer theme {
|
|
7
|
+
:root,
|
|
8
|
+
.md,
|
|
9
|
+
.md.dark,
|
|
10
|
+
.dark {
|
|
11
|
+
--f7-page-bg-color: var(--md-sys-color-surface);
|
|
12
|
+
--f7-text-color: var(--md-sys-color-on-surface);
|
|
13
|
+
--f7-theme-color: var(--md-sys-color-primary);
|
|
14
|
+
--f7-font-family: var(--md-ref-typeface-plain);
|
|
15
|
+
--f7-bars-bg-color: transparent;
|
|
16
|
+
--f7-page-transition-duration: var(--md-sys-motion-spring-slow-spatial-duration);
|
|
17
|
+
}
|
|
18
|
+
}
|