@humanforest/nuxt-layer 0.1.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/LICENSE +67 -0
- package/README.md +208 -0
- package/app.config.ts +10 -0
- package/assets/icons/forest/bike-asterisk.svg +21 -0
- package/assets/icons/forest/bike-down.svg +19 -0
- package/assets/icons/forest/bike-off.svg +20 -0
- package/assets/icons/forest/bike-up.svg +19 -0
- package/assets/icons/forest/bike-x.svg +19 -0
- package/assets/icons/forest/bike.svg +18 -0
- package/assets/icons/forest/mark-fill.svg +6 -0
- package/assets/icons/forest/mark.svg +6 -0
- package/assets/icons/forest/parking.svg +3 -0
- package/assets/icons/forest/star-fill.svg +4 -0
- package/assets/icons/forest/star.svg +4 -0
- package/assets/icons/forest/traffic-light-caution.svg +8 -0
- package/assets/icons/forest/traffic-light-go.svg +8 -0
- package/assets/icons/forest/traffic-light-lit.svg +8 -0
- package/assets/icons/forest/traffic-light-stop.svg +8 -0
- package/assets/icons/forest/traffic-light.svg +8 -0
- package/nuxt.config.ts +81 -0
- package/package.json +35 -0
- package/packages/charts/src/FBoroughShape.vue +52 -0
- package/packages/charts/src/FCalendarGrid.vue +332 -0
- package/packages/charts/src/FCellLegend.vue +54 -0
- package/packages/charts/src/FChartFrame.vue +201 -0
- package/packages/charts/src/FChartFrameSingle.vue +91 -0
- package/packages/charts/src/FChartLegend.vue +26 -0
- package/packages/charts/src/FDistributionBar.vue +224 -0
- package/packages/charts/src/FDottedMap.vue +335 -0
- package/packages/charts/src/FLondonMap.vue +399 -0
- package/packages/charts/src/FSparkline.vue +198 -0
- package/packages/charts/src/FStatusTrack.vue +151 -0
- package/packages/charts/src/cellSize.ts +46 -0
- package/packages/charts/src/distribution.ts +99 -0
- package/packages/charts/src/engine.ts +15 -0
- package/packages/charts/src/forestTooltip.ts +110 -0
- package/packages/charts/src/fromCategories.ts +43 -0
- package/packages/charts/src/index.ts +87 -0
- package/packages/charts/src/londonAreas.ts +75 -0
- package/packages/charts/src/londonGrid.ts +67 -0
- package/packages/charts/src/motionDuration.ts +49 -0
- package/packages/charts/src/presets.ts +143 -0
- package/packages/charts/src/resolveVar.ts +21 -0
- package/packages/charts/src/snapTooltip.ts +154 -0
- package/packages/charts/src/tooltipStandIn.ts +58 -0
- package/packages/charts/src/useCellScale.ts +155 -0
- package/packages/charts/src/useChartPalette.ts +61 -0
- package/packages/charts/src/useChartRepaintKey.ts +22 -0
- package/packages/charts/src/useThemeVersion.ts +17 -0
- package/packages/frames/src/FBrowserFrame.vue +53 -0
- package/packages/frames/src/FDeviceFrame.vue +242 -0
- package/packages/frames/src/FFitFrame.vue +83 -0
- package/packages/frames/src/art/iphone-11.svg +124 -0
- package/packages/frames/src/art/iphone-12.svg +601 -0
- package/packages/frames/src/art/iphone-13-pro.svg +820 -0
- package/packages/frames/src/art/iphone-13.svg +849 -0
- package/packages/frames/src/art/iphone-14.svg +681 -0
- package/packages/frames/src/art/iphone-15-pro.svg +633 -0
- package/packages/frames/src/art/iphone-15.svg +713 -0
- package/packages/frames/src/art/iphone-16-plus.svg +685 -0
- package/packages/frames/src/art/iphone-16-pro-max.svg +611 -0
- package/packages/frames/src/art/iphone-16-pro.svg +631 -0
- package/packages/frames/src/art/iphone-16.svg +713 -0
- package/packages/frames/src/art/iphone-17-pro-max.svg +355 -0
- package/packages/frames/src/art/iphone-17.svg +305 -0
- package/packages/frames/src/art/iphone-air.svg +389 -0
- package/packages/frames/src/art/iphone-se-3.svg +473 -0
- package/packages/frames/src/art/iphone-x.svg +652 -0
- package/packages/frames/src/art/pixel-10-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-10-pro.svg +32 -0
- package/packages/frames/src/art/pixel-10.svg +32 -0
- package/packages/frames/src/art/pixel-8-pro.svg +208 -0
- package/packages/frames/src/art/pixel-8.svg +208 -0
- package/packages/frames/src/art/pixel-8a.svg +8 -0
- package/packages/frames/src/art/pixel-9-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-9-pro.svg +34 -0
- package/packages/frames/src/art/pixel-9a.svg +25 -0
- package/packages/frames/src/art.ts +29 -0
- package/packages/frames/src/devices.ts +337 -0
- package/packages/frames/src/index.ts +15 -0
- package/packages/tokens/geo/london-boroughs.json +1 -0
- package/packages/tokens/geo/london-dots-coarse.json +1 -0
- package/packages/tokens/geo/london-dots-fine.json +1 -0
- package/packages/tokens/geo/london-dots-medium.json +1 -0
- package/packages/tokens/geo/london-dots-ultra.json +1 -0
- package/packages/tokens/geo/london-wards.json +1 -0
- package/packages/tokens/glyphs/glyphs.json +1 -0
- package/packages/tokens/logos/f-rot.svg +4 -0
- package/packages/tokens/logos/f.svg +4 -0
- package/packages/tokens/logos/favicon-c2w.svg +9 -0
- package/packages/tokens/logos/favicon-console.svg +9 -0
- package/packages/tokens/logos/favicon-ds.svg +9 -0
- package/packages/tokens/logos/favicon-fleet.svg +9 -0
- package/packages/tokens/logos/favicon-flex.svg +9 -0
- package/packages/tokens/logos/favicon-plus.svg +9 -0
- package/packages/tokens/logos/favicon-radar.svg +9 -0
- package/packages/tokens/logos/favicon-tower.svg +9 -0
- package/packages/tokens/logos/lockup-horizontal.svg +17 -0
- package/packages/tokens/logos/lockup-vertical.svg +17 -0
- package/packages/tokens/logos/mark-mono.svg +4 -0
- package/packages/tokens/logos/mark.svg +7 -0
- package/packages/tokens/logos/wordmark.svg +9 -0
- package/packages/tokens/src/colourEngine.ts +604 -0
- package/packages/tokens/src/glyphs.ts +127 -0
- package/packages/tokens/src/logo.ts +50 -0
- package/packages/tokens/src/subbrands.ts +44 -0
- package/packages/tokens/src/sublogo.ts +80 -0
- package/packages/ui/icons/forest/map/forest-place-count.svg +21 -0
- package/packages/ui/icons/forest/map/forest-place-marker.svg +17 -0
- package/packages/ui/icons/forest/map/forest-vehicle-marker.svg +73 -0
- package/packages/ui/icons/forest/map/manifest.json +373 -0
- package/packages/ui/icons/forest/map/place-count-manifest.json +83 -0
- package/packages/ui/icons/forest/map/place-manifest.json +74 -0
- package/packages/ui/src/address/address.ts +125 -0
- package/packages/ui/src/address/autofill.ts +78 -0
- package/packages/ui/src/address/geocode.ts +55 -0
- package/packages/ui/src/address/index.ts +31 -0
- package/packages/ui/src/address/validate.ts +91 -0
- package/packages/ui/src/canvas/FCanvasCard.vue +97 -0
- package/packages/ui/src/canvas/FCanvasControl.vue +121 -0
- package/packages/ui/src/canvas/FCanvasControlGroup.vue +100 -0
- package/packages/ui/src/canvas/FCanvasFlyout.vue +216 -0
- package/packages/ui/src/canvas/FCanvasHud.vue +402 -0
- package/packages/ui/src/canvas/canvasSurface.ts +39 -0
- package/packages/ui/src/canvas/index.ts +17 -0
- package/packages/ui/src/contexts/ForestContext.vue +53 -0
- package/packages/ui/src/contexts/contexts.ts +153 -0
- package/packages/ui/src/contexts/index.ts +23 -0
- package/packages/ui/src/contexts/marketing-theme.ts +214 -0
- package/packages/ui/src/contexts/mobile-theme.ts +402 -0
- package/packages/ui/src/filter/FFilterBar.vue +72 -0
- package/packages/ui/src/filter/FFilterChip.vue +362 -0
- package/packages/ui/src/filter/FFilterClauses.vue +104 -0
- package/packages/ui/src/filter/FFilterTrigger.vue +183 -0
- package/packages/ui/src/filter/filter.theme.ts +27 -0
- package/packages/ui/src/filter/filterClause.ts +265 -0
- package/packages/ui/src/filter/filterPredicate.ts +134 -0
- package/packages/ui/src/filter/filterSchema.ts +82 -0
- package/packages/ui/src/filter/index.ts +36 -0
- package/packages/ui/src/forest-preset.ts +68 -0
- package/packages/ui/src/icons.ts +79 -0
- package/packages/ui/src/index.ts +17 -0
- package/packages/ui/src/kpi/FKpi.vue +253 -0
- package/packages/ui/src/kpi/FKpiGroup.vue +161 -0
- package/packages/ui/src/kpi/index.ts +20 -0
- package/packages/ui/src/kpi/kpi.theme.ts +168 -0
- package/packages/ui/src/kpi/kpiDelta.ts +82 -0
- package/packages/ui/src/kpi/kpiFormat.ts +72 -0
- package/packages/ui/src/kpi/kpiGroupState.ts +63 -0
- package/packages/ui/src/logo/FLogo.vue +52 -0
- package/packages/ui/src/logo/FSubLogo.vue +214 -0
- package/packages/ui/src/logo/index.ts +8 -0
- package/packages/ui/src/map/FMap.vue +355 -0
- package/packages/ui/src/map/FMapCompass.vue +154 -0
- package/packages/ui/src/map/FMapCoordinates.vue +134 -0
- package/packages/ui/src/map/FMapDrawTools.vue +116 -0
- package/packages/ui/src/map/FMapFullscreen.vue +92 -0
- package/packages/ui/src/map/FMapGeolocate.vue +208 -0
- package/packages/ui/src/map/FMapLayers.vue +75 -0
- package/packages/ui/src/map/FMapLegend.vue +45 -0
- package/packages/ui/src/map/FMapPopup.vue +129 -0
- package/packages/ui/src/map/FMapRamp.vue +69 -0
- package/packages/ui/src/map/FMapRegion.vue +77 -0
- package/packages/ui/src/map/FMapScale.vue +107 -0
- package/packages/ui/src/map/FMapSearch.vue +362 -0
- package/packages/ui/src/map/FMapTilt.vue +87 -0
- package/packages/ui/src/map/FMapTools.vue +61 -0
- package/packages/ui/src/map/FMapZoom.vue +81 -0
- package/packages/ui/src/map/FMarker.vue +218 -0
- package/packages/ui/src/map/FTrackScrubber.vue +94 -0
- package/packages/ui/src/map/FVehicleLegend.vue +72 -0
- package/packages/ui/src/map/FVehicleMark.vue +166 -0
- package/packages/ui/src/map/bin.ts +196 -0
- package/packages/ui/src/map/coordinates.ts +83 -0
- package/packages/ui/src/map/density.ts +401 -0
- package/packages/ui/src/map/draw.ts +356 -0
- package/packages/ui/src/map/ease.ts +67 -0
- package/packages/ui/src/map/fullscreen.ts +59 -0
- package/packages/ui/src/map/heat.ts +160 -0
- package/packages/ui/src/map/index.ts +351 -0
- package/packages/ui/src/map/liveMarkers.ts +60 -0
- package/packages/ui/src/map/map.css +116 -0
- package/packages/ui/src/map/mapColor.ts +31 -0
- package/packages/ui/src/map/mapContext.ts +12 -0
- package/packages/ui/src/map/mapLayers.ts +55 -0
- package/packages/ui/src/map/mapLegend.ts +70 -0
- package/packages/ui/src/map/mapSearch.theme.ts +79 -0
- package/packages/ui/src/map/mapSearch.ts +279 -0
- package/packages/ui/src/map/mapUrl.ts +117 -0
- package/packages/ui/src/map/markerAsset.ts +3 -0
- package/packages/ui/src/map/padding.ts +39 -0
- package/packages/ui/src/map/place.ts +192 -0
- package/packages/ui/src/map/ramp.ts +95 -0
- package/packages/ui/src/map/regionSelect.ts +163 -0
- package/packages/ui/src/map/route.ts +167 -0
- package/packages/ui/src/map/scale.ts +329 -0
- package/packages/ui/src/map/scaleBar.ts +67 -0
- package/packages/ui/src/map/selectionModel.ts +50 -0
- package/packages/ui/src/map/sources/coordinates.ts +58 -0
- package/packages/ui/src/map/sources/mapboxPlaces.ts +71 -0
- package/packages/ui/src/map/style.ts +14 -0
- package/packages/ui/src/map/track.ts +279 -0
- package/packages/ui/src/map/useFeatureSelect.ts +230 -0
- package/packages/ui/src/map/useLiveMarkers.ts +84 -0
- package/packages/ui/src/map/useMap.ts +150 -0
- package/packages/ui/src/map/useMapDraw.ts +776 -0
- package/packages/ui/src/map/useMapOverlay.ts +421 -0
- package/packages/ui/src/map/useMapTableSelection.ts +101 -0
- package/packages/ui/src/map/useMapUrlState.ts +155 -0
- package/packages/ui/src/map/useMarkerSource.ts +163 -0
- package/packages/ui/src/map/useRegionSelect.ts +309 -0
- package/packages/ui/src/map/useTokenMode.ts +61 -0
- package/packages/ui/src/map/useTrackPlayback.ts +131 -0
- package/packages/ui/src/map/useVehicleFilter.ts +51 -0
- package/packages/ui/src/map/useVisibleFeatures.ts +124 -0
- package/packages/ui/src/map/vehicle.ts +807 -0
- package/packages/ui/src/map/vehicleFilter.ts +284 -0
- package/packages/ui/src/map/vehicleLegend.ts +155 -0
- package/packages/ui/src/map/visibleFeatures.ts +192 -0
- package/packages/ui/src/map/zone.ts +291 -0
- package/packages/ui/src/shell/FAppNavbar.vue +68 -0
- package/packages/ui/src/shell/FAppShell.vue +163 -0
- package/packages/ui/src/shell/FAppSidebar.vue +215 -0
- package/packages/ui/src/shell/FPageToolbar.vue +126 -0
- package/packages/ui/src/shell/FScrollShadow.vue +43 -0
- package/packages/ui/src/shell/FSidePanel.vue +258 -0
- package/packages/ui/src/shell/FToolbarActions.vue +32 -0
- package/packages/ui/src/shell/index.ts +33 -0
- package/packages/ui/src/shell/nav.ts +37 -0
- package/packages/ui/src/shell/pageToolbar.ts +69 -0
- package/packages/ui/src/shell/shell.ts +74 -0
- package/packages/ui/src/shell/sidePanel.ts +115 -0
- package/packages/ui/src/themes/accordion.ts +36 -0
- package/packages/ui/src/themes/alert.ts +16 -0
- package/packages/ui/src/themes/auth-form.ts +19 -0
- package/packages/ui/src/themes/avatar-group.ts +9 -0
- package/packages/ui/src/themes/avatar.ts +18 -0
- package/packages/ui/src/themes/badge.ts +51 -0
- package/packages/ui/src/themes/banner.ts +21 -0
- package/packages/ui/src/themes/breadcrumb.ts +10 -0
- package/packages/ui/src/themes/button.mechanics.ts +191 -0
- package/packages/ui/src/themes/button.ts +136 -0
- package/packages/ui/src/themes/calendar.ts +29 -0
- package/packages/ui/src/themes/card.ts +67 -0
- package/packages/ui/src/themes/carousel.ts +41 -0
- package/packages/ui/src/themes/checkbox-group.ts +20 -0
- package/packages/ui/src/themes/checkbox.ts +47 -0
- package/packages/ui/src/themes/chip.ts +50 -0
- package/packages/ui/src/themes/collapsible.ts +26 -0
- package/packages/ui/src/themes/color-picker.ts +13 -0
- package/packages/ui/src/themes/command-palette.ts +37 -0
- package/packages/ui/src/themes/container.ts +14 -0
- package/packages/ui/src/themes/context-menu.ts +38 -0
- package/packages/ui/src/themes/dashboard-group.ts +11 -0
- package/packages/ui/src/themes/dashboard-navbar.ts +10 -0
- package/packages/ui/src/themes/dashboard-panel.ts +18 -0
- package/packages/ui/src/themes/dashboard-resize-handle.ts +35 -0
- package/packages/ui/src/themes/dashboard-sidebar-collapse.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar-toggle.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar.ts +58 -0
- package/packages/ui/src/themes/dashboard-toolbar.ts +15 -0
- package/packages/ui/src/themes/drawer.ts +45 -0
- package/packages/ui/src/themes/dropdown-menu.ts +38 -0
- package/packages/ui/src/themes/empty.ts +43 -0
- package/packages/ui/src/themes/field-group.ts +52 -0
- package/packages/ui/src/themes/file-upload.ts +38 -0
- package/packages/ui/src/themes/footer-columns.ts +13 -0
- package/packages/ui/src/themes/footer.ts +9 -0
- package/packages/ui/src/themes/form-field.ts +25 -0
- package/packages/ui/src/themes/form.ts +22 -0
- package/packages/ui/src/themes/header.ts +12 -0
- package/packages/ui/src/themes/icon.ts +61 -0
- package/packages/ui/src/themes/index.ts +99 -0
- package/packages/ui/src/themes/input-date.ts +73 -0
- package/packages/ui/src/themes/input-menu.ts +66 -0
- package/packages/ui/src/themes/input-number.ts +58 -0
- package/packages/ui/src/themes/input-rating.ts +25 -0
- package/packages/ui/src/themes/input-tags.ts +63 -0
- package/packages/ui/src/themes/input-time.ts +70 -0
- package/packages/ui/src/themes/input.ts +148 -0
- package/packages/ui/src/themes/kbd.ts +27 -0
- package/packages/ui/src/themes/link.ts +27 -0
- package/packages/ui/src/themes/listbox.ts +48 -0
- package/packages/ui/src/themes/main.ts +6 -0
- package/packages/ui/src/themes/marquee.ts +45 -0
- package/packages/ui/src/themes/modal.ts +46 -0
- package/packages/ui/src/themes/navigation-menu.ts +80 -0
- package/packages/ui/src/themes/page-anchors.ts +7 -0
- package/packages/ui/src/themes/page-aside.ts +7 -0
- package/packages/ui/src/themes/page-card.ts +22 -0
- package/packages/ui/src/themes/page-cta.ts +18 -0
- package/packages/ui/src/themes/page-feature.ts +13 -0
- package/packages/ui/src/themes/page-header.ts +16 -0
- package/packages/ui/src/themes/page-hero.ts +15 -0
- package/packages/ui/src/themes/page-links.ts +12 -0
- package/packages/ui/src/themes/page-section.ts +14 -0
- package/packages/ui/src/themes/page.ts +7 -0
- package/packages/ui/src/themes/pagination.ts +17 -0
- package/packages/ui/src/themes/pin-input.ts +73 -0
- package/packages/ui/src/themes/popover.ts +30 -0
- package/packages/ui/src/themes/progress.ts +29 -0
- package/packages/ui/src/themes/prose.ts +45 -0
- package/packages/ui/src/themes/radio-group.ts +39 -0
- package/packages/ui/src/themes/scroll-area.ts +23 -0
- package/packages/ui/src/themes/select-menu.ts +71 -0
- package/packages/ui/src/themes/select.ts +72 -0
- package/packages/ui/src/themes/separator.ts +28 -0
- package/packages/ui/src/themes/sidebar.ts +127 -0
- package/packages/ui/src/themes/skeleton.ts +15 -0
- package/packages/ui/src/themes/slideover.ts +87 -0
- package/packages/ui/src/themes/slider.ts +48 -0
- package/packages/ui/src/themes/stepper.ts +41 -0
- package/packages/ui/src/themes/switch.ts +63 -0
- package/packages/ui/src/themes/table.ts +34 -0
- package/packages/ui/src/themes/tabs.ts +74 -0
- package/packages/ui/src/themes/textarea.ts +77 -0
- package/packages/ui/src/themes/timeline.ts +25 -0
- package/packages/ui/src/themes/toast.ts +29 -0
- package/packages/ui/src/themes/toaster.ts +66 -0
- package/packages/ui/src/themes/tooltip.ts +31 -0
- package/packages/ui/src/themes/tree.ts +41 -0
- package/packages/ui/src/themes/user.ts +28 -0
- package/styles.cdn.gen.css +2047 -0
- package/theme.gen.json +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Forest's canvas HUD — one entry, matching ./map.
|
|
2
|
+
//
|
|
3
|
+
// <FCanvasHud> occluding overlay rails that compose with a shell, reflowing on container query
|
|
4
|
+
// <FCanvasControl> one control on the canvas — the chrome surface and its pressed cut, owned
|
|
5
|
+
// <FCanvasControlGroup> a merged cluster of them: one surface, hairlines inside, outer radius
|
|
6
|
+
// <FCanvasCard> a surface on the canvas — readout, legend, stat panel. Not interactive.
|
|
7
|
+
// <FCanvasFlyout> one control that opens into a cluster of them, in place
|
|
8
|
+
// CANVAS_RAIL_BOTTOM_MAX the bottom rail's ceiling before it starts to crowd the canvas
|
|
9
|
+
// CANVAS_CARD_MAX a card's ceiling — half the canvas per band, then it scrolls
|
|
10
|
+
// CanvasSurface which surface a rail is anchored to
|
|
11
|
+
export { default as FCanvasHud } from './FCanvasHud.vue';
|
|
12
|
+
export { default as FCanvasControl } from './FCanvasControl.vue';
|
|
13
|
+
export { default as FCanvasControlGroup } from './FCanvasControlGroup.vue';
|
|
14
|
+
export { default as FCanvasCard } from './FCanvasCard.vue';
|
|
15
|
+
export { default as FCanvasFlyout } from './FCanvasFlyout.vue';
|
|
16
|
+
export { CANVAS_RAIL_BOTTOM_MAX, CANVAS_CARD_MAX } from './canvasSurface';
|
|
17
|
+
export type { CanvasSurface } from './canvasSurface';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The single consumption primitive for Forest contexts (CONTEXTS-SPEC.md §2):
|
|
3
|
+
// no tag = product web; one tag = the bold voice at that scale.
|
|
4
|
+
//
|
|
5
|
+
// <ForestContext name="mobile"> … </ForestContext>
|
|
6
|
+
//
|
|
7
|
+
// Resolves theme + props from the contexts barrel (manifest-driven — a new utheme context appears
|
|
8
|
+
// here with zero wrapper changes) and renders <UTheme> around the slot. UTheme is provide/inject,
|
|
9
|
+
// so slot-class values survive teleports for components fired inside the subtree (select panels,
|
|
10
|
+
// modals). Toasts do NOT — they render at the host's UApp Toaster; an app-root tag must sit
|
|
11
|
+
// OUTSIDE <UApp> if toasts are to speak the context's voice.
|
|
12
|
+
//
|
|
13
|
+
// data-context is DIAGNOSTIC ONLY — the bleed check reads it; no CSS may target it (test-asserted).
|
|
14
|
+
// The wrapper ALSO carries a `forest-context` / `forest-context-<name>` MARKER CLASS — that is the
|
|
15
|
+
// sanctioned CSS hook for the few context-scoped rules the UTheme slot route can't express (the
|
|
16
|
+
// drawer edge radius, which stock pins in compoundVariants, and the roomier drawer padding shared by
|
|
17
|
+
// two contexts — both in styles/forest.css). It sits on the very same element as data-context, so its cascade reach is
|
|
18
|
+
// identical — but it is a real marker class, not the diagnostic attribute the hygiene test forbids.
|
|
19
|
+
//
|
|
20
|
+
// Nesting guard: one context per subtree (defu merges same-slot strings by WHOLESALE replace — a
|
|
21
|
+
// nested context would silently strip the outer cut). Dev = hard error; prod = the inner tag
|
|
22
|
+
// renders as a pass-through and the OUTER voice wins, predictably.
|
|
23
|
+
//
|
|
24
|
+
// <UTheme> resolves at runtime via the Nuxt UI plugin's global registration — no import needed.
|
|
25
|
+
import { computed, inject, provide, type InjectionKey } from 'vue';
|
|
26
|
+
import { forestContexts, type ForestContextName } from './contexts';
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{ name: ForestContextName }>();
|
|
29
|
+
|
|
30
|
+
// Symbol.for: a single key even if two copies of the package end up in one app.
|
|
31
|
+
const KEY = Symbol.for('forest.context') as InjectionKey<string>;
|
|
32
|
+
const parent = inject(KEY, undefined);
|
|
33
|
+
const nested = parent !== undefined;
|
|
34
|
+
|
|
35
|
+
if (nested) {
|
|
36
|
+
const msg = `[ForestContext] "${props.name}" is nested inside "${parent}" — one context per subtree (CONTEXTS-SPEC.md §2). The inner tag is a pass-through; the outer voice wins.`;
|
|
37
|
+
if (import.meta.env.DEV) throw new Error(msg);
|
|
38
|
+
console.warn(msg);
|
|
39
|
+
} else {
|
|
40
|
+
provide(KEY, props.name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ctx = computed(() => forestContexts[props.name]);
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<slot v-if="nested" />
|
|
48
|
+
<div v-else :data-context="name" :class="`forest-context forest-context-${name}`" style="display: contents">
|
|
49
|
+
<UTheme :ui="ctx.theme" :props="ctx.props">
|
|
50
|
+
<slot />
|
|
51
|
+
</UTheme>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest contexts — the barrel the tag, the docs register, and the tests all consume.
|
|
3
|
+
* Spec: CONTEXTS-SPEC.md (§3 barrel, §4 manifest, §5 checks).
|
|
4
|
+
*
|
|
5
|
+
* A context = a UTheme delta + prop defaults + a manifest recording what it touches. The manifest's
|
|
6
|
+
* `moves` sentence is the only hand-trusted field; `channels` is derived from the theme/props
|
|
7
|
+
* exports by `deriveChannels` and test-asserted, so it cannot rot.
|
|
8
|
+
*/
|
|
9
|
+
import * as themes from '../themes';
|
|
10
|
+
import { forestMobileTheme, forestMobileProps, forestMobileManifest } from './mobile-theme';
|
|
11
|
+
import { forestMarketingTheme, forestMarketingProps, forestMarketingManifest } from './marketing-theme';
|
|
12
|
+
|
|
13
|
+
export type ContextChannel = 'var' | 'slot' | 'props' | 'marker';
|
|
14
|
+
|
|
15
|
+
export interface ContextManifest {
|
|
16
|
+
/** Context name — shared with the type.ts contexts. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** 'utheme' = Vue subtree via <ForestContext>; 'css' = framework-free artefact (email, deferred). */
|
|
19
|
+
binding: 'utheme' | 'css';
|
|
20
|
+
/** Intended real surfaces. */
|
|
21
|
+
surfaces: string[];
|
|
22
|
+
/** Display prose for the register — the normative policy table lives in CONTEXTS-SPEC.md §2. */
|
|
23
|
+
darkPolicy: string;
|
|
24
|
+
/**
|
|
25
|
+
* Structured counterpart to darkPolicy for machinery to read (the prose is for humans). `true` =
|
|
26
|
+
* this context's canonical surface is locked light and never follows OS/host dark — so a docs
|
|
27
|
+
* specimen rendered in this context must force light even on a dark page. Marketing standalone is
|
|
28
|
+
* the case; mobile follows OS, so it is absent (falsy). Parse code reads THIS, never the sentence.
|
|
29
|
+
*/
|
|
30
|
+
lockLight?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* `true` = this context's canonical surface is a COARSE (touch) pointer, so it is inherently
|
|
33
|
+
* coarse — the `coarse:` size step-up always applies there and a "simulate coarse pointer" control
|
|
34
|
+
* is meaningless (you cannot have a mobile app that is not a touch surface). Docs read THIS to force
|
|
35
|
+
* the coarse simulation on and lock the pointer toggle in this context. Mobile is the case;
|
|
36
|
+
* marketing is pointer-agnostic (viewable on desktop or mobile web), so it is absent (falsy).
|
|
37
|
+
*/
|
|
38
|
+
coarse?: boolean;
|
|
39
|
+
components: Record<
|
|
40
|
+
string,
|
|
41
|
+
{
|
|
42
|
+
/** ONE human sentence describing what this context changes on the component. */
|
|
43
|
+
moves: string;
|
|
44
|
+
/** Derived by deriveChannels() and test-asserted — do not hand-tune. */
|
|
45
|
+
channels: ContextChannel[];
|
|
46
|
+
}
|
|
47
|
+
>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Bare hook classes that look marker-shaped but are deliberately outside the marker taxonomy.
|
|
52
|
+
* `cut-fixed` = the coarse-ergonomics opt-out (consumed by the `coarse:` variant's
|
|
53
|
+
* `:not(.cut-fixed)` in styles/forest.css); classified as channel `slot`.
|
|
54
|
+
*/
|
|
55
|
+
export const HOOK_CLASSES = ['cut-fixed'] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Register rows: every REVIEWED component — one per themes/ fragment (an empty fragment records a
|
|
59
|
+
* component whose stock recipe is correct as-is). Single source for ContextRegister.vue and the
|
|
60
|
+
* static tests.
|
|
61
|
+
*/
|
|
62
|
+
export const CONTEXT_REGISTER_ROWS = [...Object.keys(themes)].sort() as string[];
|
|
63
|
+
|
|
64
|
+
/** All theme-string leaf values of a component's theme entry (skips defaultVariants — those are
|
|
65
|
+
* variant NAMES, not classes). */
|
|
66
|
+
const collectStrings = (node: unknown, out: string[] = []): string[] => {
|
|
67
|
+
if (typeof node === 'string') out.push(node);
|
|
68
|
+
else if (Array.isArray(node)) node.forEach((n) => collectStrings(n, out));
|
|
69
|
+
else if (node && typeof node === 'object')
|
|
70
|
+
Object.entries(node).forEach(([k, v]) => {
|
|
71
|
+
if (k !== 'defaultVariants') collectStrings(v, out);
|
|
72
|
+
});
|
|
73
|
+
return out;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Split a utility token into variant prefixes + leaf, bracket-aware (a ':' inside [...] is not a
|
|
77
|
+
* variant separator: `[--btn-edge:2px]`, `[&.forest-keycap]:rounded-lg`). */
|
|
78
|
+
const splitToken = (token: string): { prefixes: string[]; leaf: string } => {
|
|
79
|
+
const parts: string[] = [];
|
|
80
|
+
let depth = 0;
|
|
81
|
+
let cur = '';
|
|
82
|
+
for (const ch of token) {
|
|
83
|
+
if (ch === '[') depth++;
|
|
84
|
+
if (ch === ']') depth--;
|
|
85
|
+
if (ch === ':' && depth === 0) {
|
|
86
|
+
parts.push(cur);
|
|
87
|
+
cur = '';
|
|
88
|
+
} else cur += ch;
|
|
89
|
+
}
|
|
90
|
+
parts.push(cur);
|
|
91
|
+
return { prefixes: parts.slice(0, -1), leaf: parts[parts.length - 1] };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const MARKER_RE = /^forest-[\w-]+$/;
|
|
95
|
+
|
|
96
|
+
/** Tokenise every string of a theme entry into { prefixes, leaf } records. */
|
|
97
|
+
export const themeTokens = (themeEntry: unknown) =>
|
|
98
|
+
collectStrings(themeEntry)
|
|
99
|
+
.flatMap((s) => s.split(/\s+/))
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
.map(splitToken);
|
|
102
|
+
|
|
103
|
+
/** Bare `forest-*` marker classes STAMPED by a theme entry (hook classes excluded). */
|
|
104
|
+
export const stampedMarkers = (themeEntry: unknown): string[] => [
|
|
105
|
+
...new Set(
|
|
106
|
+
themeTokens(themeEntry)
|
|
107
|
+
.filter(({ leaf }) => MARKER_RE.test(leaf) && !(HOOK_CLASSES as readonly string[]).includes(leaf))
|
|
108
|
+
.map(({ leaf }) => leaf),
|
|
109
|
+
),
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
/** Marker names CONSUMED by `[&.forest-…]` variant prefixes in a theme entry. */
|
|
113
|
+
export const consumedMarkers = (themeEntry: unknown): string[] => [
|
|
114
|
+
...new Set(
|
|
115
|
+
themeTokens(themeEntry)
|
|
116
|
+
.flatMap(({ prefixes }) => prefixes)
|
|
117
|
+
.map((p) => /\[&\.(forest-[\w-]+)\]/.exec(p)?.[1])
|
|
118
|
+
.filter((m): m is string => !!m),
|
|
119
|
+
),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Derive the delivery channels for one component of one context (CONTEXTS-SPEC.md §5.4):
|
|
124
|
+
* `[--foo:…]` custom-property class → var; stamped or `[&.forest-…]`-scoped marker → marker;
|
|
125
|
+
* presence in the props export → props; any other utility → slot.
|
|
126
|
+
*/
|
|
127
|
+
export const deriveChannels = (themeEntry: unknown, hasProps: boolean): ContextChannel[] => {
|
|
128
|
+
const channels = new Set<ContextChannel>();
|
|
129
|
+
for (const { prefixes, leaf } of themeTokens(themeEntry)) {
|
|
130
|
+
if (prefixes.some((p) => /\[&\.forest-[\w-]+\]/.test(p))) channels.add('marker');
|
|
131
|
+
if (leaf.startsWith('[--')) channels.add('var');
|
|
132
|
+
else if (MARKER_RE.test(leaf) && !(HOOK_CLASSES as readonly string[]).includes(leaf)) channels.add('marker');
|
|
133
|
+
else channels.add('slot');
|
|
134
|
+
}
|
|
135
|
+
if (hasProps) channels.add('props');
|
|
136
|
+
return [...channels].sort() as ContextChannel[];
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** The contexts. The tag's `name` union and the register both derive from this object. */
|
|
140
|
+
export const forestContexts = {
|
|
141
|
+
mobile: {
|
|
142
|
+
theme: forestMobileTheme,
|
|
143
|
+
props: forestMobileProps,
|
|
144
|
+
manifest: forestMobileManifest,
|
|
145
|
+
},
|
|
146
|
+
marketing: {
|
|
147
|
+
theme: forestMarketingTheme,
|
|
148
|
+
props: forestMarketingProps,
|
|
149
|
+
manifest: forestMarketingManifest,
|
|
150
|
+
},
|
|
151
|
+
} as const;
|
|
152
|
+
|
|
153
|
+
export type ForestContextName = keyof typeof forestContexts;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Forest's expressiveness contexts — one entry, matching ./map.
|
|
2
|
+
//
|
|
3
|
+
// <ForestContext> wraps a subtree in a context (restrained or bold voice). One per subtree,
|
|
4
|
+
// never nested, and it sits OUTSIDE UApp so toasts inherit the right voice
|
|
5
|
+
// forestContexts the registry: which theme deltas and hook classes each context applies
|
|
6
|
+
// themeTokens() the tokens a context changes, for the docs' context panels
|
|
7
|
+
// forestMobileTheme the mobile context's theme delta, prop defaults and manifest
|
|
8
|
+
// forestMarketingTheme the marketing context's theme delta, prop defaults and manifest
|
|
9
|
+
export { default as ForestContext } from './ForestContext.vue';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
forestContexts,
|
|
13
|
+
CONTEXT_REGISTER_ROWS,
|
|
14
|
+
HOOK_CLASSES,
|
|
15
|
+
deriveChannels,
|
|
16
|
+
themeTokens,
|
|
17
|
+
stampedMarkers,
|
|
18
|
+
consumedMarkers,
|
|
19
|
+
} from './contexts';
|
|
20
|
+
export type { ContextManifest, ContextChannel, ForestContextName } from './contexts';
|
|
21
|
+
|
|
22
|
+
export { forestMobileTheme, forestMobileProps, forestMobileManifest } from './mobile-theme';
|
|
23
|
+
export { forestMarketingTheme, forestMarketingProps, forestMarketingManifest } from './marketing-theme';
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest MARKETING context override for Nuxt UI.
|
|
3
|
+
*
|
|
4
|
+
* `forestPreset` is the canonical (web) theme; this is the DELTA for marketing surfaces — promo
|
|
5
|
+
* cards, hero panels, upsell bands ("in-app bands") and, at page scope, standalone marketing pages.
|
|
6
|
+
* Feed it to `<UTheme :ui="forestMarketingTheme" :props="forestMarketingProps">` around the subtree
|
|
7
|
+
* (the docs wrap this in <MarketingVariant>). Sibling of ./mobile-theme.ts — same channel, different
|
|
8
|
+
* voice; both are documented in EXPRESSIVENESS-MODES-RESEARCH.md.
|
|
9
|
+
*
|
|
10
|
+
* Voice: rounder, heavier, Mohr where it counts — but a FINE-pointer surface, so unlike mobile it
|
|
11
|
+
* keeps hover states, keeps the web focus ring, and does NOT stamp `cut-fixed`: on a touch device a
|
|
12
|
+
* marketing button still promotes one size via the preset's `coarse:` bump (marketing pages are web
|
|
13
|
+
* pages — adaptive, not baked like the native cut). Under coarse the preset's per-size lip vars
|
|
14
|
+
* outrank the base-level ones here (`coarse:` compiles to `:not(.cut-fixed)` — higher specificity);
|
|
15
|
+
* ergonomics beating voice is the intended order.
|
|
16
|
+
*
|
|
17
|
+
* Card: one radius step up (2xl/32 vs web xl/24) — --forest-card-radius follows so the nested-card
|
|
18
|
+
* concentric calc (forest.css § card nesting) stays true. The accent tint is untouched — the soft
|
|
19
|
+
* tint IS the accents' character; card boldness comes from shape + the Mohr title alone, both modes.
|
|
20
|
+
* Title: the --fx-title-* traits flip to Mohr + uppercase (the preset's title slot reads them);
|
|
21
|
+
* font-black rides along as a plain class (Mohr's only weight — not a trait, see the preset note).
|
|
22
|
+
*
|
|
23
|
+
* Button: keycap voice — rounder (lg), 2px edge, deeper lip with a bigger hover lift (rest −3px,
|
|
24
|
+
* hover −5px; web md is −2/−3). Labels stay GT Haptik: Mohr is for headlines — the italic-Mohr
|
|
25
|
+
* button label is the MOBILE voice, not marketing's. The base classes below re-voice the
|
|
26
|
+
* edge/lip-bearing rungs (keycap, pad, framed, gloss, subtle, outline — they read --btn-edge /
|
|
27
|
+
* --btn-y*); the flat rungs (solid, soft, ghost, link) read neither and share the restrained web
|
|
28
|
+
* rendering — bold delivery rides on the keycap/lg prop default (CONTEXTS-SPEC.md §6).
|
|
29
|
+
*
|
|
30
|
+
* NOT composable with the mobile delta: <UTheme> configs defu-merge and same-slot strings REPLACE
|
|
31
|
+
* wholesale, so nesting this inside the mobile cut would strip cut-fixed/pill/Mohr-label from the
|
|
32
|
+
* inner subtree. One context per subtree; an in-app promo inside the MOBILE app uses mobile's own
|
|
33
|
+
* emphasis ladder (or a dedicated hybrid delta, if that surface ever becomes real).
|
|
34
|
+
*/
|
|
35
|
+
import type { ContextManifest } from './contexts';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The marketing field edge, shared by every control that can join a UFieldGroup. `ring-2` matches the
|
|
39
|
+
* 2px --btn-edge the button already carries on this cut. --forest-field-edge declares how far the ring
|
|
40
|
+
* exceeds the 1px the field recipes assume; they add it to --forest-input-depth, so the recess holds
|
|
41
|
+
* its reading at every size instead of the ramp flattening. No `cut-fixed`: this is a fine-pointer
|
|
42
|
+
* surface and keeps the preset's adaptive coarse promotion.
|
|
43
|
+
*
|
|
44
|
+
* `[&.ring]` scopes both to the ringed variants, the same guard the badge cut carries. A borderless
|
|
45
|
+
* field — `variant="none"`, which is what a command palette renders its search box as — has no `ring`
|
|
46
|
+
* class to thicken, so an unscoped edge painted a 2px box around it and fed --forest-field-edge into
|
|
47
|
+
* the depth-line shadow, bending the seam under the search row around the field's own corners.
|
|
48
|
+
*/
|
|
49
|
+
const FIELD_EDGE = '[&.ring]:ring-2 [&.ring]:[--forest-field-edge:1px]';
|
|
50
|
+
|
|
51
|
+
export const forestMarketingTheme = {
|
|
52
|
+
card: {
|
|
53
|
+
// Traits re-pointed on SLOT classes (they ride the component through teleports; a wrapper-scoped
|
|
54
|
+
// var would not). Root: one radius step up + the concentric var + roomier p-5 (--forest-card-pad
|
|
55
|
+
// follows so nested corners stay concentric, 32→12→8) to carry the bigger title.
|
|
56
|
+
// The --fx-metric traits ride the ROOT, not a slot: a KPI figure sits in the card's body, so the
|
|
57
|
+
// var has to be declared above it. Upright Mohr, matching the title's cut.
|
|
58
|
+
root: 'rounded-2xl [--forest-card-radius:2rem] [--forest-card-pad:1.25rem] [--fx-metric-family:var(--font-display)] [--fx-metric-transform:uppercase]',
|
|
59
|
+
body: 'sm:p-5',
|
|
60
|
+
header: 'sm:p-5',
|
|
61
|
+
footer: 'sm:p-5',
|
|
62
|
+
// Title: Mohr UPPERCASE (upright — not italic; italic is mobile's cut) via the traits the preset's
|
|
63
|
+
// title slot reads, one size up; weight/tracking as plain classes.
|
|
64
|
+
title: '[--fx-title-family:var(--font-display)] [--fx-title-transform:uppercase] font-black tracking-[0.01em] text-xl',
|
|
65
|
+
// Description one size up for the louder voice; family stays GT Haptik (--font-sans, unchanged).
|
|
66
|
+
description: 'text-base',
|
|
67
|
+
},
|
|
68
|
+
button: {
|
|
69
|
+
// Deeper lip declared at base — under a coarse pointer the preset's per-size `coarse:` lip vars
|
|
70
|
+
// win (intended; see header note). No hover pinning: marketing is a hover surface.
|
|
71
|
+
base: 'rounded-lg [--btn-edge:2px] [--btn-y:-3px] [--btn-yi:3px] [--btn-y-h:-5px] [--btn-yhi:5px]',
|
|
72
|
+
},
|
|
73
|
+
// Field family: RADIUS is still untouched — the per-size ladder (8/8/12/12/12) comes from the
|
|
74
|
+
// preset, because a context cannot express a per-size radius override (no variants.size, and sm↔md
|
|
75
|
+
// are not hook-separable). The EDGE is a context matter and is set here: every segment takes the
|
|
76
|
+
// same 2px the button carries, so a joined bar reads as one slab instead of a 2px control welded to
|
|
77
|
+
// a 1px field. No `cut-fixed` — marketing is a fine-pointer surface and keeps the preset's adaptive
|
|
78
|
+
// coarse promotion, unlike the mobile cut's baked ladder.
|
|
79
|
+
input: { base: FIELD_EDGE },
|
|
80
|
+
textarea: { base: FIELD_EDGE },
|
|
81
|
+
select: { base: FIELD_EDGE },
|
|
82
|
+
selectMenu: { base: FIELD_EDGE },
|
|
83
|
+
inputMenu: { base: FIELD_EDGE },
|
|
84
|
+
inputNumber: { base: FIELD_EDGE },
|
|
85
|
+
inputTags: { base: FIELD_EDGE },
|
|
86
|
+
pinInput: { base: FIELD_EDGE },
|
|
87
|
+
inputDate: { base: FIELD_EDGE },
|
|
88
|
+
inputTime: { base: FIELD_EDGE },
|
|
89
|
+
// Overlap must equal the segment edge or a joined seam paints both edges side by side; stock's
|
|
90
|
+
// -space-x-px collapses two 1px rings, this cut's 2px edge needs 2px.
|
|
91
|
+
fieldGroup: { base: '-space-x-0.5' },
|
|
92
|
+
// Kbd: rounder corner (rounded-sm over web's rounded-xs); sm keeps rounded-xs (`[&.h-4]:`, stock
|
|
93
|
+
// sm height, (0,2,0) beats base) — at 16px rounded-sm reads as a pill. Padding untouched: the
|
|
94
|
+
// preset already runs px-2 (sm px-1), inherited here. Same override as the mobile cut.
|
|
95
|
+
kbd: { base: 'rounded-sm [&.h-4]:rounded-xs' },
|
|
96
|
+
// Alert: rounder banner (rounded-xl over web's rounded-lg) — the bold voice softens the corner a
|
|
97
|
+
// step further. Title weight comes from the preset, inherited. Same as the mobile cut.
|
|
98
|
+
alert: { root: 'rounded-xl' },
|
|
99
|
+
// Listbox: rounder shell (rounded-xl over the stock rounded-lg the standard cut leaves alone) —
|
|
100
|
+
// the bold voice takes the list from a field to a card. The corner has to be paid for: at 24px the
|
|
101
|
+
// arc cuts into the first and last rows, so the row inset steps 4px→12px. It lands on two slots
|
|
102
|
+
// because stock pads a grouped list on `group` but a virtualised one on `content`; `[&.isolate]`
|
|
103
|
+
// scopes the content half to the virtualised cut so a grouped list is not double-padded. Same
|
|
104
|
+
// override as the mobile cut.
|
|
105
|
+
listbox: { root: 'rounded-xl', group: 'p-3', content: '[&.isolate]:p-3' },
|
|
106
|
+
// Badge: full pill (rounded-full over web's rounded-sm) — the bold voice rounds the chip all the
|
|
107
|
+
// way. Weight/case/tracking come from the preset, inherited here. Radius matches the mobile cut,
|
|
108
|
+
// but the touch behaviour does NOT: marketing stamps no cut-fixed and sets no size default, so a
|
|
109
|
+
// touched marketing badge adapts via the preset's coarse: bump (marketing pages are web pages),
|
|
110
|
+
// rather than the mobile cut's baked fixed increase.
|
|
111
|
+
// `[&.ring]` scopes the 2px edge to the ringed variants (outline/subtle) — a bare `ring-2` would
|
|
112
|
+
// paint a ring on solid/soft, which carry no ring classes at all. Coupled to stock's literal `ring`
|
|
113
|
+
// class from its colour×variant compounds; if that token is ever renamed the edge silently reverts.
|
|
114
|
+
badge: { base: 'rounded-full [&.ring]:ring-2' },
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Default PROPS for a marketing subtree (the `props` channel — `ui` only carries slot classes).
|
|
119
|
+
* Hero CTAs default to the signature keycap at lg; explicit props on a component still win.
|
|
120
|
+
*/
|
|
121
|
+
export const forestMarketingProps = {
|
|
122
|
+
button: { variant: 'keycap', size: 'lg' },
|
|
123
|
+
} as const;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Manifest — what the marketing context touches (CONTEXTS-SPEC.md §4). `moves` is the hand-written
|
|
127
|
+
* record; `channels` is derived from the exports above and test-asserted.
|
|
128
|
+
*/
|
|
129
|
+
export const forestMarketingManifest: ContextManifest = {
|
|
130
|
+
name: 'marketing',
|
|
131
|
+
binding: 'utheme',
|
|
132
|
+
surfaces: ['standalone marketing/landing pages', 'in-app promo bands (product app)'],
|
|
133
|
+
darkPolicy: 'standalone: locked light · in-app band: follows host',
|
|
134
|
+
// Standalone marketing never goes dark (the app mounts no colour-mode code); a docs specimen in
|
|
135
|
+
// this context forces light even on a dark page. The in-app band does follow host, but the
|
|
136
|
+
// canonical surface a specimen represents is the standalone page. See ContextScope/ContextSpread.
|
|
137
|
+
lockLight: true,
|
|
138
|
+
components: {
|
|
139
|
+
input: {
|
|
140
|
+
moves:
|
|
141
|
+
'Takes the shared 2px field edge, matching the --btn-edge the button carries on this cut, and declares --forest-field-edge so the depth line grows with it and the recess holds its reading at every size. Radius is left to the preset ladder. No cut-fixed — this cut stays adaptive on touch.',
|
|
142
|
+
channels: ['slot', 'var'],
|
|
143
|
+
},
|
|
144
|
+
textarea: {
|
|
145
|
+
moves: 'Mirrors input — the shared 2px field edge and the matching depth compensation.',
|
|
146
|
+
channels: ['slot', 'var'],
|
|
147
|
+
},
|
|
148
|
+
select: {
|
|
149
|
+
moves: 'Trigger takes the shared 2px field edge and the matching depth compensation.',
|
|
150
|
+
channels: ['slot', 'var'],
|
|
151
|
+
},
|
|
152
|
+
selectMenu: {
|
|
153
|
+
moves: 'Trigger takes the shared 2px field edge and the matching depth compensation.',
|
|
154
|
+
channels: ['slot', 'var'],
|
|
155
|
+
},
|
|
156
|
+
inputMenu: {
|
|
157
|
+
moves: 'Takes the shared 2px field edge and the matching depth compensation.',
|
|
158
|
+
channels: ['slot', 'var'],
|
|
159
|
+
},
|
|
160
|
+
inputNumber: {
|
|
161
|
+
moves: 'Takes the shared 2px field edge and the matching depth compensation.',
|
|
162
|
+
channels: ['slot', 'var'],
|
|
163
|
+
},
|
|
164
|
+
inputTags: {
|
|
165
|
+
moves: 'Takes the shared 2px field edge and the matching depth compensation.',
|
|
166
|
+
channels: ['slot', 'var'],
|
|
167
|
+
},
|
|
168
|
+
pinInput: {
|
|
169
|
+
moves: 'Each cell takes the shared 2px field edge and the matching depth compensation.',
|
|
170
|
+
channels: ['slot', 'var'],
|
|
171
|
+
},
|
|
172
|
+
inputDate: {
|
|
173
|
+
moves: 'Takes the shared 2px field edge and the matching depth compensation.',
|
|
174
|
+
channels: ['slot', 'var'],
|
|
175
|
+
},
|
|
176
|
+
inputTime: {
|
|
177
|
+
moves: 'Takes the shared 2px field edge and the matching depth compensation.',
|
|
178
|
+
channels: ['slot', 'var'],
|
|
179
|
+
},
|
|
180
|
+
fieldGroup: {
|
|
181
|
+
moves:
|
|
182
|
+
'Steps the overlap to 2px (-space-x-0.5) to match this cut\'s 2px segment edge, so a joined seam collapses to one line instead of painting both edges.',
|
|
183
|
+
channels: ['slot'],
|
|
184
|
+
},
|
|
185
|
+
card: {
|
|
186
|
+
moves:
|
|
187
|
+
'One radius step up (2xl/32; the concentric nesting var follows); title flips to Mohr uppercase (upright) via the --fx-title traits, one size up; description one size up, still GT Haptik. A KPI figure inside the card takes the same face through the --fx-metric traits. Mobile uses the same face italic instead.',
|
|
188
|
+
channels: ['slot', 'var'],
|
|
189
|
+
},
|
|
190
|
+
button: {
|
|
191
|
+
moves:
|
|
192
|
+
'Keycap voice at billboard scale: rounded-lg, 2px edge, deeper lip with a bigger hover lift; keycap/lg by default; labels stay GT Haptik. Flat rungs (solid/soft/ghost/link) share the restrained rendering.',
|
|
193
|
+
channels: ['props', 'slot', 'var'],
|
|
194
|
+
},
|
|
195
|
+
kbd: {
|
|
196
|
+
moves: 'Rounder corner (rounded-sm); the sm size keeps the tight rounded-xs. Same as mobile.',
|
|
197
|
+
channels: ['slot'],
|
|
198
|
+
},
|
|
199
|
+
badge: {
|
|
200
|
+
moves:
|
|
201
|
+
'Full-pill corner (rounded-full over the standard rounded-sm tag). Stays adaptive on touch — no cut-fixed, so it inherits the preset coarse bump like standard (unlike the mobile cut, which bakes a fixed increase).',
|
|
202
|
+
channels: ['slot'],
|
|
203
|
+
},
|
|
204
|
+
alert: {
|
|
205
|
+
moves: 'Rounder banner corner (rounded-xl over the standard rounded-lg). Same as mobile.',
|
|
206
|
+
channels: ['slot'],
|
|
207
|
+
},
|
|
208
|
+
listbox: {
|
|
209
|
+
moves:
|
|
210
|
+
'Rounder shell (rounded-xl over the stock corner the standard cut leaves alone), with the row inset stepped 4px→12px so the first and last rows clear the arc instead of tucking under it. The inset lands on both group and content because stock pads a grouped list and a virtualised one in different slots. Same as mobile.',
|
|
211
|
+
channels: ['slot'],
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
};
|