@vectoriox/iox-ui 5.6.0 → 5.7.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.
@@ -4953,6 +4953,201 @@ function partClassName(type, partKey) {
4953
4953
  return PART_CLASS_NAMES[type]?.[partKey];
4954
4954
  }
4955
4955
 
4956
+ function parseHexColor(hex) {
4957
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec((hex ?? '').trim());
4958
+ if (!m)
4959
+ return null;
4960
+ let h = m[1];
4961
+ if (h.length === 3)
4962
+ h = h.split('').map(c => c + c).join('');
4963
+ const n = parseInt(h, 16);
4964
+ return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };
4965
+ }
4966
+ function toHexColor(c) {
4967
+ const q = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, '0');
4968
+ return `#${q(c.r)}${q(c.g)}${q(c.b)}`;
4969
+ }
4970
+ const srgbToLinear = (v) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));
4971
+ const linearToSrgb = (v) => (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055);
4972
+ function rgbToOklab(c) {
4973
+ const r = srgbToLinear(c.r), g = srgbToLinear(c.g), b = srgbToLinear(c.b);
4974
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
4975
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
4976
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
4977
+ return {
4978
+ L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
4979
+ a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
4980
+ b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
4981
+ };
4982
+ }
4983
+ function oklabToRgb(c) {
4984
+ const l = Math.pow(c.L + 0.3963377774 * c.a + 0.2158037573 * c.b, 3);
4985
+ const m = Math.pow(c.L - 0.1055613458 * c.a - 0.0638541728 * c.b, 3);
4986
+ const s = Math.pow(c.L - 0.0894841775 * c.a - 1.291485548 * c.b, 3);
4987
+ return {
4988
+ r: linearToSrgb(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
4989
+ g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
4990
+ b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
4991
+ };
4992
+ }
4993
+ const inGamut = (c) => [c.r, c.g, c.b].every(v => v >= -1e-6 && v <= 1 + 1e-6);
4994
+ /** The OKLab colour at (L, hue, chroma), chroma reduced until it fits sRGB. */
4995
+ function atLightness(L, hueA, hueB, chroma) {
4996
+ const len = Math.hypot(hueA, hueB) || 1;
4997
+ const ua = hueA / len, ub = hueB / len;
4998
+ let c = chroma;
4999
+ for (let i = 0; i < 24; i++) {
5000
+ const rgb = oklabToRgb({ L, a: ua * c, b: ub * c });
5001
+ if (inGamut(rgb))
5002
+ return rgb;
5003
+ c *= 0.82;
5004
+ }
5005
+ return oklabToRgb({ L, a: 0, b: 0 });
5006
+ }
5007
+ // ── WCAG 2 (the gate) ────────────────────────────────────────────────────────
5008
+ function relativeLuminance(c) {
5009
+ return 0.2126 * srgbToLinear(c.r) + 0.7152 * srgbToLinear(c.g) + 0.0722 * srgbToLinear(c.b);
5010
+ }
5011
+ /** WCAG 2 contrast ratio between two colours (order-independent), 1..21. */
5012
+ function wcagContrastRatio(a, b) {
5013
+ const ca = typeof a === 'string' ? parseHexColor(a) : a;
5014
+ const cb = typeof b === 'string' ? parseHexColor(b) : b;
5015
+ if (!ca || !cb)
5016
+ return 1;
5017
+ const la = relativeLuminance(ca), lb = relativeLuminance(cb);
5018
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
5019
+ return (hi + 0.05) / (lo + 0.05);
5020
+ }
5021
+ // ── APCA (the read-out — WCAG 2 stays the gate) ─────────────────────────────
5022
+ /**
5023
+ * APCA-W3 lightness contrast Lc (0.0.98G-4g constants), text-on-background and signed:
5024
+ * positive for dark-on-light, negative for light-on-dark; |Lc| ≈ 106–108 at the extremes.
5025
+ * Shown in the picker as the perceptual read-out; never used as the pass/fail gate here.
5026
+ */
5027
+ function apcaLc(text, bg) {
5028
+ const toY = (c) => 0.2126729 * Math.pow(c.r, 2.4) + 0.7151522 * Math.pow(c.g, 2.4) + 0.072175 * Math.pow(c.b, 2.4);
5029
+ const clamp = (y) => (y < 0.022 ? y + Math.pow(0.022 - y, 1.414) : y);
5030
+ const ct = typeof text === 'string' ? parseHexColor(text) : text;
5031
+ const cb = typeof bg === 'string' ? parseHexColor(bg) : bg;
5032
+ if (!ct || !cb)
5033
+ return 0;
5034
+ const yTxt = clamp(toY(ct)), yBg = clamp(toY(cb));
5035
+ let lc;
5036
+ if (yBg > yTxt)
5037
+ lc = (Math.pow(yBg, 0.56) - Math.pow(yTxt, 0.57)) * 1.14;
5038
+ else
5039
+ lc = (Math.pow(yBg, 0.65) - Math.pow(yTxt, 0.62)) * 1.14;
5040
+ if (Math.abs(lc) < 0.1)
5041
+ return 0;
5042
+ return Math.round((lc > 0 ? lc - 0.027 : lc + 0.027) * 1000) / 10;
5043
+ }
5044
+ // ── The ramp ─────────────────────────────────────────────────────────────────
5045
+ /** Fixed, perceptually even lightness ladder for brand-50 … brand-900. */
5046
+ const RAMP_STEPS = [
5047
+ { step: 50, L: 0.97, chromaScale: 0.22 },
5048
+ { step: 100, L: 0.93, chromaScale: 0.42 },
5049
+ { step: 200, L: 0.86, chromaScale: 0.66 },
5050
+ { step: 300, L: 0.78, chromaScale: 0.85 },
5051
+ { step: 400, L: 0.70, chromaScale: 0.96 },
5052
+ { step: 500, L: 0.62, chromaScale: 1.0 },
5053
+ { step: 600, L: 0.53, chromaScale: 0.96 },
5054
+ { step: 700, L: 0.45, chromaScale: 0.88 },
5055
+ { step: 800, L: 0.36, chromaScale: 0.76 },
5056
+ { step: 900, L: 0.27, chromaScale: 0.62 },
5057
+ ];
5058
+ const BLACK = { r: 0, g: 0, b: 0 };
5059
+ const WHITE = { r: 1, g: 1, b: 1 };
5060
+ /** Snap to the 8-bit hex the token will actually carry — contrast is judged on WHAT SHIPS.
5061
+ * (The first version searched in float space and hex rounding landed 6.9596 where 7 was owed.) */
5062
+ const quant = (c) => parseHexColor(toHexColor(c));
5063
+ /** Binary-search an OKLab lightness so `contrast(colour, against) ≥ ratio`, darkening-or-lightening. */
5064
+ function atContrast(against, ratio, hueA, hueB, chroma, darker) {
5065
+ let lo = darker ? 0 : rgbToOklab(against).L;
5066
+ let hi = darker ? rgbToOklab(against).L : 1;
5067
+ let best = quant(atLightness(darker ? 0.18 : 0.92, hueA, hueB, chroma));
5068
+ for (let i = 0; i < 28; i++) {
5069
+ const mid = (lo + hi) / 2;
5070
+ const candidate = quant(atLightness(mid, hueA, hueB, chroma));
5071
+ if (wcagContrastRatio(candidate, against) >= ratio) {
5072
+ best = candidate;
5073
+ if (darker)
5074
+ lo = mid;
5075
+ else
5076
+ hi = mid; // pull back toward `against`: the LOWEST passing step
5077
+ }
5078
+ else {
5079
+ if (darker)
5080
+ hi = mid;
5081
+ else
5082
+ lo = mid;
5083
+ }
5084
+ }
5085
+ return best;
5086
+ }
5087
+ /**
5088
+ * The derived brand tokens (plan §6's table, every row asserted in brand.spec.ts):
5089
+ * `brand-50…900` (monotonic OKLab lightness, gamut-safe) · `brandInk` (black or white, ≥4.5 on
5090
+ * brand-500 — brand-500's lightness is nudged when neither side clears it) · `surface`/`ink`/
5091
+ * `inkMuted` (seed-tinted neutrals, ≥7:1 / ≥4.5:1 on surface) · `line` (the LIGHTEST neutral that
5092
+ * still clears 3:1 on surface) · `accent` (pack-rotated hue, ≥4.5:1 on surface) — with dark-scheme
5093
+ * values for the neutral family riding `values.dark`.
5094
+ *
5095
+ * Throws on an unparseable seed: callers (the picker) validate; a silent fallback here would
5096
+ * launch a studio into a palette nobody chose.
5097
+ */
5098
+ function deriveBrandTokens(seed, pack) {
5099
+ const seedRgb = parseHexColor(seed);
5100
+ if (!seedRgb)
5101
+ throw new TypeError(`deriveBrandTokens: not a colour: "${seed}"`);
5102
+ const lab = rgbToOklab(seedRgb);
5103
+ const chroma = Math.hypot(lab.a, lab.b);
5104
+ const ua = chroma > 1e-6 ? lab.a : 0.0, ub = chroma > 1e-6 ? lab.b : 1e-6; // grey seeds: keep a stable (arbitrary) hue axis
5105
+ const baseChroma = Math.max(chroma, 0.02); // near-grey seeds still get a usable, quiet ramp
5106
+ const token = (name, value, dark) => ({ name, value, ...(dark ? { values: { dark } } : {}), type: 'color', group: 'colour' });
5107
+ // 1 — the ramp
5108
+ const ramp = RAMP_STEPS.map(({ step, L, chromaScale }) => ({ step, rgb: atLightness(L, ua, ub, baseChroma * chromaScale) }));
5109
+ // 2 — brandInk on brand-500, nudging 500 (within its neighbours) when neither ink clears 4.5
5110
+ let five = ramp.find(r => r.step === 500);
5111
+ five.rgb = quant(five.rgb);
5112
+ const inkFor = (bg) => (wcagContrastRatio(WHITE, bg) >= wcagContrastRatio(BLACK, bg) ? WHITE : BLACK);
5113
+ for (let i = 0; i < 40 && wcagContrastRatio(inkFor(five.rgb), five.rgb) < 4.5; i++) {
5114
+ const L = rgbToOklab(five.rgb).L;
5115
+ // Mid tones can fail both inks — walk toward whichever side is already winning, but
5116
+ // never past the neighbouring steps: the ramp's monotonic lightness is a guarantee.
5117
+ const towardDark = wcagContrastRatio(WHITE, five.rgb) >= wcagContrastRatio(BLACK, five.rgb);
5118
+ const next = Math.min(0.695, Math.max(0.545, L + (towardDark ? -0.012 : 0.012)));
5119
+ if (next === L)
5120
+ break;
5121
+ five.rgb = quant(atLightness(next, ua, ub, baseChroma));
5122
+ }
5123
+ const brandInk = inkFor(five.rgb);
5124
+ // 3 — the neutral family, faintly tinted by the seed hue. Surfaces are quantized FIRST:
5125
+ // every later guarantee is measured against the hex that ships, not a float ideal.
5126
+ const tint = 0.006;
5127
+ const surface = quant(atLightness(0.985, ua, ub, tint));
5128
+ const surfaceDark = quant(atLightness(0.145, ua, ub, tint * 1.5));
5129
+ const ink = atContrast(surface, 7, ua, ub, tint * 2, true);
5130
+ const inkDark = atContrast(surfaceDark, 7, ua, ub, tint * 2, false);
5131
+ const inkMuted = atContrast(surface, 4.5, ua, ub, tint * 2, true);
5132
+ const inkMutedDark = atContrast(surfaceDark, 4.5, ua, ub, tint * 2, false);
5133
+ const line = atContrast(surface, 3, ua, ub, tint, true);
5134
+ const lineDark = atContrast(surfaceDark, 3, ua, ub, tint, false);
5135
+ // 4 — the accent: pack-defined hue rotation from the seed, readable on surface
5136
+ const rot = ((pack.accentHueDeg ?? 150) * Math.PI) / 180;
5137
+ const ra = ua * Math.cos(rot) - ub * Math.sin(rot);
5138
+ const rb = ua * Math.sin(rot) + ub * Math.cos(rot);
5139
+ const accent = atContrast(surface, 4.5, ra, rb, Math.max(baseChroma, 0.06), true);
5140
+ return [
5141
+ ...ramp.map(({ step, rgb }) => token(`brand-${step}`, toHexColor(rgb))),
5142
+ token('brandInk', toHexColor(brandInk)),
5143
+ token('surface', toHexColor(surface), toHexColor(surfaceDark)),
5144
+ token('ink', toHexColor(ink), toHexColor(inkDark)),
5145
+ token('inkMuted', toHexColor(inkMuted), toHexColor(inkMutedDark)),
5146
+ token('line', toHexColor(line), toHexColor(lineDark)),
5147
+ token('accent', toHexColor(accent)),
5148
+ ];
5149
+ }
5150
+
4956
5151
  /**
4957
5152
  * Design-token → CSS compilation, shared by BOTH renderers (flavors plan F0).
4958
5153
  *
@@ -5088,6 +5283,12 @@ const SWISS_FLAVOR = {
5088
5283
  ButtonBlock: { borderRadius: 'radius-control', padding: 'space-control' },
5089
5284
  Card: { borderRadius: 'radius-surface' },
5090
5285
  },
5286
+ // F2: Swiss turns the seed toward a cool counterpoint. NOTE deliberately: the brand tokens
5287
+ // deriveBrandTokens() emits are SEEDED per org — they do not join `defaults` here, because a
5288
+ // colour default repaints legacy nodes whose saved styleProps lack the key (the engine fills
5289
+ // missing keys from config defaults). Coloured element defaults are a Swiss v2 upgrade
5290
+ // decision (§10 C2), taken with the picker in front of the author — never a side effect.
5291
+ accentHueDeg: 150,
5091
5292
  };
5092
5293
  /** What both renderers use until F2 lets an org choose. */
5093
5294
  const DEFAULT_FLAVOR = SWISS_FLAVOR;
@@ -6929,5 +7130,5 @@ function effectiveTransitionId(anim) {
6929
7130
  * Generated bundle index. Do not edit.
6930
7131
  */
6931
7132
 
6932
- export { AOSService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ContainerComponent, ContextStore, DEFAULT_FLAVOR, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SWISS_FLAVOR, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, compileTokenCss, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, flavorDefaultDeclarations, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveFlavorDefaults, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, tokensToDarkVarMap, tokensToVarMap, validateFlavorPack, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
7133
+ export { AOSService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ContainerComponent, ContextStore, DEFAULT_FLAVOR, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SWISS_FLAVOR, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, activeActions, apcaLc, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, compileTokenCss, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveBrandTokens, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, flavorDefaultDeclarations, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, oklabToRgb, parentBreakpoint, parseHexColor, parseUrlList, partClassName, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, relativeLuminance, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveFlavorDefaults, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, rgbToOklab, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, toHexColor, tokensToDarkVarMap, tokensToVarMap, validateFlavorPack, variableAliases, variableItemsFrom, wcagContrastRatio, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
6933
7134
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map