{
  "_comment": "Single source of truth for Nixtla brand tokens. scripts/nixtla_theme.js and scripts/build_css.py both read this file. Hex values are 6-digit, WITHOUT a leading '#', because that is the form pptxgenjs requires. A leading '#' or an 8-digit alpha hex corrupts the .pptx.",
  "_source": "Generated by scripts/sync_from_web.py from nixtla/web src/styles/colors.css. Do not hand-edit the palette block.",
  "palette": {
    "neutral": {
      "50": "FFFFFF",
      "100": "F6F4F0",
      "200": "EAE9E6",
      "300": "B8A79F",
      "400": "8A776F",
      "500": "68544C",
      "600": "4C3B33",
      "700": "2E2B27",
      "800": "222121",
      "900": "201F1D",
      "950": "171614"
    },
    "haze": {
      "50": "FCF4FF",
      "100": "F5E8FA",
      "200": "E9D7F2",
      "300": "D5BFDF",
      "400": "A98DB6",
      "500": "9276A0",
      "600": "7B6188",
      "700": "685173",
      "800": "56455F",
      "900": "45384C",
      "950": "2D2331"
    },
    "water": {
      "50": "F0F1FF",
      "100": "E4E6FF",
      "200": "BFD1FF",
      "300": "AEB4EB",
      "400": "8A94D4",
      "500": "6773B4",
      "600": "505D98",
      "700": "434E82",
      "800": "384068",
      "900": "333856",
      "950": "1D2035"
    },
    "aubergine": {
      "50": "F5F4FB",
      "100": "E7E5F5",
      "200": "CFCCEA",
      "300": "B3AEDB",
      "400": "918BC6",
      "500": "6D65AE",
      "600": "565091",
      "700": "433E6F",
      "800": "36325D",
      "900": "2A264A",
      "950": "1A1830"
    },
    "teal": {
      "50": "E7FAF4",
      "100": "D1F2E9",
      "200": "A9E4D5",
      "300": "81CEBB",
      "400": "2BA08A",
      "500": "008F78",
      "600": "007762",
      "700": "006250",
      "800": "005042",
      "900": "004136",
      "950": "002921"
    },
    "red": {
      "50": "FFEEE9",
      "100": "FFDDD4",
      "200": "FFC0B0",
      "300": "FFA08D",
      "400": "E66F59",
      "500": "CA4730",
      "600": "AB321E",
      "700": "8E2918",
      "800": "742417",
      "900": "5E1F15",
      "950": "3D120A"
    },
    "orange": {
      "50": "FFF1E4",
      "100": "FFE3CC",
      "200": "FFC9A1",
      "300": "EFAC79",
      "400": "D48038",
      "500": "B85D00",
      "600": "9C4800",
      "700": "803B00",
      "800": "693200",
      "900": "552A00",
      "950": "371900"
    }
  },
  "_named_comment": "Names the brand actually uses in Figma, kept so a request like 'use deep haze' resolves to the right step instead of a guess.",
  "named": {
    "black": "palette.neutral.800",
    "haze": "palette.haze.300",
    "deepHaze": "palette.haze.400",
    "deepWater": "palette.water.400",
    "aubergineMain": "palette.aubergine.700"
  },
  "_semantic_comment": "Mirrors the website's CSS variables (nixtla/web src/styles/tokens.css). Build slides against THESE names, not raw ramp steps — that is the brand's 'semantic colors only' rule, and it makes a light/dark flip a one-line change. Two things here surprise people: the light canvas is CREAM (#F6F4F0), not white, and the light border is near-BLACK (#222121), not a pale hairline. Both are load-bearing brand traits — see references/design-system.md.",
  "semantic": {
    "light": {
      "background": {
        "$ref": "palette.neutral.100"
      },
      "surface": {
        "$ref": "palette.neutral.200"
      },
      "card": {
        "$ref": "palette.neutral.50"
      },
      "foreground": {
        "$ref": "palette.neutral.800"
      },
      "muted": "7B7A78",
      "border": {
        "$ref": "palette.neutral.800"
      },
      "accent": {
        "$ref": "palette.water.600"
      },
      "accentSoft": {
        "$ref": "palette.water.200"
      },
      "highlight": {
        "$ref": "palette.water.200"
      },
      "success": {
        "$ref": "palette.teal.600"
      },
      "warning": {
        "$ref": "palette.orange.600"
      },
      "destructive": {
        "$ref": "palette.red.600"
      },
      "thinking": {
        "$ref": "palette.aubergine.400"
      },
      "deephaze": {
        "$ref": "palette.haze.400"
      }
    },
    "dark": {
      "background": {
        "$ref": "palette.neutral.800"
      },
      "surface": {
        "$ref": "palette.neutral.700"
      },
      "card": {
        "$ref": "palette.neutral.950"
      },
      "foreground": {
        "$ref": "palette.neutral.100"
      },
      "muted": "9D9B99",
      "border": "737170",
      "accent": {
        "$ref": "palette.water.400"
      },
      "accentSoft": {
        "$ref": "palette.water.800"
      },
      "highlight": {
        "$ref": "palette.aubergine.400"
      },
      "success": {
        "$ref": "palette.teal.400"
      },
      "warning": {
        "$ref": "palette.orange.400"
      },
      "destructive": {
        "$ref": "palette.red.400"
      },
      "thinking": {
        "$ref": "palette.aubergine.400"
      },
      "deephaze": {
        "$ref": "palette.haze.400"
      }
    }
  },
  "_muted_comment": "The site computes --muted-foreground and the dark --border with color-mix(). A .pptx cannot hold a computed color, so the two values above are the mixes pre-flattened against their own background: light muted = foreground 58% on #F6F4F0; dark muted = foreground 58% on #222121; dark border = neutral-100 at 38% on #222121. Recompute them with scripts/validate.py if the ramps change.",
  "_series_comment": "Categorical order, taken verbatim from the website's --chart-1..8 so a deck chart and a site chart of the same data come out the same colors. Water leads; neutral is deliberately NOT in the list because it is reserved for actuals and reference lines (see chartRoles).",
  "chartSeries": [
    "palette.water.400",
    "palette.teal.400",
    "palette.haze.400",
    "palette.aubergine.500",
    "palette.teal.300",
    "palette.water.500",
    "palette.haze.600",
    "palette.aubergine.700"
  ],
  "_chart_roles_comment": "Non-categorical chart roles, also from the site's tokens.",
  "chartRoles": {
    "threshold": {
      "light": "palette.aubergine.800",
      "dark": "palette.aubergine.100"
    },
    "muted": {
      "light": "palette.neutral.300",
      "dark": "palette.neutral.300"
    },
    "anomaly": {
      "light": "palette.red.500",
      "dark": "palette.red.500"
    }
  },
  "_fonts_comment": "CRITICAL: Pangram Pangram ships each weight as its OWN font family — the family name inside PPNeueMontreal-Medium is literally 'PP Neue Montreal Medium', with subfamily 'Regular'. So `bold: true` does NOT get you Medium in PowerPoint; it gets you a synthesised faux-bold of Book. Always select a weight by naming its family. The web CSS uses the shorter 'NeueMontreal'/'Supply' aliases because it defines its own @font-face; those names are NOT what a desktop install registers.",
  "fonts": {
    "heading": {
      "family": "PP Neue Montreal Book",
      "file": "PPNeueMontreal-Book",
      "weight": 400,
      "fallback": "Arial"
    },
    "headingMedium": {
      "family": "PP Neue Montreal Medium",
      "file": "PPNeueMontreal-Medium",
      "weight": 500,
      "fallback": "Arial"
    },
    "body": {
      "family": "PP Neue Montreal Book",
      "file": "PPNeueMontreal-Book",
      "weight": 400,
      "fallback": "Arial"
    },
    "mono": {
      "family": "PP Supply Mono",
      "file": "PPSupplyMono-Regular",
      "weight": 400,
      "fallback": "Courier New",
      "case": "uppercase-only"
    },
    "monoMedium": {
      "family": "PP Supply Mono Medium",
      "file": "PPSupplyMono-Medium",
      "weight": 500,
      "fallback": "Courier New",
      "case": "uppercase-only"
    },
    "monoLight": {
      "family": "PP Supply Mono Ultralight",
      "file": "PPSupplyMono-Ultralight",
      "weight": 200,
      "fallback": "Courier New",
      "case": "uppercase-only"
    },
    "accent": {
      "family": "NixtlaDot16",
      "file": "NixtlaDot16-Regular",
      "weight": 400,
      "fallback": "DotKern16, DotGothic16, Courier New",
      "sha256": "751d7eb9293814eefb706b7a343c1f8e104421270c9faf116eb4d7afb89db5f9",
      "note": "In-house re-spaced fork of DotGothic16, not an upstream release. Build recipe, pinned input and licence reasoning: assets/brand/fonts/NixtlaDot16-PROVENANCE.md. Ship NixtlaDot16-OFL.txt alongside. Not fixed-pitch; never add tracking. Was DotKern16 until 2026-09-07 — same bytes but for the name table, so DotKern16 stays in the fallback for decks written before the rename."
    },
    "_webAliases": {
      "sans": "NeueMontreal",
      "mono": "Supply",
      "alt": "DotGothic16"
    }
  },
  "_licence_comment": "PP Neue Montreal and PP Supply Mono are commercial Pangram Pangram faces with fsType=4 (Preview & Print embedding). Embedding them read-only in a distributed deck is permitted by that bit; installable/editable embedding is not. DotGothic16 is fsType=0 (SIL OFL, free). See SKILL.md 'Fonts and licensing'.",
  "logos": {
    "_comment": "aspect is width/height, read from each file's viewBox. They differ per file — full-white carries ~9% vertical padding that full-black does not — so never assume one ratio for both, and never set both w and h from a single guess.",
    "full-black": {
      "aspect": 7.03125,
      "viewBox": "0 0 450 64"
    },
    "full-white": {
      "aspect": 5.75,
      "viewBox": "0 0 115 20"
    },
    "icon-black": {
      "aspect": 1.745,
      "viewBox": "0 0 349 200"
    },
    "icon-white": {
      "aspect": 1.745,
      "viewBox": "0 0 349 200"
    },
    "minWidthIn": {
      "full": 1.25,
      "icon": 0.33
    }
  },
  "_type_comment": "The website's desktop (>=1024px) scale, in px, kept for reference and for the HTML deck. The slide scale in nixtla_theme.js is derived from these ratios, not copied from them — a 62px web h1 is not a 62pt slide title.",
  "webTypeScale": {
    "heading1": {
      "size": 62,
      "lineHeight": 62,
      "tracking": -1.5
    },
    "heading2": {
      "size": 50,
      "lineHeight": 50,
      "tracking": -1.0
    },
    "heading3": {
      "size": 40,
      "lineHeight": 48,
      "tracking": -0.5
    },
    "heading4": {
      "size": 34,
      "lineHeight": 40.8,
      "tracking": 0
    },
    "heading5": {
      "size": 28,
      "lineHeight": 37.3,
      "tracking": 0
    },
    "display1": {
      "size": 100,
      "tracking": -2
    },
    "display2": {
      "size": 64,
      "tracking": -0.5
    },
    "paragraphLarge": {
      "size": 30,
      "lineHeight": 31.2
    },
    "paragraphRegular": {
      "size": 20,
      "lineHeight": 22
    },
    "paragraphSmall": {
      "size": 16,
      "lineHeight": 19
    },
    "paragraphMini": {
      "size": 12,
      "lineHeight": 16
    },
    "caption": {
      "size": 14,
      "lineHeight": 15.4,
      "tracking": -1.12
    },
    "monoRegular": {
      "size": 16,
      "lineHeight": 24,
      "tracking": -1
    },
    "monoMini": {
      "size": 14,
      "lineHeight": 15.6,
      "tracking": -1
    },
    "monoNano": {
      "size": 11,
      "lineHeight": 13,
      "tracking": 0
    }
  },
  "_spacing_comment": "Base unit 4px. The site separates compact 'content' gaps from larger 'flow' and 'block' rhythm; the slide geometry in nixtla_theme.js keeps the same two-tier idea in inches.",
  "spacing": {
    "content": [
      12,
      16,
      20,
      24,
      32
    ],
    "flow": [
      40,
      48,
      64,
      80
    ],
    "block": [
      128,
      160
    ],
    "radius": 0,
    "siteMaxWidth": 1792
  },
  "assetUrls": {
    "_comment": "Everything here is already bundled under assets/brand/. These URLs are the upstream originals, for re-syncing only.",
    "_base": "https://www.nixtla.io",
    "logos": {
      "full-black": "/img/logo/full-black.svg",
      "full-white": "/img/logo/full-white.svg",
      "icon-black": "/img/logo/icon-black.svg",
      "icon-white": "/img/logo/icon-white.svg"
    },
    "fonts": {
      "PPNeueMontreal-Book": "/fonts/PPNeueMontreal-Book.woff2",
      "PPNeueMontreal-Medium": "/fonts/PPNeueMontreal-Medium.woff2",
      "PPSupplyMono-Ultralight": "/fonts/PPSupplyMono-Ultralight.woff2",
      "PPSupplyMono-Regular": "/fonts/PPSupplyMono-Regular.woff2",
      "PPSupplyMono-Medium": "/fonts/PPSupplyMono-Medium.woff2",
      "DotGothic16-Regular": "/fonts/DotGothic16-Regular.woff2"
    },
    "backgrounds": "/img/backgrounds/{name}.webp",
    "backgroundNames": [
      "bg-02",
      "bg-03",
      "bg-04",
      "bg-05",
      "bg-08",
      "bg-09",
      "bg-10",
      "bg-11",
      "bg-12",
      "bg-13",
      "bg-14",
      "bg-15",
      "bg-16",
      "bg-17",
      "bg-chat",
      "bg-chat-dark",
      "bg-terminal",
      "bg-terminal-dark",
      "bg-carousel-1",
      "bg-carousel-2",
      "bg-carousel-3",
      "bg-use-case-1",
      "bg-use-case-2",
      "bg-use-case-3"
    ],
    "brandKitPage": "https://www.nixtla.io/brand-kit"
  },
  "_code_comment": "Syntax highlighting, ported verbatim from nixtla/web src/components/ai-elements/code-block-theme.ts. Do not re-pick these hues. The site's mapping was solved for two constraints that are invisible until you break them: every colour clears WCAG AA on its ground, and function/type sit ~60deg apart in hue because the obvious blue-for-function choice was indistinguishable from the type purple under red-green colour vision deficiency. scripts/highlight.mjs builds Shiki themes from this block.",
  "codeTheme": {
    "_variants": "dark = fixed chrome, the blog's code card. canvas = follows the slide, transparent ground, for code that must sit on the cream surface.",
    "dark": {
      "bg": "171614",
      "fg": "F6F4F0",
      "comment": "rgba(244, 241, 234, 0.62)",
      "keyword": "918BC6",
      "string": "A98DB6",
      "number": "81CEBB",
      "function": "2BA08A",
      "type": "8A94D4",
      "invalid": "E66F59"
    },
    "canvas": {
      "_comment": "Sourced from the --code-* tokens, independently solved for AA against both canvases. The dark variant's hex reused on cream drops to ~1.2:1 for number — the bug this variant exists to avoid.",
      "light": {
        "bg": "transparent",
        "fg": "222121",
        "comment": "7B7A78",
        "keyword": "6D65AE",
        "string": "7B6188",
        "number": "005042",
        "function": "2BA08A",
        "type": "505D98",
        "invalid": "AB321E"
      },
      "dark": {
        "bg": "transparent",
        "fg": "F6F4F0",
        "comment": "9D9B99",
        "keyword": "918BC6",
        "string": "A98DB6",
        "number": "81CEBB",
        "function": "2BA08A",
        "type": "8A94D4",
        "invalid": "E66F59"
      }
    },
    "chrome": {
      "_comment": "The blog's code-card chrome (article.css --blog-code-*).",
      "headerBg": "201F1D",
      "border": "3D3B38",
      "muted": "A29F99"
    },
    "languages": [
      "python",
      "javascript",
      "html",
      "css",
      "json"
    ]
  },
  "_brand_comment": "The typographic spec as it ships on nixtla.io today. This is what you build against. The faces are commercially licensed and must not be redistributed — see DESIGN.md section 10 before bundling font files anywhere.",
  "brand": {
    "licence": "commercial (Pangram Pangram), fsType 4 Preview & Print",
    "redistributable": false,
    "annualCostUSD": 1550,
    "costBreakdown": [
      "PP Supply Mono, 3 weights (Regular, Medium, Light) @ $310 = $930",
      "PP Neue Montreal, 2 weights (Book, Medium) @ $310 = $620",
      "DotGothic16 — free (SIL OFL)"
    ],
    "licenceScope": [
      "Print — 3 workstations",
      "Social — up to 25k combined followers (currently ~16k: 12k LinkedIn, 4k X)",
      "Web — up to 50k pageviews/month"
    ],
    "roles": {
      "body": {
        "family": "PP Neue Montreal",
        "weight": "Light/Book",
        "usage": "Regular paragraphs"
      },
      "heading": {
        "family": "PP Neue Montreal",
        "weight": "Medium",
        "usage": "Regular headings"
      },
      "mono": {
        "family": "PP Supply Mono",
        "weight": "Ultralight",
        "usage": "Code blocks"
      },
      "overline": {
        "family": "PP Supply Mono",
        "weight": "Medium, caps",
        "usage": "Overlines, caps buttons, labels"
      },
      "displayAlt": {
        "family": "DotGothic16",
        "weight": "Regular",
        "usage": "Display effects"
      }
    },
    "knownIssues": [
      "PP Supply Mono Ultralight is too light for code blocks — Light is the fix, and Light is in the purchase.",
      "DotGothic16 is duo-spaced (every Latin glyph centred in a fixed 500-unit cell), not un-kerned — it has no kern pairs and never did. RESOLVED: NixtlaDot16 (shipped as DotKern16 until 2026-09-07), an in-house re-spaced fork, makes the Latin proportional. It sets narrower than the original; do not add tracking to compensate. Renaming was a trademark matter, not an OFL Reserved Font Name requirement."
    ]
  }
}
