@takuhon/core 0.14.0 → 0.15.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/dist/index.d.ts CHANGED
@@ -3697,20 +3697,69 @@ declare class ConflictError extends StorageError {
3697
3697
  * snapshot always renders the same markup, so both rendering surfaces (the
3698
3698
  * static HTML export and the React profile) and their tests stay in lockstep.
3699
3699
  *
3700
+ * The card paints an **opaque background rectangle** first, so it stays legible
3701
+ * wherever it is embedded — including a GitHub README served through the Camo
3702
+ * image proxy, where no surrounding page styles apply and a transparent card
3703
+ * would render invisibly on a dark theme. The colour set is supplied by a
3704
+ * {@link Palette}: {@link renderActivitySvg} defaults to {@link LIGHT_PALETTE},
3705
+ * and a transport (a Worker route, the CLI build) can pass {@link DARK_PALETTE}
3706
+ * to serve a dark variant. Both palettes are plain data, so the render stays
3707
+ * deterministic.
3708
+ *
3700
3709
  * Every snapshot-derived string (language names, dates) is XML-escaped before
3701
3710
  * it reaches the markup: language names come from an external API and are
3702
3711
  * treated as untrusted. An empty snapshot (no metric fields) renders to `''`
3703
3712
  * so callers can omit the section with a truthiness check.
3704
3713
  */
3705
3714
 
3715
+ /**
3716
+ * Colour set for an activity card. Kept as plain data (no CSS variables, no
3717
+ * `currentColor`) so the rendered SVG is fully self-contained and legible when
3718
+ * served as an image through Camo, where no page styles apply.
3719
+ */
3720
+ interface Palette {
3721
+ /** Fill of the opaque background rectangle behind the whole card. */
3722
+ background: string;
3723
+ /** Primary text (language legend, coding-time figure). */
3724
+ text: string;
3725
+ /** Secondary text (captions, rank score, "Last synced"). */
3726
+ muted: string;
3727
+ /** Accent fill (the rank badge disc). */
3728
+ accent: string;
3729
+ /**
3730
+ * Order-based palette for the language bar and its legend chips; entries are
3731
+ * assigned to the top languages in order, and anything past the end (or the
3732
+ * folded "Other" segment) uses {@link Palette.otherColor}.
3733
+ */
3734
+ languageColors: readonly string[];
3735
+ /** Colour of the folded "Other" language segment. */
3736
+ otherColor: string;
3737
+ /** Five-step contribution heat ramp, lowest (zero) first. */
3738
+ heatColors: readonly string[];
3739
+ }
3740
+ /** Default palette: dark text on an opaque white card. */
3741
+ declare const LIGHT_PALETTE: Palette;
3742
+ /**
3743
+ * Dark variant: light text on an opaque dark card (tuned to read on a GitHub
3744
+ * dark-theme README). The heat ramp stays in the card's blue family rather than
3745
+ * borrowing GitHub's green, so light and dark cards look like the same design.
3746
+ */
3747
+ declare const DARK_PALETTE: Palette;
3748
+ /** Options accepted by {@link renderActivitySvg}. */
3749
+ interface RenderActivitySvgOptions {
3750
+ /** Colour set to render with. Defaults to {@link LIGHT_PALETTE}. */
3751
+ palette?: Palette;
3752
+ }
3706
3753
  /**
3707
3754
  * Render the activity snapshot as a self-contained `<svg>` string, or `''`
3708
3755
  * when the snapshot carries no metric data (so callers can omit the section).
3709
3756
  * Sections render independently — whatever the snapshot holds is shown, the
3710
3757
  * rest is left out — and `lastSyncedAt`'s date is always stamped in the footer
3711
- * so staleness stays visible.
3758
+ * so staleness stays visible. Colours come from `options.palette`
3759
+ * ({@link LIGHT_PALETTE} by default); the card always paints an opaque
3760
+ * background so it stays legible when embedded as an image.
3712
3761
  */
3713
- declare function renderActivitySvg(snapshot: ActivitySnapshot): string;
3762
+ declare function renderActivitySvg(snapshot: ActivitySnapshot, options?: RenderActivitySvgOptions): string;
3714
3763
 
3715
3764
  /**
3716
3765
  * Résumé / CV projection of a profile.
@@ -4010,4 +4059,4 @@ declare function stripImageMetadata(bytes: Uint8Array, mime: AcceptedImageMime):
4010
4059
  */
4011
4060
  declare const SCHEMA_VERSION = "0.5.0";
4012
4061
 
4013
- export { ACCEPTED_IMAGE_MIME_TYPES, type AcceptedImageMime, type ActivitySettings, type ActivitySnapshot, type ActivityStorage, type Address, type AssetOptions, type AssetRecord, type Avatar, type Career, type Certification, type CodingTime, ConflictError, type Contact, type ContentLicense, type ContentLicenseAttribution, type ContributionCalendar, type ContributionDay, type Course, type CvDocument, type CvHeader, type CvSection, type CvSectionKind, type Education, type ExportOptions, type ExportedTakuhon, type Honor, IMAGE_EXTENSIONS, type ImageInfo, ImportError, type Iso3166Alpha2, type IsoDateTime, type Language, type LanguageBreakdown, type LanguageProficiency, type Link, type LinkBuiltin, type LinkCustom, type LinkType, type LocaleTag, type LocalizedAddress, type LocalizedAvatar, type LocalizedBody, type LocalizedCareer, type LocalizedCertification, type LocalizedCourse, type LocalizedEducation, type LocalizedHonor, type LocalizedLanguage, type LocalizedLink, type LocalizedLinkBuiltin, type LocalizedLinkCustom, type LocalizedMembership, type LocalizedPatent, type LocalizedProfile, type LocalizedProject, type LocalizedPublication, type LocalizedRecommendation, type LocalizedRecommendationAuthor, type LocalizedTakuhon, type LocalizedTestScore, type LocalizedTitle, type LocalizedVolunteering, MAX_IMAGE_BYTES, MAX_IMAGE_DIMENSION, MAX_IMAGE_FRAMES, MCP_PROFILE_SECTIONS, MCP_RESOURCES, MCP_TOOLS, type McpInputSchema, type McpProfileSection, McpRequestError, type McpResourceDefinition, type McpResourceResult, type McpToolDefinition, type McpToolResult, type Membership, type Meta, type MetaPrivacy, type Migration, MigrationError, type NormalizedTakuhon, NotFoundError, type Patent, type PatentStatus, type Profile, type Project, type Publication, RANK_FULL_CODING_HOURS, RANK_FULL_CONTRIBUTIONS, RANK_TIER_THRESHOLDS, type RankInput, type RankTier, type RankTierLabel, type Recommendation, type RecommendationAuthor, SCHEMA_VERSION, SUPPORTED_SCHEMA_VERSIONS, type Schema, type Settings, type Skill, type Slug, StorageError, type Takuhon, type TakuhonAssetStorage, type TakuhonStorage, type TestScore, type ValidationError, type ValidationResult, type Volunteering, type YearMonth, applyPublicPrivacyFilter, computeLanguagePercentages, deriveCv, deriveRankTier, detectImageMime, executeMcpTool, exportTakuhon, formatCodingTime, generateJsonLd, generatePersonJsonLd, generateProfilePageJsonLd, importTakuhon, isActivitySnapshot, migrateTakuhon, migrations, normalize, readImageInfo, readMcpResource, renderActivitySvg, resolveLocale, schema, stripImageMetadata, validate };
4062
+ export { ACCEPTED_IMAGE_MIME_TYPES, type AcceptedImageMime, type ActivitySettings, type ActivitySnapshot, type ActivityStorage, type Address, type AssetOptions, type AssetRecord, type Avatar, type Career, type Certification, type CodingTime, ConflictError, type Contact, type ContentLicense, type ContentLicenseAttribution, type ContributionCalendar, type ContributionDay, type Course, type CvDocument, type CvHeader, type CvSection, type CvSectionKind, DARK_PALETTE, type Education, type ExportOptions, type ExportedTakuhon, type Honor, IMAGE_EXTENSIONS, type ImageInfo, ImportError, type Iso3166Alpha2, type IsoDateTime, LIGHT_PALETTE, type Language, type LanguageBreakdown, type LanguageProficiency, type Link, type LinkBuiltin, type LinkCustom, type LinkType, type LocaleTag, type LocalizedAddress, type LocalizedAvatar, type LocalizedBody, type LocalizedCareer, type LocalizedCertification, type LocalizedCourse, type LocalizedEducation, type LocalizedHonor, type LocalizedLanguage, type LocalizedLink, type LocalizedLinkBuiltin, type LocalizedLinkCustom, type LocalizedMembership, type LocalizedPatent, type LocalizedProfile, type LocalizedProject, type LocalizedPublication, type LocalizedRecommendation, type LocalizedRecommendationAuthor, type LocalizedTakuhon, type LocalizedTestScore, type LocalizedTitle, type LocalizedVolunteering, MAX_IMAGE_BYTES, MAX_IMAGE_DIMENSION, MAX_IMAGE_FRAMES, MCP_PROFILE_SECTIONS, MCP_RESOURCES, MCP_TOOLS, type McpInputSchema, type McpProfileSection, McpRequestError, type McpResourceDefinition, type McpResourceResult, type McpToolDefinition, type McpToolResult, type Membership, type Meta, type MetaPrivacy, type Migration, MigrationError, type NormalizedTakuhon, NotFoundError, type Palette, type Patent, type PatentStatus, type Profile, type Project, type Publication, RANK_FULL_CODING_HOURS, RANK_FULL_CONTRIBUTIONS, RANK_TIER_THRESHOLDS, type RankInput, type RankTier, type RankTierLabel, type Recommendation, type RecommendationAuthor, type RenderActivitySvgOptions, SCHEMA_VERSION, SUPPORTED_SCHEMA_VERSIONS, type Schema, type Settings, type Skill, type Slug, StorageError, type Takuhon, type TakuhonAssetStorage, type TakuhonStorage, type TestScore, type ValidationError, type ValidationResult, type Volunteering, type YearMonth, applyPublicPrivacyFilter, computeLanguagePercentages, deriveCv, deriveRankTier, detectImageMime, executeMcpTool, exportTakuhon, formatCodingTime, generateJsonLd, generatePersonJsonLd, generateProfilePageJsonLd, importTakuhon, isActivitySnapshot, migrateTakuhon, migrations, normalize, readImageInfo, readMcpResource, renderActivitySvg, resolveLocale, schema, stripImageMetadata, validate };
package/dist/index.js CHANGED
@@ -11413,18 +11413,30 @@ function deriveRankTier(input) {
11413
11413
  var WIDTH = 520;
11414
11414
  var PAD = 16;
11415
11415
  var INNER = WIDTH - PAD * 2;
11416
- var TEXT_COLOR = "#1a1a1a";
11417
- var MUTED_COLOR = "#666666";
11418
- var ACCENT_COLOR = "#0b5fff";
11419
11416
  var FONT_FAMILY = "system-ui,-apple-system,Segoe UI,Roboto,sans-serif";
11420
- var LANGUAGE_COLORS = ["#0b5fff", "#7c3aed", "#059669", "#d97706", "#dc2626", "#0891b2"];
11421
- var OTHER_COLOR = "#9ca3af";
11422
11417
  var MAX_LANGUAGES = 6;
11423
- var HEAT_COLORS = ["#ebedf0", "#cce0ff", "#99c2ff", "#4d8aff", "#0b5fff"];
11424
11418
  var CELL = 7;
11425
11419
  var CELL_PITCH = 9;
11426
11420
  var CALENDAR_ROWS = 7;
11427
11421
  var MAX_CALENDAR_DAYS = 53 * CALENDAR_ROWS;
11422
+ var LIGHT_PALETTE = {
11423
+ background: "#ffffff",
11424
+ text: "#1a1a1a",
11425
+ muted: "#666666",
11426
+ accent: "#0b5fff",
11427
+ languageColors: ["#0b5fff", "#7c3aed", "#059669", "#d97706", "#dc2626", "#0891b2"],
11428
+ otherColor: "#9ca3af",
11429
+ heatColors: ["#ebedf0", "#cce0ff", "#99c2ff", "#4d8aff", "#0b5fff"]
11430
+ };
11431
+ var DARK_PALETTE = {
11432
+ background: "#0d1117",
11433
+ text: "#e6edf3",
11434
+ muted: "#9198a1",
11435
+ accent: "#4493f8",
11436
+ languageColors: ["#4493f8", "#a371f7", "#3fb950", "#d29922", "#f85149", "#39c5cf"],
11437
+ otherColor: "#6e7681",
11438
+ heatColors: ["#161b22", "#0a2c5e", "#11468f", "#1f6fdb", "#4493f8"]
11439
+ };
11428
11440
  function escapeXml(value) {
11429
11441
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
11430
11442
  }
@@ -11441,23 +11453,23 @@ function text(x, y, content, opts) {
11441
11453
  const anchor = opts.anchor !== void 0 ? ` text-anchor="${opts.anchor}"` : "";
11442
11454
  return `<text x="${String(x)}" y="${String(y)}" font-family="${FONT_FAMILY}" font-size="${String(opts.size)}" fill="${opts.fill}"${weight}${anchor}>${escapeXml(content)}</text>`;
11443
11455
  }
11444
- function caption(x, y, label) {
11445
- return text(x, y, label, { size: 12, fill: MUTED_COLOR, weight: 600 });
11456
+ function caption(x, y, label, fill) {
11457
+ return text(x, y, label, { size: 12, fill, weight: 600 });
11446
11458
  }
11447
11459
  function rect(x, y, w, h, fill, rx = 0) {
11448
11460
  const corner = rx > 0 ? ` rx="${String(rx)}"` : "";
11449
11461
  return `<rect x="${String(round2(x))}" y="${String(y)}" width="${String(round2(w))}" height="${String(h)}" fill="${fill}"${corner}/>`;
11450
11462
  }
11451
- function languageSegments(languages) {
11463
+ function languageSegments(languages, palette) {
11452
11464
  const segments = languages.slice(0, MAX_LANGUAGES).map((l, i) => ({
11453
11465
  name: l.name,
11454
11466
  percent: l.percent,
11455
- color: LANGUAGE_COLORS[i] ?? OTHER_COLOR
11467
+ color: palette.languageColors[i] ?? palette.otherColor
11456
11468
  }));
11457
11469
  const rest = languages.slice(MAX_LANGUAGES);
11458
11470
  if (rest.length > 0) {
11459
11471
  const percent = round2(rest.reduce((sum, l) => sum + l.percent, 0));
11460
- segments.push({ name: "Other", percent, color: OTHER_COLOR });
11472
+ segments.push({ name: "Other", percent, color: palette.otherColor });
11461
11473
  }
11462
11474
  return segments.filter((s) => s.percent > 0);
11463
11475
  }
@@ -11465,7 +11477,8 @@ function heatLevel(count, max) {
11465
11477
  if (count <= 0) return 0;
11466
11478
  return Math.min(4, Math.max(1, Math.ceil(count / max * 4)));
11467
11479
  }
11468
- function renderActivitySvg(snapshot) {
11480
+ function renderActivitySvg(snapshot, options = {}) {
11481
+ const palette = options.palette ?? LIGHT_PALETTE;
11469
11482
  const hasLanguages = snapshot.languages !== void 0 && snapshot.languages.length > 0;
11470
11483
  const hasContributions = snapshot.contributions !== void 0 && snapshot.contributions.days.length > 0;
11471
11484
  const hasCodingTime = snapshot.codingTime !== void 0;
@@ -11474,9 +11487,9 @@ function renderActivitySvg(snapshot) {
11474
11487
  const parts = [];
11475
11488
  let y = PAD;
11476
11489
  if (hasLanguages && snapshot.languages) {
11477
- const segments = languageSegments(snapshot.languages);
11490
+ const segments = languageSegments(snapshot.languages, palette);
11478
11491
  const total = segments.reduce((sum, s) => sum + s.percent, 0);
11479
- parts.push(caption(PAD, y + 11, "Languages"));
11492
+ parts.push(caption(PAD, y + 11, "Languages", palette.muted));
11480
11493
  y += 22;
11481
11494
  let x = PAD;
11482
11495
  for (const segment of segments) {
@@ -11493,7 +11506,7 @@ function renderActivitySvg(snapshot) {
11493
11506
  parts.push(
11494
11507
  text(cx + 16, cy + 9, `${segment.name} ${String(segment.percent)}%`, {
11495
11508
  size: 12,
11496
- fill: TEXT_COLOR
11509
+ fill: palette.text
11497
11510
  })
11498
11511
  );
11499
11512
  });
@@ -11503,13 +11516,18 @@ function renderActivitySvg(snapshot) {
11503
11516
  const days = snapshot.contributions.days.slice(-MAX_CALENDAR_DAYS);
11504
11517
  const max = days.reduce((m, d) => Math.max(m, d.count), 1);
11505
11518
  parts.push(
11506
- caption(PAD, y + 11, `Contributions \xB7 ${groupDigits(snapshot.contributions.total)}`)
11519
+ caption(
11520
+ PAD,
11521
+ y + 11,
11522
+ `Contributions \xB7 ${groupDigits(snapshot.contributions.total)}`,
11523
+ palette.muted
11524
+ )
11507
11525
  );
11508
11526
  y += 22;
11509
11527
  days.forEach((day, i) => {
11510
11528
  const column = Math.floor(i / CALENDAR_ROWS);
11511
11529
  const row = i % CALENDAR_ROWS;
11512
- const fill = HEAT_COLORS[heatLevel(day.count, max)] ?? HEAT_COLORS[0];
11530
+ const fill = palette.heatColors[heatLevel(day.count, max)] ?? palette.heatColors[0];
11513
11531
  parts.push(rect(PAD + column * CELL_PITCH, y + row * CELL_PITCH, CELL, CELL, fill, 1.5));
11514
11532
  });
11515
11533
  y += CALENDAR_ROWS * CELL_PITCH - (CELL_PITCH - CELL) + 12;
@@ -11518,21 +11536,21 @@ function renderActivitySvg(snapshot) {
11518
11536
  const rankX = PAD + INNER - 120;
11519
11537
  if (hasCodingTime && snapshot.codingTime) {
11520
11538
  const t = snapshot.codingTime;
11521
- parts.push(caption(PAD, y + 11, "Coding time"));
11539
+ parts.push(caption(PAD, y + 11, "Coding time", palette.muted));
11522
11540
  parts.push(
11523
11541
  text(PAD, y + 38, `${groupDigits(t.hours)}h ${String(t.minutes)}m`, {
11524
11542
  size: 22,
11525
- fill: TEXT_COLOR,
11543
+ fill: palette.text,
11526
11544
  weight: 700
11527
11545
  })
11528
11546
  );
11529
11547
  }
11530
11548
  if (hasRank && snapshot.rank) {
11531
11549
  const rank = snapshot.rank;
11532
- parts.push(caption(rankX, y + 11, "Rank"));
11550
+ parts.push(caption(rankX, y + 11, "Rank", palette.muted));
11533
11551
  const centerY = y + 32;
11534
11552
  parts.push(
11535
- `<circle cx="${String(rankX + 14)}" cy="${String(centerY)}" r="14" fill="${ACCENT_COLOR}"/>`
11553
+ `<circle cx="${String(rankX + 14)}" cy="${String(centerY)}" r="14" fill="${palette.accent}"/>`
11536
11554
  );
11537
11555
  parts.push(
11538
11556
  `<text x="${String(rankX + 14)}" y="${String(centerY)}" font-family="${FONT_FAMILY}" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle" dominant-baseline="central">${escapeXml(rank.tier)}</text>`
@@ -11540,7 +11558,7 @@ function renderActivitySvg(snapshot) {
11540
11558
  parts.push(
11541
11559
  text(rankX + 36, centerY + 4, `score ${String(rank.score)}`, {
11542
11560
  size: 12,
11543
- fill: MUTED_COLOR
11561
+ fill: palette.muted
11544
11562
  })
11545
11563
  );
11546
11564
  }
@@ -11549,13 +11567,14 @@ function renderActivitySvg(snapshot) {
11549
11567
  parts.push(
11550
11568
  text(WIDTH - PAD, y + 11, `Last synced ${snapshot.lastSyncedAt.slice(0, 10)}`, {
11551
11569
  size: 11,
11552
- fill: MUTED_COLOR,
11570
+ fill: palette.muted,
11553
11571
  anchor: "end"
11554
11572
  })
11555
11573
  );
11556
11574
  y += 22;
11557
11575
  const height = y + PAD - 12;
11558
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(WIDTH)}" height="${String(height)}" viewBox="0 0 ${String(WIDTH)} ${String(height)}" role="img" aria-label="Developer activity"><title>Developer activity</title>${parts.join("")}</svg>`;
11576
+ const background = rect(0, 0, WIDTH, height, palette.background);
11577
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(WIDTH)}" height="${String(height)}" viewBox="0 0 ${String(WIDTH)} ${String(height)}" role="img" aria-label="Developer activity"><title>Developer activity</title>${background}${parts.join("")}</svg>`;
11559
11578
  }
11560
11579
 
11561
11580
  // src/cv.ts
@@ -12077,8 +12096,10 @@ var SCHEMA_VERSION = "0.5.0";
12077
12096
  export {
12078
12097
  ACCEPTED_IMAGE_MIME_TYPES,
12079
12098
  ConflictError,
12099
+ DARK_PALETTE,
12080
12100
  IMAGE_EXTENSIONS,
12081
12101
  ImportError,
12102
+ LIGHT_PALETTE,
12082
12103
  MAX_IMAGE_BYTES,
12083
12104
  MAX_IMAGE_DIMENSION,
12084
12105
  MAX_IMAGE_FRAMES,