@samva/markup 0.6.0 → 0.8.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.
Files changed (83) hide show
  1. package/AGENTS.md +94 -47
  2. package/README.md +1 -1
  3. package/dist/brand-DcoVkh_S.d.ts +88 -0
  4. package/dist/{catalog-60eaYC7b.d.ts → catalog-CKuOooB8.d.ts} +13 -15
  5. package/dist/{checks-DBweSIPN.js → checks-D73Xizbw.js} +414 -2
  6. package/dist/checks.js +1 -1
  7. package/dist/classes.d.ts +2 -3
  8. package/dist/compile-sms.d.ts +1 -1
  9. package/dist/compile-whatsapp.d.ts +1 -1
  10. package/dist/compiler.d.ts +62 -45
  11. package/dist/compiler.js +206 -863
  12. package/dist/components.d.ts +2 -2
  13. package/dist/{corpus-BjYMqOoY.d.ts → corpus-CPPfgAHN.d.ts} +14 -14
  14. package/dist/{css-COcFOT1y.d.ts → css-CS2jLID6.d.ts} +28 -2
  15. package/dist/email/assets.d.ts +40 -0
  16. package/dist/email/assets.js +63 -0
  17. package/dist/email/brand-footer.d.ts +34 -0
  18. package/dist/email/brand-footer.js +62 -0
  19. package/dist/email/brand.d.ts +2 -0
  20. package/dist/email/brand.js +148 -0
  21. package/dist/email/html.d.ts +2 -42
  22. package/dist/email/html.js +11 -0
  23. package/dist/email/render.d.ts +12 -5
  24. package/dist/email/render.js +2 -5
  25. package/dist/email/selection.d.ts +2 -91
  26. package/dist/email/stylesheets.d.ts +1 -1
  27. package/dist/email/stylesheets.js +3 -0
  28. package/dist/emit.js +2 -2
  29. package/dist/html-Bl9BAdqc.d.ts +42 -0
  30. package/dist/{ops-2aiiYO7m.js → ops-BvwqHl7V.js} +2 -2
  31. package/dist/ops.js +1 -1
  32. package/dist/{parser-CQcASEyy.d.ts → parser-t29cHLVL.d.ts} +2 -2
  33. package/dist/parser.d.ts +1 -1
  34. package/dist/parser.js +1 -2
  35. package/dist/registry-Dcp6QEK_.d.ts +73 -0
  36. package/dist/registry.d.ts +2 -42
  37. package/dist/{render-aaJBnsXK.js → render-vAv_4r9v.js} +174 -17
  38. package/dist/selection-CCArx9lU.d.ts +91 -0
  39. package/dist/{showcase-DGZAWn4Z.d.ts → showcase-egkusQBf.d.ts} +15 -56
  40. package/dist/{presets → starters}/category-mapping.d.ts +5 -5
  41. package/dist/{presets → starters}/category-mapping.js +5 -5
  42. package/dist/starters/corpus.d.ts +2 -0
  43. package/dist/{presets → starters}/corpus.js +15 -15
  44. package/dist/starters/library.d.ts +18 -0
  45. package/dist/starters/library.js +692 -0
  46. package/dist/{presets → starters}/public-gallery.d.ts +47 -47
  47. package/dist/{presets → starters}/public-gallery.js +92 -92
  48. package/dist/{presets → starters}/public-handoff.d.ts +14 -14
  49. package/dist/{presets → starters}/public-handoff.js +40 -40
  50. package/dist/{presets → starters}/public-preview-artifacts.d.ts +4 -4
  51. package/dist/{presets → starters}/public-preview-artifacts.js +15 -15
  52. package/dist/{presets → starters}/public-preview.d.ts +1 -1
  53. package/dist/{presets → starters}/public-preview.js +3 -3
  54. package/dist/starters/showcase.d.ts +2 -0
  55. package/dist/starters/showcase.js +335 -0
  56. package/dist/starters/sources.d.ts +45 -0
  57. package/dist/{presets → starters}/sources.js +1951 -272
  58. package/dist/starters/theme.d.ts +45 -0
  59. package/dist/starters/theme.js +77 -0
  60. package/dist/starters.d.ts +2 -0
  61. package/dist/{presets.js → starters.js} +4 -11
  62. package/dist/tailwind-BYq7Lo6u.js +727 -0
  63. package/dist/theme-css-hFqcPSar.js +587 -0
  64. package/dist/theme-css.d.ts +56 -15
  65. package/dist/theme-css.js +2 -509
  66. package/dist/tsx-source-instrumentation.d.ts +9 -0
  67. package/dist/tsx-source-instrumentation.js +20 -2
  68. package/dist/tsx-tracer.js +3 -3
  69. package/dist/unsubscribe-link-CoesnQDU.js +26 -0
  70. package/package.json +56 -44
  71. package/samva-brand.d.ts +14 -0
  72. package/dist/footer.d.ts +0 -66
  73. package/dist/footer.js +0 -93
  74. package/dist/presets/corpus.d.ts +0 -2
  75. package/dist/presets/showcase.d.ts +0 -2
  76. package/dist/presets/showcase.js +0 -627
  77. package/dist/presets/sources.d.ts +0 -38
  78. package/dist/presets.d.ts +0 -2
  79. package/dist/theme-6ApjjiBo.d.ts +0 -116
  80. package/dist/theme.d.ts +0 -2
  81. package/dist/theme.js +0 -118
  82. package/dist/tokens.d.ts +0 -6
  83. package/dist/tokens.js +0 -417
package/AGENTS.md CHANGED
@@ -1,47 +1,94 @@
1
- # Samva Markup consumer guide
2
-
3
- This package is plain TypeScript. It depends on no application runtime, so a template project
4
- installs the compiler and nothing that comes with one. The structured SML surface — `parseSml`,
5
- `compileSml`, `compileSource` — returns a better-result `Result` you branch on with `Result.isOk`
6
- / `Result.isErr`, carrying a `TaggedError` such as `SmlParseError` or `SmlCompileError` that you
7
- discriminate by `_tag`. Email compilation reports through `EmailDiagnostic` values instead. A
8
- declaration the compiler cannot accept at all throws a `TypeError`: a template id or fixture key
9
- that is not one plain path segment, a nonportable input schema, a project holding a reserved
10
- compiler path.
11
-
12
- Markup's own data models are types and interfaces, not schema declarations, so there is no schema
13
- library to adopt to consume them. Their unions discriminate on `type`: an `EmailNode` is `Text`,
14
- `Element`, `Fragment` or `Raw`, and an `SmlOp` names its edit the same way.
15
- Only the better-result error classes carry `_tag`. A template's JSON input contract is the one
16
- exception to the no-schema rule, because it has to be checked against real input: `inputSchema`
17
- accepts any Standard Schema through the `@standard-schema/spec` types and returns a portable JSON
18
- Schema with its validator, so which schema library an author reaches for stays theirs.
19
-
20
- Email entries default-export `defineEmail` definitions. SMS and WhatsApp use the structured
21
- channel runtime at `@samva/markup` and components at `@samva/markup/components`.
22
- Use documented package subpaths only. Template entry discovery is `.tsx` only.
23
- `presets/sources.gen.ts` is generated: run `bun run codegen:presets` from this package, never hand-edit
24
- it.
25
-
26
- Object email templates use `jsxImportSource: "@samva/markup/email"` and
27
- `defineEmail` from `@samva/markup/template`. Their render function receives actual validated
28
- JSON input and returns subject, optional preheader, body, and optional text. A template id and a
29
- fixture key are both written into file paths, so each must match `TEMPLATE_NAME_PATTERN` — one
30
- plain path segment — and `defineEmail` refuses anything else where the name is declared. A consumer
31
- that resolves a path from those names imports that pattern; it never restates it. The derived
32
- semantic tree lowers directly to HTML/text via `@samva/markup/email/render`; do not route object
33
- templates through recording proxies or textual SML. Authoring-source provenance belongs to semantic
34
- nodes.
35
-
36
- Ordinary HTML is typed. `@samva/markup/email/elements` is the one table behind both the JSX
37
- intrinsic prop types and the serializer's allowlist, so an unsupported element or attribute fails at
38
- typecheck and again at compile with a source location. Email primitives — `Email`, `Section`,
39
- `Columns`/`Column`, `Button`, `Spacer`, `Divider`, `Image`, `Link` — come from
40
- `@samva/markup/email/components`; they generate table markup and classic-Outlook fallbacks, and
41
- every wrapper they generate carries the authoring call site as its origin. Findings are
42
- `EmailDiagnostic` values from `@samva/markup/email/diagnostics`: errors throw `EmailCompileError`,
43
- warnings ride back on the render result. Nothing an author passes as a string becomes markup.
44
-
45
- `@samva/markup/compiler` is the bundler-agnostic compiler boundary. `@samva/vite` and
46
- `apps/agents/src/editor` are adapters over it — they own resolution, watching and transport, never
47
- compilation semantics — and the module imports no Vite, esbuild or Node builtins.
1
+ # @samva/markup agent guide
2
+
3
+ `@samva/markup` is the authoring language and compiler for Samva templates. Email templates are
4
+ TSX modules that default-export a `defineEmail` definition and render through a JSX runtime that
5
+ lowers to email-safe HTML and plain text. SMS and WhatsApp templates use the structured SML runtime.
6
+ The package is plain TypeScript with no application runtime; the local editor, project builds, and
7
+ exports live in `@samva/vite`.
8
+
9
+ ## Entry points
10
+
11
+ - `@samva/markup/template` — `defineEmail`, `TemplateDefinition`, `TemplateInput`, and
12
+ `TEMPLATE_NAME_PATTERN`.
13
+ - `@samva/markup/email` — the JSX runtime. Set `jsxImportSource: "@samva/markup/email"` for email
14
+ entries.
15
+ - `@samva/markup/email/components` — email primitives (`Email`, `Section`, `Columns`, `Button`,
16
+ and the rest of its exports). They generate table layout and classic-Outlook fallbacks.
17
+ - `@samva/markup/email/render` — `renderEmail(template, data)`, which validates input and returns
18
+ subject, preheader, HTML, text, and the derived tree.
19
+ - `@samva/markup/email/diagnostics` — `EmailDiagnostic` and `EmailCompileError`.
20
+ - `@samva/markup/email/assets` — `isAssetPath` and `ASSET_CONTENT_TYPES`: which imported files
21
+ become content-addressed assets, without loading the compiler.
22
+ - `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
23
+ portable JSON Schema and validator a template's input is checked against.
24
+ - `@samva/markup/compiler` — `compileEmailProject`, the bundler-agnostic compile step. It imports no
25
+ Vite, esbuild, or Node builtins, so it runs in any JavaScript host.
26
+ - `samva:brand` — `BrandLogo` (light logo, swapped for the dark one where the client honors dark
27
+ mode) and `BrandFooter` (company, postal address, social links and the unsubscribe row the send
28
+ path fills). The compiler binds them to the brand the build resolves; add
29
+ `"types": ["@samva/markup/samva-brand"]` to the project's tsconfig for `tsc`. In `theme.css`,
30
+ `@import "samva:brand";` layers the brand theme; `samva:brand/<slug>` names one brand.
31
+ - `jsxImportSource: "@samva/markup"` and `@samva/markup/components` — the structured SMS and
32
+ WhatsApp runtime and components. `parseSml` (`@samva/markup/parser`) and `compileSml` /
33
+ `compileSource` (`@samva/markup/compile`) work on SML source.
34
+
35
+ The package's `generated/` directory ships the SML and email language references and agent
36
+ contracts; read those before authoring. Import only subpaths declared in `package.json` `exports`.
37
+
38
+ ## How you hurt yourself here
39
+
40
+ - **Passing a string where you want markup.** Nothing an author passes as a string becomes markup;
41
+ strings render as escaped text. There is no raw-HTML escape hatch: compatibility markup comes only
42
+ from the email primitives.
43
+ - **Using an element or attribute the serializer does not allow.** `@samva/markup/email/elements` is
44
+ the one table behind both the JSX intrinsic prop types and the serializer's allowlist, so an
45
+ unsupported element or attribute fails at typecheck and again at compile with a source location.
46
+ - **Naming a template or fixture like a path.** A template id and a fixture key are both written
47
+ into file paths, so each must match `TEMPLATE_NAME_PATTERN`, one plain path segment.
48
+ `defineEmail` throws a `TypeError` where the name is declared. Code that builds a path from these
49
+ names imports the pattern instead of restating it.
50
+ - **Reading the clock or randomness in a template.** Samva's hosted render has no ambient clock and
51
+ no entropy source: reading the current instant or a random value there throws unless the
52
+ definition declares the fixed value in its `runtime` (`TemplateRuntime`). A local render does not
53
+ enforce this, so a template that reads `Date.now()` can pass locally and fail once published. Take
54
+ times from validated input, or declare them.
55
+ - **Declaring a webfont as if every client loads it.** A project font is an ordinary `@font-face`
56
+ in the project's CSS (usually `theme.css`) whose `src` is a `.woff2` file in the project, named
57
+ by a path relative to that stylesheet; the compiler serves it as a content-addressed asset and
58
+ hides the rule from classic Outlook. Gmail and Outlook never load it, so a `font-family` stack
59
+ that names a project font must end in a generic (`sans-serif`, `serif`, `monospace`) or the
60
+ render fails with `email-font-fallback`. Only WOFF2 is accepted; an `https:` `src` stays an
61
+ external reference.
62
+ - **Treating `renderEmail` as a sandbox.** It executes the template's TypeScript in the calling
63
+ process. Run untrusted template source in an isolated host.
64
+ - **Expecting the schema library to do the validation.** A template's input schema is converted to
65
+ a portable JSON Schema and validated by that document, not by the library that produced it. A
66
+ refinement that the converter cannot export is refused, and defaults are never inserted or values
67
+ coerced. The README lists the supported keywords.
68
+
69
+ ## Rules
70
+
71
+ - **Email entries default-export a `defineEmail` definition** from a `.tsx` file. The definition
72
+ owns a stable, project-unique `id`, an input schema, named fixtures, and a synchronous `render`
73
+ that receives the validated JSON input unchanged. Helper modules are ordinary imports, not
74
+ catalog entries.
75
+ - **Email templates have no textual SML form.** The derived semantic tree lowers directly to HTML
76
+ and text through `@samva/markup/email/render`; SML is the SMS and WhatsApp language.
77
+ - **Structured SML is a closed grammar.** `parseSml` and `compileSml` reject anything outside it
78
+ rather than passing it through.
79
+ - **Failures are typed.** The SML surface returns a better-result `Result`; branch with
80
+ `Result.isOk` / `Result.isErr` and discriminate the `TaggedError` (`SmlParseError`,
81
+ `SmlCompileError`) by `_tag`. Email compilation reports `EmailDiagnostic` values: errors throw
82
+ `EmailCompileError`, warnings ride back on the render result. A declaration the compiler cannot
83
+ accept at all, such as an invalid name or a nonportable input schema, throws a `TypeError`.
84
+ - **Data models are plain types.** Markup's data models are TypeScript types, not schema
85
+ declarations, so there is no schema library to adopt to consume them. Unions discriminate on
86
+ `type` (an `EmailNode`, an `SmlOp`); only error classes carry `_tag`. The input schema is the one
87
+ place a Standard Schema is accepted, and which library produces it is yours to choose.
88
+
89
+ ## Proving your setup
90
+
91
+ - `tsc --noEmit` over the template project catches unsupported elements, attributes, and props.
92
+ - `samva templates check` (from `@samva/cli`) compiles every entry and renders every fixture,
93
+ reporting findings with source locations.
94
+ - `renderEmail(template, fixture)` in a unit test proves one template's output without a build.
package/README.md CHANGED
@@ -101,7 +101,7 @@ a value at import. `new Date(value)` with an explicit argument always works, so
101
101
  takes its time from validated input or states it once as configuration. Locale (`en-US`) and time
102
102
  zone (UTC) are publication-wide and back every `Intl` constructor and `toLocale*` call.
103
103
 
104
- Portable validation uses `@cfworker/json-schema` 4.1.1 without runtime code generation. The
104
+ Portable validation uses `@cfworker/json-schema` without runtime code generation. The
105
105
  2020-12 meta-schema is checked at every supported schema position, and the explicit profile rejects
106
106
  keywords outside its contract before interpretation. The AJV dependency supplies static meta-schema
107
107
  JSON; its code generator is not invoked in a loaded template.
@@ -0,0 +1,88 @@
1
+ import { m as EmailNode } from "./elements-BzjjnoCS.js";
2
+ //#region src/internal/unsubscribe-link.d.ts
3
+ /**
4
+ * The per-recipient unsubscribe URL is not known at render: the send path
5
+ * substitutes each delivery's link for this placeholder, or removes the
6
+ * unsubscribe row when the send resolved no unsubscribe group.
7
+ */
8
+ declare const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
9
+ //#endregion
10
+ //#region src/email/brand.d.ts
11
+ interface EmailBrandAddress {
12
+ readonly street1: string;
13
+ readonly street2?: string | undefined;
14
+ readonly city: string;
15
+ readonly region: string;
16
+ readonly postalCode: string;
17
+ readonly country: string;
18
+ }
19
+ interface EmailBrandSocialLink {
20
+ readonly label: string;
21
+ readonly url: string;
22
+ }
23
+ interface EmailBrandAssets {
24
+ readonly logoUrl?: string | undefined;
25
+ readonly logoDarkUrl?: string | undefined;
26
+ readonly iconUrl?: string | undefined;
27
+ }
28
+ interface EmailBrandFooter {
29
+ readonly companyName?: string | undefined;
30
+ readonly address?: EmailBrandAddress | undefined;
31
+ readonly socialLinks?: readonly EmailBrandSocialLink[] | undefined;
32
+ }
33
+ interface EmailBrand {
34
+ /** The brand's slug; `samva:brand/<slug>` must name it. */
35
+ readonly slug: string;
36
+ /** Brand theme CSS in the closed grammar `parseBrandCss` accepts. */
37
+ readonly css: string;
38
+ readonly assets?: EmailBrandAssets | undefined;
39
+ readonly footer?: EmailBrandFooter | undefined;
40
+ }
41
+ /** Marks the block the send path recognizes as the template's own footer. */
42
+ declare const FOOTER_ATTRIBUTE = "data-samva-footer";
43
+ /** Marks the footer row that carries the unsubscribe link. */
44
+ declare const UNSUBSCRIBE_ROW_ATTRIBUTE = "data-samva-unsubscribe";
45
+ /** Every class the brand components use, compiled for any project that imports them. */
46
+ declare const BRAND_COMPONENT_CLASSES: readonly ["dark:hidden!", "hidden", "dark:block!", "text-xs", "text-gray-500", "text-center", "font-semibold", "underline"];
47
+ interface BrandLogoProps {
48
+ /** Rendered width in pixels; the height follows the image. */
49
+ readonly width?: number | undefined;
50
+ /** Defaults to the brand footer's company name. */
51
+ readonly alt?: string | undefined;
52
+ readonly align?: "left" | "center" | "right" | undefined;
53
+ readonly className?: string | undefined;
54
+ }
55
+ interface BrandFooterProps {
56
+ /** Text of the unsubscribe link. */
57
+ readonly unsubscribeLabel?: string | undefined;
58
+ readonly className?: string | undefined;
59
+ }
60
+ interface BrandComponents {
61
+ readonly BrandLogo: (props: BrandLogoProps) => EmailNode | null;
62
+ readonly BrandFooter: (props: BrandFooterProps) => EmailNode | null;
63
+ }
64
+ /** Bind `BrandLogo` and `BrandFooter` to one brand. */
65
+ declare const brandComponents: (brand: EmailBrand) => BrandComponents;
66
+ /** What a rendered message says about the footer it carries. */
67
+ interface RenderedFooterMarkers {
68
+ /** The message renders `BrandFooter` (or claims to, with the marker). */
69
+ readonly footer: boolean;
70
+ /** The message carries the unsubscribe placeholder link only `BrandFooter` can write. */
71
+ readonly unsubscribeLink: boolean;
72
+ }
73
+ /** Read the footer markers from rendered HTML. */
74
+ declare const renderedFooterMarkers: (html: string) => RenderedFooterMarkers;
75
+ /**
76
+ * Drop the unsubscribe row from a rendered message, for a send that resolved
77
+ * no unsubscribe group: there is no link to put there, so the row goes.
78
+ */
79
+ declare const withoutUnsubscribeRow: (rendered: {
80
+ readonly html?: string | undefined;
81
+ readonly text?: string | undefined;
82
+ }) => {
83
+ readonly html: string | undefined;
84
+ readonly text: string | undefined;
85
+ };
86
+ //#endregion
87
+ export { EmailBrand as a, EmailBrandFooter as c, RenderedFooterMarkers as d, UNSUBSCRIBE_ROW_ATTRIBUTE as f, UNSUBSCRIBE_URL_PLACEHOLDER as g, withoutUnsubscribeRow as h, BrandLogoProps as i, EmailBrandSocialLink as l, renderedFooterMarkers as m, BrandComponents as n, EmailBrandAddress as o, brandComponents as p, BrandFooterProps as r, EmailBrandAssets as s, BRAND_COMPONENT_CLASSES as t, FOOTER_ATTRIBUTE as u };
88
+ //# sourceMappingURL=brand-DcoVkh_S.d.ts.map
@@ -1,16 +1,15 @@
1
1
  import { l as Channel } from "./ast-BrDtApwS.js";
2
- import { o as TokenOverrides } from "./theme-6ApjjiBo.js";
3
- //#region presets/catalog.d.ts
4
- type PresetCategory = "transactional" | "digest" | "marketing" | "lifecycle";
5
- interface PresetMeta {
2
+ //#region starters/catalog.d.ts
3
+ type StarterCategory = "transactional" | "digest" | "marketing" | "lifecycle";
4
+ interface StarterMeta {
6
5
  /** Stable id and source filename stem (`${id}.tsx`). */
7
6
  readonly id: string;
8
7
  /** The source's root-tag channel; the gallery filters by it. */
9
8
  readonly channel: Channel;
10
- readonly category: PresetCategory;
9
+ readonly category: StarterCategory;
11
10
  /** Human title for the gallery card. */
12
11
  readonly name: string;
13
- /** One-line description of when to reach for this preset. */
12
+ /** One-line description of when to reach for this starter. */
14
13
  readonly description: string;
15
14
  /**
16
15
  * Every input property declared by the template schema, sorted.
@@ -18,8 +17,7 @@ interface PresetMeta {
18
17
  */
19
18
  readonly variables: ReadonlyArray<string>;
20
19
  }
21
- declare const RECOMMENDED_THEME: TokenOverrides;
22
- declare const presetCatalog: readonly [{
20
+ declare const starterCatalog: readonly [{
23
21
  readonly id: "linear-login-code";
24
22
  readonly channel: "email";
25
23
  readonly category: "transactional";
@@ -342,15 +340,15 @@ declare const presetCatalog: readonly [{
342
340
  readonly description: "Marketing template: limited-time offer with a copyable promo code and a shop button.";
343
341
  readonly variables: readonly ["endsToday", "firstName", "offerDetails", "offerHeadline", "promoCode", "shopUrl"];
344
342
  }];
345
- type PresetId = (typeof presetCatalog)[number]["id"];
346
- type PresetVariable = (typeof presetCatalog)[number]["variables"][number];
347
- type EmailPreset = Extract<(typeof presetCatalog)[number], {
343
+ type StarterId = (typeof starterCatalog)[number]["id"];
344
+ type StarterVariable = (typeof starterCatalog)[number]["variables"][number];
345
+ type EmailStarter = Extract<(typeof starterCatalog)[number], {
348
346
  channel: "email";
349
347
  }>;
350
- type EmailPresetId = EmailPreset["id"];
351
- type EmailPresetVariable = EmailPreset extends (infer Preset) ? Preset extends {
348
+ type EmailStarterId = EmailStarter["id"];
349
+ type EmailStarterVariable = EmailStarter extends (infer Starter) ? Starter extends {
352
350
  readonly variables: ReadonlyArray<infer Variable>;
353
351
  } ? Variable : never : never;
354
352
  //#endregion
355
- export { PresetMeta as a, presetCatalog as c, PresetId as i, EmailPresetVariable as n, PresetVariable as o, PresetCategory as r, RECOMMENDED_THEME as s, EmailPresetId as t };
356
- //# sourceMappingURL=catalog-60eaYC7b.d.ts.map
353
+ export { StarterMeta as a, StarterId as i, EmailStarterVariable as n, StarterVariable as o, StarterCategory as r, starterCatalog as s, EmailStarterId as t };
354
+ //# sourceMappingURL=catalog-CKuOooB8.d.ts.map