@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,61 @@
|
|
|
1
|
+
// Chart palette helpers for Unovis. Returns `categories` maps whose colours are
|
|
2
|
+
// var(--dataviz-*) STRINGS, not hexes — CSS resolves them, so dark mode flips charts with zero JS
|
|
3
|
+
// and the engine stays the only place values live. Engine hex imports are allowed in chart pages
|
|
4
|
+
// ONLY for CVD-simulation demos (simulateCvd needs real hexes).
|
|
5
|
+
import type { BulletLegendItemInterface } from '@unovis/ts';
|
|
6
|
+
import { DATAVIZ, DATAVIZ_PAIRS, DATAVIZ_CATEGORICAL, DATAVIZ_SEQUENTIAL, DATAVIZ_DIVERGING } from './engine';
|
|
7
|
+
|
|
8
|
+
type Categories = Record<string, BulletLegendItemInterface>;
|
|
9
|
+
export interface SeriesDef { key: string; name: string }
|
|
10
|
+
|
|
11
|
+
// Each accessor is named after the palette it returns, matching the tier names on the docs page.
|
|
12
|
+
// There is deliberately NO generic `categorical()` — the generic name used to return the VIVID set,
|
|
13
|
+
// which meant the one palette that is not colour-blind-safe owned the most authoritative-sounding name.
|
|
14
|
+
const SAFE = Object.keys(DATAVIZ.light); // 1..6 — brand-led CVD-safe, consumed by index
|
|
15
|
+
const VIVID = DATAVIZ_CATEGORICAL.names; // green, amber, red, … (first 6 = the curated 6)
|
|
16
|
+
const PAIRS = Object.keys(DATAVIZ_PAIRS.light); // forest-deep, forest-soft, warm-deep, … (family-grouped)
|
|
17
|
+
|
|
18
|
+
export const safeVar = (i: number) => `var(--dataviz-categorical-safe-${SAFE[i % SAFE.length]})`;
|
|
19
|
+
export const vividVar = (i: number) => `var(--dataviz-categorical-vivid-${VIVID[i % VIVID.length]})`;
|
|
20
|
+
export const pairsVar = (i: number) => `var(--dataviz-categorical-pairs-${PAIRS[i % PAIRS.length]})`;
|
|
21
|
+
|
|
22
|
+
/** The three categorical sets, named. Only for callers that choose a set at runtime. */
|
|
23
|
+
export type CategoricalPalette = 'cvdSafe' | 'vivid' | 'pairs';
|
|
24
|
+
const CATEGORICAL_VARS: Record<CategoricalPalette, (i: number) => string> = {
|
|
25
|
+
cvdSafe: safeVar,
|
|
26
|
+
vivid: vividVar,
|
|
27
|
+
pairs: pairsVar,
|
|
28
|
+
};
|
|
29
|
+
export const categoricalVar = (p: CategoricalPalette, i: number) => CATEGORICAL_VARS[p](i);
|
|
30
|
+
export const sequentialVar = (name: keyof typeof DATAVIZ_SEQUENTIAL, i: number) => `var(--dataviz-sequential-${name}-${i})`;
|
|
31
|
+
const kebab = (s: string) => s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
32
|
+
export const divergingVar = (name: keyof typeof DATAVIZ_DIVERGING, i: number) => `var(--dataviz-diverging-${kebab(name)}-${i})`;
|
|
33
|
+
|
|
34
|
+
const build = (series: SeriesDef[], color: (i: number) => string): Categories =>
|
|
35
|
+
Object.fromEntries(series.map((s, i) => [s.key, { name: s.name, color: color(i) }]));
|
|
36
|
+
|
|
37
|
+
export function useChartPalette() {
|
|
38
|
+
return {
|
|
39
|
+
/** The 6-colour brand-led CVD-safe set — what the Unovis adapter already ships on --vis-color0..5.
|
|
40
|
+
* The default: colour alone separates the series, so it needs no second channel. */
|
|
41
|
+
cvdSafe: (series: SeriesDef[]) => build(series, safeVar),
|
|
42
|
+
/** Vivid categorical (palette order). ≤6 where you can; 7–12 are label-only.
|
|
43
|
+
* NOT CVD-safe — the caller MUST add a second channel (legend, direct labels, dash or marker). */
|
|
44
|
+
vivid: (series: SeriesDef[]) => build(series, vividVar),
|
|
45
|
+
/** Brand pairs — 3 brand hues × deep/soft, family-grouped. Brand-forward work: decks, marketing.
|
|
46
|
+
* NOT CVD-safe — the caller MUST add a second channel (legend, direct labels, dash or marker). */
|
|
47
|
+
pairs: (series: SeriesDef[]) => build(series, pairsVar),
|
|
48
|
+
/** One of the three sets, chosen at runtime. For the docs' palette picker — production code
|
|
49
|
+
* should name the set it means, so the CVD contract above is visible at the call site. */
|
|
50
|
+
byName: (p: CategoricalPalette, series: SeriesDef[]) =>
|
|
51
|
+
build(series, (i) => categoricalVar(p, i)),
|
|
52
|
+
/** n evenly-spaced stops from a sequential ramp (low → high). */
|
|
53
|
+
sequential: (name: keyof typeof DATAVIZ_SEQUENTIAL, n: number) => {
|
|
54
|
+
const len = DATAVIZ_SEQUENTIAL[name].length;
|
|
55
|
+
return Array.from({ length: n }, (_, i) => sequentialVar(name, Math.round((i * (len - 1)) / Math.max(n - 1, 1))));
|
|
56
|
+
},
|
|
57
|
+
/** All 9 stops of a diverging ramp (low → centre → high). */
|
|
58
|
+
diverging: (name: keyof typeof DATAVIZ_DIVERGING) =>
|
|
59
|
+
DATAVIZ_DIVERGING[name].map((_, i) => divergingVar(name, i)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computed, toValue, type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remount key for Unovis charts whose series colours change identity at runtime.
|
|
5
|
+
*
|
|
6
|
+
* The bug it works around: Unovis caches series colours on mount — Bar, Donut and Bubble keep
|
|
7
|
+
* painting the colours they mounted with even after `categories` hands them new ones (Line
|
|
8
|
+
* re-reads and repaints without help, which is why the trap is easy to miss). Bind the returned
|
|
9
|
+
* key as `:key` on the chart component so a flip of any signal remounts it:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const chartKey = useChartRepaintKey(mode, vision, chartSet);
|
|
13
|
+
* // <VisSingleContainer :key="chartKey" …>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Signals are passed in as refs or getters — colour mode, CVD simulation, palette choice —
|
|
17
|
+
* rather than read from an app composable, so the package carries no app internals; the docs
|
|
18
|
+
* app passes its own colour-mode ref.
|
|
19
|
+
*/
|
|
20
|
+
export function useChartRepaintKey(...signals: MaybeRefOrGetter<unknown>[]): ComputedRef<string> {
|
|
21
|
+
return computed(() => signals.map((s) => String(toValue(s))).join('-'));
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Frame-owned repaint signal: bumps when the documentElement's class or data-theme attribute
|
|
2
|
+
// changes (i.e. on a colour-mode flip). Unovis Bar/Donut/Bubble cache resolved colours on mount,
|
|
3
|
+
// so a var(--dataviz-*) colour does NOT repaint them when dark mode lands — remounting on this
|
|
4
|
+
// signal is the workaround, owned by the frame instead of every call site.
|
|
5
|
+
import { onScopeDispose, ref, type Ref } from 'vue';
|
|
6
|
+
|
|
7
|
+
export function useThemeVersion(): Ref<number> {
|
|
8
|
+
const version = ref(0);
|
|
9
|
+
if (typeof window === 'undefined' || typeof MutationObserver === 'undefined') return version;
|
|
10
|
+
const observer = new MutationObserver(() => { version.value++; });
|
|
11
|
+
observer.observe(document.documentElement, {
|
|
12
|
+
attributes: true,
|
|
13
|
+
attributeFilter: ['class', 'data-theme'],
|
|
14
|
+
});
|
|
15
|
+
onScopeDispose(() => observer.disconnect());
|
|
16
|
+
return version;
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
// Desktop browser chrome — the web counterpart to DeviceFrame. A neutral macOS-style window
|
|
4
|
+
// (traffic lights + address pill) wrapping a live app screen rendered at the chosen logical width.
|
|
5
|
+
// The screen is a `@container`, so the slotted shell reflows against the frame width (not the
|
|
6
|
+
// viewport) — switching the Viewport control genuinely resizes the layout. Flip `dark` to theme the
|
|
7
|
+
// screen subtree (adds `.dark` over `bg-default text-default`, exactly like DeviceFrame's screen).
|
|
8
|
+
// ★ The SCREEN carries a real display aspect — 16:10 or 16:9, the two shapes a desktop actually comes in
|
|
9
|
+
// — and the height is derived from it rather than typed in. A hand-picked height drifts into ratios no
|
|
10
|
+
// monitor has, which makes a specimen's reflow decisions untestable against anything real.
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
width: number;
|
|
14
|
+
aspect?: '16/10' | '16/9';
|
|
15
|
+
/** Overrides the aspect. Only for a screen that is deliberately not a whole display. */
|
|
16
|
+
height?: number;
|
|
17
|
+
dark?: boolean;
|
|
18
|
+
url?: string;
|
|
19
|
+
}>(),
|
|
20
|
+
{ aspect: '16/10', dark: false, url: 'console.forest.bike/fleet' },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const screenH = computed(() => props.height ?? Math.round(props.width * (props.aspect === '16/9' ? 9 / 16 : 10 / 16)));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div
|
|
28
|
+
class="shrink-0 overflow-hidden rounded-xl border border-default bg-default"
|
|
29
|
+
:style="{ width: width + 'px', filter: 'drop-shadow(0 25px 45px rgba(0,0,0,.28))' }"
|
|
30
|
+
>
|
|
31
|
+
<!-- title bar -->
|
|
32
|
+
<div class="flex items-center gap-3 border-b border-default bg-elevated/60 px-4 py-2.5">
|
|
33
|
+
<div class="flex items-center gap-1.5">
|
|
34
|
+
<span class="size-3 rounded-full bg-[#ff5f57]"></span>
|
|
35
|
+
<span class="size-3 rounded-full bg-[#febc2e]"></span>
|
|
36
|
+
<span class="size-3 rounded-full bg-[#28c840]"></span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="mx-auto flex w-full max-w-xs items-center justify-center gap-1.5 rounded-md border border-default bg-default px-3 py-1 text-xs text-muted">
|
|
39
|
+
<UIcon name="i-lucide-lock" class="size-3 text-dimmed" />
|
|
40
|
+
{{ url }}
|
|
41
|
+
</div>
|
|
42
|
+
<UIcon name="i-lucide-plus" class="size-4 shrink-0 text-dimmed" />
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- live screen — its own theming context + container -->
|
|
46
|
+
<div
|
|
47
|
+
:class="['@container relative overflow-hidden bg-default text-default', { dark }]"
|
|
48
|
+
:style="{ height: screenH + 'px' }"
|
|
49
|
+
>
|
|
50
|
+
<slot />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Photoreal device shell: an unmodified Wikimedia SVG render is the hardware; the live HTML screen is
|
|
3
|
+
// overlaid at the SVG's measured screen insets and clipped to the SVG's own screen path. We draw the
|
|
4
|
+
// cutout (island/notch/punch-hole) + an OS-authentic status bar on top.
|
|
5
|
+
//
|
|
6
|
+
// Status bar matches the platform: iOS uses the system font (SF Pro via system-ui) with hand-drawn
|
|
7
|
+
// SF-style glyphs (cellular bars, wifi cone, capsule battery); Android uses Roboto with Material
|
|
8
|
+
// Symbols glyphs. Sizes follow each platform's status bar. Rendered at true 1× logical points.
|
|
9
|
+
import { computed, useId } from 'vue';
|
|
10
|
+
import { findDevice, type DeviceModel } from './devices';
|
|
11
|
+
import { artUrl } from './art';
|
|
12
|
+
|
|
13
|
+
const clipId = `screenclip-${useId()}`;
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
model: string | DeviceModel;
|
|
18
|
+
dark?: boolean;
|
|
19
|
+
statusBar?: boolean;
|
|
20
|
+
time?: string;
|
|
21
|
+
/** Overlay the safe-area insets (top/bottom) for inspection. */
|
|
22
|
+
showSafe?: boolean;
|
|
23
|
+
/** Slot content runs edge-to-edge under the status bar (maps, camera). Pad with `safe.top`. */
|
|
24
|
+
fullBleed?: boolean;
|
|
25
|
+
/** A real phone is a COARSE pointer. Opt in to stamp `[data-force-coarse]` on the screen so the
|
|
26
|
+
* preset's `coarse:` touch bumps fire for the content inside — the truthful render for a
|
|
27
|
+
* web-cut surface (standard/marketing) viewed on a device. The mobile cut stays put on its own
|
|
28
|
+
* (its `cut-fixed` opts the bump out — it already baked its size), and components with no
|
|
29
|
+
* `coarse:` bump are unaffected. Default off so existing frames don't shift. */
|
|
30
|
+
touch?: boolean;
|
|
31
|
+
}>(),
|
|
32
|
+
{ dark: false, statusBar: true, time: '9:41', showSafe: false, fullBleed: false, touch: false },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const device = computed<DeviceModel>(() =>
|
|
36
|
+
typeof props.model === 'string' ? findDevice(props.model) : props.model,
|
|
37
|
+
);
|
|
38
|
+
const art = computed(() => device.value.art!);
|
|
39
|
+
const artSrc = computed(() => artUrl(device.value));
|
|
40
|
+
const os = computed<'ios' | 'android'>(() => (device.value.brand === 'Apple' ? 'ios' : 'android'));
|
|
41
|
+
|
|
42
|
+
const dims = computed(() => {
|
|
43
|
+
const a = art.value;
|
|
44
|
+
const w = device.value.screen.w / (1 - (a.inset.left + a.inset.right) / 100);
|
|
45
|
+
return { w, h: w * a.aspect };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const screenStyle = computed(() => {
|
|
49
|
+
const a = art.value;
|
|
50
|
+
return {
|
|
51
|
+
top: a.inset.top + '%',
|
|
52
|
+
left: a.inset.left + '%',
|
|
53
|
+
right: a.inset.right + '%',
|
|
54
|
+
bottom: a.inset.bottom + '%',
|
|
55
|
+
...(a.clip
|
|
56
|
+
? { clipPath: `url(#${clipId})` }
|
|
57
|
+
: { borderRadius: (device.value.screen.w * (a.radiusPct ?? 0)) / 100 + 'px' }),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const screenW = computed(() => device.value.screen.w);
|
|
62
|
+
const statusBarH = computed(() => device.value.safe.top);
|
|
63
|
+
const cutout = computed(() => device.value.cutout);
|
|
64
|
+
const island = computed(() => ({ w: Math.round(screenW.value * 0.3), h: 36, top: 10 }));
|
|
65
|
+
const notch = computed(() => ({ w: Math.round(screenW.value * 0.42), h: 30 }));
|
|
66
|
+
const compact = computed(() => cutout.value === 'home' || cutout.value === 'none');
|
|
67
|
+
|
|
68
|
+
// Safe-area bridge. A mock phone has no notch for the browser to report, so `env(safe-area-inset-*)`
|
|
69
|
+
// resolves to 0 in here and chrome would tuck under the island. The frame publishes the device's
|
|
70
|
+
// measured insets as the vars FCanvasHud reads first; on a real device nothing sets them and the
|
|
71
|
+
// `env()` fallback takes over. Top is only owed when the content runs full-bleed — otherwise the
|
|
72
|
+
// wrapper is already offset below the status bar and charging for it twice would double the gap.
|
|
73
|
+
const safeVars = computed(() => ({
|
|
74
|
+
'--forest-safe-top': (props.fullBleed ? device.value.safe.top : 0) + 'px',
|
|
75
|
+
'--forest-safe-bottom': device.value.safe.bottom + 'px',
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Status-bar metrics, computed per device so the clock/icons sit at the cutout's vertical centre and
|
|
79
|
+
// clear the rounded-corner curve (no per-device hand-tuning, no clipped glyphs).
|
|
80
|
+
const sb = computed(() => {
|
|
81
|
+
const d = device.value;
|
|
82
|
+
const ios = os.value === 'ios';
|
|
83
|
+
// vertical centre of the row = centre of the cutout (island/punch-hole) or middle of a short bar
|
|
84
|
+
const centerY =
|
|
85
|
+
cutout.value === 'island' ? island.value.top + island.value.h / 2
|
|
86
|
+
: cutout.value === 'punch-hole' ? 19
|
|
87
|
+
: statusBarH.value * 0.5;
|
|
88
|
+
// screen corner radius (px) → how far the corner curves in at centerY
|
|
89
|
+
const cornerR = d.art.radiusPct ? (d.screen.w * d.art.radiusPct) / 100 : d.screen.w * (ios ? 0.13 : 0.12);
|
|
90
|
+
const cornerInset = centerY >= cornerR ? 0 : cornerR - Math.sqrt(cornerR * cornerR - (cornerR - centerY) ** 2);
|
|
91
|
+
const basePad = ios ? (compact.value ? 16 : 22) : 14;
|
|
92
|
+
const padX = Math.max(basePad, Math.ceil(cornerInset) + 8);
|
|
93
|
+
return ios
|
|
94
|
+
? { padX, barH: centerY * 2, clockPx: 15, clockFont: '-apple-system, "SF Pro Text", system-ui, sans-serif', clockWeight: 600, iconGap: 5 }
|
|
95
|
+
: { padX, barH: centerY * 2, clockPx: 14, clockFont: 'Roboto, system-ui, sans-serif', clockWeight: 500, iconGap: 6 };
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<div
|
|
102
|
+
class="device relative shrink-0"
|
|
103
|
+
:style="{ width: dims.w + 'px', height: dims.h + 'px', filter: 'drop-shadow(0 25px 45px rgba(0,0,0,.28))' }"
|
|
104
|
+
>
|
|
105
|
+
<img :src="artSrc" alt="" class="pointer-events-none absolute inset-0 h-full w-full select-none" draggable="false" />
|
|
106
|
+
|
|
107
|
+
<svg v-if="art.clip" class="absolute size-0" aria-hidden="true">
|
|
108
|
+
<defs>
|
|
109
|
+
<clipPath :id="clipId" clipPathUnits="objectBoundingBox"><path :d="art.clip" /></clipPath>
|
|
110
|
+
</defs>
|
|
111
|
+
</svg>
|
|
112
|
+
|
|
113
|
+
<!-- LIVE SCREEN -->
|
|
114
|
+
<div :class="['screen absolute overflow-hidden bg-default text-default', { dark }]" :style="screenStyle">
|
|
115
|
+
<!-- cutout -->
|
|
116
|
+
<div
|
|
117
|
+
v-if="cutout === 'island'"
|
|
118
|
+
class="absolute z-30 -translate-x-1/2 rounded-full bg-black"
|
|
119
|
+
:style="{ left: '50%', top: island.top + 'px', width: island.w + 'px', height: island.h + 'px' }"
|
|
120
|
+
></div>
|
|
121
|
+
<div
|
|
122
|
+
v-else-if="cutout === 'notch' && !art.clip"
|
|
123
|
+
class="absolute left-1/2 top-0 z-30 -translate-x-1/2 bg-black"
|
|
124
|
+
:style="{ width: notch.w + 'px', height: notch.h + 'px', borderRadius: '0 0 18px 18px' }"
|
|
125
|
+
></div>
|
|
126
|
+
<div
|
|
127
|
+
v-else-if="cutout === 'punch-hole'"
|
|
128
|
+
class="absolute left-1/2 top-3 z-30 size-3.5 -translate-x-1/2 rounded-full bg-black"
|
|
129
|
+
></div>
|
|
130
|
+
|
|
131
|
+
<!-- OS-authentic status bar -->
|
|
132
|
+
<div
|
|
133
|
+
v-if="statusBar"
|
|
134
|
+
class="absolute inset-x-0 top-0 z-20 flex items-center justify-between text-highlighted"
|
|
135
|
+
:style="{ height: sb.barH + 'px', paddingInline: sb.padX + 'px' }"
|
|
136
|
+
>
|
|
137
|
+
<span
|
|
138
|
+
class="tabular-nums"
|
|
139
|
+
:style="{ fontFamily: sb.clockFont, fontSize: sb.clockPx + 'px', fontWeight: sb.clockWeight, letterSpacing: os === 'ios' ? '.01em' : '0' }"
|
|
140
|
+
>{{ time }}</span>
|
|
141
|
+
|
|
142
|
+
<!-- iOS glyphs: cellular bars · wifi cone · capsule battery -->
|
|
143
|
+
<div v-if="os === 'ios'" class="flex items-center" :style="{ gap: sb.iconGap + 'px' }">
|
|
144
|
+
<svg viewBox="0 0 18 12" height="11" fill="currentColor" aria-hidden="true">
|
|
145
|
+
<rect x="0" y="8" width="3" height="4" rx="1" /><rect x="5" y="5.5" width="3" height="6.5" rx="1" />
|
|
146
|
+
<rect x="10" y="3" width="3" height="9" rx="1" /><rect x="15" y="0" width="3" height="12" rx="1" />
|
|
147
|
+
</svg>
|
|
148
|
+
<svg viewBox="0 0 18 13" height="12" fill="currentColor" aria-hidden="true">
|
|
149
|
+
<path d="M9 2.4c2.9 0 5.6 1.05 7.7 2.9a.6.6 0 0 1 .03.87L9.5 13.5a.7.7 0 0 1-1 0L1.27 6.17a.6.6 0 0 1 .03-.87C3.4 3.45 6.1 2.4 9 2.4Z" />
|
|
150
|
+
</svg>
|
|
151
|
+
<svg viewBox="0 0 28 13" height="12" aria-hidden="true">
|
|
152
|
+
<rect x=".5" y=".5" width="23" height="12" rx="3.6" fill="none" stroke="currentColor" stroke-opacity=".4" />
|
|
153
|
+
<rect x="2" y="2" width="18" height="9" rx="2.2" fill="currentColor" />
|
|
154
|
+
<path d="M25.3 4.3c1.1.25 1.1 4.15 0 4.4Z" fill="currentColor" fill-opacity=".4" />
|
|
155
|
+
</svg>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<!-- Android glyphs: Material Symbols (cellular · wifi · battery) -->
|
|
159
|
+
<div v-else class="flex items-center" :style="{ gap: sb.iconGap + 'px' }">
|
|
160
|
+
<svg viewBox="0 -960 960 960" height="15" fill="currentColor" aria-hidden="true">
|
|
161
|
+
<path d="M260-160q-25 0-42.5-17.5T200-220v-120q0-25 17.5-42.5T260-400q25 0 42.5 17.5T320-340v120q0 25-17.5 42.5T260-160Zm240 0q-25 0-42.5-17.5T440-220v-320q0-25 17.5-42.5T500-600q25 0 42.5 17.5T560-540v320q0 25-17.5 42.5T500-160Zm240 0q-25 0-42.5-17.5T680-220v-520q0-25 17.5-42.5T740-800q25 0 42.5 17.5T800-740v520q0 25-17.5 42.5T740-160Z" />
|
|
162
|
+
</svg>
|
|
163
|
+
<svg viewBox="0 -960 960 960" height="15" fill="currentColor" aria-hidden="true">
|
|
164
|
+
<path d="M174-540q67-48 145-74t161-26q83 0 161 26t145 74l58-58q-79-60-172-91t-192-31q-99 0-192 31t-172 91l58 58Zm249 363L61-539q-12-12-18-27t-6-30q0-17 7-32.5T65-656q82-71 195-107.5T480-800q107 0 220 36.5T895-656q14 12 21 27.5t7 32.5q0 15-6 30t-18 27L537-177q-12 12-27 18t-30 6q-15 0-30-6t-27-18Z" />
|
|
165
|
+
</svg>
|
|
166
|
+
<svg viewBox="0 -960 960 960" height="15" fill="currentColor" aria-hidden="true">
|
|
167
|
+
<path d="M320-80q-17 0-28.5-11.5T280-120v-640q0-17 11.5-28.5T320-800h80v-40q0-17 11.5-28.5T440-880h80q17 0 28.5 11.5T560-840v40h80q17 0 28.5 11.5T680-760v640q0 17-11.5 28.5T640-80H320Z" />
|
|
168
|
+
</svg>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- screen content — starts below the status bar unless fullBleed (maps, camera), in which
|
|
173
|
+
case it runs edge-to-edge and the status bar/cutout (z-20/z-30) draw over it -->
|
|
174
|
+
<div
|
|
175
|
+
class="absolute inset-x-0 bottom-0 overflow-hidden"
|
|
176
|
+
:style="{ top: fullBleed ? '0px' : statusBarH + 'px', ...safeVars }"
|
|
177
|
+
:data-force-coarse="touch ? '' : undefined"
|
|
178
|
+
>
|
|
179
|
+
<slot :device="device" :safe="device.safe" />
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<!-- home indicator -->
|
|
183
|
+
<div
|
|
184
|
+
v-if="!compact"
|
|
185
|
+
class="absolute bottom-2 left-1/2 z-20 h-1 -translate-x-1/2 rounded-full bg-highlighted/40"
|
|
186
|
+
:style="{ width: screenW * 0.36 + 'px' }"
|
|
187
|
+
></div>
|
|
188
|
+
|
|
189
|
+
<!-- safe-area overlay (toggle) -->
|
|
190
|
+
<template v-if="showSafe">
|
|
191
|
+
<div
|
|
192
|
+
class="pointer-events-none absolute inset-x-0 top-0 z-40 flex items-start justify-center"
|
|
193
|
+
:style="{ height: device.safe.top + 'px', background: 'rgba(56,189,248,.28)', borderBottom: '1px dashed rgba(2,132,199,.9)' }"
|
|
194
|
+
>
|
|
195
|
+
<span class="mt-0.5 rounded bg-sky-600 px-1 text-[9px] font-semibold text-white">{{ device.safe.top }}pt</span>
|
|
196
|
+
</div>
|
|
197
|
+
<div
|
|
198
|
+
v-if="device.safe.bottom > 0"
|
|
199
|
+
class="pointer-events-none absolute inset-x-0 bottom-0 z-40 flex items-end justify-center"
|
|
200
|
+
:style="{ height: device.safe.bottom + 'px', background: 'rgba(56,189,248,.28)', borderTop: '1px dashed rgba(2,132,199,.9)' }"
|
|
201
|
+
>
|
|
202
|
+
<span class="mb-0.5 rounded bg-sky-600 px-1 text-[9px] font-semibold text-white">{{ device.safe.bottom }}pt</span>
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</template>
|
|
208
|
+
|
|
209
|
+
<style scoped>
|
|
210
|
+
/* A touch glyph over the live screen, so a mouse reading a phone mock reads as a finger, not a
|
|
211
|
+
pointer: a solid grey contact dot inside a translucent halo, the shape a fingertip makes. Forced
|
|
212
|
+
onto descendants too — otherwise every button's own `cursor: pointer` wins back.
|
|
213
|
+
The SVG is baked static (no tokens, no `currentColor`), so the two modes are two encoded copies —
|
|
214
|
+
dark grey on a light screen, light grey on a dark one. Fine pointers only.
|
|
215
|
+
32px is the ceiling, not a taste call: Chrome refuses a cursor bitmap larger than 32×32 on
|
|
216
|
+
Windows and silently falls back to the arrow. */
|
|
217
|
+
@media (hover: hover) and (pointer: fine) {
|
|
218
|
+
.screen,
|
|
219
|
+
.screen :deep(*) {
|
|
220
|
+
cursor:
|
|
221
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Ccircle cx='16' cy='16' r='15' fill='%2352525B' fill-opacity='.28'/%3E%3Ccircle cx='16' cy='16' r='9.5' fill='%2352525B'/%3E%3C/svg%3E")
|
|
222
|
+
16 16,
|
|
223
|
+
pointer;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Two ways a screen ends up dark: the frame's own `dark` prop, or the whole docs app being in
|
|
227
|
+
dark mode (`html.dark`) — the screen inherits that through `bg-default` without ever carrying
|
|
228
|
+
the class itself. Both need the light-grey glyph.
|
|
229
|
+
Plain descendant selectors, NOT `:global(.dark) .screen` — Vue compiles that down to bare
|
|
230
|
+
`.dark`, which is on `<html>`, and `cursor` inherits to the whole page. Scoping only stamps the
|
|
231
|
+
last compound, so an unscoped ancestor matches on its own. */
|
|
232
|
+
.screen.dark,
|
|
233
|
+
.screen.dark :deep(*),
|
|
234
|
+
.dark .screen,
|
|
235
|
+
.dark .screen :deep(*) {
|
|
236
|
+
cursor:
|
|
237
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Ccircle cx='16' cy='16' r='15' fill='%23D4D4D8' fill-opacity='.28'/%3E%3Ccircle cx='16' cy='16' r='9.5' fill='%23D4D4D8'/%3E%3C/svg%3E")
|
|
238
|
+
16 16,
|
|
239
|
+
pointer;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
</style>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Lays its slot out at a fixed LOGICAL width and scales the result down to fit the space available.
|
|
3
|
+
//
|
|
4
|
+
// Why a specimen wants this: a shell only reflows honestly at a real width, and its measures are absolute
|
|
5
|
+
// — `railTop: 17rem` is 20% of 1360px and 65% of a 420px diagram box. Laying out at the real width and
|
|
6
|
+
// shrinking the picture keeps every proportion true; shrinking the width instead changes the design.
|
|
7
|
+
//
|
|
8
|
+
// ★ SCALED WITH `transform`, NEVER WITH `zoom`. Both shrink the frame; only one keeps a Mapbox canvas
|
|
9
|
+
// correct. Mapbox sizes its canvas from the container's LAYOUT box (clientWidth) and writes that back as
|
|
10
|
+
// the canvas's CSS size. `zoom` changes the layout box, so the factor lands twice: MEASURED at zoom 0.72,
|
|
11
|
+
// an 862px container held a 621px canvas — dead strips down two edges, and `map.resize()` cannot fix it
|
|
12
|
+
// because it recomputes the same wrong value. A `transform` leaves the layout box alone, so Mapbox writes
|
|
13
|
+
// the logical size and the transform scales the result once.
|
|
14
|
+
//
|
|
15
|
+
// The cost of a transform is that the element still OCCUPIES its unscaled box, which is what the rest of
|
|
16
|
+
// this component is for.
|
|
17
|
+
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
/** Logical width the slot lays out at, whatever the space available. */
|
|
22
|
+
width: number;
|
|
23
|
+
/** Largest scale to draw at, so a wide page does not render the specimen at 1:1. */
|
|
24
|
+
maxScale?: number;
|
|
25
|
+
}>(),
|
|
26
|
+
{ maxScale: 1 },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// The wrapper's own padding, added back to the height it reserves. Weighted downwards because a frame's
|
|
30
|
+
// shadow is cast downwards, so that is the side that needs the room.
|
|
31
|
+
const PAD_TOP = 8;
|
|
32
|
+
const PAD_BOTTOM = 24;
|
|
33
|
+
|
|
34
|
+
const inner = ref<HTMLElement | null>(null);
|
|
35
|
+
const wrapper = ref<HTMLElement | null>(null);
|
|
36
|
+
const naturalH = ref(0);
|
|
37
|
+
const availW = ref(0);
|
|
38
|
+
|
|
39
|
+
// Watched rather than read in onMounted: the refs are not guaranteed to be populated by the time this
|
|
40
|
+
// component's mounted hook runs, and a missed attach leaves the wrapper unsized with no visible error.
|
|
41
|
+
// No loop between the two: the slot's unscaled height is fixed by its own content, and the wrapper's
|
|
42
|
+
// width is its block width — neither depends on the scale derived from them.
|
|
43
|
+
const innerRO = new ResizeObserver(([e]) => { naturalH.value = e.contentRect.height; });
|
|
44
|
+
const wrapRO = new ResizeObserver(([e]) => { availW.value = e.contentRect.width; });
|
|
45
|
+
watch(inner, (el) => { innerRO.disconnect(); if (el) innerRO.observe(el); }, { immediate: true, flush: 'post' });
|
|
46
|
+
watch(wrapper, (el) => { wrapRO.disconnect(); if (el) wrapRO.observe(el); }, { immediate: true, flush: 'post' });
|
|
47
|
+
onBeforeUnmount(() => { innerRO.disconnect(); wrapRO.disconnect(); });
|
|
48
|
+
|
|
49
|
+
// ★ SCALE TO FIT, capped. A fixed factor overflows its container the moment the page is narrower than
|
|
50
|
+
// width × scale — the slot's layout width never changes, so nothing else absorbs it.
|
|
51
|
+
const fit = computed(() => (availW.value ? Math.min(props.maxScale, availW.value / props.width) : props.maxScale));
|
|
52
|
+
|
|
53
|
+
// Height the scaled frame actually takes on the page. 0 until measured, so nothing jumps.
|
|
54
|
+
const reservedH = computed(() =>
|
|
55
|
+
naturalH.value ? Math.round(naturalH.value * fit.value) + PAD_TOP + PAD_BOTTOM : 0,
|
|
56
|
+
);
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<!-- The wrapper reserves the SCALED height and the shadow's room in its own padding. It does NOT clip:
|
|
61
|
+
the height already stops the unscaled leftover box from pushing the page around, and that leftover
|
|
62
|
+
box paints nothing. Any `overflow` value here costs the shadow — `hidden` cuts it off square, and
|
|
63
|
+
`hidden`/`clip` on one axis forces the other to `auto`, which adds a scrollbar. -->
|
|
64
|
+
<div
|
|
65
|
+
ref="wrapper"
|
|
66
|
+
class="flex justify-center pt-2 pb-6"
|
|
67
|
+
:style="reservedH ? { height: `${reservedH}px` } : undefined"
|
|
68
|
+
>
|
|
69
|
+
<!-- `self-start` is load-bearing: without it the flex default stretches this to the wrapper's
|
|
70
|
+
reserved height, which the wrapper derives from THIS element's height — a loop that collapses
|
|
71
|
+
both to nothing. `shrink-0` only guards the main axis.
|
|
72
|
+
`pointer-events-none` because this box keeps its UNSCALED height, so part of it hangs past the
|
|
73
|
+
frame over whatever follows. Transparent, but it would still swallow clicks and text selection
|
|
74
|
+
there. The slot opts back in. -->
|
|
75
|
+
<div
|
|
76
|
+
ref="inner"
|
|
77
|
+
class="pointer-events-none shrink-0 self-start [&>*]:pointer-events-auto"
|
|
78
|
+
:style="{ transform: `scale(${fit})`, transformOrigin: 'top center' }"
|
|
79
|
+
>
|
|
80
|
+
<slot />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<svg width="360" height="722" version="1.1" viewBox="0 0 95.25 191.02918" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="linearGradient941">
|
|
4
|
+
<stop stop-color="#dedede" stop-opacity=".27450982" offset="0"/>
|
|
5
|
+
<stop stop-color="#dedede" offset=".05"/>
|
|
6
|
+
<stop stop-color="#dddedf" offset=".94999999"/>
|
|
7
|
+
<stop stop-color="#dedede" stop-opacity=".27450982" offset="1"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<pattern id="pattern3129" patternTransform="matrix(.26458334 0 0 .26458334 -76.445683 -5.7812651)" xlink:href="#pattern2874-7"/>
|
|
10
|
+
<linearGradient id="linearGradient5697" x1="-13.205618" x2="-13.205618" y1="26.258963" y2="47.30072" gradientTransform="translate(13.877701,13.831199)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient941"/>
|
|
11
|
+
<clipPath id="clipPath5738-4">
|
|
12
|
+
<path d="m0.45113544 39.688016c-0.24986192 0-0.4511331183 0.200755-0.4511331183 0.450619v20.944606c0 0.249864 0.2012711983 0.451135 0.4511331183 0.451135h3.47420686c0.2498645 0 0.4511357-0.201271 0.4511357-0.451135v-20.944606c0-0.249864-0.2012712-0.450619-0.4511357-0.450619z" color="#000000"/>
|
|
13
|
+
</clipPath>
|
|
14
|
+
<linearGradient id="linearGradient5475" x1="-13.205618" x2="-13.205618" y1="26.258963" y2="32.87096" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient941"/>
|
|
15
|
+
<clipPath id="clipPath5401-9">
|
|
16
|
+
<rect x="-13.8777" y="25.856594" width="4.3765388" height="7.4167299" ry=".45101735" color="#000000"/>
|
|
17
|
+
</clipPath>
|
|
18
|
+
<filter id="filter5465" x="-.32505909" y="-.025913246" width="1.6501182" height="1.0518265" color-interpolation-filters="sRGB">
|
|
19
|
+
<feGaussianBlur stdDeviation="0.07139097"/>
|
|
20
|
+
</filter>
|
|
21
|
+
<linearGradient id="linearGradient5475-9" x1="-13.205618" x2="-13.205618" y1="26.258963" y2="39.268726" gradientTransform="translate(13.877701,13.831199)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient941"/>
|
|
22
|
+
<clipPath id="clipPath5589-3">
|
|
23
|
+
<path d="m0.45113526 39.688017c-0.2498636 0-0.45113526 0.200756-0.45113526 0.45062v12.912612c0 0.249864 0.20127166 0.451136 0.45113526 0.451136h3.47420684c0.2498635 0 0.4511352-0.201272 0.4511352-0.451136v-12.912612c0-0.249864-0.2012717-0.45062-0.4511352-0.45062z" color="#000000"/>
|
|
24
|
+
</clipPath>
|
|
25
|
+
<filter id="filter5465-7" x="-.32505909" y="-.025913246" width="1.6501182" height="1.0518265" color-interpolation-filters="sRGB">
|
|
26
|
+
<feGaussianBlur stdDeviation="0.07139097"/>
|
|
27
|
+
</filter>
|
|
28
|
+
<clipPath id="clipPath5589-7-8">
|
|
29
|
+
<path d="m0.45113526 39.688017c-0.2498636 0-0.45113526 0.200756-0.45113526 0.45062v12.912612c0 0.249864 0.20127166 0.451136 0.45113526 0.451136h3.47420684c0.2498635 0 0.4511352-0.201272 0.4511352-0.451136v-12.912612c0-0.249864-0.2012717-0.45062-0.4511352-0.45062z" color="#000000"/>
|
|
30
|
+
</clipPath>
|
|
31
|
+
<filter id="filter5465-7-8" x="-.32505909" y="-.025913246" width="1.6501182" height="1.0518265" color-interpolation-filters="sRGB">
|
|
32
|
+
<feGaussianBlur stdDeviation="0.07139097"/>
|
|
33
|
+
</filter>
|
|
34
|
+
<linearGradient id="linearGradient5226" x1="340.65216" x2="349.57422" y1="361.12305" y2="361.12305" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient5220"/>
|
|
35
|
+
<linearGradient id="linearGradient5220">
|
|
36
|
+
<stop stop-color="#404040" offset="0"/>
|
|
37
|
+
<stop stop-color="#838383" offset=".59999961"/>
|
|
38
|
+
<stop stop-color="#a7a7a7" offset="1"/>
|
|
39
|
+
</linearGradient>
|
|
40
|
+
<linearGradient id="linearGradient5226-6" x1="340.65216" x2="349.57422" y1="361.12305" y2="361.12305" gradientTransform="matrix(-.26458334 0 0 .26458334 95.47647 7.5449331e-7)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient5220"/>
|
|
41
|
+
<pattern id="pattern2874-7" width="1.171848" height="1.1718804" patternTransform="matrix(.26458334 0 0 .26458334 -76.445686 -5.7812643)" patternUnits="userSpaceOnUse">
|
|
42
|
+
<g transform="matrix(3.7795274,0,0,3.7795274,288.92857,-26.720982)">
|
|
43
|
+
<rect x="-76.445686" y="7.0699267" width=".15502979" height=".15502979" fill="#212121"/>
|
|
44
|
+
<rect x="-76.290665" y="7.224957" width=".15502979" height=".15502979" fill="#212121"/>
|
|
45
|
+
<rect x="-76.290665" y="7.0699267" width=".15502979" height=".15502979" fill="#060606"/>
|
|
46
|
+
<rect x="-76.445686" y="7.224957" width=".15502979" height=".15502979" fill="#060606"/>
|
|
47
|
+
</g>
|
|
48
|
+
</pattern>
|
|
49
|
+
<filter id="filter5343" x="-.19227619" y="-.082464501" width="1.3845524" height="1.164929" color-interpolation-filters="sRGB">
|
|
50
|
+
<feGaussianBlur stdDeviation="0.030983682"/>
|
|
51
|
+
</filter>
|
|
52
|
+
<filter id="filter5347" x="-.10877967" y="-.082313925" width="1.2175593" height="1.1646278" color-interpolation-filters="sRGB">
|
|
53
|
+
<feGaussianBlur stdDeviation="0.030983682"/>
|
|
54
|
+
</filter>
|
|
55
|
+
<linearGradient id="linearGradient5832" x1="8.3152552" x2="86.934753" y1="8.4801025" y2="8.4801025" gradientTransform="translate(.06681739 -2.6058787)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient5830"/>
|
|
56
|
+
<linearGradient id="linearGradient5830">
|
|
57
|
+
<stop stop-color="#4f4f4f" stop-opacity=".13084112" offset="0"/>
|
|
58
|
+
<stop stop-color="#4f4f4f" offset=".07"/>
|
|
59
|
+
<stop stop-color="#4f4f4f" stop-opacity=".95327103" offset=".93699998"/>
|
|
60
|
+
<stop stop-color="#4f4f4f" stop-opacity="0" offset="1"/>
|
|
61
|
+
</linearGradient>
|
|
62
|
+
<linearGradient id="linearGradient5832-3" x1="8.3152552" x2="86.934753" y1="8.4801025" y2="8.4801025" gradientTransform="matrix(.99999998 0 0 -.99999998 .0668174 193.56681)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient5830"/>
|
|
63
|
+
<linearGradient id="linearGradient2973" x1="-41.981419" x2="137.23141" y1="95.480484" y2="95.480484" gradientTransform="rotate(90,47.625001,95.48048)" gradientUnits="userSpaceOnUse">
|
|
64
|
+
<stop stop-color="#303030" offset="0"/>
|
|
65
|
+
<stop stop-color="#171717" offset="1"/>
|
|
66
|
+
</linearGradient>
|
|
67
|
+
<mask id="mask1062-8" maskUnits="userSpaceOnUse">
|
|
68
|
+
<path d="m42.746746 7.2620904c-0.399166 0-0.72037 0.3212029-0.72037 0.7203695 0 0.3991665 0.321204 0.7203694 0.72037 0.7203694h9.921876c0.399166 0 0.72037-0.3212029 0.72037-0.7203694 0-0.3991666-0.321204-0.7203695-0.72037-0.7203695z" color="#000000" fill="none" stroke="#fff" stroke-linecap="round" stroke-width=".05291667"/>
|
|
69
|
+
</mask>
|
|
70
|
+
<filter id="filter1214" x="-.09973029" y="-2.5665836" width="1.1994606" height="6.1331673" color-interpolation-filters="sRGB">
|
|
71
|
+
<feGaussianBlur stdDeviation="0.49125426"/>
|
|
72
|
+
</filter>
|
|
73
|
+
<filter id="filter960" x="-.31917548" y="-.0079953911" width="1.638351" height="1.0159908" color-interpolation-filters="sRGB">
|
|
74
|
+
<feGaussianBlur stdDeviation="0.070098782"/>
|
|
75
|
+
</filter>
|
|
76
|
+
</defs>
|
|
77
|
+
<g>
|
|
78
|
+
<path d="m94.798868 44.206385c0.249863 0 0.451135 0.200755 0.451135 0.45062v20.944605c0 0.249865-0.201272 0.451136-0.451135 0.451136h-3.474207c-0.249863 0-0.451135-0.201271-0.451135-0.451136v-20.944605c0-0.249865 0.201272-0.45062 0.451135-0.45062z" color="#000000" fill="#b4b4b4"/>
|
|
79
|
+
<path transform="matrix(-1,0,0,1,95.250003,4.5183682)" d="m0.547026 40.090161c-0.222752 0-0.402042 0.17929-0.402042 0.402042v20.237673c0 0.222752 0.17929 0.402042 0.402042 0.402042h0.125057v-21.041757z" clip-path="url(#clipPath5738-4)" color="#000000" fill="url(#linearGradient5697)" filter="url(#filter960)"/>
|
|
80
|
+
<g>
|
|
81
|
+
<rect x="-5.539465e-10" y="25.856596" width="4.3765388" height="7.4167299" ry=".45101735" color="#000000" fill="#b4b4b4"/>
|
|
82
|
+
<path transform="translate(13.877701)" d="m-13.330675 26.258962c-0.222752 0-0.402042 0.17929-0.402042 0.402042v5.807914c0 0.222752 0.17929 0.402042 0.402042 0.402042h0.125057v-6.611998z" clip-path="url(#clipPath5401-9)" color="#000000" fill="url(#linearGradient5475)" filter="url(#filter5465)"/>
|
|
83
|
+
<path d="m0.45113526 39.688017c-0.2498636 0-0.45113526 0.200755-0.45113526 0.45062v12.912611c0 0.249865 0.20127166 0.451136 0.45113526 0.451136h3.47420674c0.2498635 0 0.4511353-0.201271 0.4511353-0.451136v-12.912611c0-0.249865-0.2012718-0.45062-0.4511353-0.45062z" color="#000000" fill="#b4b4b4"/>
|
|
84
|
+
</g>
|
|
85
|
+
<g>
|
|
86
|
+
<g>
|
|
87
|
+
<path d="m0.547026 40.090161c-0.222752 0-0.402042 0.17929-0.402042 0.402042v12.205679c0 0.222752 0.17929 0.402042 0.402042 0.402042h0.125057v-13.009763z" clip-path="url(#clipPath5589-3)" color="#000000" fill="url(#linearGradient5475-9)" filter="url(#filter5465-7)"/>
|
|
88
|
+
<path d="m0.45113526 57.060536c-0.2498636 0-0.45113526 0.200755-0.45113526 0.45062v12.912611c0 0.249865 0.20127166 0.451136 0.45113526 0.451136h3.47420674c0.2498635 0 0.4511353-0.201271 0.4511353-0.451136v-12.912611c0-0.249865-0.2012718-0.45062-0.4511353-0.45062z" color="#000000" fill="#b4b4b4"/>
|
|
89
|
+
<path transform="translate(0,17.372519)" d="m0.547026 40.090161c-0.222752 0-0.402042 0.17929-0.402042 0.402042v12.205679c0 0.222752 0.17929 0.402042 0.402042 0.402042h0.125057v-13.009763z" clip-path="url(#clipPath5589-7-8)" color="#000000" fill="url(#linearGradient5475-9)" filter="url(#filter5465-7-8)"/>
|
|
90
|
+
<path d="m14.483352 0.19895344c-7.7365204 0-13.96452034 6.22800506-13.96452034 13.96452256v162.633474c0 7.73652 6.22799994 13.96504 13.96452034 13.96504h28.3006l0.57826-0.81958h2.052579 4.42041 2.052072l0.578778 0.81958h28.300591c7.73652 0 13.964529-6.22852 13.964529-13.96504v-162.633474c0-7.7365175-6.228009-13.96452256-13.964529-13.96452256z" color="#000000" fill="#c6c6c6"/>
|
|
91
|
+
</g>
|
|
92
|
+
<rect x="1.4644364" y="1.0885143" width="92.644859" height="188.66818" ry="13.323661" fill-opacity=".95794391"/>
|
|
93
|
+
<path transform="scale(.26458334)" d="m56.734375 2.265625c-29.67855 0-53.5722656 23.893695-53.5722656 53.572266v14.171875h4.1660156v-15.785157c0-26.582161 21.40025-47.9804684 47.982422-47.9804684h249.378903c26.58218 0 47.98243 21.3983074 47.98243 47.9804684v15.785157h4.16601v-14.171875c0-29.678571-23.89368-53.572266-53.57227-53.572266zm-53.5722656 72.667969v572.124996h4.1660156v-572.124996zm349.5097706 0v572.124996h4.16601v-572.124996zm-349.5097706 577.050786v14.29687c0 29.67855 23.8937156 53.57227 53.5722656 53.57227h246.531245c29.67859 0 53.57227-23.89372 53.57227-53.57227v-14.29687h-4.16601v16.47656c0 26.58217-21.40025 47.98242-47.98243 47.98242h-249.378903c-26.582172 0-47.982422-21.40025-47.982422-47.98242v-16.47656z" color="#000000" fill="#dedede"/>
|
|
94
|
+
<path transform="scale(.26458334)" d="m284.4707 2.265625v3.9785156h4.92383v-3.9785156zm-281.3085906 67.744141v4.923828h4.1660156v-4.923828zm349.5097706 0v4.923828h4.16601v-4.923828zm-349.5097706 577.048824v4.92579h4.1660156v-4.92579zm349.5097706 0v4.92579h4.16601v-4.92579zm-282.066411 69.38477v3.41016h4.923828v-3.41016z" color="#000000" fill="#c3c3c3"/>
|
|
95
|
+
</g>
|
|
96
|
+
<g transform="translate(-.11323174)">
|
|
97
|
+
<path transform="scale(.26458334)" d="m325.58008 15.363281c-1.17457-0.116312-0.62011 1.054663 2.01562 3.496094 8.69619 8.055118 14.09571 19.714101 14.09571 32.691406v309.572269 309.57226c0 12.97731-5.39952 24.63629-14.09571 32.69141-2.63573 2.44142-3.19019 3.6125-2.01562 3.49609 0.70474-0.0698 2.03219-0.60282 3.9043-1.60156 12.70949-6.7804 20.08984-21.40851 20.08984-36.94531v-307.21289-307.212894c0-15.536781-7.38036-30.164944-20.08984-36.945312-1.87211-0.998744-3.19956-1.531776-3.9043-1.601563z" color="#000000" fill="url(#linearGradient5226)"/>
|
|
98
|
+
<path d="m9.3334021 4.0648688c0.310772-0.03077 0.164071 0.279046-0.533299 0.925008-2.300868 2.13125-3.729491 5.2160232-3.729491 8.6496012v81.907666 81.907656c0 3.43358 1.428623 6.51836 3.729491 8.64961 0.69737 0.64596 0.844071 0.9558 0.533299 0.925-0.186462-0.0185-0.537684-0.15949-1.033013-0.42374-3.362719-1.79398-5.315437-5.66434-5.315437-9.77512v-81.283406-81.283414c0-4.110774 1.952721-7.9811422 5.315437-9.7751142 0.495329-0.264251 0.846551-0.405283 1.033013-0.423747z" color="#000000" fill="url(#linearGradient5226-6)"/>
|
|
99
|
+
</g>
|
|
100
|
+
<g>
|
|
101
|
+
<rect x="42.02623" y="7.2618642" width="11.36291" height="1.4410343" ry=".72051716" color="#000000" fill="url(#pattern3129)"/>
|
|
102
|
+
<circle cx="57.914883" cy="7.8897233" r="1.3446997" color="#000000" fill="#161616"/>
|
|
103
|
+
<circle cx="57.914883" cy="7.8897233" r="1.0774301" color="#000000" fill="#0a0d13"/>
|
|
104
|
+
<circle cx="57.914883" cy="7.8897233" r=".82091707" color="#000000" fill="#091427"/>
|
|
105
|
+
</g>
|
|
106
|
+
<path d="m57.349775 7.3405077c-0.09735 0.010246-0.235679 0.1588277-0.245928 0.4508659-0.01024 0.292038 0.117842 0.4508656 0.189571 0.4508656 0.07173 0 0.368889-0.4252483 0.05635-0.9017315z" fill="#235a91" fill-opacity=".55607473" fill-rule="evenodd" filter="url(#filter5343)"/>
|
|
107
|
+
<path d="m58.048878 7.2382402c-0.153553 0.0472469-0.183083 0.2952938-0.177178 0.4370348 0.0059 0.1417412 0.206706 0.5138116 0.448847 0.4311293 0.242142-0.0826823 0.283483-0.3897882 0.183084-0.5964939-0.100401-0.2067057-0.283483-0.3543525-0.454753-0.2716702z" fill="#235a91" fill-opacity=".55607473" fill-rule="evenodd" filter="url(#filter5347)"/>
|
|
108
|
+
<g>
|
|
109
|
+
<path d="m14.624068 2.5772674c-2.29857 0-4.436824 0.6574018-6.2419957 1.7921386v1.5048178c1.7203777-1.1284623 3.7800397-1.7854209 6.0001507-1.7854209h26.411827 13.795537 26.411825c2.220111 0 4.279774 0.6569586 6.000153 1.7854209v-1.5048178c-1.805173-1.1347368-3.943427-1.7921386-6.241998-1.7921386h-26.16998-13.795537z" color="#000000" fill="url(#linearGradient5832)"/>
|
|
110
|
+
<path d="m14.624068 188.38368c-2.29857 0-4.436824-0.65742-6.2419957-1.79215v-1.50481c1.7203777 1.12845 3.7800397 1.78541 6.0001507 1.78541h26.411827 13.795539 26.411823c2.220111 0 4.279774-0.65696 6.000153-1.78541v1.50481c-1.805173 1.13473-3.943427 1.79215-6.241998 1.79215h-26.169978-13.795539z" color="#000000" fill="url(#linearGradient5832-3)"/>
|
|
111
|
+
<path d="m14.758273 5.8740621c-4.70114 0-8.4857901 3.7846491-8.4857901 8.4857869v162.241251c0 4.70114 3.7846501 8.4858 8.4857901 8.4858h65.733457c4.701138 0 8.485788-3.78466 8.485788-8.4858v-162.241251c0-4.7011378-3.78465-8.4857869-8.485788-8.4857869h-8.300784a1.4175986 1.4175986 0 0 0-1.417487 1.4174841v0.6526739c0 2.5541609-2.055892 4.6105709-4.610052 4.6105709h-37.076805c-2.554169 0-4.610059-2.05641-4.610059-4.6105709v-0.6526739a1.4175986 1.4175986 0 0 0-1.41748-1.4174841z" color="#000000" fill="url(#linearGradient2973)"/>
|
|
112
|
+
</g>
|
|
113
|
+
<g fill="#fff" fill-opacity=".06542059">
|
|
114
|
+
<circle cx="29.091516" cy="7.8897238" r=".89769351"/>
|
|
115
|
+
<circle cx="65.011909" cy="7.8897238" r="1.6063988"/>
|
|
116
|
+
<circle cx="36.640068" cy="7.8897238" r="1.3229167"/>
|
|
117
|
+
</g>
|
|
118
|
+
<path d="m42.746746 7.2620885c-0.399166 0-0.72037 0.3212029-0.72037 0.7203695 0 0.3991665 0.321204 0.7203694 0.72037 0.7203694h9.921876c0.399166 0 0.72037-0.3212029 0.72037-0.7203694 0-0.3991666-0.321204-0.7203695-0.72037-0.7203695z" color="#000000" fill="none" stroke="#292929" stroke-linecap="round" stroke-width=".05291667"/>
|
|
119
|
+
<g fill="#fff" filter="url(#filter1214)" mask="url(#mask1062-8)">
|
|
120
|
+
<path d="m42.25606 7.9824588a0.22968481 0.22968481 0 0 1-0.229685 0.2296847 0.22968481 0.22968481 0 0 1-0.229685-0.2296847 0.22968481 0.22968481 0 0 1 0.229685-0.2296848 0.22968481 0.22968481 0 0 1 0.229685 0.2296848z"/>
|
|
121
|
+
<path d="m53.618677 7.9824588a0.22968481 0.22968481 0 0 1-0.229685 0.2296847 0.22968481 0.22968481 0 0 1-0.229685-0.2296847 0.22968481 0.22968481 0 0 1 0.229685-0.2296848 0.22968481 0.22968481 0 0 1 0.229685 0.2296848z"/>
|
|
122
|
+
</g>
|
|
123
|
+
</g>
|
|
124
|
+
</svg>
|