Cards: optional repo link, block-level descriptions, schema + CI validation

- Project card: optional `repo` field renders a git icon corner link
  (github.com / bitbucket.org / Gitea URLs supported).
- All cards: `.card__title` and `.card__desc` are now display:block, so
  the description always starts on its own line.
- Added data/links.schema.json (JSON Schema 2020-12) describing the
  full content model. Both data files reference it via `$schema` for
  github.dev / VS Code autocomplete.
- New GitHub Action validates data/links.json on every push/PR:
  jq syntax check + ajv-cli schema validation.
This commit is contained in:
Joel Brock
2026-05-26 20:21:10 -07:00
parent 89d7e8d8ca
commit 13d646b673
6 changed files with 312 additions and 6 deletions
+193
View File
@@ -0,0 +1,193 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dlstack/links.schema.json",
"title": "dlstack links file",
"description": "Content document for a dlstack site. Lives at data/links.json and is fetched at runtime by assets/js/app.js.",
"type": "object",
"additionalProperties": false,
"required": ["profile", "sections"],
"properties": {
"$schema": { "type": "string", "description": "Reference to this schema for editor autocomplete." },
"profile": {
"type": "object",
"description": "Hero block at the top of the page.",
"additionalProperties": true,
"required": ["name"],
"properties": {
"name": { "type": "string" },
"handle": { "type": "string" },
"tagline": { "type": "string" },
"bio": { "type": "string" },
"location": { "type": "string" },
"avatar": { "type": "string", "description": "Optional avatar image URL." }
}
},
"theme": {
"type": "object",
"additionalProperties": true,
"properties": {
"accent": { "type": "string", "description": "Accent color (CSS color, usually hex)." },
"template": { "type": "string", "enum": ["editorial", "swiss", "cosmos"] }
}
},
"sections": {
"type": "array",
"description": "Ordered list of content sections.",
"items": { "$ref": "#/$defs/section" }
},
"social": {
"type": "array",
"description": "Pill buttons under the hero bio.",
"items": { "$ref": "#/$defs/social" }
},
"footer": {
"type": "object",
"additionalProperties": true,
"properties": {
"copy": { "type": "string", "description": "Left-aligned footer line." },
"year": { "type": "string", "description": "'auto' to use current year, or a fixed string." },
"editUrl": { "type": "string", "format": "uri", "description": "Optional 'Edit on GitHub' link, e.g. https://github.com/you/repo/edit/main/data/links.json. When set, an unobtrusive link appears in the footer." }
}
}
},
"$defs": {
"section": {
"type": "object",
"additionalProperties": false,
"required": ["id", "label", "items"],
"properties": {
"id": { "type": "string" },
"label": { "type": "string" },
"kicker": { "type": "string", "description": "Small line above the section label." },
"layout": { "type": "string", "enum": ["clients", "testimonials"], "description": "Special layout. Omit for the default bento grid." },
"headless": { "type": "boolean", "description": "Render section body without the header — useful for chaining a continuation onto the previous section." },
"items": { "type": "array", "items": { "$ref": "#/$defs/item" } }
}
},
"item": {
"oneOf": [
{ "$ref": "#/$defs/itemLink" },
{ "$ref": "#/$defs/itemCard" },
{ "$ref": "#/$defs/itemYouTube" },
{ "$ref": "#/$defs/itemPortfolio" },
{ "$ref": "#/$defs/itemClient" },
{ "$ref": "#/$defs/itemTestimonial" }
]
},
"date": {
"type": "string",
"description": "Optional date. Accepts ISO 'YYYY', 'YYYY-MM', 'YYYY-MM-DD', or free-form strings like '1837'.",
"pattern": "^.+$"
},
"itemLink": {
"type": "object",
"additionalProperties": false,
"required": ["type", "title", "url"],
"properties": {
"type": { "const": "link" },
"title": { "type": "string" },
"url": { "type": "string", "format": "uri-reference" },
"description": { "type": "string" },
"image": { "type": "string", "description": "Custom logo URL. Overrides the auto-fetched favicon." },
"icon": { "type": "string", "description": "Alias for 'image'." },
"featured": { "type": "boolean" },
"date": { "$ref": "#/$defs/date" }
}
},
"itemCard": {
"type": "object",
"additionalProperties": false,
"required": ["type", "title"],
"properties": {
"type": { "const": "card" },
"title": { "type": "string" },
"url": { "type": "string", "format": "uri-reference", "description": "Primary link. When present, the title becomes a link." },
"repo": { "type": "string", "format": "uri", "description": "Optional secondary link to the project's source repo. Renders a git icon in the corner. Supports github.com, bitbucket.org, and Gitea/self-hosted URLs." },
"description": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } },
"featured": { "type": "boolean", "description": "Hero card — spans 8/12 columns, fills with accent color." },
"date": { "$ref": "#/$defs/date" }
}
},
"itemYouTube": {
"type": "object",
"additionalProperties": false,
"required": ["type", "id", "title"],
"properties": {
"type": { "const": "youtube" },
"id": { "type": "string", "description": "YouTube video ID (the 11-char string after v=)." },
"title": { "type": "string" },
"featured": { "type": "boolean", "description": "When true, card spans the wider layout." },
"date": { "$ref": "#/$defs/date" }
}
},
"itemPortfolio": {
"type": "object",
"additionalProperties": false,
"required": ["type", "image"],
"properties": {
"type": { "const": "portfolio" },
"image": { "type": "string" },
"title": { "type": "string" },
"description": { "type": "string" },
"url": { "type": "string", "format": "uri-reference" },
"ratio": { "type": "string", "pattern": "^\\d+\\s*:\\s*\\d+$", "description": "Aspect ratio as 'W:H' (e.g. '16:9', '5:2'). Default '5:2'." },
"date": { "$ref": "#/$defs/date" }
}
},
"itemClient": {
"type": "object",
"additionalProperties": false,
"required": ["type", "title"],
"properties": {
"type": { "const": "client" },
"title": { "type": "string" },
"url": { "type": "string", "format": "uri-reference" },
"image": { "type": "string" },
"icon": { "type": "string", "description": "Alias for 'image'." },
"date": { "$ref": "#/$defs/date" }
}
},
"itemTestimonial": {
"type": "object",
"additionalProperties": false,
"required": ["type", "quote"],
"properties": {
"type": { "const": "testimonial" },
"quote": { "type": "string" },
"name": { "type": "string" },
"role": { "type": "string" },
"org": { "type": "string" },
"url": { "type": "string", "format": "uri-reference" },
"image": { "type": "string" },
"icon": { "type": "string", "description": "Alias for 'image'." },
"date": { "$ref": "#/$defs/date" }
}
},
"social": {
"type": "object",
"additionalProperties": false,
"required": ["label", "url"],
"properties": {
"label": { "type": "string" },
"url": { "type": "string", "format": "uri-reference" },
"icon": { "type": "string", "description": "Icon key (e.g. 'github', 'linkedin', 'mail', 'rss')." }
}
}
}
}