501 technical brief submission: SUBMISSION.md — problem statement, methodology, live demo, and portfolio links for reviewers.
Package: @techystuff/feature-cards · Version: 1.1.1
Install guide: docs/INSTALL.md · Live demo: 501fun.humza-butt.space
Documentation hub: docs/README.md
One accessible, responsive, CMS-agnostic Web Component that replaces hard-coded feature-card images. Native browser APIs — Shadow DOM, container queries, constructable stylesheets — authored in strict TypeScript, shipped as zero-framework vanilla JS (~25 KiB gzip).
- Why this exists
- Quick start
- Install from npm
- Data model
- Public API
- CMS integration
- Demo page themes and motion
- Accessibility
- Architecture & methodology
- Development
- Testing
- Releasing
- Deployment
- Documentation index
- Licence
| You need… | This provides… |
|---|---|
| 501 landing-page stat cards (replace designer PNGs) | layout: "stat" + live demo card editor |
| Edit top / middle / bottom / icon without a designer | Four-field schema + editor UI |
| Per-card colour, rotation, and size | appearance + 501-* themes |
| Drop-in embed for any CMS | Custom Element + IIFE script tag |
| No framework lock-in | Vanilla JS core; optional React wrapper |
| Headless content | src fetch + WordPress / Contentful / Sanity adapters |
| No-JS fallback | Plain <a> children upgrade progressively |
| Style safety on unknown pages | Shadow DOM + --fc-* token theming API |
| Sidebar and full-width layouts | Container queries — not viewport breakpoints |
| Proof of quality | axe zero violations in CI; 90%+ unit coverage |
<script src="https://cdn.jsdelivr.net/npm/@techystuff/feature-cards@1.1.1/dist/feature-cards.iife.js" defer></script>
<feature-cards heading="Why teams choose us">
<script type="application/json">
{
"cards": [
{
"id": "satisfaction",
"title": "Satisfaction that sticks",
"figure": { "value": "97%", "label": "customer satisfaction", "trend": "up" },
"cta": { "label": "Read customer stories", "href": "/customers" },
"theme": "brand-blue"
}
]
}
</script>
</feature-cards>Pin versions and add SRI — see WordPress cookbook.
Full guide: docs/INSTALL.md — npm vs CDN, all usage patterns, exports, licence, smoke test.
npm install @techystuff/feature-cards@1.1.1| Entry | Import |
|---|---|
| Web Component | import '@techystuff/feature-cards' |
| Imperative API | import { createFeatureCards } from '@techystuff/feature-cards' |
| React wrapper | import { FeatureCards } from '@techystuff/feature-cards/react' |
| CDN (no build) | See INSTALL.md § CDN |
AGPL-3.0-only — commercial licence for closed-source use.
Maintainers: docs/NPM-PUBLISH.md.
import '@techystuff/feature-cards';
const el = document.querySelector('feature-cards');
el.data = {
cards: [{ id: 'a', title: 'Hello', cta: { label: 'Go', href: '/go' } }],
};<feature-cards heading="From plain links">
<a href="/docs" data-eyebrow="Guides" data-description="Integrate in an afternoon">
Read the documentation
</a>
</feature-cards><feature-cards src="https://cms.example.com/api/cards" adapter="wordpress"></feature-cards>import { createFeatureCards } from '@techystuff/feature-cards';
createFeatureCards({
target: '#cards-host',
src: 'https://cms.example.com/api/cards',
adapter: 'wordpress',
onReady: ({ count }) => console.log(`Rendered ${count} cards`),
});import { FeatureCards } from '@techystuff/feature-cards/react';
<FeatureCards
data={{ cards: [{ id: 'a', title: 'Hello', cta: { label: 'Go', href: '/go' } }] }}
onCardClick={({ id }) => console.log(id)}
/>All sources normalise to one JSON shape. Full reference: docs/SCHEMA.md.
| Editor label | JSON field |
|---|---|
| Top text | eyebrow |
| Middle text | figure.value |
| Bottom text | figure.label |
| Icon / image | media.src + alt |
Use layout: "stat" and optional appearance for colours, rotation, scale, and
min-height. Try the 501 feature cards — live editor at the top of the demo page.
{
"cards": [
{
"id": "guests",
"layout": "stat",
"eyebrow": "More than",
"figure": { "value": "12,000,000", "label": "delighted guests" },
"media": { "src": "/icons/lucide/users.svg", "alt": "" },
"theme": "501-green",
"appearance": { "background": "#c6f135", "rotateDeg": 0, "scale": 1 }
}
]
}{
"heading": "Optional section title",
"cards": [
{
"id": "unique-id",
"layout": "standard",
"title": "Required card title",
"eyebrow": "Optional kicker",
"description": "Supporting copy",
"figure": { "value": "97%", "label": "customer satisfaction", "trend": "up" },
"media": { "src": "/img.svg", "alt": "" },
"cta": { "label": "Learn more", "href": "/path" },
"theme": "brand-blue"
}
]
}Precedence (highest wins): data property → inline JSON → src + adapter → light-DOM links.
| Attribute | Type | Default | Description |
|---|---|---|---|
src |
URL | — | JSON endpoint to fetch card data |
adapter |
generic | wordpress | contentful | sanity |
generic |
CMS payload mapper |
columns |
1–6 |
auto-fit | Caps grid track count |
heading |
string | from data | Section heading (overrides data heading) |
heading-level |
1–6 |
2 |
Section heading level; card titles = level + 1 |
theme |
brand-blue | brand-green | brand-amber |
— | Host theme (per-card theme in data wins) |
| Property | Type | Description |
|---|---|---|
data |
FeatureCardsData | undefined |
Validated data; highest precedence. Reads back current render state. |
provenance |
object (readonly) | Inert authorship record (UUID, repo, licence) |
| Event | detail |
When |
|---|---|---|
featurecards:ready |
{ count } |
Render completed |
featurecards:error |
{ issues[], problem } |
Invalid data or fetch failure — never throws |
featurecards:cardclick |
{ id, card } |
Card link activated |
| Slot | Purpose |
|---|---|
| (default) | No-JS fallback / invalid-data fallback |
heading |
Replace generated section heading |
| Token | Purpose | Token | Purpose |
|---|---|---|---|
--fc-font |
Font stack | --fc-card-min |
Min card track width |
--fc-bg |
Section background | --fc-gap |
Grid gap |
--fc-fg |
Primary text | --fc-pad |
Card padding |
--fc-muted |
Secondary text | --fc-radius |
Corner radius |
--fc-accent |
Accent colour | --fc-shadow / --fc-shadow-hover |
Elevation |
--fc-card-bg |
Card background | --fc-ring |
Focus ring |
--fc-card-border |
Card border | --fc-transition |
Motion timing |
::part(section) · ::part(heading) · ::part(grid) · ::part(card) ·
::part(link) · ::part(eyebrow) · ::part(title) · ::part(description) ·
::part(figure) · ::part(value) · ::part(label) · ::part(media) · ::part(cta)
feature-cards {
--fc-accent: rebeccapurple;
--fc-radius: 4px;
}
feature-cards::part(card):hover {
outline: 2px solid rebeccapurple;
}TypeDoc: npm run docs:api → docs/api/ (CI uploads artefacts).
One canonical schema; adapters map CMS JSON into it.
<!-- WordPress REST (+ _embed for media) -->
<feature-cards src="https://site.com/wp-json/wp/v2/posts?_embed" adapter="wordpress"></feature-cards>
<!-- Contentful Delivery API -->
<feature-cards src="https://cdn.contentful.com/spaces/SPACE/entries?content_type=card&access_token=TOKEN" adapter="contentful"></feature-cards>
<!-- Sanity GROQ HTTP API -->
<feature-cards src="https://PROJECT.api.sanity.io/v2024-01-01/data/query/production?query=..." adapter="sanity"></feature-cards>
<!-- Already canonical JSON -->
<feature-cards src="/api/cards" adapter="generic"></feature-cards>| Guide | Topics |
|---|---|
| WordPress | functions.php, REST, imperative mount, SRI |
| Contentful | Content model, Delivery API, webhook → static JSON |
| Sanity | GROQ, HTTP API, Studio preview workflow |
New CMS = one ~40-line adapter + registry line. Mock Worker OpenAPI: live schema · source.
Stuck? → docs/TROUBLESHOOTING.md · docs/FAQ.md
The live demo includes a Vibe check picker — twelve parody-named page themes
(Corporate Daydream™, Pager Duty Noir, …). Themes swap via --page-* CSS
properties with animated crossfade; choice persists in localStorage
(fc-page-theme).
Scroll reveals, hero stagger, schema validation flashes, and component enter/hover
animations live in demo/motion/. Both layers honour prefers-reduced-motion.
Note: Page themes are demo-only — not part of the npm package. Production sites use
--fc-*component tokens. Full guide: docs/DEMO.md.
Semantic structure, configurable heading levels, single-link cards, trend announcements, keyboard-native interaction, reduced-motion and high-contrast support, and an axe-core CI gate at zero violations.
| Topic | Document |
|---|---|
| WCAG mapping, keyboard, SR behaviour | ACCESSIBILITY.md |
| FAQ | docs/FAQ.md |
| Tests | docs/TESTING.md |
| ADR | Decision |
|---|---|
| 0001 | Custom Element over framework |
| 0002 | Shadow DOM encapsulation |
| 0003 | Zod schema + adapters |
| 0004 | AGPL + canary provenance |
| 0005 | Container-query layout |
| 0006 | Demo themes & motion |
Narrative: ARCHITECTURE.md · Diagrams: docs/diagrams/architecture.md
npm run setup # first-time: env, deps, Playwright, build:lib, doctor
npm run dev # demo → http://localhost:5173
npm run serve:cms # mock CMS → http://localhost:8787/api/cards (second terminal)
npm run check # typecheck + lint + format + tests + size| Command | Output |
|---|---|
npm run docs:api |
TypeDoc → docs/api/ |
npm run cem / cem:check |
Custom Elements Manifest |
npm run sri |
IIFE Subresource Integrity hash |
npm run validate:cms -- <url> |
Smoke-test CMS JSON |
npm run rules:sync |
Mirror agent rules to .claude/ + .agents/ |
| Artefact | Purpose |
|---|---|
| custom-elements.json | CEM — IDE autocomplete |
| .vscode/html-custom-data.json | VS Code tag hints |
| docs/api/ | Generated API reference |
Contributors: CONTRIBUTING.md · Agents: AGENTS.md
npm run test:unit # Vitest
npm run test:contracts # MSW adapter contracts
npm run test:fuzz # Property-based schema
npm run test:a11y # axe zero violations
npm run test:e2e # Playwright demo flows
npm run test:visual # Screenshot regression (Chromium)
npm run test:browser # Web Test Runner
npm run check # All gatesFull matrix: docs/TESTING.md
npm run release:current
npm run release:patch # bump + changelog + tag
npm run release:minor # 1.0.x → 1.1.0
npm run release:patch -- --publish # tag + npm publish (use --otp= for 2FA)Playbook: docs/RELEASE.md · Stable v*.*.* tags publish to npm via publish-npm.yml (trusted publishing or NPM_TOKEN).
Production demo: 501fun.humza-butt.space
| Service | URL |
|---|---|
| Demo (Cloudflare Pages) | https://501fun.humza-butt.space |
| Mock CMS (Worker) | https://cms.501fun.humza-butt.space/api/cards |
Push to master triggers CI deploy (see config/site.json).
PRs receive *.pages.dev preview URLs.
- DNS —
501fun.humza-butt.spaceon Cloudflare zonehumza-butt.space - GitHub secrets —
CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID - Token permissions — Pages Edit, Workers Edit, Zone DNS Edit
Local deploy uses .env via scripts/run-wrangler.mjs — not wrangler login OAuth.
npm run deploy
npm run canary:verify -- https://501fun.humza-butt.space| Document | Description |
|---|---|
| docs/README.md | Master documentation hub |
| docs/SCHEMA.md | Canonical JSON reference |
| docs/FAQ.md | Frequently asked questions |
| docs/TROUBLESHOOTING.md | Symptom → fix guide |
| docs/TESTING.md | Test matrix and conventions |
| docs/DEMO.md | Demo page themes & motion |
| docs/RELEASE.md | Release playbook |
| docs/BRANCHING.md | Branch strategy |
| ARCHITECTURE.md | Design narrative |
| ACCESSIBILITY.md | a11y specification |
| SECURITY.md | Security & canary verification |
| CHANGELOG.md | Version history |
AGPL-3.0-only — © 2026 Humza Butt. See LICENSE and NOTICE.
You may read, run, and evaluate freely. Deploying modified versions as a network service requires offering corresponding source under the same licence. Commercial closed-source use requires a commercial licence.
| Document | Purpose |
|---|---|
| LEGAL.md | Enforcement guide — evidence, takedowns, contacts |
| TRADEMARK.md | Name and logo usage policy |
| TERMS_OF_USE.md | Live demo site terms |
| SECURITY.md | Canary watermark verification |
Inert authorship markers are verifiable: npm run canary:verify -- <url>.
