yumiamd 0.1.8 → 0.1.9

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/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-WUJKHJJU.js";
4
+ } from "./chunk-F3CX7UG6.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -213,6 +213,28 @@ var DefaultYumiaParser = class {
213
213
  result.theme = metadata["theme"];
214
214
  if (typeof metadata["aspectRatio"] === "string")
215
215
  result.aspectRatio = metadata["aspectRatio"];
216
+ const colors = {};
217
+ if (typeof metadata["background"] === "string")
218
+ colors.background = metadata["background"];
219
+ if (typeof metadata["bg"] === "string")
220
+ colors.background = metadata["bg"];
221
+ if (typeof metadata["primary"] === "string")
222
+ colors.primary = metadata["primary"];
223
+ if (typeof metadata["secondary"] === "string")
224
+ colors.secondary = metadata["secondary"];
225
+ if (typeof metadata["surface"] === "string")
226
+ colors.surface = metadata["surface"];
227
+ if (typeof metadata["text"] === "string")
228
+ colors.text = metadata["text"];
229
+ if (typeof metadata["muted"] === "string")
230
+ colors.muted = metadata["muted"];
231
+ if (typeof metadata["accent"] === "string")
232
+ colors.accent = metadata["accent"];
233
+ if (typeof metadata["border"] === "string")
234
+ colors.border = metadata["border"];
235
+ if (Object.keys(colors).length > 0) {
236
+ result.colors = colors;
237
+ }
216
238
  return result;
217
239
  }
218
240
  splitSlides(content, startLineOffset) {
@@ -797,9 +819,10 @@ var DefaultLayoutEngine = class {
797
819
  return lines * 34 + 32;
798
820
  }
799
821
  estimateTableHeight(table) {
800
- const headerHeight = table.headers && table.headers.length > 0 ? 56 : 0;
801
- const rowsHeight = (table.rows ? table.rows.length : 0) * 44;
802
- return Math.max(60, headerHeight + rowsHeight + 20);
822
+ const headerHeight = table.headers && table.headers.length > 0 ? 50 : 0;
823
+ const rowsCount = table.rows ? table.rows.length : 0;
824
+ const rowsHeight = rowsCount * 46;
825
+ return Math.max(80, headerHeight + rowsHeight + 20);
803
826
  }
804
827
  estimateImageHeight(image) {
805
828
  if (typeof image.height === "number") {
@@ -808,7 +831,7 @@ var DefaultLayoutEngine = class {
808
831
  return 320;
809
832
  }
810
833
  estimateMetricHeight(metric) {
811
- return metric.change ? 140 : 120;
834
+ return metric.change ? 160 : 130;
812
835
  }
813
836
  };
814
837
 
@@ -1515,6 +1538,15 @@ var CSS_NAMED_COLORS = {
1515
1538
  slate: "0f172a",
1516
1539
  transparent: "ffffff"
1517
1540
  };
1541
+ function cleanFontFace(fontString) {
1542
+ if (!fontString)
1543
+ return "Segoe UI";
1544
+ const first = fontString.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "") || "";
1545
+ if (!first || first === "system-ui" || first === "-apple-system" || first === "sans-serif" || first === "monospace" || first === "serif") {
1546
+ return "Segoe UI";
1547
+ }
1548
+ return first;
1549
+ }
1518
1550
  function parseInlineMarkdown(rawText, baseOptions) {
1519
1551
  const chunks = [];
1520
1552
  const regex = /(\*\*.*?\*\*|\*.*?\*|`.*?`)/g;
@@ -1556,7 +1588,8 @@ var PptxRenderer = class {
1556
1588
  async render(presentation, context = {}) {
1557
1589
  const PptxConstructor = pptxgen.default || pptxgen;
1558
1590
  const pptx = new PptxConstructor();
1559
- const resolvedTheme = presentation.metadata.theme ? resolveTheme(presentation.metadata.theme) : defaultTheme;
1591
+ const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
1592
+ const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
1560
1593
  const theme = context.theme || resolvedTheme;
1561
1594
  const title = presentation.metadata.title || "Yumia Presentation";
1562
1595
  const author = presentation.metadata.author || "YumiaMD";
@@ -1636,7 +1669,7 @@ var PptxRenderer = class {
1636
1669
  const chunks = parseInlineMarkdown(heading.text, {
1637
1670
  fontSize,
1638
1671
  bold: true,
1639
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1672
+ fontFace: cleanFontFace(theme.typography.headingFont),
1640
1673
  color
1641
1674
  });
1642
1675
  pptxSlide.addText(chunks, {
@@ -1654,7 +1687,7 @@ var PptxRenderer = class {
1654
1687
  const color = this.cleanHexColor(theme.colors.text);
1655
1688
  const chunks = parseInlineMarkdown(paragraph.text, {
1656
1689
  fontSize,
1657
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1690
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1658
1691
  color
1659
1692
  });
1660
1693
  pptxSlide.addText(chunks, {
@@ -1671,11 +1704,11 @@ var PptxRenderer = class {
1671
1704
  const fontSize = theme.typography.sizes?.body || 18;
1672
1705
  const color = this.cleanHexColor(theme.colors.text);
1673
1706
  const allChunks = [];
1674
- list.items.forEach((item) => {
1707
+ list.items.forEach((item, itemIdx) => {
1675
1708
  const itemChunks = parseInlineMarkdown(item.text, {
1676
1709
  fontSize,
1677
1710
  color,
1678
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1711
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1679
1712
  indentLevel: item.depth || 0,
1680
1713
  paraSpaceAfter: 8
1681
1714
  });
@@ -1684,6 +1717,12 @@ var PptxRenderer = class {
1684
1717
  ...itemChunks[0].options,
1685
1718
  bullet: list.ordered ? { type: "number" } : true
1686
1719
  };
1720
+ if (itemIdx < list.items.length - 1) {
1721
+ itemChunks[itemChunks.length - 1].options = {
1722
+ ...itemChunks[itemChunks.length - 1].options,
1723
+ breakLine: true
1724
+ };
1725
+ }
1687
1726
  }
1688
1727
  allChunks.push(...itemChunks);
1689
1728
  });
@@ -1700,6 +1739,10 @@ var PptxRenderer = class {
1700
1739
  const tableRows = [];
1701
1740
  const headerBg = this.cleanHexColor(theme.colors.primary);
1702
1741
  const borderColor = this.cleanHexColor(theme.colors.border || "#cbd5e1");
1742
+ const isDark = this.isDarkColor(theme.colors.background);
1743
+ const rowBg1 = this.cleanHexColor(theme.components?.table?.rowAlternateBackground || (isDark ? theme.colors.surface : "ffffff"));
1744
+ const rowBg2 = this.cleanHexColor(isDark ? "#1a1a2e" : "f8fafc");
1745
+ const cellTextColor = this.cleanHexColor(theme.colors.text);
1703
1746
  if (table.headers && table.headers.length > 0) {
1704
1747
  tableRows.push(table.headers.map((h) => ({
1705
1748
  text: h.replace(/\*\*/g, ""),
@@ -1708,19 +1751,21 @@ var PptxRenderer = class {
1708
1751
  color: "ffffff",
1709
1752
  fill: { color: headerBg },
1710
1753
  fontSize: 14,
1754
+ fontFace: cleanFontFace(theme.typography.headingFont),
1711
1755
  align: "center"
1712
1756
  }
1713
1757
  })));
1714
1758
  }
1715
1759
  if (table.rows) {
1716
1760
  table.rows.forEach((row, rowIndex) => {
1717
- const rowBg = rowIndex % 2 === 1 ? "f8fafc" : "ffffff";
1761
+ const rowBg = rowIndex % 2 === 1 ? rowBg2 : rowBg1;
1718
1762
  tableRows.push(row.map((cell) => ({
1719
1763
  text: cell.replace(/\*\*/g, ""),
1720
1764
  options: {
1721
- color: this.cleanHexColor(theme.colors.text),
1765
+ color: cellTextColor,
1722
1766
  fill: { color: rowBg },
1723
- fontSize: 13
1767
+ fontSize: 13,
1768
+ fontFace: cleanFontFace(theme.typography.bodyFont)
1724
1769
  }
1725
1770
  })));
1726
1771
  });
@@ -1731,7 +1776,7 @@ var PptxRenderer = class {
1731
1776
  y: rect.y,
1732
1777
  w: rect.w,
1733
1778
  border: { type: "solid", pt: 1, color: borderColor },
1734
- margin: [4, 8, 4, 8]
1779
+ margin: [6, 10, 6, 10]
1735
1780
  });
1736
1781
  }
1737
1782
  }
@@ -1784,43 +1829,44 @@ var PptxRenderer = class {
1784
1829
  line: { color: borderColor, width: 1.5 },
1785
1830
  rectRadius: 0.08
1786
1831
  });
1832
+ const hasChange = Boolean(metric.change);
1787
1833
  pptxSlide.addText(metric.label.toUpperCase(), {
1788
- x: rect.x + 0.15,
1789
- y: rect.y + 0.15,
1790
- w: rect.w - 0.3,
1791
- h: 0.25,
1792
- fontSize: 11,
1834
+ x: rect.x + 0.1,
1835
+ y: rect.y + 0.1,
1836
+ w: rect.w - 0.2,
1837
+ h: 0.22,
1838
+ fontSize: 10,
1793
1839
  bold: true,
1794
1840
  color: this.cleanHexColor(theme.colors.muted || "#64748b"),
1795
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1841
+ fontFace: cleanFontFace(theme.typography.headingFont),
1796
1842
  align: "center",
1797
1843
  valign: "middle"
1798
1844
  });
1799
1845
  const displayValue = metric.unit ? `${metric.value} ${metric.unit}` : metric.value;
1800
1846
  pptxSlide.addText(displayValue, {
1801
1847
  x: rect.x + 0.1,
1802
- y: rect.y + 0.4,
1848
+ y: rect.y + 0.34,
1803
1849
  w: rect.w - 0.2,
1804
- h: 0.55,
1805
- fontSize: 30,
1850
+ h: 0.44,
1851
+ fontSize: 26,
1806
1852
  bold: true,
1807
1853
  color: this.cleanHexColor(accentColor),
1808
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1854
+ fontFace: cleanFontFace(theme.typography.headingFont),
1809
1855
  align: "center",
1810
1856
  valign: "middle"
1811
1857
  });
1812
- if (metric.change) {
1858
+ if (hasChange && metric.change) {
1813
1859
  const isPositive = metric.change.startsWith("+");
1814
1860
  const changeColor = isPositive ? this.cleanHexColor(theme.colors.success || "#10b981") : this.cleanHexColor(theme.colors.danger || "#ef4444");
1815
1861
  pptxSlide.addText(metric.change, {
1816
1862
  x: rect.x + 0.1,
1817
- y: rect.y + rect.h - 0.32,
1863
+ y: rect.y + 0.8,
1818
1864
  w: rect.w - 0.2,
1819
1865
  h: 0.22,
1820
- fontSize: 12,
1866
+ fontSize: 11,
1821
1867
  bold: true,
1822
1868
  color: changeColor,
1823
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1869
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1824
1870
  align: "center",
1825
1871
  valign: "middle"
1826
1872
  });
@@ -1856,7 +1902,7 @@ var PptxRenderer = class {
1856
1902
  fontSize: 20,
1857
1903
  bold: true,
1858
1904
  color: titleColor,
1859
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial"
1905
+ fontFace: cleanFontFace(theme.typography.headingFont)
1860
1906
  });
1861
1907
  }
1862
1908
  if (node.children) {
@@ -1917,7 +1963,7 @@ var PptxRenderer = class {
1917
1963
  fontSize: 18,
1918
1964
  italic: true,
1919
1965
  color: this.cleanHexColor(theme.colors.muted || theme.colors.text),
1920
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1966
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1921
1967
  valign: "middle"
1922
1968
  });
1923
1969
  }
@@ -1944,6 +1990,14 @@ var PptxRenderer = class {
1944
1990
  return 18;
1945
1991
  }
1946
1992
  }
1993
+ isDarkColor(rawHex) {
1994
+ const hex = this.cleanHexColor(rawHex);
1995
+ const r = parseInt(hex.substring(0, 2), 16) || 0;
1996
+ const g = parseInt(hex.substring(2, 4), 16) || 0;
1997
+ const b = parseInt(hex.substring(4, 6), 16) || 0;
1998
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
1999
+ return luminance < 0.5;
2000
+ }
1947
2001
  cleanHexColor(raw) {
1948
2002
  if (!raw)
1949
2003
  return "000000";
@@ -1966,7 +2020,7 @@ var PptxRenderer = class {
1966
2020
  // src/cli.ts
1967
2021
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
1968
2022
  import { basename, dirname, extname, join, resolve } from "path";
1969
- var VERSION = "0.1.8";
2023
+ var VERSION = "0.1.9";
1970
2024
  function printHelp() {
1971
2025
  return `
1972
2026
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -1982,7 +2036,15 @@ Commands:
1982
2036
  schema Output machine-readable JSON schema for AI agents
1983
2037
  build <file> Compile a presentation to editable PowerPoint (.pptx)
1984
2038
 
1985
- Options:
2039
+ Theming & Color Options:
2040
+ --theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
2041
+ --bg, --background Hex background color (e.g. "#0B0B12" or "#FFFFFF")
2042
+ --primary, -p Hex primary accent color (e.g. "#FF2E88" or "#2563EB")
2043
+ --secondary Hex secondary color (e.g. "#00F0FF")
2044
+ --text Hex text color (e.g. "#FFFFFF" or "#0F172A")
2045
+ --accent Hex accent bar & highlight color
2046
+
2047
+ Compiler & Output Options:
1986
2048
  --out, -o <file> Specify output file path (default: dist/<name>.pptx)
1987
2049
  --format, -f <fmt> Target output format: pptx (default)
1988
2050
  --strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
@@ -1992,6 +2054,21 @@ Options:
1992
2054
  -v, --version Show CLI version
1993
2055
  `.trim();
1994
2056
  }
2057
+ function getFlagValue(args, flagNames) {
2058
+ for (let i = 0; i < args.length; i++) {
2059
+ const arg = args[i];
2060
+ if (!arg) continue;
2061
+ for (const flag of flagNames) {
2062
+ if (arg === flag && args[i + 1] && !args[i + 1].startsWith("-")) {
2063
+ return args[i + 1];
2064
+ }
2065
+ if (arg.startsWith(`${flag}=`)) {
2066
+ return arg.slice(flag.length + 1);
2067
+ }
2068
+ }
2069
+ }
2070
+ return void 0;
2071
+ }
1995
2072
  async function runCli(argv) {
1996
2073
  const args = argv.slice(2);
1997
2074
  if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
@@ -2005,6 +2082,12 @@ async function runCli(argv) {
2005
2082
  const nonFlagArgs = args.filter((a) => !a.startsWith("-"));
2006
2083
  const command = nonFlagArgs[0];
2007
2084
  const target = nonFlagArgs[1];
2085
+ const cliTheme = getFlagValue(args, ["--theme", "-t"]);
2086
+ const cliBg = getFlagValue(args, ["--bg", "--background"]);
2087
+ const cliPrimary = getFlagValue(args, ["--primary", "-p"]);
2088
+ const cliSecondary = getFlagValue(args, ["--secondary"]);
2089
+ const cliText = getFlagValue(args, ["--text"]);
2090
+ const cliAccent = getFlagValue(args, ["--accent"]);
2008
2091
  if (command === "schema") {
2009
2092
  const compiler = new YumiaCompiler();
2010
2093
  return {
@@ -2019,10 +2102,19 @@ async function runCli(argv) {
2019
2102
  mkdirSync(projectDir, { recursive: true });
2020
2103
  mkdirSync(join(projectDir, "assets"), { recursive: true });
2021
2104
  mkdirSync(join(projectDir, "themes"), { recursive: true });
2105
+ const chosenTheme = cliTheme || "default";
2106
+ const colorLines = [];
2107
+ if (cliBg) colorLines.push(`background: "${cliBg}"`);
2108
+ if (cliPrimary) colorLines.push(`primary: "${cliPrimary}"`);
2109
+ if (cliSecondary) colorLines.push(`secondary: "${cliSecondary}"`);
2110
+ if (cliText) colorLines.push(`text: "${cliText}"`);
2111
+ if (cliAccent) colorLines.push(`accent: "${cliAccent}"`);
2112
+ const colorsYaml = colorLines.length > 0 ? `
2113
+ ${colorLines.join("\n")}` : "";
2022
2114
  const sampleContent = `---
2023
2115
  title: ${projectName}
2024
- theme: default
2025
- aspectRatio: "16:9"
2116
+ theme: ${chosenTheme}
2117
+ aspectRatio: "16:9"${colorsYaml}
2026
2118
  ---
2027
2119
 
2028
2120
  # ${projectName}
@@ -2039,7 +2131,7 @@ Opening slide introducing the presentation deck.
2039
2131
  :::columns ratios="50:50"
2040
2132
 
2041
2133
  :::column
2042
- :::card Core Pipeline
2134
+ :::card Core Pipeline variant="primary"
2043
2135
  - Semantic AST model
2044
2136
  - Markdown + Directive parser
2045
2137
  - 100% Deterministic layout
@@ -2047,7 +2139,7 @@ Opening slide introducing the presentation deck.
2047
2139
  :::
2048
2140
 
2049
2141
  :::column
2050
- :::card Native Output
2142
+ :::card Native Output variant="warning"
2051
2143
  - Fully editable PowerPoint objects
2052
2144
  - Vector PDF documents
2053
2145
  - Interactive HTML decks
@@ -2065,7 +2157,8 @@ Opening slide introducing the presentation deck.
2065
2157
  success: true,
2066
2158
  project: projectName,
2067
2159
  path: projectDir,
2068
- entry: join(projectDir, "presentation.yumia.md")
2160
+ entry: join(projectDir, "presentation.yumia.md"),
2161
+ theme: chosenTheme
2069
2162
  },
2070
2163
  null,
2071
2164
  2
@@ -2075,6 +2168,7 @@ Opening slide introducing the presentation deck.
2075
2168
  return {
2076
2169
  exitCode: 0,
2077
2170
  output: `\u2713 Created presentation project '${projectName}' at ./${projectName}
2171
+ Theme: ${chosenTheme}${colorsYaml ? ` (with custom colors)` : ""}
2078
2172
  Edit ./${projectName}/presentation.yumia.md to get started!
2079
2173
  Then compile with: yumia build ./${projectName}/presentation.yumia.md`
2080
2174
  };
@@ -2241,7 +2335,18 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
2241
2335
  mkdirSync(dirname(outputPath), { recursive: true });
2242
2336
  const compiler = new YumiaCompiler();
2243
2337
  const renderer = new PptxRenderer();
2244
- const result = await compiler.compile(source, renderer);
2338
+ const cliColorOverrides = {};
2339
+ if (cliBg) cliColorOverrides.background = cliBg;
2340
+ if (cliPrimary) cliColorOverrides.primary = cliPrimary;
2341
+ if (cliSecondary) cliColorOverrides.secondary = cliSecondary;
2342
+ if (cliText) cliColorOverrides.text = cliText;
2343
+ if (cliAccent) cliColorOverrides.accent = cliAccent;
2344
+ const renderTheme = cliTheme || Object.keys(cliColorOverrides).length > 0 ? resolveTheme(cliTheme || "default", {
2345
+ ...Object.keys(cliColorOverrides).length > 0 ? { colors: cliColorOverrides } : {}
2346
+ }) : void 0;
2347
+ const result = await compiler.compile(source, renderer, {
2348
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
2349
+ });
2245
2350
  const buffer = result.data instanceof Uint8Array ? Buffer.from(result.data) : Buffer.from(new Uint8Array(result.data));
2246
2351
  writeFileSync(outputPath, buffer);
2247
2352
  if (isJson) {
@@ -2313,6 +2418,7 @@ export {
2313
2418
  createTheme,
2314
2419
  YumiaLinter,
2315
2420
  YumiaCompiler,
2421
+ cleanFontFace,
2316
2422
  parseInlineMarkdown,
2317
2423
  PptxRenderer,
2318
2424
  VERSION,
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export * from '@yumiamd/layout';
6
6
  export * from '@yumiamd/renderer';
7
7
  export * from '@yumiamd/renderer-pptx';
8
8
 
9
- declare const VERSION = "0.1.8";
9
+ declare const VERSION = "0.1.9";
10
10
  declare function printHelp(): string;
11
11
  declare function runCli(argv: string[]): Promise<{
12
12
  exitCode: number;
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  YumiaCompiler,
10
10
  YumiaLinter,
11
11
  academicTheme,
12
+ cleanFontFace,
12
13
  corporateTheme,
13
14
  createCard,
14
15
  createCode,
@@ -35,7 +36,7 @@ import {
35
36
  resolveTheme,
36
37
  runCli,
37
38
  terminalTheme
38
- } from "./chunk-WUJKHJJU.js";
39
+ } from "./chunk-F3CX7UG6.js";
39
40
  export {
40
41
  DEFAULT_VIEWPORT,
41
42
  DefaultLayoutEngine,
@@ -47,6 +48,7 @@ export {
47
48
  YumiaCompiler,
48
49
  YumiaLinter,
49
50
  academicTheme,
51
+ cleanFontFace,
50
52
  corporateTheme,
51
53
  createCard,
52
54
  createCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yumiamd",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Command line interface and compiler for YumiaMD presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,13 +20,13 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.5.1",
23
- "@yumiamd/ast": "0.1.8",
24
- "@yumiamd/parser": "0.1.8",
25
- "@yumiamd/core": "0.1.8",
26
- "@yumiamd/renderer": "0.1.8",
27
- "@yumiamd/layout": "0.1.8",
28
- "@yumiamd/renderer-pptx": "0.1.8",
29
- "@yumiamd/theme": "0.1.8"
23
+ "@yumiamd/core": "0.1.9",
24
+ "@yumiamd/ast": "0.1.9",
25
+ "@yumiamd/parser": "0.1.9",
26
+ "@yumiamd/renderer": "0.1.9",
27
+ "@yumiamd/layout": "0.1.9",
28
+ "@yumiamd/renderer-pptx": "0.1.9",
29
+ "@yumiamd/theme": "0.1.9"
30
30
  },
31
31
  "keywords": [
32
32
  "yumia",