@takuhon/core 1.1.0 → 1.3.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.js CHANGED
@@ -305,7 +305,7 @@ var require_takuhon_validator = __commonJS({
305
305
  "use strict";
306
306
  module.exports = validate20;
307
307
  module.exports.default = validate20;
308
- var schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://takuhon.example/schemas/1.1.0/takuhon.schema.json", "title": "Takuhon Profile", "description": "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.", "type": "object", "additionalProperties": false, "required": ["schemaVersion", "profile", "contact", "settings", "meta"], "properties": { "schemaVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "description": "Semantic version of the takuhon schema this document conforms to." }, "profile": { "$ref": "#/$defs/Profile" }, "links": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Link" } }, "careers": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Career" } }, "projects": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Project" } }, "skills": { "type": "array", "maxItems": 200, "items": { "$ref": "#/$defs/Skill" } }, "certifications": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Certification" } }, "memberships": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Membership" } }, "volunteering": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Volunteering" } }, "honors": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Honor" } }, "education": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Education" } }, "publications": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Publication" } }, "languages": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Language" } }, "courses": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Course" } }, "patents": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Patent" } }, "testScores": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/TestScore" } }, "recommendations": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Recommendation" } }, "contact": { "$ref": "#/$defs/Contact" }, "settings": { "$ref": "#/$defs/Settings" }, "meta": { "$ref": "#/$defs/Meta" } }, "$defs": { "LocaleTag": { "type": "string", "minLength": 2, "maxLength": 35, "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "description": "BCP-47 language tag (e.g., 'en', 'ja', 'zh-Hant', 'pt-BR')." }, "Iso3166Alpha2": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 country code (uppercase, two letters)." }, "YearMonth": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$", "description": "Year-month in 'YYYY-MM' format (Gregorian calendar)." }, "IsoDateTime": { "type": "string", "format": "date-time", "description": "ISO 8601 date-time (e.g., 2026-05-11T12:34:56Z)." }, "Url": { "type": "string", "format": "uri", "maxLength": 2048 }, "Email": { "type": "string", "format": "email", "maxLength": 254 }, "Slug": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "URL-safe identifier (lowercase alphanumerics and hyphens, must start with alphanumeric)." }, "Visibility": { "type": "string", "enum": ["public", "private"], "description": "Per-item public visibility (added in 0.7.0). 'private' removes the item from every public surface via applyPublicPrivacyFilter; an absent value or 'public' keeps the item public." }, "LocalizedTitle": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value). Each value must contain at least one non-whitespace character." }, "LocalizedBody": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 5e3, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value). Each value must contain at least one non-whitespace character." }, "LinkType": { "type": "string", "enum": ["website", "blog", "github", "gitlab", "linkedin", "x", "mastodon", "bluesky", "instagram", "youtube", "threads", "facebook", "email", "rss", "custom"], "description": "Identifies the kind of link. 'custom' requires iconUrl." }, "Profile": { "type": "object", "additionalProperties": false, "required": ["displayName"], "properties": { "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "tagline": { "$ref": "#/$defs/LocalizedTitle" }, "bio": { "$ref": "#/$defs/LocalizedBody" }, "avatar": { "$ref": "#/$defs/Avatar" }, "location": { "$ref": "#/$defs/Address" } } }, "Avatar": { "type": "object", "additionalProperties": false, "required": ["url"], "properties": { "url": { "type": "string", "format": "uri-reference", "maxLength": 2048, "description": "URL or path to the avatar image." }, "alt": { "$ref": "#/$defs/LocalizedTitle" } } }, "Address": { "type": "object", "additionalProperties": false, "properties": { "country": { "$ref": "#/$defs/Iso3166Alpha2" }, "region": { "type": "string", "maxLength": 100 }, "locality": { "$ref": "#/$defs/LocalizedTitle" }, "display": { "$ref": "#/$defs/LocalizedTitle" } } }, "Link": { "type": "object", "additionalProperties": false, "required": ["id", "type", "url"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "type": { "$ref": "#/$defs/LinkType" }, "label": { "$ref": "#/$defs/LocalizedTitle" }, "url": { "$ref": "#/$defs/Url" }, "featured": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 }, "iconUrl": { "$ref": "#/$defs/Url" } }, "allOf": [{ "if": { "properties": { "type": { "const": "custom" } }, "required": ["type"] }, "then": { "properties": { "iconUrl": { "$ref": "#/$defs/Url" } }, "required": ["iconUrl"] } }] }, "Career": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "location": { "$ref": "#/$defs/Address" }, "order": { "type": "integer", "minimum": 0 } } }, "Project": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "tags": { "type": "array", "maxItems": 30, "items": { "type": "string", "minLength": 1, "maxLength": 50 } }, "relatedCareerId": { "$ref": "#/$defs/Slug" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "highlighted": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 } } }, "Skill": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "label": { "type": "string", "minLength": 1, "maxLength": 100 }, "category": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Recommended values (extensible): programming, design, business, communication, language, music, art, sports, other." }, "order": { "type": "integer", "minimum": 0 } } }, "Contact": { "type": "object", "additionalProperties": false, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" } } }, "ContactSettings": { "type": "object", "additionalProperties": false, "description": "Opt-in contact form configuration (added in 1.1.0). Only public values live here; the Turnstile site key is safe to embed in the page. The Turnstile secret, the recipient address, and the From label are provisioned out of band as environment configuration and are never stored in takuhon.json. Adapters that support the form mount the widget and a POST endpoint when 'enabled' is true.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), no contact form is mounted even if a site key is present." }, "turnstileSiteKey": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Public Cloudflare Turnstile site key, safe to embed in the page. Without it the widget is not mounted." }, "endpoint": { "type": "string", "minLength": 1, "maxLength": 2048, "description": "Override the POST endpoint the widget submits to. Defaults to /api/contact on the same origin." }, "subjectPrefix": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Subject prefix for the delivered email, e.g. '[example.com contact]'." } } }, "PublicVisibility": { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } }, "ActivitySettings": { "type": "object", "additionalProperties": false, "description": "Opt-in developer-activity dashboard configuration (GitHub / WakaTime). Only the owner-curated settings live here; secrets are provisioned out of band and the synced metrics are stored in a separate document, never in takuhon.json.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), the activity section is not rendered even if a snapshot exists." }, "github": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 39, "description": "GitHub login whose public activity is summarized." }, "showLanguages": { "type": "boolean", "default": true }, "showContributions": { "type": "boolean", "default": true } } }, "wakatime": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 255, "description": "WakaTime username whose coding-time stats are summarized." }, "showCodingTime": { "type": "boolean", "default": true } } }, "showRank": { "type": "boolean", "default": true, "description": "Display the derived activity rank / badge." }, "refreshHintHours": { "type": "integer", "minimum": 1, "maximum": 168, "description": "Advisory refresh cadence in hours. The real cadence is how often the sync step (CLI command or scheduled job) runs." } } }, "Meta": { "type": "object", "additionalProperties": false, "required": ["contentLicense"], "properties": { "createdAt": { "$ref": "#/$defs/IsoDateTime" }, "updatedAt": { "$ref": "#/$defs/IsoDateTime" }, "generator": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Tool that produced this document (e.g. 'Takuhon', 'create-takuhon@0.1.0')." }, "contentLicense": { "$ref": "#/$defs/ContentLicense" }, "privacy": { "$ref": "#/$defs/MetaPrivacy" } } }, "ContentLicense": { "type": "object", "additionalProperties": false, "required": ["spdxId"], "properties": { "spdxId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "SPDX identifier (e.g., 'CC-BY-4.0', 'CC0-1.0') or 'Proprietary'. No default; the profile owner must choose explicitly." }, "url": { "$ref": "#/$defs/Url" }, "attribution": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 200 }, "url": { "$ref": "#/$defs/Url" } } }, "rights": { "type": "string", "minLength": 1, "maxLength": 1e3, "description": "Free-form rights statement (used when spdxId='Proprietary' or for additional notices)." } } }, "MetaPrivacy": { "type": "object", "additionalProperties": false, "description": "Opt-out flags that strip personally identifying fields from public API output (GET /api/profile, /api/jsonld, /takuhon.json). Admin endpoints (PUT /api/admin/*, GET /api/admin/export) ignore these flags. Privacy-by-default: omitting the object or individual flags is equivalent to true.", "properties": { "hideCredentialIds": { "type": "boolean", "default": true, "description": "When true (default), strip certifications[*].credentialId from public responses." }, "hideEducationGrades": { "type": "boolean", "default": true, "description": "When true (default), strip education[*].grade from public responses." } } }, "Certification": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuingOrganization": { "$ref": "#/$defs/LocalizedTitle" }, "issueDate": { "$ref": "#/$defs/YearMonth" }, "expirationDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = explicitly 'no expiration'. Omit if unknown/unstated." }, "credentialId": { "type": "string", "minLength": 1, "maxLength": 100, "description": "License or certificate number. Public exposure controlled by meta.privacy.hideCredentialIds." }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Membership": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = ongoing. Omit if unknown." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Volunteering": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "cause": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Honor": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuer": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Education": { "type": "object", "additionalProperties": false, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "institution": { "$ref": "#/$defs/LocalizedTitle" }, "degree": { "$ref": "#/$defs/LocalizedTitle" }, "fieldOfStudy": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "grade": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form grade / class / GPA. Public exposure controlled by meta.privacy.hideEducationGrades." }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = currently enrolled." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Publication": { "type": "object", "additionalProperties": false, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "publisher": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "doi": { "type": "string", "minLength": 1, "maxLength": 200, "description": "DOI identifier (e.g. '10.1145/3548643.3548644'). The full URL goes in 'url'." }, "coAuthors": { "type": "array", "maxItems": 50, "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": "Co-author names in original script. Excludes the profile owner." }, "order": { "type": "integer", "minimum": 0 } } }, "Language": { "type": "object", "additionalProperties": false, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "language": { "$ref": "#/$defs/LocaleTag" }, "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "proficiency": { "type": "string", "enum": ["native", "fluent", "professional", "intermediate", "basic"], "description": "LinkedIn-compatible 5-level proficiency." }, "order": { "type": "integer", "minimum": 0 } } }, "Course": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "provider": { "$ref": "#/$defs/LocalizedTitle" }, "courseNumber": { "type": "string", "minLength": 1, "maxLength": 50 }, "description": { "$ref": "#/$defs/LocalizedBody" }, "completionDate": { "$ref": "#/$defs/YearMonth" }, "certificateUrl": { "$ref": "#/$defs/Url" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for university coursework)." }, "order": { "type": "integer", "minimum": 0 } } }, "Patent": { "type": "object", "additionalProperties": false, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "patentNumber": { "type": "string", "minLength": 1, "maxLength": 100 }, "office": { "type": "string", "maxLength": 100, "description": "Patent office name (e.g. 'USPTO', 'JPO', 'EPO')." }, "status": { "type": "string", "enum": ["pending", "issued", "expired", "abandoned"] }, "description": { "$ref": "#/$defs/LocalizedBody" }, "filingDate": { "$ref": "#/$defs/YearMonth" }, "grantDate": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "coInventors": { "type": "array", "maxItems": 20, "items": { "type": "string", "minLength": 1, "maxLength": 100 } }, "order": { "type": "integer", "minimum": 0 } } }, "TestScore": { "type": "object", "additionalProperties": false, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "score": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form score string (e.g. '112 / 120', '330', 'N1 Pass', or a percentile). The validator does not interpret its contents." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for a university course exam)." }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Recommendation": { "type": "object", "additionalProperties": false, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "body": { "$ref": "#/$defs/LocalizedBody" }, "author": { "$ref": "#/$defs/RecommendationAuthor" }, "relationship": { "$ref": "#/$defs/LocalizedTitle", "description": "How the recommender relates to the profile owner (e.g. 'managed directly', 'worked together')." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedCareerId": { "$ref": "#/$defs/Slug", "description": "Optional reference to a careers[].id (the position the recommendation pertains to)." }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. a recommendation from a professor)." }, "order": { "type": "integer", "minimum": 0 } } }, "RecommendationAuthor": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Recommender's name, in its original script. Owner-curated; takuhon does not verify it." }, "headline": { "$ref": "#/$defs/LocalizedTitle", "description": "Recommender's title / role / organization at the time of the recommendation." }, "url": { "$ref": "#/$defs/Url", "description": "Link to the recommender's profile, for external verification by the reader." } } } } };
308
+ var schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://takuhon.example/schemas/1.3.0/takuhon.schema.json", "title": "Takuhon Profile", "description": "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.", "type": "object", "additionalProperties": false, "required": ["schemaVersion", "profile", "contact", "settings", "meta"], "properties": { "schemaVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "description": "Semantic version of the takuhon schema this document conforms to." }, "profile": { "$ref": "#/$defs/Profile" }, "links": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Link" } }, "careers": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Career" } }, "projects": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Project" } }, "skills": { "type": "array", "maxItems": 200, "items": { "$ref": "#/$defs/Skill" } }, "certifications": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Certification" } }, "memberships": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Membership" } }, "volunteering": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Volunteering" } }, "honors": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Honor" } }, "education": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Education" } }, "publications": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Publication" } }, "languages": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Language" } }, "courses": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Course" } }, "patents": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Patent" } }, "testScores": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/TestScore" } }, "recommendations": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Recommendation" } }, "contact": { "$ref": "#/$defs/Contact" }, "settings": { "$ref": "#/$defs/Settings" }, "meta": { "$ref": "#/$defs/Meta" } }, "$defs": { "LocaleTag": { "type": "string", "minLength": 2, "maxLength": 35, "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "description": "BCP-47 language tag (e.g., 'en', 'ja', 'zh-Hant', 'pt-BR')." }, "Iso3166Alpha2": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 country code (uppercase, two letters)." }, "YearMonth": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$", "description": "Year-month in 'YYYY-MM' format (Gregorian calendar)." }, "IsoDateTime": { "type": "string", "format": "date-time", "description": "ISO 8601 date-time (e.g., 2026-05-11T12:34:56Z)." }, "Url": { "type": "string", "format": "uri", "maxLength": 2048 }, "Email": { "type": "string", "format": "email", "maxLength": 254 }, "Slug": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "URL-safe identifier (lowercase alphanumerics and hyphens, must start with alphanumeric)." }, "Visibility": { "type": "string", "enum": ["public", "private"], "description": "Per-item public visibility (added in 0.7.0). 'private' removes the item from every public surface via applyPublicPrivacyFilter; an absent value or 'public' keeps the item public." }, "LocalizedTitle": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value). Each value must contain at least one non-whitespace character." }, "LocalizedBody": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 5e3, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value). Each value must contain at least one non-whitespace character." }, "LinkType": { "type": "string", "enum": ["website", "blog", "github", "gitlab", "linkedin", "x", "mastodon", "bluesky", "instagram", "youtube", "threads", "facebook", "email", "rss", "custom"], "description": "Identifies the kind of link. 'custom' requires iconUrl." }, "Profile": { "type": "object", "additionalProperties": false, "required": ["displayName"], "properties": { "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "tagline": { "$ref": "#/$defs/LocalizedTitle" }, "bio": { "$ref": "#/$defs/LocalizedBody" }, "avatar": { "$ref": "#/$defs/Avatar" }, "location": { "$ref": "#/$defs/Address" } } }, "Avatar": { "type": "object", "additionalProperties": false, "required": ["url"], "properties": { "url": { "type": "string", "format": "uri-reference", "maxLength": 2048, "description": "URL or path to the avatar image." }, "alt": { "$ref": "#/$defs/LocalizedTitle" } } }, "Address": { "type": "object", "additionalProperties": false, "properties": { "country": { "$ref": "#/$defs/Iso3166Alpha2" }, "region": { "type": "string", "maxLength": 100 }, "locality": { "$ref": "#/$defs/LocalizedTitle" }, "display": { "$ref": "#/$defs/LocalizedTitle" } } }, "Link": { "type": "object", "additionalProperties": false, "required": ["id", "type", "url"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "type": { "$ref": "#/$defs/LinkType" }, "label": { "$ref": "#/$defs/LocalizedTitle" }, "url": { "$ref": "#/$defs/Url" }, "featured": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 }, "iconUrl": { "$ref": "#/$defs/Url" } }, "allOf": [{ "if": { "properties": { "type": { "const": "custom" } }, "required": ["type"] }, "then": { "properties": { "iconUrl": { "$ref": "#/$defs/Url" } }, "required": ["iconUrl"] } }] }, "Career": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "location": { "$ref": "#/$defs/Address" }, "order": { "type": "integer", "minimum": 0 } } }, "Project": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "tags": { "type": "array", "maxItems": 30, "items": { "type": "string", "minLength": 1, "maxLength": 50 } }, "relatedCareerId": { "$ref": "#/$defs/Slug" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "highlighted": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 } } }, "Skill": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "label": { "type": "string", "minLength": 1, "maxLength": 100 }, "category": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Recommended values (extensible): programming, design, business, communication, language, music, art, sports, other." }, "order": { "type": "integer", "minimum": 0 } } }, "SkillCategory": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Matches the `category` value on the skills that belong to this group." }, "label": { "$ref": "#/$defs/LocalizedTitle", "description": "Localized display heading for the group." } } }, "Contact": { "type": "object", "additionalProperties": false, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" }, "appearance": { "$ref": "#/$defs/AppearanceSettings" }, "skillCategories": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/SkillCategory" }, "description": "Ordered skill-category display groups (added in 1.3.0). When present, the rendered profile groups skills by their `category` under these localized headings, in array order; absent = the flat skill list. A skill whose `category` is not listed here (or which has none) renders in a trailing group so nothing is dropped." } } }, "AppearanceSettings": { "type": "object", "additionalProperties": false, "description": "Opt-in design tokens for the rendered profile (added in 1.2.0). A declarative re-skin seam: owners override the standard renderer's built-in color and font defaults. This is a token map only, not arbitrary CSS \u2014 every value is length- and pattern-constrained so it cannot break out of the inline <style>. Overriding tokens re-skins the page but cannot re-layout it (spacing/radius/type-scale are intentionally not exposed). Absent = the built-in defaults are used unchanged.", "properties": { "fontFamily": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": `^[A-Za-z0-9\\s,'"._-]+$`, "description": 'CSS font-family stack for the page body, e.g. "Inter, system-ui, sans-serif". Quotes and commas are allowed; CSS-structural characters (; { } < >) are not.' }, "colors": { "$ref": "#/$defs/AppearanceColors", "description": "Light-mode (:root) color overrides." }, "colorsDark": { "$ref": "#/$defs/AppearanceColors", "description": "Dark-mode (prefers-color-scheme: dark) color overrides. Note: until the standard renderer ships a default dark palette, only the keys set here change in dark mode." } } }, "AppearanceColors": { "type": "object", "additionalProperties": false, "description": `A set of overridable color tokens. Each value is a CSS color (hex, rgb()/rgba(), hsl()/hsla(), or a named color); CSS-structural characters (; { } < > " ' \\) are disallowed so a value cannot escape the inline <style>.`, "properties": { "bg": { "$ref": "#/$defs/CssColor", "description": "Page background." }, "surface": { "$ref": "#/$defs/CssColor", "description": "Raised surfaces such as skill/tag chips." }, "text": { "$ref": "#/$defs/CssColor", "description": "Primary body text." }, "textMuted": { "$ref": "#/$defs/CssColor", "description": "Secondary/muted text (taglines, meta, captions)." }, "border": { "$ref": "#/$defs/CssColor", "description": "Hairline borders and rules." }, "accent": { "$ref": "#/$defs/CssColor", "description": "Accent color for emphasis and focus affordances." }, "primary": { "$ref": "#/$defs/CssColor", "description": "Primary interactive color, e.g. links." }, "primaryContrast": { "$ref": "#/$defs/CssColor", "description": "Foreground color used on top of the primary color." } } }, "CssColor": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$", "description": "A CSS color value, restricted to a safe allowlist: a hex color (#rgb/#rgba/#rrggbb/#rrggbbaa), a bare keyword (named colors, currentColor, transparent), or a color function (rgb/rgba/hsl/hsla/hwb/lab/lch/oklab/oklch/color). url(), image-set(), var(), and other fetch- or reference-bearing functions are intentionally excluded so a value can never trigger an external request or escape the inline <style>." }, "ContactSettings": { "type": "object", "additionalProperties": false, "description": "Opt-in contact form configuration (added in 1.1.0). Only public values live here; the Turnstile site key is safe to embed in the page. The Turnstile secret, the recipient address, and the From label are provisioned out of band as environment configuration and are never stored in takuhon.json. Adapters that support the form mount the widget and a POST endpoint when 'enabled' is true.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), no contact form is mounted even if a site key is present." }, "turnstileSiteKey": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Public Cloudflare Turnstile site key, safe to embed in the page. Without it the widget is not mounted." }, "endpoint": { "type": "string", "minLength": 1, "maxLength": 2048, "description": "Override the POST endpoint the widget submits to. Defaults to /api/contact on the same origin." }, "subjectPrefix": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Subject prefix for the delivered email, e.g. '[example.com contact]'." } } }, "PublicVisibility": { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } }, "ActivitySettings": { "type": "object", "additionalProperties": false, "description": "Opt-in developer-activity dashboard configuration (GitHub / WakaTime). Only the owner-curated settings live here; secrets are provisioned out of band and the synced metrics are stored in a separate document, never in takuhon.json.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), the activity section is not rendered even if a snapshot exists." }, "github": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 39, "description": "GitHub login whose public activity is summarized." }, "showLanguages": { "type": "boolean", "default": true }, "showContributions": { "type": "boolean", "default": true } } }, "wakatime": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 255, "description": "WakaTime username whose coding-time stats are summarized." }, "showCodingTime": { "type": "boolean", "default": true } } }, "showRank": { "type": "boolean", "default": true, "description": "Display the derived activity rank / badge." }, "refreshHintHours": { "type": "integer", "minimum": 1, "maximum": 168, "description": "Advisory refresh cadence in hours. The real cadence is how often the sync step (CLI command or scheduled job) runs." } } }, "Meta": { "type": "object", "additionalProperties": false, "required": ["contentLicense"], "properties": { "createdAt": { "$ref": "#/$defs/IsoDateTime" }, "updatedAt": { "$ref": "#/$defs/IsoDateTime" }, "generator": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Tool that produced this document (e.g. 'Takuhon', 'create-takuhon@0.1.0')." }, "contentLicense": { "$ref": "#/$defs/ContentLicense" }, "privacy": { "$ref": "#/$defs/MetaPrivacy" } } }, "ContentLicense": { "type": "object", "additionalProperties": false, "required": ["spdxId"], "properties": { "spdxId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "SPDX identifier (e.g., 'CC-BY-4.0', 'CC0-1.0') or 'Proprietary'. No default; the profile owner must choose explicitly." }, "url": { "$ref": "#/$defs/Url" }, "attribution": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 200 }, "url": { "$ref": "#/$defs/Url" } } }, "rights": { "type": "string", "minLength": 1, "maxLength": 1e3, "description": "Free-form rights statement (used when spdxId='Proprietary' or for additional notices)." } } }, "MetaPrivacy": { "type": "object", "additionalProperties": false, "description": "Opt-out flags that strip personally identifying fields from public API output (GET /api/profile, /api/jsonld, /takuhon.json). Admin endpoints (PUT /api/admin/*, GET /api/admin/export) ignore these flags. Privacy-by-default: omitting the object or individual flags is equivalent to true.", "properties": { "hideCredentialIds": { "type": "boolean", "default": true, "description": "When true (default), strip certifications[*].credentialId from public responses." }, "hideEducationGrades": { "type": "boolean", "default": true, "description": "When true (default), strip education[*].grade from public responses." } } }, "Certification": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuingOrganization": { "$ref": "#/$defs/LocalizedTitle" }, "issueDate": { "$ref": "#/$defs/YearMonth" }, "expirationDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = explicitly 'no expiration'. Omit if unknown/unstated." }, "credentialId": { "type": "string", "minLength": 1, "maxLength": 100, "description": "License or certificate number. Public exposure controlled by meta.privacy.hideCredentialIds." }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Membership": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = ongoing. Omit if unknown." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Volunteering": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "cause": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Honor": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuer": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Education": { "type": "object", "additionalProperties": false, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "institution": { "$ref": "#/$defs/LocalizedTitle" }, "degree": { "$ref": "#/$defs/LocalizedTitle" }, "fieldOfStudy": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "grade": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form grade / class / GPA. Public exposure controlled by meta.privacy.hideEducationGrades." }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = currently enrolled." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Publication": { "type": "object", "additionalProperties": false, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "publisher": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "doi": { "type": "string", "minLength": 1, "maxLength": 200, "description": "DOI identifier (e.g. '10.1145/3548643.3548644'). The full URL goes in 'url'." }, "coAuthors": { "type": "array", "maxItems": 50, "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": "Co-author names in original script. Excludes the profile owner." }, "order": { "type": "integer", "minimum": 0 } } }, "Language": { "type": "object", "additionalProperties": false, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "language": { "$ref": "#/$defs/LocaleTag" }, "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "proficiency": { "type": "string", "enum": ["native", "fluent", "professional", "intermediate", "basic"], "description": "LinkedIn-compatible 5-level proficiency." }, "order": { "type": "integer", "minimum": 0 } } }, "Course": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "provider": { "$ref": "#/$defs/LocalizedTitle" }, "courseNumber": { "type": "string", "minLength": 1, "maxLength": 50 }, "description": { "$ref": "#/$defs/LocalizedBody" }, "completionDate": { "$ref": "#/$defs/YearMonth" }, "certificateUrl": { "$ref": "#/$defs/Url" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for university coursework)." }, "order": { "type": "integer", "minimum": 0 } } }, "Patent": { "type": "object", "additionalProperties": false, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "patentNumber": { "type": "string", "minLength": 1, "maxLength": 100 }, "office": { "type": "string", "maxLength": 100, "description": "Patent office name (e.g. 'USPTO', 'JPO', 'EPO')." }, "status": { "type": "string", "enum": ["pending", "issued", "expired", "abandoned"] }, "description": { "$ref": "#/$defs/LocalizedBody" }, "filingDate": { "$ref": "#/$defs/YearMonth" }, "grantDate": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "coInventors": { "type": "array", "maxItems": 20, "items": { "type": "string", "minLength": 1, "maxLength": 100 } }, "order": { "type": "integer", "minimum": 0 } } }, "TestScore": { "type": "object", "additionalProperties": false, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "score": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form score string (e.g. '112 / 120', '330', 'N1 Pass', or a percentile). The validator does not interpret its contents." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for a university course exam)." }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Recommendation": { "type": "object", "additionalProperties": false, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "body": { "$ref": "#/$defs/LocalizedBody" }, "author": { "$ref": "#/$defs/RecommendationAuthor" }, "relationship": { "$ref": "#/$defs/LocalizedTitle", "description": "How the recommender relates to the profile owner (e.g. 'managed directly', 'worked together')." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedCareerId": { "$ref": "#/$defs/Slug", "description": "Optional reference to a careers[].id (the position the recommendation pertains to)." }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. a recommendation from a professor)." }, "order": { "type": "integer", "minimum": 0 } } }, "RecommendationAuthor": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Recommender's name, in its original script. Owner-curated; takuhon does not verify it." }, "headline": { "$ref": "#/$defs/LocalizedTitle", "description": "Recommender's title / role / organization at the time of the recommendation." }, "url": { "$ref": "#/$defs/Url", "description": "Link to the recommender's profile, for external verification by the reader." } } } } };
309
309
  var func1 = Object.prototype.hasOwnProperty;
310
310
  var pattern4 = new RegExp("^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "u");
311
311
  var pattern5 = new RegExp("^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "u");
@@ -8861,9 +8861,631 @@ var require_takuhon_validator = __commonJS({
8861
8861
  return errors === 0;
8862
8862
  }
8863
8863
  validate60.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
8864
- var schema168 = { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" } } };
8864
+ var schema168 = { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" }, "appearance": { "$ref": "#/$defs/AppearanceSettings" }, "skillCategories": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/SkillCategory" }, "description": "Ordered skill-category display groups (added in 1.3.0). When present, the rendered profile groups skills by their `category` under these localized headings, in array order; absent = the flat skill list. A skill whose `category` is not listed here (or which has none) renders in a trailing group so nothing is dropped." } } };
8865
8865
  var schema173 = { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } };
8866
8866
  var func0 = require_equal().default;
8867
+ var pattern133 = new RegExp(`^[A-Za-z0-9\\s,'"._-]+$`, "u");
8868
+ var pattern134 = new RegExp("^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$", "u");
8869
+ function validate64(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
8870
+ let vErrors = null;
8871
+ let errors = 0;
8872
+ const evaluated0 = validate64.evaluated;
8873
+ if (evaluated0.dynamicProps) {
8874
+ evaluated0.props = void 0;
8875
+ }
8876
+ if (evaluated0.dynamicItems) {
8877
+ evaluated0.items = void 0;
8878
+ }
8879
+ if (data && typeof data == "object" && !Array.isArray(data)) {
8880
+ for (const key0 in data) {
8881
+ if (!(key0 === "bg" || key0 === "surface" || key0 === "text" || key0 === "textMuted" || key0 === "border" || key0 === "accent" || key0 === "primary" || key0 === "primaryContrast")) {
8882
+ const err0 = { instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" };
8883
+ if (vErrors === null) {
8884
+ vErrors = [err0];
8885
+ } else {
8886
+ vErrors.push(err0);
8887
+ }
8888
+ errors++;
8889
+ }
8890
+ }
8891
+ if (data.bg !== void 0) {
8892
+ let data0 = data.bg;
8893
+ if (typeof data0 === "string") {
8894
+ if (func2(data0) > 64) {
8895
+ const err1 = { instancePath: instancePath + "/bg", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
8896
+ if (vErrors === null) {
8897
+ vErrors = [err1];
8898
+ } else {
8899
+ vErrors.push(err1);
8900
+ }
8901
+ errors++;
8902
+ }
8903
+ if (func2(data0) < 1) {
8904
+ const err2 = { instancePath: instancePath + "/bg", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
8905
+ if (vErrors === null) {
8906
+ vErrors = [err2];
8907
+ } else {
8908
+ vErrors.push(err2);
8909
+ }
8910
+ errors++;
8911
+ }
8912
+ if (!pattern134.test(data0)) {
8913
+ const err3 = { instancePath: instancePath + "/bg", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
8914
+ if (vErrors === null) {
8915
+ vErrors = [err3];
8916
+ } else {
8917
+ vErrors.push(err3);
8918
+ }
8919
+ errors++;
8920
+ }
8921
+ } else {
8922
+ const err4 = { instancePath: instancePath + "/bg", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
8923
+ if (vErrors === null) {
8924
+ vErrors = [err4];
8925
+ } else {
8926
+ vErrors.push(err4);
8927
+ }
8928
+ errors++;
8929
+ }
8930
+ }
8931
+ if (data.surface !== void 0) {
8932
+ let data1 = data.surface;
8933
+ if (typeof data1 === "string") {
8934
+ if (func2(data1) > 64) {
8935
+ const err5 = { instancePath: instancePath + "/surface", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
8936
+ if (vErrors === null) {
8937
+ vErrors = [err5];
8938
+ } else {
8939
+ vErrors.push(err5);
8940
+ }
8941
+ errors++;
8942
+ }
8943
+ if (func2(data1) < 1) {
8944
+ const err6 = { instancePath: instancePath + "/surface", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
8945
+ if (vErrors === null) {
8946
+ vErrors = [err6];
8947
+ } else {
8948
+ vErrors.push(err6);
8949
+ }
8950
+ errors++;
8951
+ }
8952
+ if (!pattern134.test(data1)) {
8953
+ const err7 = { instancePath: instancePath + "/surface", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
8954
+ if (vErrors === null) {
8955
+ vErrors = [err7];
8956
+ } else {
8957
+ vErrors.push(err7);
8958
+ }
8959
+ errors++;
8960
+ }
8961
+ } else {
8962
+ const err8 = { instancePath: instancePath + "/surface", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
8963
+ if (vErrors === null) {
8964
+ vErrors = [err8];
8965
+ } else {
8966
+ vErrors.push(err8);
8967
+ }
8968
+ errors++;
8969
+ }
8970
+ }
8971
+ if (data.text !== void 0) {
8972
+ let data2 = data.text;
8973
+ if (typeof data2 === "string") {
8974
+ if (func2(data2) > 64) {
8975
+ const err9 = { instancePath: instancePath + "/text", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
8976
+ if (vErrors === null) {
8977
+ vErrors = [err9];
8978
+ } else {
8979
+ vErrors.push(err9);
8980
+ }
8981
+ errors++;
8982
+ }
8983
+ if (func2(data2) < 1) {
8984
+ const err10 = { instancePath: instancePath + "/text", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
8985
+ if (vErrors === null) {
8986
+ vErrors = [err10];
8987
+ } else {
8988
+ vErrors.push(err10);
8989
+ }
8990
+ errors++;
8991
+ }
8992
+ if (!pattern134.test(data2)) {
8993
+ const err11 = { instancePath: instancePath + "/text", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
8994
+ if (vErrors === null) {
8995
+ vErrors = [err11];
8996
+ } else {
8997
+ vErrors.push(err11);
8998
+ }
8999
+ errors++;
9000
+ }
9001
+ } else {
9002
+ const err12 = { instancePath: instancePath + "/text", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9003
+ if (vErrors === null) {
9004
+ vErrors = [err12];
9005
+ } else {
9006
+ vErrors.push(err12);
9007
+ }
9008
+ errors++;
9009
+ }
9010
+ }
9011
+ if (data.textMuted !== void 0) {
9012
+ let data3 = data.textMuted;
9013
+ if (typeof data3 === "string") {
9014
+ if (func2(data3) > 64) {
9015
+ const err13 = { instancePath: instancePath + "/textMuted", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9016
+ if (vErrors === null) {
9017
+ vErrors = [err13];
9018
+ } else {
9019
+ vErrors.push(err13);
9020
+ }
9021
+ errors++;
9022
+ }
9023
+ if (func2(data3) < 1) {
9024
+ const err14 = { instancePath: instancePath + "/textMuted", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9025
+ if (vErrors === null) {
9026
+ vErrors = [err14];
9027
+ } else {
9028
+ vErrors.push(err14);
9029
+ }
9030
+ errors++;
9031
+ }
9032
+ if (!pattern134.test(data3)) {
9033
+ const err15 = { instancePath: instancePath + "/textMuted", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
9034
+ if (vErrors === null) {
9035
+ vErrors = [err15];
9036
+ } else {
9037
+ vErrors.push(err15);
9038
+ }
9039
+ errors++;
9040
+ }
9041
+ } else {
9042
+ const err16 = { instancePath: instancePath + "/textMuted", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9043
+ if (vErrors === null) {
9044
+ vErrors = [err16];
9045
+ } else {
9046
+ vErrors.push(err16);
9047
+ }
9048
+ errors++;
9049
+ }
9050
+ }
9051
+ if (data.border !== void 0) {
9052
+ let data4 = data.border;
9053
+ if (typeof data4 === "string") {
9054
+ if (func2(data4) > 64) {
9055
+ const err17 = { instancePath: instancePath + "/border", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9056
+ if (vErrors === null) {
9057
+ vErrors = [err17];
9058
+ } else {
9059
+ vErrors.push(err17);
9060
+ }
9061
+ errors++;
9062
+ }
9063
+ if (func2(data4) < 1) {
9064
+ const err18 = { instancePath: instancePath + "/border", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9065
+ if (vErrors === null) {
9066
+ vErrors = [err18];
9067
+ } else {
9068
+ vErrors.push(err18);
9069
+ }
9070
+ errors++;
9071
+ }
9072
+ if (!pattern134.test(data4)) {
9073
+ const err19 = { instancePath: instancePath + "/border", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
9074
+ if (vErrors === null) {
9075
+ vErrors = [err19];
9076
+ } else {
9077
+ vErrors.push(err19);
9078
+ }
9079
+ errors++;
9080
+ }
9081
+ } else {
9082
+ const err20 = { instancePath: instancePath + "/border", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9083
+ if (vErrors === null) {
9084
+ vErrors = [err20];
9085
+ } else {
9086
+ vErrors.push(err20);
9087
+ }
9088
+ errors++;
9089
+ }
9090
+ }
9091
+ if (data.accent !== void 0) {
9092
+ let data5 = data.accent;
9093
+ if (typeof data5 === "string") {
9094
+ if (func2(data5) > 64) {
9095
+ const err21 = { instancePath: instancePath + "/accent", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9096
+ if (vErrors === null) {
9097
+ vErrors = [err21];
9098
+ } else {
9099
+ vErrors.push(err21);
9100
+ }
9101
+ errors++;
9102
+ }
9103
+ if (func2(data5) < 1) {
9104
+ const err22 = { instancePath: instancePath + "/accent", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9105
+ if (vErrors === null) {
9106
+ vErrors = [err22];
9107
+ } else {
9108
+ vErrors.push(err22);
9109
+ }
9110
+ errors++;
9111
+ }
9112
+ if (!pattern134.test(data5)) {
9113
+ const err23 = { instancePath: instancePath + "/accent", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
9114
+ if (vErrors === null) {
9115
+ vErrors = [err23];
9116
+ } else {
9117
+ vErrors.push(err23);
9118
+ }
9119
+ errors++;
9120
+ }
9121
+ } else {
9122
+ const err24 = { instancePath: instancePath + "/accent", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9123
+ if (vErrors === null) {
9124
+ vErrors = [err24];
9125
+ } else {
9126
+ vErrors.push(err24);
9127
+ }
9128
+ errors++;
9129
+ }
9130
+ }
9131
+ if (data.primary !== void 0) {
9132
+ let data6 = data.primary;
9133
+ if (typeof data6 === "string") {
9134
+ if (func2(data6) > 64) {
9135
+ const err25 = { instancePath: instancePath + "/primary", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9136
+ if (vErrors === null) {
9137
+ vErrors = [err25];
9138
+ } else {
9139
+ vErrors.push(err25);
9140
+ }
9141
+ errors++;
9142
+ }
9143
+ if (func2(data6) < 1) {
9144
+ const err26 = { instancePath: instancePath + "/primary", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9145
+ if (vErrors === null) {
9146
+ vErrors = [err26];
9147
+ } else {
9148
+ vErrors.push(err26);
9149
+ }
9150
+ errors++;
9151
+ }
9152
+ if (!pattern134.test(data6)) {
9153
+ const err27 = { instancePath: instancePath + "/primary", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
9154
+ if (vErrors === null) {
9155
+ vErrors = [err27];
9156
+ } else {
9157
+ vErrors.push(err27);
9158
+ }
9159
+ errors++;
9160
+ }
9161
+ } else {
9162
+ const err28 = { instancePath: instancePath + "/primary", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9163
+ if (vErrors === null) {
9164
+ vErrors = [err28];
9165
+ } else {
9166
+ vErrors.push(err28);
9167
+ }
9168
+ errors++;
9169
+ }
9170
+ }
9171
+ if (data.primaryContrast !== void 0) {
9172
+ let data7 = data.primaryContrast;
9173
+ if (typeof data7 === "string") {
9174
+ if (func2(data7) > 64) {
9175
+ const err29 = { instancePath: instancePath + "/primaryContrast", schemaPath: "#/$defs/CssColor/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9176
+ if (vErrors === null) {
9177
+ vErrors = [err29];
9178
+ } else {
9179
+ vErrors.push(err29);
9180
+ }
9181
+ errors++;
9182
+ }
9183
+ if (func2(data7) < 1) {
9184
+ const err30 = { instancePath: instancePath + "/primaryContrast", schemaPath: "#/$defs/CssColor/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9185
+ if (vErrors === null) {
9186
+ vErrors = [err30];
9187
+ } else {
9188
+ vErrors.push(err30);
9189
+ }
9190
+ errors++;
9191
+ }
9192
+ if (!pattern134.test(data7)) {
9193
+ const err31 = { instancePath: instancePath + "/primaryContrast", schemaPath: "#/$defs/CssColor/pattern", keyword: "pattern", params: { pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$" }, message: 'must match pattern "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$"' };
9194
+ if (vErrors === null) {
9195
+ vErrors = [err31];
9196
+ } else {
9197
+ vErrors.push(err31);
9198
+ }
9199
+ errors++;
9200
+ }
9201
+ } else {
9202
+ const err32 = { instancePath: instancePath + "/primaryContrast", schemaPath: "#/$defs/CssColor/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9203
+ if (vErrors === null) {
9204
+ vErrors = [err32];
9205
+ } else {
9206
+ vErrors.push(err32);
9207
+ }
9208
+ errors++;
9209
+ }
9210
+ }
9211
+ } else {
9212
+ const err33 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9213
+ if (vErrors === null) {
9214
+ vErrors = [err33];
9215
+ } else {
9216
+ vErrors.push(err33);
9217
+ }
9218
+ errors++;
9219
+ }
9220
+ validate64.errors = vErrors;
9221
+ return errors === 0;
9222
+ }
9223
+ validate64.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
9224
+ function validate63(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
9225
+ let vErrors = null;
9226
+ let errors = 0;
9227
+ const evaluated0 = validate63.evaluated;
9228
+ if (evaluated0.dynamicProps) {
9229
+ evaluated0.props = void 0;
9230
+ }
9231
+ if (evaluated0.dynamicItems) {
9232
+ evaluated0.items = void 0;
9233
+ }
9234
+ if (data && typeof data == "object" && !Array.isArray(data)) {
9235
+ for (const key0 in data) {
9236
+ if (!(key0 === "fontFamily" || key0 === "colors" || key0 === "colorsDark")) {
9237
+ const err0 = { instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" };
9238
+ if (vErrors === null) {
9239
+ vErrors = [err0];
9240
+ } else {
9241
+ vErrors.push(err0);
9242
+ }
9243
+ errors++;
9244
+ }
9245
+ }
9246
+ if (data.fontFamily !== void 0) {
9247
+ let data0 = data.fontFamily;
9248
+ if (typeof data0 === "string") {
9249
+ if (func2(data0) > 256) {
9250
+ const err1 = { instancePath: instancePath + "/fontFamily", schemaPath: "#/properties/fontFamily/maxLength", keyword: "maxLength", params: { limit: 256 }, message: "must NOT have more than 256 characters" };
9251
+ if (vErrors === null) {
9252
+ vErrors = [err1];
9253
+ } else {
9254
+ vErrors.push(err1);
9255
+ }
9256
+ errors++;
9257
+ }
9258
+ if (func2(data0) < 1) {
9259
+ const err2 = { instancePath: instancePath + "/fontFamily", schemaPath: "#/properties/fontFamily/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9260
+ if (vErrors === null) {
9261
+ vErrors = [err2];
9262
+ } else {
9263
+ vErrors.push(err2);
9264
+ }
9265
+ errors++;
9266
+ }
9267
+ if (!pattern133.test(data0)) {
9268
+ const err3 = { instancePath: instancePath + "/fontFamily", schemaPath: "#/properties/fontFamily/pattern", keyword: "pattern", params: { pattern: `^[A-Za-z0-9\\s,'"._-]+$` }, message: `must match pattern "^[A-Za-z0-9\\s,'"._-]+$"` };
9269
+ if (vErrors === null) {
9270
+ vErrors = [err3];
9271
+ } else {
9272
+ vErrors.push(err3);
9273
+ }
9274
+ errors++;
9275
+ }
9276
+ } else {
9277
+ const err4 = { instancePath: instancePath + "/fontFamily", schemaPath: "#/properties/fontFamily/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9278
+ if (vErrors === null) {
9279
+ vErrors = [err4];
9280
+ } else {
9281
+ vErrors.push(err4);
9282
+ }
9283
+ errors++;
9284
+ }
9285
+ }
9286
+ if (data.colors !== void 0) {
9287
+ if (!validate64(data.colors, { instancePath: instancePath + "/colors", parentData: data, parentDataProperty: "colors", rootData, dynamicAnchors })) {
9288
+ vErrors = vErrors === null ? validate64.errors : vErrors.concat(validate64.errors);
9289
+ errors = vErrors.length;
9290
+ }
9291
+ }
9292
+ if (data.colorsDark !== void 0) {
9293
+ if (!validate64(data.colorsDark, { instancePath: instancePath + "/colorsDark", parentData: data, parentDataProperty: "colorsDark", rootData, dynamicAnchors })) {
9294
+ vErrors = vErrors === null ? validate64.errors : vErrors.concat(validate64.errors);
9295
+ errors = vErrors.length;
9296
+ }
9297
+ }
9298
+ } else {
9299
+ const err5 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9300
+ if (vErrors === null) {
9301
+ vErrors = [err5];
9302
+ } else {
9303
+ vErrors.push(err5);
9304
+ }
9305
+ errors++;
9306
+ }
9307
+ validate63.errors = vErrors;
9308
+ return errors === 0;
9309
+ }
9310
+ validate63.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
9311
+ function validate68(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
9312
+ let vErrors = null;
9313
+ let errors = 0;
9314
+ const evaluated0 = validate68.evaluated;
9315
+ if (evaluated0.dynamicProps) {
9316
+ evaluated0.props = void 0;
9317
+ }
9318
+ if (evaluated0.dynamicItems) {
9319
+ evaluated0.items = void 0;
9320
+ }
9321
+ if (data && typeof data == "object" && !Array.isArray(data)) {
9322
+ if (data.id === void 0) {
9323
+ const err0 = { instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: "id" }, message: "must have required property 'id'" };
9324
+ if (vErrors === null) {
9325
+ vErrors = [err0];
9326
+ } else {
9327
+ vErrors.push(err0);
9328
+ }
9329
+ errors++;
9330
+ }
9331
+ if (data.label === void 0) {
9332
+ const err1 = { instancePath, schemaPath: "#/required", keyword: "required", params: { missingProperty: "label" }, message: "must have required property 'label'" };
9333
+ if (vErrors === null) {
9334
+ vErrors = [err1];
9335
+ } else {
9336
+ vErrors.push(err1);
9337
+ }
9338
+ errors++;
9339
+ }
9340
+ for (const key0 in data) {
9341
+ if (!(key0 === "id" || key0 === "label")) {
9342
+ const err2 = { instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" };
9343
+ if (vErrors === null) {
9344
+ vErrors = [err2];
9345
+ } else {
9346
+ vErrors.push(err2);
9347
+ }
9348
+ errors++;
9349
+ }
9350
+ }
9351
+ if (data.id !== void 0) {
9352
+ let data0 = data.id;
9353
+ if (typeof data0 === "string") {
9354
+ if (func2(data0) > 64) {
9355
+ const err3 = { instancePath: instancePath + "/id", schemaPath: "#/properties/id/maxLength", keyword: "maxLength", params: { limit: 64 }, message: "must NOT have more than 64 characters" };
9356
+ if (vErrors === null) {
9357
+ vErrors = [err3];
9358
+ } else {
9359
+ vErrors.push(err3);
9360
+ }
9361
+ errors++;
9362
+ }
9363
+ if (func2(data0) < 1) {
9364
+ const err4 = { instancePath: instancePath + "/id", schemaPath: "#/properties/id/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9365
+ if (vErrors === null) {
9366
+ vErrors = [err4];
9367
+ } else {
9368
+ vErrors.push(err4);
9369
+ }
9370
+ errors++;
9371
+ }
9372
+ } else {
9373
+ const err5 = { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9374
+ if (vErrors === null) {
9375
+ vErrors = [err5];
9376
+ } else {
9377
+ vErrors.push(err5);
9378
+ }
9379
+ errors++;
9380
+ }
9381
+ }
9382
+ if (data.label !== void 0) {
9383
+ let data1 = data.label;
9384
+ if (data1 && typeof data1 == "object" && !Array.isArray(data1)) {
9385
+ if (Object.keys(data1).length < 1) {
9386
+ const err6 = { instancePath: instancePath + "/label", schemaPath: "#/$defs/LocalizedTitle/minProperties", keyword: "minProperties", params: { limit: 1 }, message: "must NOT have fewer than 1 properties" };
9387
+ if (vErrors === null) {
9388
+ vErrors = [err6];
9389
+ } else {
9390
+ vErrors.push(err6);
9391
+ }
9392
+ errors++;
9393
+ }
9394
+ for (const key1 in data1) {
9395
+ const _errs7 = errors;
9396
+ if (typeof key1 === "string") {
9397
+ if (!pattern5.test(key1)) {
9398
+ const err7 = { instancePath: instancePath + "/label", schemaPath: "#/$defs/LocalizedTitle/propertyNames/pattern", keyword: "pattern", params: { pattern: "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, message: 'must match pattern "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$"', propertyName: key1 };
9399
+ if (vErrors === null) {
9400
+ vErrors = [err7];
9401
+ } else {
9402
+ vErrors.push(err7);
9403
+ }
9404
+ errors++;
9405
+ }
9406
+ } else {
9407
+ const err8 = { instancePath: instancePath + "/label", schemaPath: "#/$defs/LocalizedTitle/propertyNames/type", keyword: "type", params: { type: "string" }, message: "must be string", propertyName: key1 };
9408
+ if (vErrors === null) {
9409
+ vErrors = [err8];
9410
+ } else {
9411
+ vErrors.push(err8);
9412
+ }
9413
+ errors++;
9414
+ }
9415
+ var valid2 = _errs7 === errors;
9416
+ if (!valid2) {
9417
+ const err9 = { instancePath: instancePath + "/label", schemaPath: "#/$defs/LocalizedTitle/propertyNames", keyword: "propertyNames", params: { propertyName: key1 }, message: "property name must be valid" };
9418
+ if (vErrors === null) {
9419
+ vErrors = [err9];
9420
+ } else {
9421
+ vErrors.push(err9);
9422
+ }
9423
+ errors++;
9424
+ }
9425
+ }
9426
+ for (const key2 in data1) {
9427
+ let data2 = data1[key2];
9428
+ if (typeof data2 === "string") {
9429
+ if (func2(data2) > 200) {
9430
+ const err10 = { instancePath: instancePath + "/label/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), schemaPath: "#/$defs/LocalizedTitle/additionalProperties/maxLength", keyword: "maxLength", params: { limit: 200 }, message: "must NOT have more than 200 characters" };
9431
+ if (vErrors === null) {
9432
+ vErrors = [err10];
9433
+ } else {
9434
+ vErrors.push(err10);
9435
+ }
9436
+ errors++;
9437
+ }
9438
+ if (func2(data2) < 1) {
9439
+ const err11 = { instancePath: instancePath + "/label/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), schemaPath: "#/$defs/LocalizedTitle/additionalProperties/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9440
+ if (vErrors === null) {
9441
+ vErrors = [err11];
9442
+ } else {
9443
+ vErrors.push(err11);
9444
+ }
9445
+ errors++;
9446
+ }
9447
+ if (!pattern6.test(data2)) {
9448
+ const err12 = { instancePath: instancePath + "/label/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), schemaPath: "#/$defs/LocalizedTitle/additionalProperties/pattern", keyword: "pattern", params: { pattern: "\\S" }, message: 'must match pattern "\\S"' };
9449
+ if (vErrors === null) {
9450
+ vErrors = [err12];
9451
+ } else {
9452
+ vErrors.push(err12);
9453
+ }
9454
+ errors++;
9455
+ }
9456
+ } else {
9457
+ const err13 = { instancePath: instancePath + "/label/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"), schemaPath: "#/$defs/LocalizedTitle/additionalProperties/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9458
+ if (vErrors === null) {
9459
+ vErrors = [err13];
9460
+ } else {
9461
+ vErrors.push(err13);
9462
+ }
9463
+ errors++;
9464
+ }
9465
+ }
9466
+ } else {
9467
+ const err14 = { instancePath: instancePath + "/label", schemaPath: "#/$defs/LocalizedTitle/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9468
+ if (vErrors === null) {
9469
+ vErrors = [err14];
9470
+ } else {
9471
+ vErrors.push(err14);
9472
+ }
9473
+ errors++;
9474
+ }
9475
+ }
9476
+ } else {
9477
+ const err15 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9478
+ if (vErrors === null) {
9479
+ vErrors = [err15];
9480
+ } else {
9481
+ vErrors.push(err15);
9482
+ }
9483
+ errors++;
9484
+ }
9485
+ validate68.errors = vErrors;
9486
+ return errors === 0;
9487
+ }
9488
+ validate68.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
8867
9489
  function validate62(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
8868
9490
  let vErrors = null;
8869
9491
  let errors = 0;
@@ -9716,12 +10338,47 @@ var require_takuhon_validator = __commonJS({
9716
10338
  errors++;
9717
10339
  }
9718
10340
  }
10341
+ if (data.appearance !== void 0) {
10342
+ if (!validate63(data.appearance, { instancePath: instancePath + "/appearance", parentData: data, parentDataProperty: "appearance", rootData, dynamicAnchors })) {
10343
+ vErrors = vErrors === null ? validate63.errors : vErrors.concat(validate63.errors);
10344
+ errors = vErrors.length;
10345
+ }
10346
+ }
10347
+ if (data.skillCategories !== void 0) {
10348
+ let data43 = data.skillCategories;
10349
+ if (Array.isArray(data43)) {
10350
+ if (data43.length > 50) {
10351
+ const err78 = { instancePath: instancePath + "/skillCategories", schemaPath: "#/properties/skillCategories/maxItems", keyword: "maxItems", params: { limit: 50 }, message: "must NOT have more than 50 items" };
10352
+ if (vErrors === null) {
10353
+ vErrors = [err78];
10354
+ } else {
10355
+ vErrors.push(err78);
10356
+ }
10357
+ errors++;
10358
+ }
10359
+ const len1 = data43.length;
10360
+ for (let i2 = 0; i2 < len1; i2++) {
10361
+ if (!validate68(data43[i2], { instancePath: instancePath + "/skillCategories/" + i2, parentData: data43, parentDataProperty: i2, rootData, dynamicAnchors })) {
10362
+ vErrors = vErrors === null ? validate68.errors : vErrors.concat(validate68.errors);
10363
+ errors = vErrors.length;
10364
+ }
10365
+ }
10366
+ } else {
10367
+ const err79 = { instancePath: instancePath + "/skillCategories", schemaPath: "#/properties/skillCategories/type", keyword: "type", params: { type: "array" }, message: "must be array" };
10368
+ if (vErrors === null) {
10369
+ vErrors = [err79];
10370
+ } else {
10371
+ vErrors.push(err79);
10372
+ }
10373
+ errors++;
10374
+ }
10375
+ }
9719
10376
  } else {
9720
- const err78 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
10377
+ const err80 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9721
10378
  if (vErrors === null) {
9722
- vErrors = [err78];
10379
+ vErrors = [err80];
9723
10380
  } else {
9724
- vErrors.push(err78);
10381
+ vErrors.push(err80);
9725
10382
  }
9726
10383
  errors++;
9727
10384
  }
@@ -9730,10 +10387,10 @@ var require_takuhon_validator = __commonJS({
9730
10387
  }
9731
10388
  validate62.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
9732
10389
  var formats36 = require_formats().fullFormats["date-time"];
9733
- function validate65(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
10390
+ function validate72(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
9734
10391
  let vErrors = null;
9735
10392
  let errors = 0;
9736
- const evaluated0 = validate65.evaluated;
10393
+ const evaluated0 = validate72.evaluated;
9737
10394
  if (evaluated0.dynamicProps) {
9738
10395
  evaluated0.props = void 0;
9739
10396
  }
@@ -9949,14 +10606,14 @@ var require_takuhon_validator = __commonJS({
9949
10606
  }
9950
10607
  errors++;
9951
10608
  }
9952
- validate65.errors = vErrors;
10609
+ validate72.errors = vErrors;
9953
10610
  return errors === 0;
9954
10611
  }
9955
- validate65.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
9956
- function validate64(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
10612
+ validate72.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
10613
+ function validate71(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
9957
10614
  let vErrors = null;
9958
10615
  let errors = 0;
9959
- const evaluated0 = validate64.evaluated;
10616
+ const evaluated0 = validate71.evaluated;
9960
10617
  if (evaluated0.dynamicProps) {
9961
10618
  evaluated0.props = void 0;
9962
10619
  }
@@ -10060,8 +10717,8 @@ var require_takuhon_validator = __commonJS({
10060
10717
  }
10061
10718
  }
10062
10719
  if (data.contentLicense !== void 0) {
10063
- if (!validate65(data.contentLicense, { instancePath: instancePath + "/contentLicense", parentData: data, parentDataProperty: "contentLicense", rootData, dynamicAnchors })) {
10064
- vErrors = vErrors === null ? validate65.errors : vErrors.concat(validate65.errors);
10720
+ if (!validate72(data.contentLicense, { instancePath: instancePath + "/contentLicense", parentData: data, parentDataProperty: "contentLicense", rootData, dynamicAnchors })) {
10721
+ vErrors = vErrors === null ? validate72.errors : vErrors.concat(validate72.errors);
10065
10722
  errors = vErrors.length;
10066
10723
  }
10067
10724
  }
@@ -10120,10 +10777,10 @@ var require_takuhon_validator = __commonJS({
10120
10777
  }
10121
10778
  errors++;
10122
10779
  }
10123
- validate64.errors = vErrors;
10780
+ validate71.errors = vErrors;
10124
10781
  return errors === 0;
10125
10782
  }
10126
- validate64.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
10783
+ validate71.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
10127
10784
  function validate20(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
10128
10785
  ;
10129
10786
  let vErrors = null;
@@ -10668,8 +11325,8 @@ var require_takuhon_validator = __commonJS({
10668
11325
  }
10669
11326
  }
10670
11327
  if (data.meta !== void 0) {
10671
- if (!validate64(data.meta, { instancePath: instancePath + "/meta", parentData: data, parentDataProperty: "meta", rootData, dynamicAnchors })) {
10672
- vErrors = vErrors === null ? validate64.errors : vErrors.concat(validate64.errors);
11328
+ if (!validate71(data.meta, { instancePath: instancePath + "/meta", parentData: data, parentDataProperty: "meta", rootData, dynamicAnchors })) {
11329
+ vErrors = vErrors === null ? validate71.errors : vErrors.concat(validate71.errors);
10673
11330
  errors = vErrors.length;
10674
11331
  }
10675
11332
  }
@@ -10692,7 +11349,7 @@ var require_takuhon_validator = __commonJS({
10692
11349
  // takuhon.schema.json
10693
11350
  var takuhon_schema_default = {
10694
11351
  $schema: "https://json-schema.org/draft/2020-12/schema",
10695
- $id: "https://takuhon.example/schemas/1.1.0/takuhon.schema.json",
11352
+ $id: "https://takuhon.example/schemas/1.3.0/takuhon.schema.json",
10696
11353
  title: "Takuhon Profile",
10697
11354
  description: "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.",
10698
11355
  type: "object",
@@ -11004,6 +11661,23 @@ var takuhon_schema_default = {
11004
11661
  order: { type: "integer", minimum: 0 }
11005
11662
  }
11006
11663
  },
11664
+ SkillCategory: {
11665
+ type: "object",
11666
+ additionalProperties: false,
11667
+ required: ["id", "label"],
11668
+ properties: {
11669
+ id: {
11670
+ type: "string",
11671
+ minLength: 1,
11672
+ maxLength: 64,
11673
+ description: "Matches the `category` value on the skills that belong to this group."
11674
+ },
11675
+ label: {
11676
+ $ref: "#/$defs/LocalizedTitle",
11677
+ description: "Localized display heading for the group."
11678
+ }
11679
+ }
11680
+ },
11007
11681
  Contact: {
11008
11682
  type: "object",
11009
11683
  additionalProperties: false,
@@ -11055,9 +11729,75 @@ var takuhon_schema_default = {
11055
11729
  },
11056
11730
  activity: { $ref: "#/$defs/ActivitySettings" },
11057
11731
  publicVisibility: { $ref: "#/$defs/PublicVisibility" },
11058
- contact: { $ref: "#/$defs/ContactSettings" }
11732
+ contact: { $ref: "#/$defs/ContactSettings" },
11733
+ appearance: { $ref: "#/$defs/AppearanceSettings" },
11734
+ skillCategories: {
11735
+ type: "array",
11736
+ maxItems: 50,
11737
+ items: { $ref: "#/$defs/SkillCategory" },
11738
+ description: "Ordered skill-category display groups (added in 1.3.0). When present, the rendered profile groups skills by their `category` under these localized headings, in array order; absent = the flat skill list. A skill whose `category` is not listed here (or which has none) renders in a trailing group so nothing is dropped."
11739
+ }
11740
+ }
11741
+ },
11742
+ AppearanceSettings: {
11743
+ type: "object",
11744
+ additionalProperties: false,
11745
+ description: "Opt-in design tokens for the rendered profile (added in 1.2.0). A declarative re-skin seam: owners override the standard renderer's built-in color and font defaults. This is a token map only, not arbitrary CSS \u2014 every value is length- and pattern-constrained so it cannot break out of the inline <style>. Overriding tokens re-skins the page but cannot re-layout it (spacing/radius/type-scale are intentionally not exposed). Absent = the built-in defaults are used unchanged.",
11746
+ properties: {
11747
+ fontFamily: {
11748
+ type: "string",
11749
+ minLength: 1,
11750
+ maxLength: 256,
11751
+ pattern: `^[A-Za-z0-9\\s,'"._-]+$`,
11752
+ description: 'CSS font-family stack for the page body, e.g. "Inter, system-ui, sans-serif". Quotes and commas are allowed; CSS-structural characters (; { } < >) are not.'
11753
+ },
11754
+ colors: {
11755
+ $ref: "#/$defs/AppearanceColors",
11756
+ description: "Light-mode (:root) color overrides."
11757
+ },
11758
+ colorsDark: {
11759
+ $ref: "#/$defs/AppearanceColors",
11760
+ description: "Dark-mode (prefers-color-scheme: dark) color overrides. Note: until the standard renderer ships a default dark palette, only the keys set here change in dark mode."
11761
+ }
11762
+ }
11763
+ },
11764
+ AppearanceColors: {
11765
+ type: "object",
11766
+ additionalProperties: false,
11767
+ description: `A set of overridable color tokens. Each value is a CSS color (hex, rgb()/rgba(), hsl()/hsla(), or a named color); CSS-structural characters (; { } < > " ' \\) are disallowed so a value cannot escape the inline <style>.`,
11768
+ properties: {
11769
+ bg: { $ref: "#/$defs/CssColor", description: "Page background." },
11770
+ surface: {
11771
+ $ref: "#/$defs/CssColor",
11772
+ description: "Raised surfaces such as skill/tag chips."
11773
+ },
11774
+ text: { $ref: "#/$defs/CssColor", description: "Primary body text." },
11775
+ textMuted: {
11776
+ $ref: "#/$defs/CssColor",
11777
+ description: "Secondary/muted text (taglines, meta, captions)."
11778
+ },
11779
+ border: { $ref: "#/$defs/CssColor", description: "Hairline borders and rules." },
11780
+ accent: {
11781
+ $ref: "#/$defs/CssColor",
11782
+ description: "Accent color for emphasis and focus affordances."
11783
+ },
11784
+ primary: {
11785
+ $ref: "#/$defs/CssColor",
11786
+ description: "Primary interactive color, e.g. links."
11787
+ },
11788
+ primaryContrast: {
11789
+ $ref: "#/$defs/CssColor",
11790
+ description: "Foreground color used on top of the primary color."
11791
+ }
11059
11792
  }
11060
11793
  },
11794
+ CssColor: {
11795
+ type: "string",
11796
+ minLength: 1,
11797
+ maxLength: 64,
11798
+ pattern: "^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\([A-Za-z0-9.,%/\\s-]*\\))$",
11799
+ description: "A CSS color value, restricted to a safe allowlist: a hex color (#rgb/#rgba/#rrggbb/#rrggbbaa), a bare keyword (named colors, currentColor, transparent), or a color function (rgb/rgba/hsl/hsla/hwb/lab/lch/oklab/oklch/color). url(), image-set(), var(), and other fetch- or reference-bearing functions are intentionally excluded so a value can never trigger an external request or escape the inline <style>."
11800
+ },
11061
11801
  ContactSettings: {
11062
11802
  type: "object",
11063
11803
  additionalProperties: false,
@@ -11560,7 +12300,9 @@ var SUPPORTED_SCHEMA_VERSIONS = [
11560
12300
  "0.6.0",
11561
12301
  "0.7.0",
11562
12302
  "1.0.0",
11563
- "1.1.0"
12303
+ "1.1.0",
12304
+ "1.2.0",
12305
+ "1.3.0"
11564
12306
  ];
11565
12307
  function validate(data) {
11566
12308
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
@@ -11919,7 +12661,7 @@ function resolveLocale(data, locale, fallbackLocale) {
11919
12661
  testScores: data.testScores.map((t) => resolveTestScore(t, candidates)),
11920
12662
  recommendations: data.recommendations.map((r) => resolveRecommendation(r, candidates)),
11921
12663
  contact: data.contact,
11922
- settings: data.settings,
12664
+ settings: resolveSettings(data.settings, candidates),
11923
12665
  meta: data.meta,
11924
12666
  resolvedLocale: displayPick?.tag ?? candidates[0] ?? ""
11925
12667
  };
@@ -11962,6 +12704,17 @@ function pickLocalizedWithTag(field, candidates) {
11962
12704
  function pickLocalized(field, candidates) {
11963
12705
  return pickLocalizedWithTag(field, candidates)?.value;
11964
12706
  }
12707
+ function resolveSettings(settings, candidates) {
12708
+ const { skillCategories, ...rest } = settings;
12709
+ if (!skillCategories) return rest;
12710
+ return {
12711
+ ...rest,
12712
+ skillCategories: skillCategories.map((c) => ({
12713
+ id: c.id,
12714
+ label: pickLocalized(c.label, candidates) ?? c.id
12715
+ }))
12716
+ };
12717
+ }
11965
12718
  function resolveProfile(profile, candidates, displayName) {
11966
12719
  const out = { displayName };
11967
12720
  const tagline = pickLocalized(profile.tagline, candidates);
@@ -12806,6 +13559,30 @@ var v1_0_0_to_v1_1_0 = {
12806
13559
  }
12807
13560
  };
12808
13561
 
13562
+ // src/migrations/v1.1.0-to-v1.2.0.ts
13563
+ var v1_1_0_to_v1_2_0 = {
13564
+ from: "1.1.0",
13565
+ to: "1.2.0",
13566
+ migrate(data) {
13567
+ return {
13568
+ ...data,
13569
+ schemaVersion: "1.2.0"
13570
+ };
13571
+ }
13572
+ };
13573
+
13574
+ // src/migrations/v1.2.0-to-v1.3.0.ts
13575
+ var v1_2_0_to_v1_3_0 = {
13576
+ from: "1.2.0",
13577
+ to: "1.3.0",
13578
+ migrate(data) {
13579
+ return {
13580
+ ...data,
13581
+ schemaVersion: "1.3.0"
13582
+ };
13583
+ }
13584
+ };
13585
+
12809
13586
  // src/migrations/index.ts
12810
13587
  var migrations = [
12811
13588
  v0_1_0_to_v0_2_0,
@@ -12815,7 +13592,9 @@ var migrations = [
12815
13592
  v0_5_0_to_v0_6_0,
12816
13593
  v0_6_0_to_v0_7_0,
12817
13594
  v0_7_0_to_v1_0_0,
12818
- v1_0_0_to_v1_1_0
13595
+ v1_0_0_to_v1_1_0,
13596
+ v1_1_0_to_v1_2_0,
13597
+ v1_2_0_to_v1_3_0
12819
13598
  ];
12820
13599
 
12821
13600
  // src/migrate.ts
@@ -13805,7 +14584,7 @@ function stripImageMetadata(bytes, mime) {
13805
14584
  }
13806
14585
 
13807
14586
  // src/index.ts
13808
- var SCHEMA_VERSION = "1.1.0";
14587
+ var SCHEMA_VERSION = "1.3.0";
13809
14588
  export {
13810
14589
  ACCEPTED_IMAGE_MIME_TYPES,
13811
14590
  ConflictError,