Skip to content
Merged
7 changes: 5 additions & 2 deletions COPY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ These exact strings. Do not paraphrase them per page.
| Demo provenance | {Agent logo} {model}, such as Opus 5.5 | guidance only: the recording line on the Skill page panel and the homepage stage. Screen readers and the title get "Recorded with {agent}, {model}". No date and no GitHub link: the Skill page is where visitors inspect a Skill |
| Demo outdated | Recorded on an older version of this Skill. | guidance only: under the Demo panel when the Skill moved past the recorded commit |
| Home section: changes | Keep up with skill changes. | guidance only: the homepage section heading for watching and the digest |
| Home section: why | What skilld does differently. | guidance only: the homepage Why band heading, after trending. No line under it: the three columns from `HOME_WHY_REASONS` follow directly, each a title, `summary`, and picture. Their skills.sh lines stay on `/vs/skills-sh` |
| Why trust line | Built by Harlan Wilton Β· Compare with skills.sh | guidance only: the line that closes the homepage Why band. The avatar and name link Harlan's GitHub, and the button opens `/vs/skills-sh`. The columns carry every claim, so the line carries none |
| Why reasons | Human first Β· Preview the output Β· Independent and open source Β· Open trending weights Β· No more skill bloat Β· Know what it runs Β· Know what it costs Β· No telemetry | guidance only: the reason titles. Each has a one-line `summary` for the homepage and a full `line` for the comparison. The homepage shows the first three, which Harlan chose on 2026-10-07. `WHY_REASONS` in `app/utils/why-skilld.ts` holds each title, its line, and the sourced skills.sh line, and both surfaces read it. The Preview line opens with the Demos line above. Open trending weights links the ranking code, so its weights must match `shared/trending-skill-score.ts`. Know what it runs never claims a Skill is safe |
| Comparison H1 | skilld vs skills.sh | guidance only: the `/vs/skills-sh` H1. Its lead says what skills.sh is before what skilld is. Every skills.sh fact carries its source link and the date it was checked |
| Home section: authoring | Write a skill for your project. | guidance only: the homepage section heading for making a Skill |
| Nav: developers | Developers | guidance only: the header menu trigger, and the group label in the mobile menu. The menu holds the four items below |
| Nav: CLI | CLI Β· Search, run, install, and keep Skills current. | guidance only: the first compact row in the Developers menu: name, line. Links `/cli`. The mobile menu shows the name only. The line is the CLI line, so change that first |
Expand All @@ -66,7 +70,7 @@ These exact strings. Do not paraphrase them per page.
| CLI claims line | Open-source CLI Β· No telemetry Β· 19 Agent targets | guidance only: the `/cli` claims line. The page counts the `--agent` values of `skilld install --help` |
| CLI install label | Install the CLI | guidance only: the label beside the CLI install chip on `/cli`. A switch above the chip picks `macOS / Linux`, `Windows`, or `npm`, and `macOS / Linux` is preselected |
| CLI install consequence | One native binary. It upgrades itself from signed releases. Β· Needs Node.js. npm handles upgrades. | guidance only: the line under the CLI install chip on `/cli`. The first is for `macOS / Linux` and `Windows`, the second for `npm`. `crates/skilld-command/src/upgrade.rs` in the CLI decides both. If it changes, change these strings |
| Claim: open source | open-source CLI | guidance only: the CLI. Never call skilld.dev or its site open source |
| Claim: open source | open source | guidance only: the CLI and the site. `skilld-dev/skilld` and `skilld-dev/skilld.dev` are both public under MIT, checked 2026-10-07. If either repository goes private, narrow the claim to the one a reader can clone. "No telemetry" still names the CLI only |
| Claim: telemetry | no telemetry | guidance only: the CLI. Its README states that it sends no telemetry or analytics. Never say it of the site, which keeps anonymous analytics |
| Claim: privacy | analytics without cookies or IPs | guidance only: analytics and privacy copy. Never the bare "privacy-friendly" |
| Claim: comparison | skills.sh alternative | guidance only: always a link to `/vs/skills-sh` |
Expand Down Expand Up @@ -225,7 +229,6 @@ near-miss is also banned.
| people (for our audience) | dev, devs | They are developers, and "people" is a vaguer word doing a smaller job. "Developers" in full is fine where the short form reads clipped |
| first person in a CTA | the content, then the destination | "We send this every Monday" spends the words on us. "Trending skills to your inbox every Monday" gives the reader both things that matter |
| privacy-friendly (bare) | analytics without cookies or IPs | A bare privacy claim cannot be checked. The mechanism can |
| open source (for the site or skilld.dev) | open-source CLI | A reader must be able to clone what the claim names, and the claim names the CLI |

**Exception, the humans-versus-generated claim.** When the contrast is authorship by a human
against machine generation, "human" and "person" are the right words and "dev" weakens the
Expand Down
105 changes: 105 additions & 0 deletions app/components/HomeWhy.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import type { RecentPullRequest } from '#shared/open-source-pull-requests'
import type { TrendingBoardRow } from '#shared/trending-range'
import type { HomeDemoItem } from '~/utils/home-demos'
import { HOME_WHY_REASONS, WHY_REASONS } from '~/utils/why-skilld'
import WhyTrustLine from './why/_WhyTrustLine.vue'
import WhyVisual from './why/_WhyVisual.vue'

/**
* What skilld does differently from skills.sh, as three columns: a title, one
* line, and a small picture of the real interface. The skills.sh side of each
* reason lives on `/vs/skills-sh`, so the homepage stays light.
*
* From 1024px the columns share one subgrid, so the titles, lines and pictures
* each sit on one row, and every column ends at the same height.
*/
const { trendingRow, demos, pulls } = defineProps<{
/** The first trending row ranked by posts, for the people picture. */
trendingRow: TrendingBoardRow | null
/** The approved demos, for the previews picture. */
demos: readonly HomeDemoItem[]
/** Recently merged pull requests, for the open source picture. */
pulls: readonly RecentPullRequest[]
}>()

const reasons = HOME_WHY_REASONS.map(id => WHY_REASONS[id])
</script>

<template>
<section id="why" class="home-wm" aria-labelledby="why-heading">
<span class="home-watermark" aria-hidden="true">Why</span>
<div class="mx-auto max-w-6xl px-4 py-12 sm:px-6 md:py-16">
<h2 id="why-heading" class="home-h2 text-balance">
What skilld <span class="home-ink">does differently</span>.
</h2>

<ul class="home-why mt-8 list-none p-0">
<li v-for="reason in reasons" :key="reason.id" class="home-why__col">
<h3 class="home-why__title">
<UIcon :name="reason.icon" class="size-4 shrink-0 text-muted" aria-hidden="true" />
{{ reason.title }}
</h3>
<p class="home-why__line">
{{ reason.summary }}
</p>
<WhyVisual :id="reason.id" :trending-row="trendingRow" :demos :pulls compact class="home-why__visual" />
</li>
</ul>

<WhyTrustLine class="mt-8" />
</div>
</section>
</template>

<style scoped>
.home-why {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 2.5rem;
}

.home-why__col {
display: flex;
min-width: 0;
flex-direction: column;
}

/* One subgrid: each row takes its tallest cell, so the pictures stretch to match. */
@media (min-width: 64rem) {
.home-why {
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: auto auto 1fr;
column-gap: 1.5rem;
row-gap: 0;
}

.home-why__col {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
}
}

.home-why__title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
font-weight: 600;
line-height: 1.5rem;
color: var(--ui-text-highlighted);
}

.home-why__line {
margin-top: 0.25rem;
font-size: 0.875rem;
line-height: 1.6;
color: var(--ui-text-muted);
text-wrap: pretty;
}

.home-why__visual {
margin-top: 1rem;
}
</style>
103 changes: 103 additions & 0 deletions app/components/why/_WhyBehaviorsVisual.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script setup lang="ts">
import WhyPanel from './_WhyPanel.vue'

/**
* Skill behaviors, drawn small. The first list is what `anthropics/skills/pdf`
* asks an Agent to do, as production listed it on 2026-10-07, each with where
* it appears. The second is the set the released CLI holds for approval:
* a remote `skilld run` stops before any of them and changes nothing until the
* user allows it. Labels are the rule labels from `skilld-protocol`.
*/
const SHOWN = [
{ icon: 'i-lucide-terminal', label: 'Runs shell commands', where: 'SKILL.md:192' },
{ icon: 'i-lucide-file-code', label: 'Ships scripts', where: '8 files' },
{ icon: 'i-lucide-package', label: 'Installs or runs packages', where: 'SKILL.md:235' },
]

const HELD = [
'Runs code downloaded from the network',
'Reads credential files or tokens',
'Contains invisible characters',
]

/** Approval behaviors past the three listed: root commands and destructive deletes. */
const MORE_HELD = 2
</script>

<template>
<WhyPanel label="anthropics/skills/pdf Β· Skill behaviors">
<ul class="why-behaviors" aria-label="What the pdf Skill asks your agent to do">
<li v-for="behavior in SHOWN" :key="behavior.label" class="why-behaviors__row">
<UIcon :name="behavior.icon" class="why-behaviors__icon text-muted" aria-hidden="true" />
<span class="why-behaviors__label">{{ behavior.label }}</span>
<span class="why-behaviors__where">{{ behavior.where }}</span>
</li>
</ul>
<p class="why-behaviors__heading">
<code>skilld run</code> waits for your approval:
</p>
<ul class="why-behaviors" aria-label="Behaviors skilld run holds until you approve them">
<li v-for="label in HELD" :key="label" class="why-behaviors__row">
<UIcon name="i-lucide-triangle-alert" class="why-behaviors__icon text-warning" aria-hidden="true" />
<span class="why-behaviors__label">{{ label }}</span>
</li>
<li class="why-behaviors__row why-behaviors__more">
+ {{ MORE_HELD }} more
</li>
</ul>
</WhyPanel>
</template>

<style scoped>
.why-behaviors {
display: grid;
gap: 0.125rem;
margin: 0;
padding: 0;
list-style: none;
}

.why-behaviors__row {
display: flex;
min-width: 0;
align-items: center;
gap: 0.5rem;
min-height: 1.5rem;
}

.why-behaviors__icon {
flex: none;
width: 0.875rem;
height: 0.875rem;
}

.why-behaviors__label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--font-sans);
font-size: 0.8125rem;
color: var(--ui-text);
}

.why-behaviors__where {
flex: none;
margin-left: auto;
color: var(--ui-text-dimmed);
}

.why-behaviors__more {
padding-left: 1.375rem;
color: var(--ui-text-dimmed);
}

.why-behaviors__heading {
margin-top: 0.875rem;
margin-bottom: 0.375rem;
padding-top: 0.75rem;
border-top: 1px dashed var(--ui-border);
/* Normal case, so the name keeps its lowercase (COPY.md). */
color: var(--ui-text-muted);
}
</style>
117 changes: 117 additions & 0 deletions app/components/why/_WhyCostVisual.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script setup lang="ts">
import WhyPanel from './_WhyPanel.vue'

/**
* What a Skill costs an Agent's context, by when each part loads, as the
* Skill page counts and names it: four bytes per token. The name and description load
* in every session, SKILL.md when the Agent uses the Skill, and the other
* Markdown only when SKILL.md sends the Agent there. Scripts run, so only
* their output lands. `anthropics/skills/pdf` as production listed it on
* 2026-10-07. Each bar is drawn in stone dots, its length to scale.
*/
interface Stage {
when: string
what: string
tokens: number
label: string
}

const STAGES: Stage[] = [
{ when: 'Always', what: 'name and description', tokens: 111, label: 'β‰ˆ111' },
{ when: 'When used', what: 'SKILL.md', tokens: 1887, label: 'β‰ˆ1.9k' },
{ when: 'On demand', what: 'forms.md, reference.md', tokens: 7137, label: 'β‰ˆ7.1k' },
]

const MAX_TOKENS = Math.max(...STAGES.map(stage => stage.tokens))

function barWidth(tokens: number): string {
// A floor, so the smallest stage still draws a dot or two.
return `${Math.max(tokens / MAX_TOKENS * 100, 2.5)}%`
}
</script>

<template>
<WhyPanel label="anthropics/skills/pdf Β· 11 files">
<ul class="why-cost" aria-label="Tokens the pdf Skill adds to your agent's context">
<li v-for="stage in STAGES" :key="stage.when" class="why-cost__stage">
<span class="why-cost__head">
<span class="why-cost__when">{{ stage.when }}</span>
<span class="why-cost__tokens">{{ stage.label }} tokens</span>
</span>
<span class="why-cost__track" aria-hidden="true">
<span class="why-cost__bar" :style="{ width: barWidth(stage.tokens) }" />
</span>
<span class="why-cost__what">{{ stage.what }}</span>
</li>
</ul>
<p class="why-cost__scripts">
<span>scripts/ Β· 8 files</span>
<span>run, never read into context</span>
</p>
</WhyPanel>
</template>

<style scoped>
.why-cost {
display: grid;
gap: 0.875rem;
margin: 0 0 0.875rem;
padding: 0;
list-style: none;
}

.why-cost__stage {
display: grid;
gap: 0.25rem;
}

.why-cost__head {
display: flex;
align-items: baseline;
gap: 0.5rem;
}

.why-cost__when {
font-family: var(--font-sans);
font-size: 0.8125rem;
color: var(--ui-text-highlighted);
}

.why-cost__tokens {
margin-left: auto;
color: var(--ui-text);
font-variant-numeric: tabular-nums;
}

/* The brand atom: a bar is a run of stone dots, never a fill. */
.why-cost__track {
display: block;
height: 0.75rem;
}

.why-cost__bar {
display: block;
height: 100%;
background-image: radial-gradient(circle, var(--ui-text-muted) 1.6px, transparent 1.9px);
background-size: 6px 6px;
background-position: 0 0;
}

.why-cost__what {
color: var(--ui-text-dimmed);
}

.why-cost__scripts {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.5rem;
margin-top: auto;
padding-top: 0.875rem;
border-top: 1px dashed var(--ui-border);
color: var(--ui-text-dimmed);
}

.why-cost__scripts > span:last-child {
margin-left: auto;
}
</style>
Loading
Loading