From 413a6f5d5ff9ac8f620e2235fa63cd559f81e6da Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 5 Oct 2026 23:59:58 +1100 Subject: [PATCH 1/4] feat(brand): explore portable Skill knowledge --- .../brand-experiments/PortableKnowledge.vue | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 app/components/brand-experiments/PortableKnowledge.vue diff --git a/app/components/brand-experiments/PortableKnowledge.vue b/app/components/brand-experiments/PortableKnowledge.vue new file mode 100644 index 00000000..7a24195c --- /dev/null +++ b/app/components/brand-experiments/PortableKnowledge.vue @@ -0,0 +1,280 @@ + + + + + From 9af9588b692334a4d1ceba696ef48c8218c90bce Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 00:00:09 +1100 Subject: [PATCH 2/4] feat(brand): explore freshness and human provenance --- .../brand-experiments/HumanProvenance.vue | 88 +++++++++++++++++++ .../brand-experiments/KnowledgeFreshness.vue | 74 ++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 app/components/brand-experiments/HumanProvenance.vue create mode 100644 app/components/brand-experiments/KnowledgeFreshness.vue diff --git a/app/components/brand-experiments/HumanProvenance.vue b/app/components/brand-experiments/HumanProvenance.vue new file mode 100644 index 00000000..f2f9e0df --- /dev/null +++ b/app/components/brand-experiments/HumanProvenance.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/app/components/brand-experiments/KnowledgeFreshness.vue b/app/components/brand-experiments/KnowledgeFreshness.vue new file mode 100644 index 00000000..be8549d7 --- /dev/null +++ b/app/components/brand-experiments/KnowledgeFreshness.vue @@ -0,0 +1,74 @@ + + + + + From ac658c73f38b19063be9215084b2e720ed819f7f Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 00:00:31 +1100 Subject: [PATCH 3/4] feat(brand): explore session boundary and Skill bloat visuals --- .../brand-experiments/SessionBoundary.vue | 74 +++++++++++++++++ .../brand-experiments/SkillBloat.vue | 80 +++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 app/components/brand-experiments/SessionBoundary.vue create mode 100644 app/components/brand-experiments/SkillBloat.vue diff --git a/app/components/brand-experiments/SessionBoundary.vue b/app/components/brand-experiments/SessionBoundary.vue new file mode 100644 index 00000000..177c5bf9 --- /dev/null +++ b/app/components/brand-experiments/SessionBoundary.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/app/components/brand-experiments/SkillBloat.vue b/app/components/brand-experiments/SkillBloat.vue new file mode 100644 index 00000000..bf711e41 --- /dev/null +++ b/app/components/brand-experiments/SkillBloat.vue @@ -0,0 +1,80 @@ + + + + + From a73e4db74239d30879ee1b2ae856a88ade512ca6 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 6 Oct 2026 00:14:42 +1100 Subject: [PATCH 4/4] feat(brand): add five interactive visual experiments --- COPY.md | 36 ++++++++ DESIGN.md | 7 ++ .../brand-experiments/HumanProvenance.vue | 4 +- .../brand-experiments/KnowledgeFreshness.vue | 4 +- .../brand-experiments/SessionBoundary.vue | 4 +- .../brand-experiments/SkillBloat.vue | 4 +- app/pages/brand-kit/experiments.vue | 90 +++++++++++++++++++ app/pages/brand-kit/index.vue | 36 ++++++++ nuxt.config.ts | 4 +- test/a11y.spec.ts | 17 ++++ 10 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 app/pages/brand-kit/experiments.vue create mode 100644 app/pages/brand-kit/index.vue diff --git a/COPY.md b/COPY.md index bea4a98a..15d37e9c 100644 --- a/COPY.md +++ b/COPY.md @@ -9,6 +9,42 @@ The canonical source for skilld's verbal identity. Pages, meta tags and registry from here; when a canonical string changes, change it here first, then propagate. A string that contradicts this file is a bug. +## Brand kit experiment register + +`/brand-kit/experiments` uses exploratory copy. It does not replace the approved tagline or homepage hero. +Use existing product nouns. Label illustrative states without implying measured results or automatic updates. + +- Brand kit: `Explore the visual stories behind skilld.` +- Experiment link: `Five brand experiments`. +- Experiment summary: `Session, bloat, freshness, provenance, and portability.` +- Badge summary: `A small mark that links a README to its Skill or Repository.` +- Experiment title: `Five ways to show what skilld solves.` +- Experiment description: `Each experiment starts with a product limit. These are exploratory visuals, not adopted brand assets.` +- Selectors: `Session`, `Bloat`, `Freshness`, `Provenance`, `Portability`. +- Theme controls: `Light mode`, `Dark mode`. + +| Experiment | Product limit | Review question | +| --- | --- | --- | +| Session | Run a Skill without keeping it. | Can the session boundary become a recognisable brand shape? | +| Bloat | Keep only the Skills you need. | Can a dense weave resolving into one thread show the cost of Skill bloat? | +| Freshness | The source can change while your copy stays still. | Can a visible change seam give watch its own visual language? | +| Provenance | Knowledge has a human source. | Can a continuous thread make source ownership visible? | +| Portability | The Skill stays the same when you change Agents. | Can one stable shape moving across contexts express independence? | + +Specimen strings: + +| Specimen | Heading and explanation | Controls and labels | +| --- | --- | --- | +| Session | `Knowledge for this session.` · `Run a Skill for the current session. Nothing lands in your repository.` | `End session`, `Run again`, `source`, `session`, `no files written`, `SKILL.md` | +| Bloat | `One task. One Skill.` · `Give this task one Skill. Install only what earns a place.` | `Focus one Skill`, `Show all Skills`, `one task · one Skill`, `what does this task need?`, `SKILL.md` | +| Freshness | `See when knowledge moves.` · `The source changes. Your local copy stays still. Watch makes the difference visible.` | `Hide differences`, `Show differences`, `Replay`, `Local copy`, `Illustrative source`, `Kept in your project`, `Changed at the source`, `Watch for changes` | +| Provenance | `Knowledge has an author.` · `A Skill reaches your Agent. The thread leads back to the person who wrote it.` | `Hide source thread`, `Trace the source`, `Replay`, `Your Agent`, `Read the Skill`, `Illustrative source`, `Written by a human.`, `The author keeps the source.`, `Source stays attached.`, `SKILL.md` | +| Portability | `Same Skill. Your Agent.` · `The Agent changes. The knowledge stays yours.` | `Change Agent`, `Claude Code`, `Codex`, `Cursor`, `One Skill`, `SKILL.md`, `skilld`, `{Agent} selected. Same SKILL.md.` | + +Freshness footnote: `Illustration only. Watching reports changes. It does not update your local copy.` +Provenance footnote: `Illustrative source. The thread represents authorship, not a safety claim.` +Accessible state descriptions name the same illustrated transition. They make no additional product claim. + ## Canonical assets Comparison navigation uses “Compare Humanizer, Stop Slop, and No AI Slop” on the homepage, writing track, and featured Skill pages. diff --git a/DESIGN.md b/DESIGN.md index c8703885..c3c5e857 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -68,6 +68,13 @@ components: ## Design Principles +### Brand kit experiments + +`/brand-kit/experiments` explores five product constraints through isolated visual specimens. +New shapes and motion there are exploratory exceptions, not adopted brand assets. +Keep warm stone, rose, readable labels, keyboard controls, and reduced-motion alternatives. +The homepage and shared components retain the existing identity rules. + ### 1. Quiet **"Confidence is quiet. The interface recedes; the content speaks."** diff --git a/app/components/brand-experiments/HumanProvenance.vue b/app/components/brand-experiments/HumanProvenance.vue index f2f9e0df..f5c86214 100644 --- a/app/components/brand-experiments/HumanProvenance.vue +++ b/app/components/brand-experiments/HumanProvenance.vue @@ -58,7 +58,7 @@ const revision = ref(0) .experiment-heading p { max-width: 40ch; font-size: 16px; line-height: 1.6; color: var(--ui-text-muted); margin: 0; } .provenance-stage { position: relative; height: 330px; overflow: hidden; border-block: 1px solid var(--ui-border); margin: 24px 0 16px; } .skill-leaf { position: absolute; left: 8%; top: 28px; width: 49%; padding: 16px; border: 1px solid var(--ui-border-accented); background: var(--ui-bg); transform: rotate(-3deg); } -.leaf-label, .leaf-end, .trace-label { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; } +.leaf-label, .leaf-end, .trace-label { font-family: var(--font-mono); font-size: 14px; line-height: 1.5; } .leaf-label { display: block; color: var(--ui-text-muted); margin-bottom: 8px; } .skill-leaf strong { font-family: var(--font-mono); font-size: 22px; font-weight: 500; } .written-lines { display: flex; gap: 7px; flex-direction: column; padding: 16px 0; } @@ -83,6 +83,6 @@ const revision = ref(0) .experiment-controls :deep(button) { min-height: 44px; } .experiment-footnote { font-size: 14px; line-height: 1.6; color: var(--ui-text-muted); max-width: 58ch; margin: 12px 0 0; } @keyframes thread-reveal { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } } -@container (max-width: 400px) { .skill-leaf { left: 5%; width: 62%; padding: 12px; } .source-leaf { left: 23%; right: 4%; padding-left: 24px; } .trace-label { top: 208px; left: 4%; font-size: 11px; } .thread-turn { right: 10%; } .thread-top { width: 48%; } } +@container (max-width: 400px) { .skill-leaf { left: 5%; width: 62%; padding: 12px; } .source-leaf { left: 23%; right: 4%; padding-left: 24px; } .trace-label { top: auto; bottom: 0; left: 4%; font-size: 14px; } .thread-turn { right: 10%; } .thread-top { width: 48%; } } @media (prefers-reduced-motion: reduce) { .thread-path, .trace-label { animation: none !important; transition: none; } } diff --git a/app/components/brand-experiments/KnowledgeFreshness.vue b/app/components/brand-experiments/KnowledgeFreshness.vue index be8549d7..44ce3e8b 100644 --- a/app/components/brand-experiments/KnowledgeFreshness.vue +++ b/app/components/brand-experiments/KnowledgeFreshness.vue @@ -53,7 +53,7 @@ const revision = ref(0) .sheet { position: absolute; width: 48%; height: 226px; border: 1px solid var(--ui-border-accented); background: var(--ui-bg); padding: 16px; display: flex; flex-direction: column; justify-content: space-between; } .sheet-local { left: 8%; top: 68px; transform: rotate(-7deg); } .sheet-source { left: 44%; top: 27px; transform: rotate(7deg); animation: source-moves 360ms ease-out both; } -.sheet-caption, .sheet-foot, .change-seam { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; } +.sheet-caption, .sheet-foot, .change-seam { font-family: var(--font-mono); font-size: 14px; line-height: 1.5; } .sheet-caption { color: var(--ui-text); } .sheet-foot { color: var(--ui-text-muted); } .dot-matrix { display: grid; grid-template-columns: repeat(12, 1fr); gap: 7px; padding-block: 16px; } @@ -69,6 +69,6 @@ const revision = ref(0) .experiment-controls :deep(button) { min-height: 44px; } .experiment-footnote { font-size: 14px; line-height: 1.6; color: var(--ui-text-muted); max-width: 58ch; margin: 12px 0 0; } @keyframes source-moves { from { transform: rotate(-7deg) translate(-16px, 16px); } to { transform: rotate(7deg); } } -@container (max-width: 400px) { .sheet { padding: 12px; width: 54%; } .sheet-local { left: 3%; } .sheet-source { left: 41%; } .dot-matrix { gap: 6px; } .sheet-foot { font-size: 11px; } } +@container (max-width: 400px) { .sheet { padding: 12px; width: 54%; } .sheet-local { left: 3%; } .sheet-source { left: 41%; } .dot-matrix { gap: 6px; } .sheet-foot { font-size: 14px; } } @media (prefers-reduced-motion: reduce) { .sheet-source { animation: none; } .change-seam { transition: none; } } diff --git a/app/components/brand-experiments/SessionBoundary.vue b/app/components/brand-experiments/SessionBoundary.vue index 177c5bf9..1554c308 100644 --- a/app/components/brand-experiments/SessionBoundary.vue +++ b/app/components/brand-experiments/SessionBoundary.vue @@ -51,7 +51,7 @@ const sessionOpen = ref(true) .session-specimen__ribbon { position: absolute; left: 8%; top: 121px; width: 55%; height: 76px; display: flex; align-items: center; gap: 18px; padding: 0 24px; background: var(--ui-primary); color: var(--color-white); clip-path: polygon(0 0, 94% 0, 100% 50%, 94% 100%, 0 100%, 5% 50%); transform: rotate(-11deg); transition: transform 900ms cubic-bezier(.22,.8,.22,1), opacity 300ms 450ms; } .session-specimen__ribbon > span:first-child { font-family: var(--font-mono); font-size: clamp(18px, 3vw, 25px); font-weight: 500; white-space: nowrap; } .session-specimen__ribbon-lines { width: 100%; height: 24px; background: repeating-linear-gradient(to bottom, currentColor 0 1px, transparent 1px 7px); opacity: .55; } -.session-specimen__source, .session-specimen__trace { position: absolute; font: 12px var(--font-mono); } +.session-specimen__source, .session-specimen__trace { position: absolute; font: 14px var(--font-mono); } .session-specimen__source { left: 5%; top: 40px; color: var(--ui-text-muted); } .session-specimen__trace { right: 5%; bottom: 26px; color: var(--ui-text-muted); } .is-ended .session-specimen__ribbon { transform: translateX(190%) rotate(-11deg); opacity: 0; } @@ -59,7 +59,7 @@ const sessionOpen = ref(true) .session-specimen__caption > div { flex: 1; } .session-specimen h3 { margin: 0; font-family: var(--font-sans); font-size: clamp(23px, 4vw, 30px); font-weight: 600; letter-spacing: -.04em; line-height: 1.2; } .session-specimen p { font-size: 14px; line-height: 1.6; margin: 12px 0 0; color: var(--ui-text-muted); } -.session-specimen button { flex-shrink: 0; display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 0 12px; border: 1px solid var(--ui-border); font: 12px var(--font-mono); color: var(--ui-text); background: var(--ui-bg); cursor: pointer; } +.session-specimen button { flex-shrink: 0; display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 0 12px; border: 1px solid var(--ui-border); font: 14px var(--font-mono); color: var(--ui-text); background: var(--ui-bg); cursor: pointer; } .session-specimen button:hover { border-color: var(--ui-text-muted); } .session-specimen button:focus-visible { outline: 2px solid var(--ui-primary); outline-offset: 4px; } @media (max-width: 450px) { diff --git a/app/components/brand-experiments/SkillBloat.vue b/app/components/brand-experiments/SkillBloat.vue index bf711e41..81134f6b 100644 --- a/app/components/brand-experiments/SkillBloat.vue +++ b/app/components/brand-experiments/SkillBloat.vue @@ -57,7 +57,7 @@ const fragments = Array.from({ length: 25 }, (_, index) => ({ .bloat-specimen__ribbon { position: relative; width: 100%; height: 60px; padding-inline: 20px; display: flex; align-items: center; gap: 8px; background: var(--ui-primary); color: var(--color-white); transform: rotate(-9deg) translateY(-30px); transition: transform 700ms; } .bloat-specimen__ribbon span { font: 500 20px var(--font-mono); padding-right: 12px; } .bloat-specimen__ribbon i { width: 1px; height: 24px; flex: 1; border-block: 1px solid currentColor; opacity: .6; } -.bloat-specimen__annotation { position: absolute; bottom: 24px; left: 24px; right: 24px; font: 12px var(--font-mono); color: var(--ui-text-muted); } +.bloat-specimen__annotation { position: absolute; bottom: 24px; left: 24px; right: 24px; font: 14px var(--font-mono); color: var(--ui-text-muted); } .is-focused .bloat-specimen__fragment { opacity: .08; transform: translateY(20px) rotate(0deg); } .is-focused .bloat-specimen__number { opacity: 1; transform: translateY(0); } .is-focused .bloat-specimen__ribbon { transform: rotate(0) translateY(14px); } @@ -65,7 +65,7 @@ const fragments = Array.from({ length: 25 }, (_, index) => ({ .bloat-specimen__caption > div { flex: 1; } .bloat-specimen h3 { margin: 0; font: 600 clamp(23px, 4vw, 30px)/1.2 var(--font-sans); letter-spacing: -.04em; } .bloat-specimen p { font-size: 14px; line-height: 1.6; margin: 12px 0 0; color: var(--ui-text-muted); } -.bloat-specimen button { flex-shrink: 0; min-height: 44px; display: flex; align-items: center; gap: 12px; padding: 0 12px; border: 1px solid var(--ui-border); font: 12px var(--font-mono); color: var(--ui-text); background: var(--ui-bg); cursor: pointer; } +.bloat-specimen button { flex-shrink: 0; min-height: 44px; display: flex; align-items: center; gap: 12px; padding: 0 12px; border: 1px solid var(--ui-border); font: 14px var(--font-mono); color: var(--ui-text); background: var(--ui-bg); cursor: pointer; } .bloat-specimen button:hover { border-color: var(--ui-text-muted); } .bloat-specimen button:focus-visible { outline: 2px solid var(--ui-primary); outline-offset: 4px; } @media (max-width: 450px) { diff --git a/app/pages/brand-kit/experiments.vue b/app/pages/brand-kit/experiments.vue new file mode 100644 index 00000000..1d859025 --- /dev/null +++ b/app/pages/brand-kit/experiments.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/app/pages/brand-kit/index.vue b/app/pages/brand-kit/index.vue new file mode 100644 index 00000000..e2651d3e --- /dev/null +++ b/app/pages/brand-kit/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/nuxt.config.ts b/nuxt.config.ts index 5ceedb71..e18b778f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -604,6 +604,8 @@ export default defineNuxtConfig({ // kit page and Nuxt made it a route. The page went indexable today, so the // empty sibling redirects instead of shipping next to it. '/brand-kit/_GithubBadgePreview': { redirect: { to: '/brand-kit/github-badge', statusCode: 301 } } as any, + '/brand-kit': { headers: { 'x-robots-tag': 'noindex, nofollow' } }, + '/brand-kit/experiments': { headers: { 'x-robots-tag': 'noindex, nofollow' } }, // 2026-08-22: llms-full.txt inlined every page's markdown into one 28.5 MB // file; agents truncate or time out on it. llms.txt now links each page's // .md (aiReady.llmsTxt.markdownLinks), so the dump redirects there. 302 so @@ -806,7 +808,7 @@ export default defineNuxtConfig({ // `/learn` index is a 55-word card list, noindex since 2026-08-22 // (GOOGLE_RECOVERY.md). Articles stay indexable and sitemap-listed; only // the bare index leaves. Global so no child sitemap can re-adopt it. - exclude: ['/learn'], + exclude: ['/learn', '/brand-kit', '/brand-kit/experiments'], sitemaps: { pages: { includeAppSources: true, diff --git a/test/a11y.spec.ts b/test/a11y.spec.ts index 097f0356..2c8e98ea 100644 --- a/test/a11y.spec.ts +++ b/test/a11y.spec.ts @@ -85,6 +85,23 @@ afterEach(() => { }) describe('accessibility: components', () => { + it.each([ + 'SessionBoundary', + 'SkillBloat', + 'KnowledgeFreshness', + 'HumanProvenance', + 'PortableKnowledge', + ])('brand experiment %s has no accessibility violations', async (name) => { + const container = createIsolatedContainer() + const wrapper = await mountSuspended( + await loadComponent(`brand-experiments/${name}`), + { attachTo: container }, + ) + const results = await runAxe(container) + expect(results.violations, formatViolations(results)).toHaveLength(0) + wrapper.unmount() + }) + it('appLogo has no violations', async () => { const container = createIsolatedContainer() const wrapper = await mountSuspended(