{
  "id": "https://perso.thecardproject.uk/perso-schema",
  "canonicalUrl": "https://perso.thecardproject.uk/perso-schema",
  "versionUrl": "https://perso.thecardproject.uk/perso-schema/1.0.0.json",
  "specificationVersion": "1.0.0",
  "designSchemaVersion": 2,
  "title": "PersoFile authoring specification",
  "status": "public",
  "language": "en",
  "purpose": "The authoritative public specification for new standalone PersoFile designs accepted by PersoFiles and the Vibed card profile. Read this entire document before generating artwork. JSON Schema and SVG semantic rules apply together.",
  "authority": {
    "owner": "PersoFiles",
    "canonicalDocument": "https://perso.thecardproject.uk/perso-schema",
    "scope": "New standalone design authoring, not database records, authentication, print jobs or inheritance patches.",
    "precedence": "This document defines the authoring format. Application validators enforce it. Follow the cardProfile when producing cards for Vibed. Do not invent another field format or a /contract/v1 context. Report a mismatch rather than inventing a workaround.",
    "compatibility": "Design schema version remains 2. Specification version is separate. The versioned URL is pinned; the unversioned URL points to the current published version."
  },
  "representations": {
    "canonicalJson": {
      "schema": "designSchema",
      "description": "One JSON object with schemaVersion:2, name, description, tags, fields, svg. The svg value is one complete XML SVG string, not separate front_svg/rear_svg strings. APIs assign IDs, revision and timestamps; do not invent those. Existing stored designs may also include server-managed inheritance metadata; it is outside this standalone authoring schema."
    },
    "singleSvg": {
      "schema": "manifestSchema",
      "description": "For copy/paste or an SVG attachment, put the metadata inside exactly one direct root desc with id=\"vibed-design\". Its textContent is JSON matching manifestSchema. The entire SVG document becomes the canonical svg property on import. This desc is already permitted by the PersoFiles SVG vocabulary. It is a transport envelope, not JSON-LD or executable code.",
      "xmlEscaping": "Within desc and text nodes escape & as &amp; and < as &lt;. Escape attribute quotes as &quot;. JSON string escaping alone does not make XML well formed. A CDATA section may carry JSON text, but must not contain the CDATA closing sequence.",
      "doNotUse": [
        "script, including type=\"application/ld+json\"",
        "metadata element",
        "selectors or CSS classes as field bindings",
        "id/name/default in place of key/label/defaultValue"
      ],
      "compatibility": "Existing SVG exports without a manifest can have fields inferred by Vibed from data-field. New agent output must include the manifest so labels, required flags and defaults are preserved."
    }
  },
  "designSchema": {
    "$schema": "https://json-schema.org/draft-07/schema#",
    "$id": "https://perso.thecardproject.uk/perso-schema/1.0.0.json#designSchema",
    "title": "PersoFiles v2 saveable design",
    "type": "object",
    "additionalProperties": false,
    "required": [
      "schemaVersion",
      "name",
      "description",
      "tags",
      "fields",
      "svg"
    ],
    "properties": {
      "schemaVersion": {
        "const": 2
      },
      "name": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "description": {
        "type": "string",
        "maxLength": 2000
      },
      "tags": {
        "type": "array",
        "maxItems": 30,
        "uniqueItems": true,
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 50
        }
      },
      "fields": {
        "type": "array",
        "maxItems": 60,
        "items": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "key",
            "label",
            "type",
            "required",
            "defaultValue"
          ],
          "properties": {
            "key": {
              "type": "string",
              "pattern": "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"
            },
            "label": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "type": {
              "enum": [
                "text",
                "image"
              ]
            },
            "required": {
              "type": "boolean"
            },
            "defaultValue": {
              "type": "string",
              "maxLength": 2000,
              "description": "Text sample/default. For images use empty string; optional initial image belongs in SVG href."
            }
          }
        }
      },
      "svg": {
        "type": "string",
        "maxLength": 2000000,
        "description": "Complete standalone SVG. Semantic SVG rules in the home page JSON-LD are mandatory in addition to this JSON Schema."
      }
    }
  },
  "manifestSchema": {
    "$schema": "https://json-schema.org/draft-07/schema#",
    "$id": "https://perso.thecardproject.uk/perso-schema/1.0.0.json#manifestSchema",
    "title": "Vibed SVG manifest / PersoFiles v2 design metadata",
    "type": "object",
    "additionalProperties": false,
    "required": [
      "schemaVersion",
      "name",
      "description",
      "tags",
      "fields"
    ],
    "properties": {
      "schemaVersion": {
        "const": 2
      },
      "name": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "description": {
        "type": "string",
        "maxLength": 2000
      },
      "tags": {
        "type": "array",
        "maxItems": 30,
        "uniqueItems": true,
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 50
        }
      },
      "fields": {
        "type": "array",
        "maxItems": 60,
        "items": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "key",
            "label",
            "type",
            "required",
            "defaultValue"
          ],
          "properties": {
            "key": {
              "type": "string",
              "pattern": "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"
            },
            "label": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "type": {
              "enum": [
                "text",
                "image"
              ]
            },
            "required": {
              "type": "boolean"
            },
            "defaultValue": {
              "type": "string",
              "maxLength": 2000,
              "description": "Text sample/default. For images use empty string; optional initial image belongs in SVG href."
            }
          }
        }
      }
    }
  },
  "svgVocabulary": {
    "elements": [
      "svg",
      "g",
      "defs",
      "rect",
      "circle",
      "ellipse",
      "line",
      "polyline",
      "polygon",
      "path",
      "text",
      "tspan",
      "linearGradient",
      "radialGradient",
      "stop",
      "clipPath",
      "mask",
      "pattern",
      "image",
      "title",
      "desc"
    ],
    "attributes": [
      "xmlns",
      "xmlns:xlink",
      "viewBox",
      "width",
      "height",
      "x",
      "y",
      "x1",
      "x2",
      "y1",
      "y2",
      "cx",
      "cy",
      "r",
      "rx",
      "ry",
      "d",
      "points",
      "fill",
      "fill-opacity",
      "fill-rule",
      "stroke",
      "stroke-width",
      "stroke-opacity",
      "stroke-linecap",
      "stroke-linejoin",
      "stroke-dasharray",
      "opacity",
      "transform",
      "id",
      "clip-path",
      "clip-rule",
      "clipPathUnits",
      "mask",
      "maskUnits",
      "maskContentUnits",
      "font-family",
      "font-size",
      "font-weight",
      "font-style",
      "text-anchor",
      "dominant-baseline",
      "letter-spacing",
      "textLength",
      "lengthAdjust",
      "dx",
      "dy",
      "gradientUnits",
      "gradientTransform",
      "offset",
      "stop-color",
      "stop-opacity",
      "spreadMethod",
      "patternUnits",
      "patternTransform",
      "preserveAspectRatio",
      "href",
      "xlink:href",
      "data-children-locked",
      "data-asset-id",
      "data-asset-revision",
      "data-field",
      "data-surface",
      "data-label",
      "data-width-mm",
      "data-height-mm"
    ]
  },
  "svgRules": [
    "Well-formed XML in the http://www.w3.org/2000/svg namespace. Root viewBox is exactly four positive-dimension numeric values starting 0 0. Use a UTF-8 file <= 2,000,000 bytes for interoperable import; the existing PersoFiles validator additionally limits the SVG string to 2,000,000 characters. Up to 20,000 XML nodes.",
    "Only the elements and attributes in svgVocabulary are supported. Attribute and element spelling is case-sensitive. No CSS, class/style, script (including non-executable JSON scripts), metadata, foreignObject, use, filters, events, animation, DTD, entities or processing instructions. XML comments and descriptive title/desc text are permitted. Use presentation attributes such as fill, stroke and font-size.",
    "Root element children may only be defs, title, desc and nested svg elements. Artwork belongs inside those nested surfaces. A surface is a direct nested svg bearing data-surface, data-label, data-width-mm, data-height-mm, viewBox and placement x/y/width/height. Do not use g as a surface. There must be 1–32 surfaces in the general format.",
    "Surface IDs are unique, match ^[a-z][a-z0-9-]{0,63}$, and must never equal back. Surface labels contain 1–100 characters. Physical dimensions are 1–10000 mm. Surface viewBox is 0 0 W H; W/H are finite positive values. abs(W/H - widthMm/heightMm) <= 0.001. Give each surface explicit position and dimensions in the root preview and avoid overlap.",
    "SVG element IDs and surface IDs are different concepts. Element IDs, where supplied, must be globally unique; give newly authored artwork stable IDs. Root defs are copied into extracted surfaces. Use local url(#id) paint references only, with IDs matching [a-zA-Z_][\\w.-]*. External resources and CSS url expressions are rejected.",
    "href and xlink:href may only contain local #id references or embedded data:image/png;base64,..., data:image/jpeg;base64,... or data:image/webp;base64,... values. Empty image placeholders omit href; a bound empty href is canonicalized by validation. Never reference an external image or font.",
    "Create editable vectors and real text, using explicit coordinates, supported presentation attributes and Arial for predictable fallback. No automatic text layout/wrapping is provided. Fonts/metrics in a browser can differ from final raster rendering. All artwork should fit inside its surface."
  ],
  "fieldRules": [
    "Each field has key, label, type, required (boolean) and defaultValue (string). Unique keys match ^[a-zA-Z][a-zA-Z0-9_]{0,63}$. Maximum 60 fields; text defaults maximum 2000 characters. Labels contain 1–100 characters.",
    "Bind using data-field=\"key\" on text, tspan or image only, never on groups, rect or paths. Every binding must reference a declared field; new authoring should bind every declared field. The same key may appear on multiple faces/elements and receives the same value everywhere.",
    "type=text binds to text/tspan. Personalization sets textContent, so nested markup is replaced. Keep static labels separate, avoid nested bound elements, and match initial artwork text to the defaultValue. Values are text, never XML/HTML.",
    "type=image binds to a real image element. For the card/SVG transport profile use defaultValue=\"\" and, if supplied, embed an initial image in href. Explicit x/y/width/height are required for useful image placement; preserveAspectRatio=\"xMidYMid slice\" for portraits or \"xMidYMid meet\" for logos. An unbound silhouette or a metadata selector is not an editable photo.",
    "Personalization accepts only embedded PNG/JPEG/WebP base64 image values. Setting an image to empty removes href and xlink:href. Missing values leave existing SVG artwork unchanged; callers apply text defaults. required is application metadata: Vibed checks it before download; the low-level renderSvg function does not enforce required.",
    "Vibed preserves editability when exporting: it updates text defaults in the desc manifest and actual SVG values. Canonical JSON exports carry the same fields and SVG. Saving, payment and printing are separate application actions, not implied by a preview."
  ],
  "cardProfile": {
    "name": "Vibed ID cards",
    "version": "1.0.0",
    "surfaces": [
      "front",
      "rear"
    ],
    "defaultSizeMm": {
      "width": 85.6,
      "height": 54
    },
    "instructions": [
      "Work with the customer iteratively. If your tools can render SVG, first show a rendered preview of the actual SVG for every face, invite changes and regenerate the preview after edits. Never substitute an AI-generated raster approximation for the SVG. If rendering is unavailable, say so and offer Vibed as the preview. When the customer is ready, deliver the complete final SVG in ONE copyable fenced code block labelled svg, with no prose inside the code block. Also offer a downloadable UTF-8 .svg file if your tools support it; a file is optional. Never split artwork or metadata across multiple code blocks or return an HTML or separate JSON attachment. Do not call any Vibed API or web agent: Vibed publishes this contract only. Follow the customer brief while obeying this output contract.",
      "The SVG must be well-formed XML with xmlns=\"http://www.w3.org/2000/svg\" and root viewBox=\"0 0 W H\" with positive numeric W/H. Maximum 2,000,000 UTF-8 bytes and 20,000 nodes. Give all artwork elements globally unique IDs.",
      "Root direct children may only be title, desc, defs, and named nested svg surfaces. A single-sided card has exactly one data-surface=\"front\". A duplex card has exactly front and rear. Never use back, Front, Rear, numbers, duplicate names or nested surface declarations. Display labels may be Front and Rear.",
      "Each surface is a direct child svg with data-surface, data-label, data-width-mm, data-height-mm, viewBox=\"0 0 W H\", x, y, width, height. Physical dimensions are millimetres, 1 to 10000. Use 85.6 by 54 mm unless asked otherwise. At 10 SVG units/mm use viewBox=\"0 0 856 540\". Surface viewBox ratio must match widthMm/heightMm within 0.001. Lay out front/rear side by side without overlap and fit the root viewBox around them.",
      "Include exactly one root <desc id=\"vibed-design\"> whose text is JSON conforming to manifestSchema. XML-escape JSON text: & becomes &amp; and < becomes &lt;. Do not use metadata, script or custom XML elements. schemaVersion is 2. Manifest has name, description, tags and fields. No svg property inside the manifest (avoid recursion).",
      "For each editable value define {key,label,type,required,defaultValue} in fields, up to 60. Keys are unique, match ^[a-zA-Z][a-zA-Z0-9_]{0,63}$, and bind only via data-field=\"key\" on text, tspan or image. Every binding must be declared and every declared field must appear in artwork. Share keys across faces to personalize together. Do not bind g, rect, path, or static labels. Avoid nesting one bound text field inside another.",
      "Text fields bind to text/tspan with sample text matching defaultValue; keep labels separate. Rendering replaces the bound element textContent, not markup. Explicit x/y, font-family=\"Arial\", font-size, fill and text-anchor. No automatic wrapping: use separate positioned text/tspan elements, including repeated bindings if intentional. Leave enough room for longer values.",
      "Image fields bind to an image element with explicit x/y/width/height and preserveAspectRatio=\"xMidYMid slice\" for portraits or \"xMidYMid meet\" for logos. Image field defaultValue is empty string. Leave href absent for an empty placeholder; draw a static background rect beneath it. If a real initial image is supplied, embed PNG/JPEG/WebP as base64 in href. Never bind a silhouette/group as a photo. Preserve logos, do not invent them.",
      "Use editable vectors first. Allowed elements and attributes are listed in svgVocabulary. Presentation attributes only; no style attributes, style elements, CSS, scripts, event handlers, foreignObject, use, filter, animation, DOCTYPE, entities or processing instructions. No external fonts, URLs or network resources. Paint references must be local url(#id). href/xlink:href must be a local #id or embedded PNG/JPEG/WebP data URL. Gradients/clipPaths may live in shared root defs; IDs unique across both faces.",
      "Design for physical readability with at least 3 mm safe margins. Prefer restrained colors, clear hierarchy and fictional sample personal details unless provided by the customer. Do not invent official accreditation, machine-readable credentials or scannable codes. Keep all artwork within its surface.",
      "Check XML, manifest JSON Schema, field bindings, unique IDs, exact surface names, dimensions, local references and allowed SVG vocabulary before delivery. The complete worked example is valid and can be adapted. JSON Schema alone does not validate SVG semantics.",
      "Vibed extracts the manifest, adds svg as a string and validates it as designSchema (the PersoFiles v2 saveable design). Preview/personalization run locally in the browser. Users can download personalized SVG or canonical design JSON for later storage/printing; this prototype does not persist to a server or print."
    ],
    "strictness": "The card profile is intentionally narrower than the general 1–32 surface format: front only or front plus rear; all declared fields must be bound, image defaultValue must be empty. These are authoring/profile rules, not a claim that every legacy database record already conforms."
  },
  "validation": {
    "steps": [
      "Parse the complete XML; do not insert untrusted SVG as live HTML/DOM.",
      "Extract and parse desc#vibed-design for SVG transport; combine with svg for canonical JSON.",
      "Validate metadata using manifestSchema and the canonical object using designSchema.",
      "Validate SVG using svgVocabulary and svgRules, dimensions, IDs, field types and references.",
      "For Vibed, apply cardProfile restrictions; render each face and test text/image replacement."
    ],
    "referenceImplementation": "PersoFiles server/design.js validateSvg, validateDesign, renderSvg; shared/surfaces.js surfaceTools. Vibed uses a reviewed snapshot of those functions.",
    "jsonSchemaLimit": "JSON Schema validates the outer JSON. Because svg is a string, the SVG semantic checks are mandatory separately.",
    "publicValidationEndpoint": null
  },
  "commonErrors": [
    {
      "error": "Unsupported SVG element: script",
      "cause": "Agent placed JSON-LD data in the output SVG.",
      "fix": "Put metadata in root desc#vibed-design, using key/label/type/required/defaultValue; do not merely remove the manifest."
    },
    {
      "error": "Unsupported SVG element: style / attribute: class",
      "fix": "Convert all CSS declarations to supported presentation attributes. Remove style and class only after preserving their appearance."
    },
    {
      "error": "Surface IDs must be unique lowercase names; use rear rather than back.",
      "fix": "Use direct nested svg surfaces named front/rear, not g#front/g#back. Lay them out separately."
    },
    {
      "error": "Bindings belong on text, tspan or image elements.",
      "fix": "Add data-field to the actual text/tspan/image, not its container. Photos need a real image element."
    },
    {
      "error": "Invalid XML",
      "fix": "Escape literal ampersands and less-than characters in visible text and desc JSON."
    }
  ],
  "examples": {
    "twoSidedSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1728 540\">\n  <title>Studio membership</title>\n  <desc id=\"vibed-design\">{\"schemaVersion\":2,\"name\":\"Studio membership\",\"description\":\"A two-sided membership card\",\"tags\":[\"membership\"],\"fields\":[{\"key\":\"holderName\",\"label\":\"Your name\",\"type\":\"text\",\"required\":true,\"defaultValue\":\"Alex Morgan\"},{\"key\":\"role\",\"label\":\"Role\",\"type\":\"text\",\"required\":false,\"defaultValue\":\"Creative member\"},{\"key\":\"photo\",\"label\":\"Portrait\",\"type\":\"image\",\"required\":false,\"defaultValue\":\"\"}]}</desc>\n  <svg id=\"front-face\" data-surface=\"front\" data-label=\"Front\" data-width-mm=\"85.6\" data-height-mm=\"54\" viewBox=\"0 0 856 540\" x=\"0\" y=\"0\" width=\"856\" height=\"540\">\n    <rect id=\"front-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#143e39\"/>\n    <circle id=\"front-orbit\" cx=\"800\" cy=\"40\" r=\"240\" fill=\"#22594f\"/>\n    <text id=\"brand\" x=\"48\" y=\"80\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" letter-spacing=\"4\">STUDIO / MEMBERSHIP</text>\n    <rect id=\"photo-bg\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" rx=\"12\" fill=\"#3b6c61\"/>\n    <image id=\"portrait\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" data-field=\"photo\" preserveAspectRatio=\"xMidYMid slice\"/>\n    <text id=\"holder-label\" x=\"274\" y=\"182\" fill=\"#a7c5b7\" font-family=\"Arial\" font-size=\"18\">CARDHOLDER</text>\n    <text id=\"holder\" x=\"274\" y=\"235\" fill=\"#ffffff\" font-family=\"Arial\" font-size=\"38\" font-weight=\"700\" data-field=\"holderName\">Alex Morgan</text>\n    <text id=\"role\" x=\"274\" y=\"280\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" data-field=\"role\">Creative member</text>\n    <line id=\"rule\" x1=\"48\" y1=\"430\" x2=\"808\" y2=\"430\" stroke=\"#62867a\" stroke-width=\"1\"/>\n    <text id=\"footer\" x=\"48\" y=\"482\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"20\">A LITTLE CARD. A WORLD OF POSSIBILITIES.</text>\n  </svg>\n  <svg id=\"rear-face\" data-surface=\"rear\" data-label=\"Rear\" data-width-mm=\"85.6\" data-height-mm=\"54\" viewBox=\"0 0 856 540\" x=\"872\" y=\"0\" width=\"856\" height=\"540\">\n    <rect id=\"rear-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#eeeadd\"/>\n    <text id=\"rear-heading\" x=\"48\" y=\"90\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"30\" font-weight=\"700\">GOOD THINGS START WITH AN IDEA.</text>\n    <text id=\"rear-name\" x=\"48\" y=\"170\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"28\" data-field=\"holderName\">Alex Morgan</text>\n    <text id=\"rear-note\" x=\"48\" y=\"460\" fill=\"#52655b\" font-family=\"Arial\" font-size=\"20\">Keep creating. This is your space.</text>\n  </svg>\n</svg>",
    "twoSidedDesign": {
      "schemaVersion": 2,
      "name": "Studio membership",
      "description": "A two-sided membership card",
      "tags": [
        "membership"
      ],
      "fields": [
        {
          "key": "holderName",
          "label": "Your name",
          "type": "text",
          "required": true,
          "defaultValue": "Alex Morgan"
        },
        {
          "key": "role",
          "label": "Role",
          "type": "text",
          "required": false,
          "defaultValue": "Creative member"
        },
        {
          "key": "photo",
          "label": "Portrait",
          "type": "image",
          "required": false,
          "defaultValue": ""
        }
      ],
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1728 540\">\n  <title>Studio membership</title>\n  <desc id=\"vibed-design\">{\"schemaVersion\":2,\"name\":\"Studio membership\",\"description\":\"A two-sided membership card\",\"tags\":[\"membership\"],\"fields\":[{\"key\":\"holderName\",\"label\":\"Your name\",\"type\":\"text\",\"required\":true,\"defaultValue\":\"Alex Morgan\"},{\"key\":\"role\",\"label\":\"Role\",\"type\":\"text\",\"required\":false,\"defaultValue\":\"Creative member\"},{\"key\":\"photo\",\"label\":\"Portrait\",\"type\":\"image\",\"required\":false,\"defaultValue\":\"\"}]}</desc>\n  <svg id=\"front-face\" data-surface=\"front\" data-label=\"Front\" data-width-mm=\"85.6\" data-height-mm=\"54\" viewBox=\"0 0 856 540\" x=\"0\" y=\"0\" width=\"856\" height=\"540\">\n    <rect id=\"front-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#143e39\"/>\n    <circle id=\"front-orbit\" cx=\"800\" cy=\"40\" r=\"240\" fill=\"#22594f\"/>\n    <text id=\"brand\" x=\"48\" y=\"80\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" letter-spacing=\"4\">STUDIO / MEMBERSHIP</text>\n    <rect id=\"photo-bg\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" rx=\"12\" fill=\"#3b6c61\"/>\n    <image id=\"portrait\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" data-field=\"photo\" preserveAspectRatio=\"xMidYMid slice\"/>\n    <text id=\"holder-label\" x=\"274\" y=\"182\" fill=\"#a7c5b7\" font-family=\"Arial\" font-size=\"18\">CARDHOLDER</text>\n    <text id=\"holder\" x=\"274\" y=\"235\" fill=\"#ffffff\" font-family=\"Arial\" font-size=\"38\" font-weight=\"700\" data-field=\"holderName\">Alex Morgan</text>\n    <text id=\"role\" x=\"274\" y=\"280\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" data-field=\"role\">Creative member</text>\n    <line id=\"rule\" x1=\"48\" y1=\"430\" x2=\"808\" y2=\"430\" stroke=\"#62867a\" stroke-width=\"1\"/>\n    <text id=\"footer\" x=\"48\" y=\"482\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"20\">A LITTLE CARD. A WORLD OF POSSIBILITIES.</text>\n  </svg>\n  <svg id=\"rear-face\" data-surface=\"rear\" data-label=\"Rear\" data-width-mm=\"85.6\" data-height-mm=\"54\" viewBox=\"0 0 856 540\" x=\"872\" y=\"0\" width=\"856\" height=\"540\">\n    <rect id=\"rear-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#eeeadd\"/>\n    <text id=\"rear-heading\" x=\"48\" y=\"90\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"30\" font-weight=\"700\">GOOD THINGS START WITH AN IDEA.</text>\n    <text id=\"rear-name\" x=\"48\" y=\"170\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"28\" data-field=\"holderName\">Alex Morgan</text>\n    <text id=\"rear-note\" x=\"48\" y=\"460\" fill=\"#52655b\" font-family=\"Arial\" font-size=\"20\">Keep creating. This is your space.</text>\n  </svg>\n</svg>"
    },
    "singleSidedDesign": {
      "schemaVersion": 2,
      "name": "Single-sided member card",
      "description": "Front-only example without an embedded manifest; use as a canonical JSON design.",
      "tags": [
        "membership"
      ],
      "fields": [
        {
          "key": "holderName",
          "label": "Your name",
          "type": "text",
          "required": true,
          "defaultValue": "Alex Morgan"
        },
        {
          "key": "role",
          "label": "Role",
          "type": "text",
          "required": false,
          "defaultValue": "Creative member"
        },
        {
          "key": "photo",
          "label": "Portrait",
          "type": "image",
          "required": false,
          "defaultValue": ""
        }
      ],
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 856 540\"><svg id=\"front__front-face\" viewBox=\"0 0 856 540\" xmlns=\"http://www.w3.org/2000/svg\" data-surface=\"front\" data-label=\"Front\" data-width-mm=\"85.6\" data-height-mm=\"54\" x=\"0\" y=\"0\" width=\"856\" height=\"540\">\n    <rect id=\"front__front-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#143e39\"/>\n    <circle id=\"front__front-orbit\" cx=\"800\" cy=\"40\" r=\"240\" fill=\"#22594f\"/>\n    <text id=\"front__brand\" x=\"48\" y=\"80\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" letter-spacing=\"4\">STUDIO / MEMBERSHIP</text>\n    <rect id=\"front__photo-bg\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" rx=\"12\" fill=\"#3b6c61\"/>\n    <image id=\"front__portrait\" x=\"48\" y=\"135\" width=\"184\" height=\"236\" data-field=\"photo\" preserveAspectRatio=\"xMidYMid slice\"/>\n    <text id=\"front__holder-label\" x=\"274\" y=\"182\" fill=\"#a7c5b7\" font-family=\"Arial\" font-size=\"18\">CARDHOLDER</text>\n    <text id=\"front__holder\" x=\"274\" y=\"235\" fill=\"#ffffff\" font-family=\"Arial\" font-size=\"38\" font-weight=\"700\" data-field=\"holderName\">Alex Morgan</text>\n    <text id=\"front__role\" x=\"274\" y=\"280\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"24\" data-field=\"role\">Creative member</text>\n    <line id=\"front__rule\" x1=\"48\" y1=\"430\" x2=\"808\" y2=\"430\" stroke=\"#62867a\" stroke-width=\"1\"/>\n    <text id=\"front__footer\" x=\"48\" y=\"482\" fill=\"#d2efb0\" font-family=\"Arial\" font-size=\"20\">A LITTLE CARD. A WORLD OF POSSIBILITIES.</text>\n  </svg></svg>"
    },
    "personalization": {
      "values": {
        "holderName": "Taylor Rivers",
        "role": "Studio member",
        "photo": ""
      },
      "expectedBehavior": "holderName changes on both front and rear. role changes on front. An uploaded embedded PNG/JPEG/WebP fills the portrait image. Empty photo leaves the static background.",
      "renderedRearSvg": "<svg id=\"rear-face\" viewBox=\"0 0 856 540\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect id=\"rear-bg\" width=\"856\" height=\"540\" rx=\"24\" fill=\"#eeeadd\"/>\n    <text id=\"rear-heading\" x=\"48\" y=\"90\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"30\" font-weight=\"700\">GOOD THINGS START WITH AN IDEA.</text>\n    <text id=\"rear-name\" x=\"48\" y=\"170\" fill=\"#143e39\" font-family=\"Arial\" font-size=\"28\" data-field=\"holderName\">Taylor Rivers</text>\n    <text id=\"rear-note\" x=\"48\" y=\"460\" fill=\"#52655b\" font-family=\"Arial\" font-size=\"20\">Keep creating. This is your space.</text>\n  </svg>"
    }
  },
  "agentChecklist": [
    "Fetch this document in full; if your fetch tool cannot access JSON-LD scripts, this URL is ordinary application/json.",
    "Follow cardProfile for Vibed. Keep website JSON-LD discovery instructions separate from SVG output metadata.",
    "Show actual SVG renders and iterate if your tools support it; never misrepresent an approximate generated image as the SVG render.",
    "Return one complete copyable svg code block; optional downloadable SVG. No omitted sections or placeholders such as remaining artwork here.",
    "Check every example/constraint before delivery; do not claim that reading the schema alone validates your SVG."
  ]
}
