kishormorol commited on
Commit
6d5ebe3
Β·
verified Β·
1 Parent(s): 0bcda29

Upload 7 files

Browse files
Files changed (7) hide show
  1. data.js +0 -0
  2. favicon.svg +16 -0
  3. index.html +418 -17
  4. manifest.json +20 -0
  5. scripts.js +858 -0
  6. skills.json +0 -0
  7. styles.css +1573 -0
data.js ADDED
The diff for this file is too large to render. See raw diff
 
favicon.svg ADDED
index.html CHANGED
@@ -1,19 +1,420 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>SkillsAllYouNeed for LLMs β€” The Open Registry of AI Skills</title>
7
+
8
+ <!-- Favicon & PWA -->
9
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
10
+ <link rel="apple-touch-icon" href="favicon.svg">
11
+ <link rel="manifest" href="manifest.json">
12
+ <meta name="theme-color" content="#F2EFE6" media="(prefers-color-scheme: light)">
13
+ <meta name="theme-color" content="#17140F" media="(prefers-color-scheme: dark)">
14
+
15
+ <!-- SEO -->
16
+ <meta name="description" content="The open registry of AI skills β€” 226 ready-to-use skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi. Browse, filter, copy the trigger phrase and go.">
17
+ <meta name="keywords" content="AI skills, Claude Code skills, ChatGPT skills, Gemini skills, LLM skills, AI capabilities, Claude skills, Perplexity skills, Copilot skills, OpenCode skills, Codex skills, Cursor skills, Kiro skills, Pi skills, Antigravity skills, GitHub Copilot skills, skill definitions">
18
+ <link rel="canonical" href="https://kishormorol.github.io/SkillsAllYouNeed/">
19
+
20
+ <!-- Open Graph (rich previews on Slack, LinkedIn, iMessage, etc.) -->
21
+ <meta property="og:type" content="website">
22
+ <meta property="og:url" content="https://kishormorol.github.io/SkillsAllYouNeed/">
23
+ <meta property="og:title" content="SkillsAllYouNeed for LLMs β€” The Open Registry of AI Skills">
24
+ <meta property="og:description" content="226 ready-to-use skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi. Browse, filter, copy the trigger phrase and go.">
25
+ <meta property="og:site_name" content="SkillsAllYouNeed">
26
+
27
+ <!-- Twitter / X card -->
28
+ <meta name="twitter:card" content="summary">
29
+ <meta name="twitter:title" content="SkillsAllYouNeed for LLMs">
30
+ <meta name="twitter:description" content="226 ready-to-use AI skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi. Free, open, filterable.">
31
+ <meta name="twitter:site" content="@kishormorol">
32
+
33
+ <!-- apply saved theme immediately so there's no flash on reload -->
34
+ <script>if(localStorage.getItem("sayn-theme")==="dark")document.documentElement.setAttribute("data-theme","dark");</script>
35
+ <link rel="preconnect" href="https://fonts.googleapis.com">
36
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
37
+ <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,300;1,6..72,400&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
38
+ <script>
39
+ /* swap fallback fonts for web fonts once the font files have loaded, so first paint never flashes the wrong face */
40
+ if (document.fonts && document.fonts.ready) {
41
+ document.fonts.ready.then(function () {
42
+ document.documentElement.classList.add("fonts-loaded");
43
+ });
44
+ }
45
+ </script>
46
+ <link rel="stylesheet" href="styles.css">
47
+
48
+
49
+
50
+ <!-- JSON-LD structured data -->
51
+ <script type="application/ld+json">
52
+ {
53
+ "@context": "https://schema.org",
54
+ "@type": "Dataset",
55
+ "name": "SkillsAllYouNeed for LLMs",
56
+ "description": "The open registry of AI skills β€” ready-to-use skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi.",
57
+ "url": "https://kishormorol.github.io/SkillsAllYouNeed/",
58
+ "license": "https://creativecommons.org/licenses/by/4.0/",
59
+ "creator": {
60
+ "@type": "Person",
61
+ "name": "Md. Kishor Morol"
62
+ },
63
+ "keywords": ["AI skills","Claude","ChatGPT","Gemini","Perplexity","Copilot","OpenCode","Codex","Cursor","GitHub Copilot","Antigravity","Kiro","Pi","LLM","prompt engineering"],
64
+ "distribution": [
65
+ {
66
+ "@type": "DataDownload",
67
+ "encodingFormat": "application/json",
68
+ "contentUrl": "https://kishormorol.github.io/SkillsAllYouNeed/skills.json"
69
+ }
70
+ ]
71
+ }
72
+ </script>
73
+ </head>
74
+
75
+ <body data-screen-label="SkillsAllYouNeed β€” single page">
76
+
77
+ <div class="shell">
78
+
79
+ <!-- ─── HERO ─── -->
80
+ <header class="hero" aria-label="Site header">
81
+ <div class="hero-top">
82
+ <div class="hero-brand">
83
+ <span class="hero-dot"></span>
84
+ SkillsAllYouNeed
85
+ </div>
86
+ <nav class="hero-nav" aria-label="Site links">
87
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed" target="_blank" rel="noopener">GitHub</a>
88
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=submit-capability.yml" target="_blank" rel="noopener">Submit skill</a>
89
+ <a id="star-btn" class="star-btn"
90
+ href="https://github.com/kishormorol/SkillsAllYouNeed"
91
+ target="_blank" rel="noopener"
92
+ aria-label="Star this project on GitHub"
93
+ title="Star on GitHub">
94
+ <span class="star-icon">β˜…</span>
95
+ Star
96
+ <span class="star-count" id="star-count" style="display:none"></span>
97
+ </a>
98
+ <button class="dark-toggle" id="dark-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">β—‘</button>
99
+ </nav>
100
+ </div>
101
+ <div class="hero-body">
102
+ <h1>Skills<em>All</em><br/>You<em>Need</em><span class="h1-sub"> for LLMs</span></h1>
103
+ <p class="hero-sub">The open registry of AI skills β€” ready-to-use skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi. Copy into your project, drop into Settings, and go.</p>
104
+ <div class="hero-stats" aria-label="Registry stats">
105
+ <div class="stat"><b id="hero-skill-count">0</b><span>skills</span></div>
106
+ <div class="stat"><b>13</b><span>ecosystems</span></div>
107
+ <div class="stat"><b>9</b><span>categories</span></div>
108
+ <div class="stat"><b>Free</b><span>to use</span></div>
109
+ </div>
110
+ <div class="hero-cta">
111
+ <a href="#catalog-section" class="cta-primary">Browse skills ↓</a>
112
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=submit-capability.yml" target="_blank" rel="noopener" class="cta-ghost">Submit a skill</a>
113
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed" target="_blank" rel="noopener" class="cta-ghost star-cta" aria-label="Star on GitHub">
114
+ β˜… Star on GitHub
115
+ <span id="star-count-cta" class="star-count-cta"></span>
116
+ </a>
117
+ </div>
118
+ </div>
119
+ </header>
120
+
121
+ <!-- ─── ONBOARDING STRIP (shown once, dismissible) ─── -->
122
+ <div class="onboarding-strip" id="onboarding-strip" aria-label="Welcome guide">
123
+ <div class="onboarding-inner">
124
+ <div class="onboarding-title">New here?</div>
125
+ <div class="onboarding-steps">
126
+ <div class="onboarding-step"><span class="step-num">1</span><span>Browse skills across Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi</span></div>
127
+ <div class="onboarding-step"><span class="step-num">2</span><span>Click any card β€” see the trigger phrase and how to activate it</span></div>
128
+ <div class="onboarding-step"><span class="step-num">3</span><span>Save favourites β™₯, export as JSON, or submit a missing skill</span></div>
129
+ </div>
130
+ <button class="onboarding-dismiss" id="onboarding-dismiss" aria-label="Dismiss welcome guide">Got it βœ•</button>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- ─── CATALOG + FILTERS (combined) ─── -->
135
+ <section class="section tight" id="catalog-section">
136
+ <div class="section-head">
137
+ <div>
138
+ <div class="label">Catalog</div>
139
+ <h2>Browse all <span class="it">skills.</span></h2>
140
+ </div>
141
+ <div class="right">
142
+ <span class="sort-toggle" aria-label="Sort entries">
143
+ sort:&nbsp;
144
+ <a id="sort-index" class="on" tabindex="0" role="button">index</a>&nbsp;|&nbsp;
145
+ <a id="sort-recent" tabindex="0" role="button">recent</a>
146
+ </span><br/>
147
+ 13 ecosystems Β· 9 categories<br/>
148
+ click any card to open
149
+ </div>
150
+ </div>
151
+
152
+ <!-- ── Filters ── -->
153
+ <div class="filters" id="filters-section">
154
+ <!-- search -->
155
+ <div class="search-wrap">
156
+ <span class="lbl">Q</span>
157
+ <input id="q" type="search" placeholder="search names, descriptions, triggers…" aria-label="Search skills" />
158
+ <span class="count"><b id="count">226</b>&nbsp;of&nbsp;<b id="total-count">226</b>&nbsp;skills</span>
159
+ </div>
160
+
161
+ <!-- quick presets -->
162
+ <div class="filter-row preset-row" role="group" aria-label="Quick filter presets">
163
+ <span class="lbl">Quick</span>
164
+ <button class="preset-btn" data-preset="claude-code" aria-label="Show Claude Code skills only">Claude Code</button>
165
+ <button class="preset-btn" data-preset="agentic" aria-label="Show Agentic category only">Agentic</button>
166
+ <button class="preset-btn" data-preset="stable" aria-label="Show Stable skills only">Stable only</button>
167
+ <button class="preset-btn" data-preset="new" aria-label="Show newly added skills">New additions ✦</button>
168
+ <button class="preset-btn preset-clear" data-preset="clear" aria-label="Clear all filters">Clear all β†Ί</button>
169
+ </div>
170
+
171
+ <!-- ecosystem chips -->
172
+ <div class="filter-row" role="group" aria-label="Filter by ecosystem">
173
+ <span class="lbl">Ecosystem</span>
174
+ <div id="eco-chips" class="filter-row" style="gap:8px"></div>
175
+ </div>
176
+
177
+ <!-- category chips -->
178
+ <div class="filter-row" role="group" aria-label="Filter by category">
179
+ <span class="lbl">Category</span>
180
+ <div id="cat-chips" class="filter-row" style="gap:8px"></div>
181
+ </div>
182
+
183
+ <!-- status chips -->
184
+ <div class="filter-row" role="group" aria-label="Filter by status">
185
+ <span class="lbl">Status</span>
186
+ <div id="status-chips" class="filter-row" style="gap:8px"></div>
187
+ <button class="chip" id="reset" style="margin-left:auto" aria-label="Reset all filters">Reset β†Ί</button>
188
+ </div>
189
+ </div>
190
+ <div id="active-filters" class="active-filters" aria-live="polite"></div>
191
+
192
+ <!-- ── Ecosystem strip ── -->
193
+ <div id="eco-strip" class="eco-strip" aria-label="Jump to an ecosystem"></div>
194
+
195
+ <!-- ── Grid toolbar ── -->
196
+ <div class="grid-toolbar" id="grid-toolbar">
197
+ <button id="surprise-btn" class="chip" aria-label="Open a random skill">Surprise me β†’</button>
198
+ <button id="add-skill-btn" class="chip" aria-label="Add a new skill">Add skill +</button>
199
+ <span class="spacer"></span>
200
+ <button id="fav-filter-btn" class="chip" aria-pressed="false" aria-label="Show saved skills only">Saved <span id="fav-count" class="badge"></span></button>
201
+ <button id="export-btn" class="chip" aria-label="Export visible skills as JSON">Export JSON ↓</button>
202
+ </div>
203
+
204
+ <div id="grid" class="grid" aria-live="polite" style="border-top:0"></div>
205
+ </section>
206
+
207
+ <!-- ─── CAPABILITY MATRIX ─── -->
208
+ <section class="section" id="matrix-section">
209
+ <div class="section-head">
210
+ <div>
211
+ <div class="label">Compare</div>
212
+ <h2>Capability <span class="it">matrix.</span></h2>
213
+ </div>
214
+ <div class="right">
215
+ <span id="matrix-count">36</span> capabilities<br/>
216
+ <span id="matrix-ecosystem-count">13</span> ecosystems<br/>
217
+ scroll horizontally to compare
218
+ </div>
219
+ </div>
220
+
221
+ <div class="matrix-wrap" role="region" aria-label="Capability comparison table, horizontally scrollable" tabindex="0">
222
+ <table class="matrix" id="matrix">
223
+ <caption><b>Comparison.</b> &nbsp;Side-by-side capability coverage across every catalogued ecosystem. Cells are coded βœ“ supported Β· βœ— not supported Β· partial (limited or via third party).</caption>
224
+ </table>
225
+ </div>
226
+ </section>
227
+
228
+ <!-- ─── CONTRIBUTE BANNER ─── -->
229
+ <section class="section tight" id="contribute-section">
230
+ <div class="section-head">
231
+ <div>
232
+ <div class="label">Contribute</div>
233
+ <h2>Missing a <span class="it">skill?</span></h2>
234
+ </div>
235
+ <div class="right">
236
+ Open a GitHub issue<br/>
237
+ reviewed within days<br/>
238
+ added to the registry
239
+ </div>
240
+ </div>
241
+ <div class="contribute-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:0;border-top:1px solid var(--rule)">
242
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=submit-capability.yml"
243
+ target="_blank" rel="noopener"
244
+ style="display:flex;flex-direction:column;padding:32px 28px;border-right:1px solid var(--rule-soft);text-decoration:none;transition:background .14s ease;"
245
+ onmouseover="this.style.background='var(--paper-2)'" onmouseout="this.style.background=''">
246
+ <span style="font-family:var(--serif-display);font-size:clamp(28px,3vw,40px);line-height:1;margin-bottom:10px;">Submit a <em style="color:var(--accent)">capability</em></span>
247
+ <span style="font-family:var(--serif-body);font-size:16px;color:var(--ink-2);line-height:1.45;max-width:40ch;">Know a first‑party skill we're missing? Fill in the structured form β€” name, ecosystem, trigger, how-to, example, source β€” and we'll add it.</span>
248
+ <span style="font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent);margin-top:20px;">β†’ Open submission form</span>
249
+ </a>
250
+ <a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=report-correction.yml"
251
+ target="_blank" rel="noopener"
252
+ style="display:flex;flex-direction:column;padding:32px 28px;text-decoration:none;transition:background .14s ease;"
253
+ onmouseover="this.style.background='var(--paper-2)'" onmouseout="this.style.background=''">
254
+ <span style="font-family:var(--serif-display);font-size:clamp(28px,3vw,40px);line-height:1;margin-bottom:10px;">Report a <em style="color:var(--accent)">correction</em></span>
255
+ <span style="font-family:var(--serif-body);font-size:16px;color:var(--ink-2);line-height:1.45;max-width:40ch;">Spotted an inaccuracy, a broken link, or a status that's changed? Flag it with the skill ID and what it should say.</span>
256
+ <span style="font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent);margin-top:20px;">β†’ Open correction form</span>
257
+ </a>
258
+ </div>
259
+ </section>
260
+
261
+ <!-- ─── COLOPHON ─── -->
262
+ <footer class="colophon">
263
+ <div>
264
+ <h5>About Β· v2.0</h5>
265
+ <p>An open registry of usable AI skill definitions β€” Claude Code .md skills, CLAUDE.md templates, Claude.ai skill uploads, ChatGPT custom instructions, and more. Composed in plain HTML &amp; CSS without a framework. <em>v1.x:</em> capability directory with filters, deep‑linking, keyboard shortcuts, favorites, Export JSON. <em>v2.0:</em> redesigned as a skills marketplace; removed academic framing.</p>
266
+ </div>
267
+ <div>
268
+ <h5>Contribute</h5>
269
+ <ul>
270
+ <li><a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=submit-capability.yml" target="_blank" rel="noopener">β†’ Submit a skill</a></li>
271
+ <li><a href="https://github.com/kishormorol/SkillsAllYouNeed/issues/new?template=report-correction.yml" target="_blank" rel="noopener">β†’ Report a correction</a></li>
272
+ <li><a href="https://github.com/kishormorol/SkillsAllYouNeed/commits/main" target="_blank" rel="noopener">β†’ View changelog</a></li>
273
+ <li><a href="#" id="export-corpus-link">β†’ Download JSON</a></li>
274
+ </ul>
275
+ </div>
276
+ <div>
277
+ <h5>Maintainer</h5>
278
+ <p>Curated &amp; maintained by <em>Kishor Morol</em>. Contributions welcome β€” open an issue or PR on GitHub.</p>
279
+ <p style="margin-top:10px"><a href="https://github.com/kishormorol/SkillsAllYouNeed" target="_blank" rel="noopener" style="border-bottom:1px solid var(--rule)">github.com/kishormorol/SkillsAllYouNeed</a></p>
280
+ </div>
281
+
282
+ <div class="end">
283
+ <span>Β© 2026 Β· SkillsAllYouNeed Β· CC BY 4.0</span>
284
+ <span>skills Β· 13 ecosystems Β· 9 categories</span>
285
+ <span>kishormorol.github.io/SkillsAllYouNeed</span>
286
+ </div>
287
+ </footer>
288
+
289
+ </div>
290
+
291
+ <!-- ─── HINT + SHORTCUTS MODAL ─── -->
292
+ <button class="hint-key" id="hint-key" aria-label="Show keyboard shortcuts"><kbd>?</kbd>shortcuts</button>
293
+ <button class="scroll-top" id="scroll-top" aria-label="Scroll to top">↑ top</button>
294
+ <aside class="shortcuts" id="shortcuts" role="dialog" aria-label="Keyboard shortcuts">
295
+ <button class="closeFn" id="closeFn" aria-label="Dismiss shortcuts">βœ•</button>
296
+ <h6>Footnote</h6>
297
+ <p class="ftitle">Keyboard shortcuts.</p>
298
+ <dl>
299
+ <dt>/</dt><dd>focus the search field</dd>
300
+ <dt>?</dt><dd>toggle this footnote</dd>
301
+ <dt>Esc</dt><dd>close any open sheet or this card</dd>
302
+ <dt>Enter</dt><dd>open the focused skill card</dd>
303
+ <dt>ΒΆ</dt><dd>copy a deep link to a skill</dd>
304
+ <dt>j / k</dt><dd>navigate forward / backward through cards</dd>
305
+ <dt>← β†’</dt><dd>previous / next skill inside an open sheet</dd>
306
+ <dt>f</dt><dd>toggle favorite on focused card or open sheet</dd>
307
+ <dt>r</dt><dd>open a random skill from current results</dd>
308
+ </dl>
309
+ </aside>
310
+
311
+ <!-- ─── ADD SKILL SCRIM ─── -->
312
+ <div class="add-scrim" id="add-scrim" role="dialog" aria-modal="true" aria-label="Add a new skill">
313
+ <div class="add-sheet">
314
+ <button class="close" id="add-close-btn" aria-label="Close" style="position:absolute;top:18px;right:18px;font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.12em;border:1px solid var(--rule);padding:6px 10px;background:none;cursor:pointer;">Close βœ•</button>
315
+ <h3>Add a <span class="it">skill.</span></h3>
316
+ <p class="form-sub">Saves to this browser Β· survives refreshes Β· export JSON to back up</p>
317
+ <form id="add-skill-form" novalidate>
318
+ <div class="form-row">
319
+ <div class="form-group">
320
+ <label for="f-name">Name *</label>
321
+ <input id="f-name" type="text" placeholder="e.g. Smart Notes" autocomplete="off" />
322
+ </div>
323
+ <div class="form-group">
324
+ <label for="f-ecosystem">Ecosystem *</label>
325
+ <select id="f-ecosystem">
326
+ <option value="">β€” select β€”</option>
327
+ <option>Claude</option>
328
+ <option>Claude Code</option>
329
+ <option>ChatGPT</option>
330
+ <option>Codex</option>
331
+ <option>OpenCode</option>
332
+ <option>Cursor</option>
333
+ <option>Gemini</option>
334
+ <option>Perplexity</option>
335
+ <option>Microsoft 365 Copilot</option>
336
+ <option>GitHub Copilot</option>
337
+ <option>Antigravity</option>
338
+ <option>Kiro</option>
339
+ <option>Pi</option>
340
+ </select>
341
+ </div>
342
+ </div>
343
+ <div class="form-row">
344
+ <div class="form-group">
345
+ <label for="f-category">Category *</label>
346
+ <select id="f-category">
347
+ <option value="">β€” select β€”</option>
348
+ <option>Document</option>
349
+ <option>Code</option>
350
+ <option>Web</option>
351
+ <option>Data</option>
352
+ <option>Memory</option>
353
+ <option>Integration</option>
354
+ <option>Visual</option>
355
+ <option>Agentic</option>
356
+ <option>API</option>
357
+ </select>
358
+ </div>
359
+ <div class="form-group">
360
+ <label for="f-status">Status *</label>
361
+ <select id="f-status">
362
+ <option value="">β€” select β€”</option>
363
+ <option>Stable</option>
364
+ <option>Beta</option>
365
+ <option>Deprecated</option>
366
+ </select>
367
+ </div>
368
+ </div>
369
+ <div class="form-row full">
370
+ <div class="form-group">
371
+ <label for="f-desc">Description *</label>
372
+ <textarea id="f-desc" rows="2" placeholder="What this skill does in one or two sentences."></textarea>
373
+ </div>
374
+ </div>
375
+ <div class="form-row full">
376
+ <div class="form-group">
377
+ <label for="f-trigger">Trigger phrase *</label>
378
+ <input id="f-trigger" type="text" placeholder='"Phrase that triggers it" Β· "alternate phrasing"' autocomplete="off" />
379
+ </div>
380
+ </div>
381
+ <div class="form-row full">
382
+ <div class="form-group">
383
+ <label for="f-howto">How to use *</label>
384
+ <input id="f-howto" type="text" placeholder="e.g. Settings β†’ Connectors β†’ enable, or `npm install -g package`" autocomplete="off" />
385
+ </div>
386
+ </div>
387
+ <div class="form-row full">
388
+ <div class="form-group">
389
+ <label for="f-example">Worked example *</label>
390
+ <textarea id="f-example" rows="2" placeholder="A concrete real-world scenario showing the skill in action."></textarea>
391
+ </div>
392
+ </div>
393
+ <div class="form-row full">
394
+ <div class="form-group">
395
+ <label for="f-source">Source URL</label>
396
+ <input id="f-source" type="url" placeholder="https://docs.example.com/this-skill" />
397
+ <span class="id-preview" id="id-preview">id: <b>β€”</b></span>
398
+ </div>
399
+ </div>
400
+ <div class="form-actions">
401
+ <button type="submit" class="btn-primary">Add to catalog β†’</button>
402
+ <button type="button" class="btn-ghost" id="add-cancel-btn">Cancel</button>
403
+ </div>
404
+ </form>
405
+ </div>
406
+ </div>
407
+
408
+ <!-- ─── TOAST ─── -->
409
+ <div class="toast" id="toast" role="status" aria-live="polite">link copied</div>
410
+
411
+ <!-- ─── DETAIL SCRIM ─── -->
412
+ <div class="scrim" id="scrim" role="dialog" aria-modal="true" aria-labelledby="sheet-title">
413
+ <div class="sheet" id="sheet"></div>
414
+ </div>
415
+
416
+
417
+ <script src="data.js"></script>
418
+ <script src="scripts.js"></script>
419
+ </body>
420
  </html>
manifest.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "SkillsAllYouNeed for LLMs",
3
+ "short_name": "SAYN",
4
+ "description": "The open registry of AI skills β€” ready-to-use skill definitions for Claude, Claude Code, ChatGPT, Codex, OpenCode, Cursor, Gemini, Perplexity, Microsoft 365 Copilot, GitHub Copilot, Antigravity, Kiro, and Pi.",
5
+ "start_url": "/SkillsAllYouNeed/",
6
+ "scope": "/SkillsAllYouNeed/",
7
+ "display": "standalone",
8
+ "orientation": "any",
9
+ "background_color": "#F2EFE6",
10
+ "theme_color": "#15130E",
11
+ "icons": [
12
+ {
13
+ "src": "favicon.svg",
14
+ "sizes": "any",
15
+ "type": "image/svg+xml",
16
+ "purpose": "any maskable"
17
+ }
18
+ ],
19
+ "categories": ["productivity", "education", "developer tools"]
20
+ }
scripts.js ADDED
@@ -0,0 +1,858 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const $ = (sel,root=document) => root.querySelector(sel);
2
+ const $$ = (sel,root=document) => Array.from(root.querySelectorAll(sel));
3
+
4
+ /* ───── Smooth scroll with ease-in-out ───── */
5
+ function smoothScrollTo(el, offset = 0) {
6
+ const start = window.scrollY;
7
+ const end = el.getBoundingClientRect().top + start + offset;
8
+ const duration = 400;
9
+ const startTime = performance.now();
10
+ function step(now) {
11
+ const elapsed = (now - startTime) / duration;
12
+ const t = Math.min(elapsed, 1);
13
+ const ease = t < .5 ? 2*t*t : -1+(4-2*t)*t; // easeInOutQuad
14
+ window.scrollTo(0, start + (end - start) * ease);
15
+ if (t < 1) requestAnimationFrame(step);
16
+ }
17
+ requestAnimationFrame(step);
18
+ }
19
+
20
+ /* ───── Build chips ───── */
21
+ function buildChips(){
22
+ const ecoBox = $("#eco-chips"); ecoBox.innerHTML = "";
23
+ ECOSYSTEMS.forEach(e=>{
24
+ const cnt = SKILLS.filter(x=>x.ecosystem===e).length;
25
+ const b = document.createElement("button");
26
+ b.className = "chip"; b.setAttribute("aria-pressed","false");
27
+ b.dataset.eco = e; b.dataset.filterVal = e;
28
+ b.innerHTML = `<span class="glyph-wrap">${uniqueSvg(GLYPHS[e])}<span>${e}</span></span><span class="badge">${cnt}</span>`;
29
+ b.addEventListener("click", ()=>toggle(state.ecosystems,e,b));
30
+ ecoBox.appendChild(b);
31
+ });
32
+
33
+ const catBox = $("#cat-chips"); catBox.innerHTML = "";
34
+ CATEGORIES.forEach(c=>{
35
+ const cnt = SKILLS.filter(x=>x.category===c).length;
36
+ const b = document.createElement("button");
37
+ b.className = "chip"; b.setAttribute("aria-pressed","false");
38
+ b.dataset.filterVal = c;
39
+ b.innerHTML = `${c}<span class="badge">${cnt}</span>`;
40
+ b.addEventListener("click", ()=>toggle(state.categories,c,b));
41
+ catBox.appendChild(b);
42
+ });
43
+
44
+ const stBox = $("#status-chips"); stBox.innerHTML = "";
45
+ STATUSES.forEach(s=>{
46
+ const cnt = SKILLS.filter(x=>x.status===s).length;
47
+ const b = document.createElement("button");
48
+ b.className = "chip"; b.setAttribute("aria-pressed","false");
49
+ b.dataset.filterVal = s;
50
+ b.innerHTML = `${s}<span class="badge">${cnt}</span>`;
51
+ b.addEventListener("click", ()=>toggle(state.statuses,s,b));
52
+ stBox.appendChild(b);
53
+ });
54
+ }
55
+
56
+ function toggle(set, value, el){
57
+ if(set.has(value)){ set.delete(value); el.setAttribute("aria-pressed","false"); }
58
+ else{ set.add(value); el.setAttribute("aria-pressed","true"); }
59
+ render();
60
+ }
61
+
62
+ /* ───── Grid render with asymmetric spans ───── */
63
+ const SPAN_PATTERN = [4, 4, 4, 6, 6, 4, 4, 4, 8, 4, 4, 4, 4, 6, 6];
64
+ function spanFor(i){ return SPAN_PATTERN[i % SPAN_PATTERN.length]; }
65
+
66
+ function matchesFilters(s){
67
+ if(state.favOnly && !favorites.has(s.id)) return false;
68
+ if(state.newOnly && !NEW_IDS.has(s.id)) return false;
69
+ if(state.ecosystems.size && !state.ecosystems.has(s.ecosystem)) return false;
70
+ if(state.categories.size && !state.categories.has(s.category)) return false;
71
+ if(state.statuses.size && !state.statuses.has(s.status)) return false;
72
+ if(state.query){
73
+ const q = state.query.toLowerCase();
74
+ const blob = (s.name+" "+s.description+" "+s.trigger+" "+s.example+" "+s.category+" "+s.ecosystem+" "+(s.howto||HOWTO[s.id]||"")).toLowerCase();
75
+ if(!blob.includes(q)) return false;
76
+ }
77
+ return true;
78
+ }
79
+
80
+ function render(){
81
+ const grid = $("#grid");
82
+ grid.innerHTML = "";
83
+ let filtered = SKILLS.filter(matchesFilters);
84
+
85
+ // sort
86
+ if(state.sort === "recent"){
87
+ const rank = s => (s.status === "Beta" ? 0 : (s.status === "Stable" ? 1 : 2));
88
+ filtered = filtered.slice().sort((a,b)=> rank(a) - rank(b));
89
+ }
90
+
91
+ lastFiltered = filtered;
92
+ focusedCardIdx = -1;
93
+
94
+ $("#count").textContent = filtered.length;
95
+ const totalEl = $("#total-count"); if(totalEl) totalEl.textContent = SKILLS.length;
96
+ /* Only update the hero counter after the first load β€” boot uses animateCount() instead */
97
+ const heroCount = $("#hero-skill-count");
98
+ if(heroCount && render._firstDone) heroCount.textContent = SKILLS.length;
99
+
100
+ renderActiveFilters();
101
+
102
+ if(filtered.length === 0){
103
+ const qLabel = state.query ? `"${escapeHTML(state.query)}"` : "that combination";
104
+ grid.innerHTML = `<div class="no-results">Nothing matched ${qLabel}.<small>Try broader terms Β· or fewer filters</small><button class="nr-reset" onclick="document.getElementById('reset').click()">Reset all filters β†Ί</button></div>`;
105
+ return;
106
+ }
107
+
108
+ filtered.forEach((s, i)=>{
109
+ const span = spanFor(i);
110
+ const featured = (span >= 8) || (i === 0);
111
+ const isSaved = favorites.has(s.id);
112
+ const card = document.createElement("article");
113
+ card.className = `card ${span===6?"span-6":""} ${span===8?"span-8":""} ${span===12?"span-12":""} ${featured?"featured":""}`;
114
+ card.tabIndex = 0;
115
+ card.setAttribute("role","button");
116
+ card.setAttribute("aria-label", `${s.name} β€” ${s.ecosystem} β€” open details`);
117
+ card.id = `skill-${s.id}`;
118
+ card.dataset.id = s.id;
119
+ card.dataset.eco = s.ecosystem;
120
+ card.dataset.trigger = s.trigger;
121
+ card.dataset.example = s.example;
122
+ card.dataset.source = s.source;
123
+
124
+ const nameHTML = s.name.replace(/(\S+)$/, '<span class="it">$1</span>');
125
+ const isNew = NEW_IDS.has(s.id);
126
+
127
+ card.innerHTML = `
128
+ <div class="top">
129
+ <div class="glyph">${uniqueSvg(GLYPHS[s.ecosystem])}</div>
130
+ <div class="id-wrap">
131
+ <div class="id">${s.id}${s._custom?'<span class="custom-badge">custom</span>':''}${isNew?'<span class="new-badge">new</span>':''}</div>
132
+ <button class="anchor" title="Copy deep link" aria-label="Copy deep link to ${s.name}" data-copy="${s.id}">ΒΆ</button>
133
+ </div>
134
+ <div class="meta-top" style="flex:1">
135
+ <span class="eco-label">${s.ecosystem}</span>
136
+ <span>β„– ${String(SKILLS.indexOf(s)+1).padStart(3,'0')} / ${String(SKILLS.length).padStart(3,'0')}</span>
137
+ </div>
138
+ </div>
139
+ <h3 class="name">${nameHTML}</h3>
140
+ <p class="desc">${codeBacktick(escapeHTML(s.description))}</p>
141
+ <p class="trigger" title="Click to copy trigger phrase">${codeBacktick(escapeHTML(s.trigger))}<span class="copy-hint" aria-hidden="true">⎘ copy</span></p>
142
+ <div class="bottom">
143
+ <span class="cat" data-cat="${s.category}">${s.category}</span>
144
+ <span class="status" data-status="${s.status}"><span class="status-dot"></span>${s.status}</span>
145
+ <button class="fav-btn${isSaved?" saved":""}" data-fav-id="${s.id}"
146
+ title="${isSaved?"Remove from saved":"Save this skill"}"
147
+ aria-label="${isSaved?"Remove from saved":"Save"}">${isSaved?"β™₯":"β™‘"}</button>
148
+ </div>
149
+ `;
150
+ card.addEventListener("click", (e)=>{
151
+ if(e.target.closest(".anchor") || e.target.closest(".fav-btn")) return;
152
+ openSheet(s);
153
+ });
154
+ card.addEventListener("keydown", e=>{
155
+ if((e.key==="Enter"||e.key===" ") && !e.target.classList.contains("anchor") && !e.target.classList.contains("fav-btn")){
156
+ e.preventDefault(); openSheet(s);
157
+ }
158
+ });
159
+ card.querySelector(".anchor").addEventListener("click", (e)=>{
160
+ e.stopPropagation(); copyDeepLink(s.id);
161
+ });
162
+ card.querySelector(".fav-btn").addEventListener("click", (e)=>{
163
+ e.stopPropagation();
164
+ const was = favorites.has(s.id);
165
+ was ? favorites.delete(s.id) : favorites.add(s.id);
166
+ saveFavs(); updateFavCount();
167
+ const btn = e.currentTarget;
168
+ btn.classList.toggle("saved", !was);
169
+ btn.textContent = !was ? "β™₯" : "β™‘";
170
+ btn.title = !was ? "Remove from saved" : "Save this skill";
171
+ if(state.favOnly) render();
172
+ });
173
+ card.querySelector(".trigger").addEventListener("click", (e)=>{
174
+ e.stopPropagation();
175
+ navigator.clipboard && navigator.clipboard.writeText(s.trigger).catch(()=>{});
176
+ showToast("trigger copied");
177
+ });
178
+ grid.appendChild(card);
179
+ });
180
+
181
+ // staggered fade-in β€” fast stagger on filter, gentle on first load
182
+ requestAnimationFrame(()=>{
183
+ const step = render._firstDone ? 8 : 18;
184
+ $$("#grid .card").forEach((c,i)=>{
185
+ setTimeout(()=>c.classList.add("in"), i*step);
186
+ });
187
+ render._firstDone = true;
188
+ });
189
+ }
190
+
191
+ /* ───── Sheet ───── */
192
+ function openSheet(s, opts){
193
+ opts = opts || {};
194
+ const sheet = $("#sheet");
195
+ const nameHTML = s.name.replace(/(\S+)$/, '<span class="it">$1</span>');
196
+ currentSheetIdx = lastFiltered.findIndex(x => x.id === s.id);
197
+ const hasPrev = currentSheetIdx > 0;
198
+ const hasNext = currentSheetIdx < lastFiltered.length - 1;
199
+ const isSaved = favorites.has(s.id);
200
+
201
+ sheet.innerHTML = `
202
+ <button class="close" aria-label="Close" id="closeBtn">Close βœ•</button>
203
+ <div class="sheet-top">
204
+ <div class="glyph-lg">${uniqueSvg(GLYPHS[s.ecosystem])}</div>
205
+ <div class="top-meta" style="flex:1">
206
+ <div><b>${s.ecosystem}</b> &nbsp;Β·&nbsp; ${s.category} &nbsp;Β·&nbsp; ${s.status}</div>
207
+ <div>id: ${s.id} &nbsp;Β·&nbsp; entry β„– ${String(SKILLS.indexOf(s)+1).padStart(3,'0')} of ${SKILLS.length} &nbsp;Β·&nbsp; <a href="#${s.id}" id="sheetCopy" style="color:var(--accent);border-bottom:1px solid var(--accent)">copy deep link</a></div>
208
+ </div>
209
+ </div>
210
+ <h3 id="sheet-title">${nameHTML}</h3>
211
+ <p class="lede">${codeBacktick(escapeHTML(s.description))}</p>
212
+
213
+ <h4>Trigger</h4>
214
+ <pre class="field">${splitTriggers(codeBacktick(escapeHTML(s.trigger)))}</pre>
215
+
216
+ <h4>How to use</h4>
217
+ <p class="field serif">${codeBacktick(escapeHTML(s.howto || HOWTO[s.id] || "See the source link below for setup instructions."))}</p>
218
+
219
+ <h4>Worked example</h4>
220
+ <p class="field serif">${codeBacktick(escapeHTML(s.example))}</p>
221
+
222
+ <h4>Source &amp; citation</h4>
223
+ <a class="src" href="${escapeHTML(s.source)}" target="_blank" rel="noopener">${escapeHTML(s.source)}</a>
224
+
225
+ <div class="sheet-actions">
226
+ <button id="favSheetBtn" class="${isSaved?"is-saved":""}" aria-label="${isSaved?"Remove from saved":"Save this skill"}">${isSaved?"β™₯ Saved":"β™‘ Save"}</button>
227
+ <button id="shareBtn" aria-label="Share this skill">Share β†’</button>
228
+ <button id="copyTriggerBtn" aria-label="Copy trigger phrase">Copy trigger</button>
229
+ </div>
230
+
231
+ ${(()=>{
232
+ const related = SKILLS.filter(r =>
233
+ r.id !== s.id && (
234
+ r.category === s.category ||
235
+ (r.ecosystem === s.ecosystem && r.id !== s.id)
236
+ )
237
+ ).slice(0,4);
238
+ if(!related.length) return "";
239
+ return `<h4>Related skills</h4>
240
+ <div class="related-list">${
241
+ related.map(r => `<button class="related-chip" data-related-id="${r.id}">${r.name}</button>`).join("")
242
+ }</div>`;
243
+ })()}
244
+
245
+ <div class="sheet-nav">
246
+ <button id="prevSkill"${!hasPrev?" disabled":""} aria-label="Previous skill">← prev</button>
247
+ <span class="nav-pos">${currentSheetIdx+1} of ${lastFiltered.length}</span>
248
+ <button id="nextSkill"${!hasNext?" disabled":""} aria-label="Next skill">next β†’</button>
249
+ </div>
250
+ `;
251
+
252
+ $("#scrim").classList.add("open");
253
+ document.body.style.overflow = "hidden";
254
+ sheet.scrollTop = 0;
255
+
256
+ $("#closeBtn").addEventListener("click", closeSheet);
257
+ $("#sheetCopy").addEventListener("click", (e)=>{ e.preventDefault(); copyDeepLink(s.id); });
258
+
259
+ if(hasPrev) $("#prevSkill").addEventListener("click", ()=>openSheet(lastFiltered[currentSheetIdx-1]));
260
+ if(hasNext) $("#nextSkill").addEventListener("click", ()=>openSheet(lastFiltered[currentSheetIdx+1]));
261
+
262
+ $("#favSheetBtn").addEventListener("click", ()=>{
263
+ const was = favorites.has(s.id);
264
+ was ? favorites.delete(s.id) : favorites.add(s.id);
265
+ saveFavs(); updateFavCount();
266
+ const fb = $("#favSheetBtn");
267
+ fb.className = favorites.has(s.id) ? "is-saved" : "";
268
+ fb.textContent = favorites.has(s.id) ? "β™₯ Saved" : "β™‘ Save";
269
+ fb.setAttribute("aria-label", favorites.has(s.id) ? "Remove from saved" : "Save this skill");
270
+ const cardFav = document.querySelector(`[data-fav-id="${s.id}"]`);
271
+ if(cardFav){ cardFav.classList.toggle("saved", favorites.has(s.id)); cardFav.textContent = favorites.has(s.id)?"β™₯":"β™‘"; }
272
+ if(state.favOnly) render();
273
+ });
274
+
275
+ $("#shareBtn").addEventListener("click", ()=>{
276
+ const url = location.origin + location.pathname + location.search + "#" + s.id;
277
+ if(navigator.share){ navigator.share({title:`${s.name} β€” SkillsAllYouNeed`, url}).catch(()=>copyDeepLink(s.id)); }
278
+ else { copyDeepLink(s.id); }
279
+ });
280
+
281
+ $("#copyTriggerBtn").addEventListener("click", ()=>{
282
+ navigator.clipboard && navigator.clipboard.writeText(s.trigger).catch(()=>{});
283
+ showToast("trigger copied");
284
+ });
285
+
286
+ $$(".related-chip", sheet).forEach(btn => {
287
+ btn.addEventListener("click", ()=>{
288
+ const target = SKILLS.find(x=>x.id === btn.dataset.relatedId);
289
+ if(target) openSheet(target);
290
+ });
291
+ });
292
+
293
+ if(s._custom){
294
+ const delBtn = document.createElement("button");
295
+ delBtn.className = "is-delete"; delBtn.textContent = "Delete βœ•";
296
+ delBtn.setAttribute("aria-label","Delete this custom skill");
297
+ delBtn.addEventListener("click", ()=>{
298
+ if(!confirm(`Delete "${s.name}"? This cannot be undone.`)) return;
299
+ const idx = SKILLS.findIndex(x=>x.id===s.id);
300
+ if(idx > -1) SKILLS.splice(idx, 1);
301
+ const remaining = JSON.parse(localStorage.getItem("skills-custom")||"[]").filter(x=>x.id!==s.id);
302
+ localStorage.setItem("skills-custom", JSON.stringify(remaining));
303
+ closeSheet(); render();
304
+ showToast(`"${s.name}" deleted`);
305
+ });
306
+ sheet.querySelector(".sheet-actions").appendChild(delBtn);
307
+ }
308
+
309
+ if(opts.pushHash !== false && location.hash !== "#"+s.id){
310
+ history.replaceState(null, "", "#"+s.id);
311
+ }
312
+ }
313
+ function closeSheet(){
314
+ $("#scrim").classList.remove("open");
315
+ document.body.style.overflow = "";
316
+ if(location.hash){ history.replaceState(null, "", location.pathname + location.search); }
317
+ }
318
+ $("#scrim").addEventListener("click", e=>{ if(e.target.id === "scrim") closeSheet(); });
319
+
320
+ /* ───── Deep link / toast ───── */
321
+ function copyDeepLink(id){
322
+ const url = location.origin + location.pathname + location.search + "#" + id;
323
+ const fallback = () => {
324
+ const ta = document.createElement("textarea");
325
+ ta.value = url; ta.style.position="fixed"; ta.style.left="-9999px";
326
+ document.body.appendChild(ta); ta.select();
327
+ try{ document.execCommand("copy"); }catch(_){}
328
+ document.body.removeChild(ta);
329
+ };
330
+ if(navigator.clipboard && navigator.clipboard.writeText){
331
+ navigator.clipboard.writeText(url).catch(fallback);
332
+ } else { fallback(); }
333
+ showToast("link copied Β· ΒΆ " + id);
334
+ }
335
+ let toastTimer;
336
+ function showToast(msg){
337
+ const t = $("#toast");
338
+ t.textContent = msg;
339
+ t.classList.add("show");
340
+ clearTimeout(toastTimer);
341
+ toastTimer = setTimeout(()=>t.classList.remove("show"), 1800);
342
+ }
343
+
344
+ /* ───── Ecosystem deep-dive strip ───── */
345
+ function buildEcoStrip(){
346
+ const wrap = $("#eco-strip");
347
+ wrap.innerHTML = "";
348
+ ECOSYSTEMS.forEach(eco => {
349
+ const count = SKILLS.filter(s => s.ecosystem === eco).length;
350
+ const cell = document.createElement("button");
351
+ cell.className = "eco-cell";
352
+ cell.dataset.eco = eco;
353
+ cell.setAttribute("aria-label", `Filter to ${eco} β€” ${count} entries`);
354
+ cell.innerHTML = `
355
+ <div class="eco-head">
356
+ <div class="eco-glyph">${uniqueSvg(GLYPHS[eco])}</div>
357
+ <div class="eco-tag"><b>${String(count).padStart(2,'0')}</b>entries</div>
358
+ </div>
359
+ <h4 class="eco-name">${eco.replace(/(\w+)$/, '<span class="it">$1</span>')}</h4>
360
+ <p class="eco-blurb">${ECO_BLURB[eco]}</p>
361
+ <div class="eco-link">β†’ Jump to ${eco} entries</div>
362
+ `;
363
+ cell.addEventListener("click", ()=>{
364
+ // single-select this ecosystem
365
+ state.ecosystems.clear();
366
+ state.ecosystems.add(eco);
367
+ $$("#eco-chips .chip").forEach(c=>{
368
+ c.setAttribute("aria-pressed", c.dataset.eco === eco ? "true" : "false");
369
+ });
370
+ render();
371
+ smoothScrollTo($("#grid"), -20);
372
+ });
373
+ wrap.appendChild(cell);
374
+ });
375
+ }
376
+
377
+ /* ───── Shortcuts modal ───── */
378
+ function openShortcuts(){ $("#shortcuts").classList.add("open"); }
379
+ function closeShortcuts(){ $("#shortcuts").classList.remove("open"); }
380
+ $("#hint-key").addEventListener("click", ()=>{
381
+ const s = $("#shortcuts");
382
+ s.classList.toggle("open");
383
+ });
384
+ $("#closeFn").addEventListener("click", closeShortcuts);
385
+
386
+ /* ───── Global keyboard shortcuts (unified) ───── */
387
+ document.addEventListener("keydown", (e)=>{
388
+ const tag = (e.target && e.target.tagName || "").toLowerCase();
389
+ const typing = tag === "input" || tag === "textarea" || e.target.isContentEditable;
390
+ const sheetOpen = $("#scrim").classList.contains("open");
391
+
392
+ // Escape closes whatever is open
393
+ if(e.key === "Escape"){
394
+ if(sheetOpen) { closeSheet(); return; }
395
+ if(document.getElementById("add-scrim").classList.contains("open")){ closeAddSheet(); return; }
396
+ if($("#shortcuts").classList.contains("open")){ closeShortcuts(); return; }
397
+ return;
398
+ }
399
+
400
+ // Arrow navigation inside open sheet
401
+ if(sheetOpen){
402
+ if(e.key === "ArrowLeft" && currentSheetIdx > 0){
403
+ e.preventDefault(); openSheet(lastFiltered[currentSheetIdx-1]); return;
404
+ }
405
+ if(e.key === "ArrowRight" && currentSheetIdx < lastFiltered.length-1){
406
+ e.preventDefault(); openSheet(lastFiltered[currentSheetIdx+1]); return;
407
+ }
408
+ }
409
+
410
+ if(typing) return;
411
+
412
+ if(e.key === "/"){
413
+ e.preventDefault(); $("#q").focus(); $("#q").select();
414
+ } else if(e.key === "?" || (e.shiftKey && e.key === "/")){
415
+ e.preventDefault(); $("#shortcuts").classList.toggle("open");
416
+ } else if((e.key === "j" || e.key === "k") && !sheetOpen){
417
+ e.preventDefault();
418
+ const cards = $$("#grid .card");
419
+ if(!cards.length) return;
420
+ focusedCardIdx = e.key==="j"
421
+ ? Math.min(focusedCardIdx+1, cards.length-1)
422
+ : Math.max(focusedCardIdx-1, 0);
423
+ cards[focusedCardIdx].focus();
424
+ cards[focusedCardIdx].scrollIntoView({block:"nearest", behavior:"smooth"});
425
+ } else if(e.key === "f"){
426
+ if(sheetOpen){
427
+ const fb = $("#favSheetBtn"); if(fb) fb.click();
428
+ } else {
429
+ const focused = document.activeElement;
430
+ if(focused && focused.classList.contains("card")){
431
+ const btn = focused.querySelector(".fav-btn"); if(btn) btn.click();
432
+ }
433
+ }
434
+ } else if(e.key === "r" && !sheetOpen){
435
+ if(lastFiltered.length) openSheet(lastFiltered[Math.floor(Math.random()*lastFiltered.length)]);
436
+ }
437
+ });
438
+
439
+ /* ───── Sort toggle ───── */
440
+ function setSort(mode){
441
+ state.sort = mode;
442
+ $("#sort-index").classList.toggle("on", mode === "index");
443
+ $("#sort-recent").classList.toggle("on", mode === "recent");
444
+ render();
445
+ }
446
+ $("#sort-index").addEventListener("click", ()=>setSort("index"));
447
+ $("#sort-recent").addEventListener("click", ()=>setSort("recent"));
448
+ ["sort-index","sort-recent"].forEach(id=>{
449
+ $("#"+id).addEventListener("keydown", e=>{
450
+ if(e.key === "Enter" || e.key === " "){ e.preventDefault(); setSort(id==="sort-index"?"index":"recent"); }
451
+ });
452
+ });
453
+
454
+ /* ───── Active filters bar ───── */
455
+ function renderActiveFilters(){
456
+ const bar = $("#active-filters"); if(!bar) return;
457
+ const tags = [];
458
+ state.ecosystems.forEach(e => tags.push({type:"eco", val:e}));
459
+ state.categories.forEach(c => tags.push({type:"cat", val:c}));
460
+ state.statuses.forEach(s => tags.push({type:"stat", val:s}));
461
+ if(state.favOnly) tags.push({type:"fav", val:"Saved only"});
462
+ if(state.newOnly) tags.push({type:"newOnly", val:"New additions"});
463
+ if(state.query) tags.push({type:"q", val:`"${state.query}"`});
464
+
465
+ if(!tags.length){ bar.style.display="none"; return; }
466
+ bar.style.display = "flex";
467
+ bar.innerHTML = `<span class="af-label">Active:</span>` + tags.map(t=>
468
+ `<button class="active-filter-tag" data-type="${t.type}" data-val="${escapeHTML(t.val)}">${escapeHTML(t.val)} Γ—</button>`
469
+ ).join("");
470
+
471
+ $$(".active-filter-tag", bar).forEach(tag => {
472
+ tag.addEventListener("click", ()=>{
473
+ const {type, val} = tag.dataset;
474
+ if(type==="eco"){
475
+ state.ecosystems.delete(val);
476
+ $$(`#eco-chips .chip`).forEach(c=>{ if(c.dataset.filterVal===val) c.setAttribute("aria-pressed","false"); });
477
+ } else if(type==="cat"){
478
+ state.categories.delete(val);
479
+ $$(`#cat-chips .chip`).forEach(c=>{ if(c.dataset.filterVal===val) c.setAttribute("aria-pressed","false"); });
480
+ } else if(type==="stat"){
481
+ state.statuses.delete(val);
482
+ $$(`#status-chips .chip`).forEach(c=>{ if(c.dataset.filterVal===val) c.setAttribute("aria-pressed","false"); });
483
+ } else if(type==="fav"){
484
+ state.favOnly = false;
485
+ $("#fav-filter-btn")?.setAttribute("aria-pressed","false");
486
+ } else if(type==="newOnly"){
487
+ state.newOnly = false;
488
+ $$(".preset-btn").forEach(b=>{ if(b.dataset.preset==="new") b.classList.remove("active"); });
489
+ } else if(type==="q"){
490
+ state.query = ""; $("#q").value = "";
491
+ }
492
+ render();
493
+ });
494
+ });
495
+ }
496
+
497
+ /* ───── Scroll-to-top ───── */
498
+ window.addEventListener("scroll", ()=>{
499
+ const btn = $("#scroll-top");
500
+ if(btn) btn.classList.toggle("visible", window.scrollY > 500);
501
+ }, {passive:true});
502
+ $("#scroll-top").addEventListener("click", ()=>window.scrollTo({top:0, behavior:"smooth"}));
503
+
504
+ /* ───── Grid toolbar ───── */
505
+ $("#surprise-btn").addEventListener("click", ()=>{
506
+ if(!lastFiltered.length) return;
507
+ openSheet(lastFiltered[Math.floor(Math.random()*lastFiltered.length)]);
508
+ });
509
+
510
+ $("#fav-filter-btn").addEventListener("click", ()=>{
511
+ state.favOnly = !state.favOnly;
512
+ $("#fav-filter-btn").setAttribute("aria-pressed", state.favOnly?"true":"false");
513
+ render();
514
+ });
515
+
516
+ $("#export-btn").addEventListener("click", ()=>{
517
+ const data = lastFiltered.map(s=>{
518
+ const h = s.howto || HOWTO[s.id] || "";
519
+ return {id:s.id,name:s.name,ecosystem:s.ecosystem,category:s.category,status:s.status,description:s.description,trigger:s.trigger,howto:h,example:s.example,source:s.source};
520
+ });
521
+ const blob = new Blob([JSON.stringify(data,null,2)], {type:"application/json"});
522
+ const url = URL.createObjectURL(blob);
523
+ const a = Object.assign(document.createElement("a"), {href:url, download:`skills-${Date.now()}.json`});
524
+ document.body.appendChild(a); a.click(); document.body.removeChild(a);
525
+ URL.revokeObjectURL(url);
526
+ showToast(`exported ${data.length} entr${data.length===1?"y":"ies"}`);
527
+ });
528
+
529
+ /* ───── Hash routing ───── */
530
+ function openFromHash(){
531
+ const id = location.hash.replace("#","");
532
+ if(!id) return;
533
+ const s = SKILLS.find(x => x.id === id);
534
+ if(s){
535
+ openSheet(s, {pushHash:false});
536
+ const card = document.getElementById("skill-"+id);
537
+ if(card){ card.classList.add("hashed"); setTimeout(()=>card.classList.remove("hashed"), 2400); }
538
+ }
539
+ }
540
+ window.addEventListener("hashchange", openFromHash);
541
+
542
+ /* ───── Matrix ───── */
543
+ function buildMatrix(){
544
+ const t = $("#matrix");
545
+ $("#matrix-count").textContent = MATRIX_ROWS.length;
546
+ $("#matrix-ecosystem-count").textContent = ECOSYSTEMS.length;
547
+ const headRow = `
548
+ <thead>
549
+ <tr>
550
+ <th>Capability</th>
551
+ ${ECOSYSTEMS.map(e=>`<th><div class="head-glyph">${uniqueSvg(GLYPHS[e])}<span>${e}</span></div></th>`).join("")}
552
+ </tr>
553
+ </thead>`;
554
+ const body = `
555
+ <tbody>
556
+ ${MATRIX_ROWS.map((row, i)=>{
557
+ const [label, ...cells] = row;
558
+ return `<tr>
559
+ <th scope="row"><span class="row-num">${String(i+1).padStart(2,'0')}</span>${label}</th>
560
+ ${cells.map(c=>{
561
+ if(c==="y") return `<td><span class="mark yes" aria-label="Supported">βœ“</span></td>`;
562
+ if(c==="n") return `<td><span class="mark no" aria-label="Not supported">βœ—</span></td>`;
563
+ return `<td><span class="mark partial" aria-label="Partial support">partial</span></td>`;
564
+ }).join("")}
565
+ </tr>`;
566
+ }).join("")}
567
+ </tbody>`;
568
+ t.insertAdjacentHTML("beforeend", headRow);
569
+ t.insertAdjacentHTML("beforeend", body);
570
+ }
571
+
572
+ /* ───── References ───── */
573
+ function buildRefs(){
574
+ const ol = $("#refs-list");
575
+ ol.innerHTML = REFS.map(r=>{
576
+ const isUrl = r.venue && !r.venue.includes(' ');
577
+ const venueHTML = isUrl
578
+ ? `<a href="https://${r.venue}" target="_blank" rel="noopener">${r.venue}</a>`
579
+ : r.venue;
580
+ return `<li>${r.who}. <em>${r.title}</em>. ${r.year}.<span class="where">${venueHTML}</span></li>`;
581
+ }).join("");
582
+ }
583
+
584
+ /* ───── Wire search ───── */
585
+ $("#q").addEventListener("input", e=>{ state.query = e.target.value.trim(); render(); });
586
+ $("#reset").addEventListener("click", ()=>{
587
+ state.query = "";
588
+ state.ecosystems.clear(); state.categories.clear(); state.statuses.clear();
589
+ state.favOnly = false; state.newOnly = false;
590
+ $("#q").value = "";
591
+ $$(".chip[aria-pressed='true']").forEach(c=>c.setAttribute("aria-pressed","false"));
592
+ $$(".preset-btn").forEach(b=>b.classList.remove("active"));
593
+ render();
594
+ });
595
+
596
+ /* ───── Add Skill modal ───── */
597
+ function genId(eco, name){
598
+ const prefix = ECO_PREFIX[eco] || "xx";
599
+ const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");
600
+ return prefix + "-" + slug;
601
+ }
602
+ function openAddSheet(){
603
+ document.getElementById("add-scrim").classList.add("open");
604
+ document.body.style.overflow = "hidden";
605
+ setTimeout(()=>document.getElementById("f-name").focus(), 60);
606
+ }
607
+ function closeAddSheet(){
608
+ document.getElementById("add-scrim").classList.remove("open");
609
+ document.body.style.overflow = "";
610
+ document.getElementById("add-skill-form").reset();
611
+ document.getElementById("id-preview").innerHTML = "id: <b>β€”</b>";
612
+ }
613
+ document.getElementById("add-skill-btn").addEventListener("click", openAddSheet);
614
+ document.getElementById("add-close-btn").addEventListener("click", closeAddSheet);
615
+ document.getElementById("add-cancel-btn").addEventListener("click", closeAddSheet);
616
+ document.getElementById("add-scrim").addEventListener("click", e=>{ if(e.target.id==="add-scrim") closeAddSheet(); });
617
+
618
+ // Live ID preview as user types name / picks ecosystem
619
+ ["f-name","f-ecosystem"].forEach(fid=>{
620
+ document.getElementById(fid).addEventListener("input", ()=>{
621
+ const name = document.getElementById("f-name").value;
622
+ const eco = document.getElementById("f-ecosystem").value;
623
+ const el = document.getElementById("id-preview");
624
+ el.innerHTML = (name && eco) ? `id: <b>${genId(eco,name)}</b>` : "id: <b>β€”</b>";
625
+ });
626
+ });
627
+
628
+ document.getElementById("add-skill-form").addEventListener("submit", e=>{
629
+ e.preventDefault();
630
+ const val = id => document.getElementById(id).value.trim();
631
+ const name = val("f-name");
632
+ const ecosystem = val("f-ecosystem");
633
+ const category = val("f-category");
634
+ const status = val("f-status");
635
+ const description = val("f-desc");
636
+ const trigger = val("f-trigger");
637
+ const howto = val("f-howto");
638
+ const example = val("f-example");
639
+ const source = val("f-source") || "#";
640
+
641
+ if(!name||!ecosystem||!category||!status||!description||!trigger||!howto||!example){
642
+ showToast("please fill in all required fields");
643
+ return;
644
+ }
645
+ const id = genId(ecosystem, name);
646
+ if(SKILLS.find(x=>x.id===id)){
647
+ showToast("a skill with that name already exists");
648
+ return;
649
+ }
650
+ const skill = {id,name,ecosystem,category,status,description,trigger,howto,example,source,_custom:true};
651
+ SKILLS.push(skill);
652
+ const saved = JSON.parse(localStorage.getItem("skills-custom")||"[]");
653
+ saved.push(skill);
654
+ localStorage.setItem("skills-custom", JSON.stringify(saved));
655
+ closeAddSheet();
656
+ render();
657
+ showToast(`"${name}" added`);
658
+ requestAnimationFrame(()=>openSheet(skill));
659
+ });
660
+
661
+ /* ───── Boot ───── */
662
+ buildChips();
663
+ buildEcoStrip();
664
+ buildMatrix();
665
+ render();
666
+ openFromHash();
667
+ updateFavCount();
668
+
669
+ /* Wire corpus download link in footer */
670
+ const _corpusLink = document.getElementById("export-corpus-link");
671
+ if(_corpusLink) _corpusLink.addEventListener("click", (e)=>{ e.preventDefault(); $("#export-btn").click(); });
672
+
673
+ /* ═══════════════════════════════════════════════════════════
674
+ DARK MODE TOGGLE
675
+ ═══════════════════════════════════════════════════════════ */
676
+ (function(){
677
+ const btn = document.getElementById("dark-toggle");
678
+ if(!btn) return;
679
+ function applyIcon(){
680
+ const dark = document.documentElement.getAttribute("data-theme") === "dark";
681
+ btn.textContent = dark ? "β—‹" : "●";
682
+ btn.title = dark ? "Switch to light mode" : "Switch to dark mode";
683
+ btn.setAttribute("aria-label", dark ? "Switch to light mode" : "Switch to dark mode");
684
+ }
685
+ applyIcon();
686
+ btn.addEventListener("click", ()=>{
687
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
688
+ document.documentElement.setAttribute("data-theme", isDark ? "light" : "dark");
689
+ localStorage.setItem("sayn-theme", isDark ? "light" : "dark");
690
+ applyIcon();
691
+ });
692
+ })();
693
+
694
+ /* ═══════════════════════════════════════════════════════════
695
+ ANIMATED HERO COUNTERS
696
+ ═══════════════════════════════════════════════════════════ */
697
+ function animateCount(el, target, duration){
698
+ if(!el) return;
699
+ duration = duration || 1100;
700
+ const startTime = performance.now();
701
+ function tick(now){
702
+ const t = Math.min((now - startTime) / duration, 1);
703
+ const eased = 1 - Math.pow(1 - t, 3); /* ease-out cubic */
704
+ el.textContent = Math.round(eased * target);
705
+ if(t < 1) requestAnimationFrame(tick);
706
+ else el.textContent = target;
707
+ }
708
+ requestAnimationFrame(tick);
709
+ }
710
+ /* Run after boot (boot is synchronous above, so this fires right after) */
711
+ setTimeout(()=>{
712
+ animateCount(document.getElementById("hero-skill-count"), SKILLS.length, 1100);
713
+ }, 80);
714
+
715
+ /* ═══════════════════════════════════════════════════════════
716
+ GITHUB STAR COUNT β€” always fetches live on every page load
717
+ so the count reflects new stars immediately.
718
+ GitHub allows 60 unauthenticated req/hr per IP β€” fine for
719
+ a static site where each visitor has their own quota.
720
+ ═══════════════════════════════════════════════════════════ */
721
+ (function(){
722
+ const REPO = "kishormorol/SkillsAllYouNeed";
723
+
724
+ function fmtStars(n){
725
+ if(n >= 1000) return (n/1000).toFixed(1).replace(/\.0$/,"") + "k";
726
+ return String(n);
727
+ }
728
+
729
+ function applyCount(n){
730
+ const navCount = document.getElementById("star-count");
731
+ const ctaCount = document.getElementById("star-count-cta");
732
+ if(n < 1){ return; } /* keep hidden until at least 1 star */
733
+ const formatted = fmtStars(n);
734
+ if(navCount){ navCount.textContent = formatted; navCount.style.display = ""; }
735
+ if(ctaCount){ ctaCount.textContent = formatted; ctaCount.classList.add("loaded"); }
736
+ }
737
+
738
+ fetch("https://api.github.com/repos/" + REPO, {cache:"no-store"})
739
+ .then(r => r.ok ? r.json() : Promise.reject(r.status))
740
+ .then(data => applyCount(data.stargazers_count || 0))
741
+ .catch(() => {}); /* fail silently β€” button still works */
742
+ })();
743
+
744
+ /* ═══════════════════════════════════════════════════════════
745
+ QUICK PRESET FILTERS
746
+ ═══════════════════════════════════════════════════════════ */
747
+ (function(){
748
+ function resetAll(){
749
+ state.ecosystems.clear(); state.categories.clear();
750
+ state.statuses.clear(); state.query = ""; state.favOnly = false; state.newOnly = false;
751
+ document.getElementById("q").value = "";
752
+ document.querySelectorAll(".chip[aria-pressed='true']").forEach(c=>c.setAttribute("aria-pressed","false"));
753
+ document.querySelectorAll(".preset-btn").forEach(b=>b.classList.remove("active"));
754
+ }
755
+ document.querySelectorAll(".preset-btn").forEach(btn=>{
756
+ btn.addEventListener("click", ()=>{
757
+ const p = btn.dataset.preset;
758
+ resetAll();
759
+
760
+ if(p === "claude-code"){
761
+ state.ecosystems.add("Claude Code");
762
+ document.querySelector(`#eco-chips .chip[data-eco="Claude Code"]`)?.setAttribute("aria-pressed","true");
763
+ btn.classList.add("active");
764
+ } else if(p === "agentic"){
765
+ state.categories.add("Agentic");
766
+ document.querySelector(`#cat-chips .chip[data-filter-val="Agentic"]`)?.setAttribute("aria-pressed","true");
767
+ btn.classList.add("active");
768
+ } else if(p === "stable"){
769
+ state.statuses.add("Stable");
770
+ document.querySelector(`#status-chips .chip[data-filter-val="Stable"]`)?.setAttribute("aria-pressed","true");
771
+ btn.classList.add("active");
772
+ } else if(p === "new"){
773
+ state.newOnly = true;
774
+ btn.classList.add("active");
775
+ }
776
+ /* "clear" just resets β€” already done above */
777
+ render();
778
+ smoothScrollTo(document.getElementById("catalog-section"), -20);
779
+ });
780
+ });
781
+
782
+ /* Deactivate preset buttons when user manually changes filters */
783
+ document.getElementById("q").addEventListener("input", ()=>{
784
+ document.querySelectorAll(".preset-btn").forEach(b=>b.classList.remove("active"));
785
+ });
786
+ document.querySelectorAll(".chip").forEach(c=>{
787
+ c.addEventListener("click", ()=>{
788
+ document.querySelectorAll(".preset-btn").forEach(b=>b.classList.remove("active"));
789
+ });
790
+ });
791
+ })();
792
+
793
+ /* ═══════════════════════════════════════════════════════════
794
+ ROTATING SEARCH PLACEHOLDER
795
+ ═══════════════════════════════════════════════════════════ */
796
+ (function(){
797
+ const hints = [
798
+ "search names, descriptions, triggers…",
799
+ "try: memory",
800
+ "try: code review",
801
+ "try: generate image",
802
+ "try: webhook",
803
+ "try: artifacts",
804
+ "try: voice mode",
805
+ "try: Claude Code",
806
+ "try: export to docx",
807
+ "try: browser automation",
808
+ ];
809
+ let idx = 0;
810
+ const q = document.getElementById("q");
811
+ setInterval(()=>{
812
+ if(document.activeElement !== q && !state.query){
813
+ idx = (idx + 1) % hints.length;
814
+ q.setAttribute("placeholder", hints[idx]);
815
+ }
816
+ }, 3200);
817
+ })();
818
+
819
+ /* ═══════════════════════════════════════════════════════════
820
+ ONBOARDING STRIP β€” show once, dismiss to localStorage
821
+ ═══════════════════════════════════════════════════════════ */
822
+ (function(){
823
+ const strip = document.getElementById("onboarding-strip");
824
+ if(!strip) return;
825
+ if(!localStorage.getItem("sayn-onboarding-seen")){
826
+ strip.style.display = "block";
827
+ }
828
+ document.getElementById("onboarding-dismiss").addEventListener("click", ()=>{
829
+ const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
830
+ strip.classList.add("is-leaving");
831
+ setTimeout(()=>{ strip.style.display = "none"; }, reduced ? 0 : 180);
832
+ localStorage.setItem("sayn-onboarding-seen", "1");
833
+ });
834
+ })();
835
+
836
+ /* ───── Smooth-scroll all internal anchor links ───── */
837
+ document.querySelectorAll('a[href^="#"]').forEach(link=>{
838
+ link.addEventListener("click", (e)=>{
839
+ const id = link.getAttribute("href").slice(1);
840
+ const target = document.getElementById(id);
841
+ if(target){ e.preventDefault(); target.scrollIntoView({behavior:"smooth", block:"start"}); }
842
+ });
843
+ });
844
+
845
+ /* ───── Chip: reset all on double-click of already-active chip ───── */
846
+ // (chips already toggle on single click; this just adds polish)
847
+
848
+
849
+ /* ───── Make add-skill-btn also reachable via keyboard Enter/Space ───── */
850
+ document.getElementById("add-skill-btn").addEventListener("keydown", e=>{
851
+ if(e.key === "Enter" || e.key === " "){ e.preventDefault(); openAddSheet(); }
852
+ });
853
+ document.getElementById("surprise-btn").addEventListener("keydown", e=>{
854
+ if(e.key === "Enter" || e.key === " "){
855
+ e.preventDefault();
856
+ if(lastFiltered.length) openSheet(lastFiltered[Math.floor(Math.random()*lastFiltered.length)]);
857
+ }
858
+ });
skills.json ADDED
The diff for this file is too large to render. See raw diff
 
styles.css ADDED
@@ -0,0 +1,1573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ─────────────────────────────────────────────────────────────
2
+ SkillsAllYouNeed β€” Open AI Skills Registry
3
+ ───────────────────────────────────────────────────────────── */
4
+
5
+ :root{
6
+ --paper: #F2EFE6; /* off-white, warm */
7
+ --paper-rgb: 242, 239, 230;
8
+ --paper-2: #EAE6DA;
9
+ --paper-edge: #DCD7C7;
10
+ --ink: #15130E; /* near-black, warm */
11
+ --ink-2: #2A2620;
12
+ --ink-soft: #5A5448;
13
+ --ink-mute: #8A8474;
14
+ --rule: #15130E;
15
+ --rule-soft: rgba(21,19,14,.18);
16
+ --accent: #C7401E; /* vermillion ink */
17
+ --accent-deep: #9E2D11;
18
+ --highlight: #F5D77A; /* archival manila tab */
19
+
20
+ --serif-display: Georgia, "Source Serif 4", serif;
21
+ --serif-body: Georgia, "Source Serif 4", serif;
22
+ --mono: ui-monospace, "Menlo", monospace;
23
+
24
+ --grid-max: 1320px;
25
+ --gutter: clamp(20px, 3.2vw, 44px);
26
+
27
+ /* Per-ecosystem brand colours */
28
+ --eco-claude: #d97757;
29
+ --eco-claude-code: #d97757;
30
+ --eco-chatgpt: #1A7A44;
31
+ --eco-gemini: #4527A0;
32
+ --eco-perplexity: #1B9E86;
33
+ --eco-copilot: #0078D4;
34
+ --eco-opencode: #14B85A;
35
+ --eco-cursor: #26251E;
36
+ --eco-codex: #19C37D;
37
+ --eco-github-copilot: #24292F;
38
+ --eco-antigravity: #4285F4;
39
+ --eco-kiro: #993FF5;
40
+ --eco-pi: #5B6470;
41
+
42
+ --eco-cl: var(--eco-claude);
43
+ --eco-cc: var(--eco-claude-code);
44
+ --eco-gpt: var(--eco-chatgpt);
45
+ --eco-cx: var(--eco-codex);
46
+ --eco-oc: var(--eco-opencode);
47
+ --eco-gm: var(--eco-gemini);
48
+ --eco-pp: var(--eco-perplexity);
49
+ --eco-mc: var(--eco-copilot);
50
+ --eco-ghc: var(--eco-github-copilot);
51
+ --eco-ag: var(--eco-antigravity);
52
+ }
53
+
54
+ *{ box-sizing:border-box; }
55
+ html{ scroll-behavior: smooth; }
56
+ html,body{ margin:0; padding:0; background:var(--paper); color:var(--ink); }
57
+ .fonts-loaded{
58
+ --serif-display: "Instrument Serif", "Source Serif 4", Georgia, serif;
59
+ --serif-body: "Newsreader", "Source Serif 4", Georgia, serif;
60
+ --mono: "JetBrains Mono", ui-monospace, "Menlo", monospace;
61
+ }
62
+ body{
63
+ font-family:var(--serif-body);
64
+ font-size:17px;
65
+ line-height:1.5;
66
+ text-rendering:optimizeLegibility;
67
+ -webkit-font-smoothing:antialiased;
68
+ font-feature-settings:"ss01","ss02","onum","liga";
69
+ background-image:
70
+ radial-gradient(rgba(21,19,14,.035) 1px, transparent 1px);
71
+ background-size: 3px 3px;
72
+ background-position: 0 0;
73
+ }
74
+
75
+ /* paper grain β€” extremely subtle */
76
+ body::before{
77
+ content:""; position:fixed; inset:0; pointer-events:none; z-index:1;
78
+ background:
79
+ repeating-linear-gradient(0deg, rgba(21,19,14,.012) 0 1px, transparent 1px 3px),
80
+ repeating-linear-gradient(90deg, rgba(21,19,14,.008) 0 1px, transparent 1px 4px);
81
+ mix-blend-mode:multiply;
82
+ }
83
+
84
+ ::selection{ background:var(--accent); color:var(--paper); }
85
+
86
+ a{ color:inherit; text-decoration:none; }
87
+ button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; padding:0; }
88
+
89
+ /* ── Layout shell ────────────────────────────────────────── */
90
+ .shell{
91
+ max-width:var(--grid-max);
92
+ margin:0 auto;
93
+ padding:0 var(--gutter);
94
+ position:relative;
95
+ z-index:2;
96
+ }
97
+
98
+ /* ── Hero / site header ──────────────────────────────────── */
99
+ .hero{ border-bottom:1px solid var(--rule); }
100
+ .hero-top{
101
+ display:flex; justify-content:space-between; align-items:center;
102
+ padding:14px 0 12px;
103
+ border-bottom:1px solid var(--rule-soft);
104
+ font-family:var(--mono);
105
+ font-size:11px;
106
+ letter-spacing:.08em;
107
+ text-transform:uppercase;
108
+ }
109
+ .hero-brand{ display:flex; align-items:center; gap:8px; font-weight:500; color:var(--ink); }
110
+ .hero-dot{ width:8px; height:8px; background:var(--accent); display:inline-block; flex-shrink:0; }
111
+ .hero-nav{ display:flex; gap:20px; align-items:center; }
112
+ .hero-nav a{ color:var(--ink-soft); transition:color .12s ease; }
113
+ .hero-nav a:hover{ color:var(--accent); }
114
+
115
+ /* GitHub star button */
116
+ .star-btn{
117
+ display:inline-flex; align-items:center; gap:6px;
118
+ font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
119
+ padding:5px 10px 5px 9px;
120
+ border:1px solid var(--rule); background:var(--paper-2);
121
+ color:var(--ink-soft); cursor:pointer; text-decoration:none;
122
+ transition: all .15s ease; white-space:nowrap;
123
+ border-radius:2px;
124
+ }
125
+ .star-btn:hover{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
126
+ .star-btn svg{ width:13px; height:13px; flex-shrink:0; }
127
+ .star-btn .star-icon{ color:var(--accent); font-size:13px; line-height:1; }
128
+ .star-btn .star-count{
129
+ font-size:10px; color:var(--ink-mute); padding-left:4px;
130
+ border-left:1px solid var(--rule); margin-left:2px;
131
+ transition: color .15s ease;
132
+ }
133
+ .star-btn:hover .star-count{ color:var(--paper); border-color:rgba(255,255,255,.2); }
134
+ [data-theme="dark"] .star-btn{ background:rgba(255,255,255,.05); }
135
+
136
+ .hero-body{ padding:56px 0 52px; position:relative; z-index:0; }
137
+ /* blurred orange glow behind hero area */
138
+ .hero-body::before{
139
+ content:""; position:absolute; z-index:-1; pointer-events:none;
140
+ left:-5%; top:10%; width:65%; height:80%;
141
+ background: radial-gradient(ellipse at 30% 50%, rgba(217,119,87,.09) 0%, transparent 70%);
142
+ filter: blur(48px);
143
+ }
144
+ .hero-body h1{
145
+ font-family:var(--serif-display);
146
+ font-weight:400;
147
+ font-size: clamp(56px, 9vw, 130px);
148
+ line-height:.9;
149
+ letter-spacing:-.025em;
150
+ margin:0 0 28px;
151
+ }
152
+ .hero-body h1 em{ font-style:italic; color:var(--accent); }
153
+ .hero-body h1 .h1-sub{
154
+ font-size: clamp(22px, 2.8vw, 44px);
155
+ font-style: normal;
156
+ color: var(--ink-soft);
157
+ letter-spacing: -.01em;
158
+ }
159
+ .hero-sub{
160
+ font-family:var(--serif-body);
161
+ font-size: clamp(17px, 1.4vw, 21px);
162
+ line-height:1.45;
163
+ color:var(--ink-2);
164
+ max-width:64ch;
165
+ margin:0 0 40px;
166
+ text-wrap:pretty;
167
+ }
168
+ .hero-stats{
169
+ display:flex; gap:0; margin-bottom:36px; flex-wrap:wrap;
170
+ }
171
+ .hero-stats .stat{
172
+ display:flex; flex-direction:column; gap:3px;
173
+ padding:16px 32px 14px;
174
+ }
175
+ .hero-stats .stat:last-child{ }
176
+ .hero-stats .stat b{
177
+ font-family:var(--serif-display);
178
+ font-size: clamp(32px, 3.8vw, 48px);
179
+ font-style:italic;
180
+ line-height:1;
181
+ color:var(--accent);
182
+ font-weight:400;
183
+ letter-spacing:-.02em;
184
+ }
185
+ .hero-stats .stat span{
186
+ font-family:var(--mono);
187
+ font-size:10px;
188
+ text-transform:uppercase;
189
+ letter-spacing:.14em;
190
+ color:var(--ink-mute);
191
+ }
192
+ @media(max-width:540px){
193
+ .hero-stats .stat{ padding:12px 20px 10px; }
194
+ }
195
+ .hero-cta{ display:flex; gap:10px; flex-wrap:wrap; }
196
+ .cta-primary{
197
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
198
+ padding:13px 26px; background:var(--ink); color:var(--paper); border:1px solid var(--ink);
199
+ display:inline-block; position:relative;
200
+ box-shadow: 4px 4px 0 var(--accent);
201
+ transition: box-shadow .14s ease, transform .14s ease, background .12s ease, border-color .12s ease;
202
+ }
203
+ .cta-primary:hover{
204
+ background:var(--accent); border-color:var(--accent);
205
+ box-shadow: none; transform: translate(4px,4px);
206
+ }
207
+ .cta-ghost{
208
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
209
+ padding:13px 26px; background:transparent; color:var(--ink-soft); border:1px solid var(--rule);
210
+ display:inline-block;
211
+ transition:background .12s ease, color .12s ease, border-color .12s ease;
212
+ }
213
+ .cta-ghost:hover{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
214
+ .star-cta{ gap:8px; }
215
+ .star-count-cta{
216
+ font-family:var(--mono); font-size:10px; letter-spacing:.04em;
217
+ background:var(--paper-edge); color:var(--ink-mute);
218
+ padding:1px 6px; border-radius:20px; display:none;
219
+ }
220
+ .star-count-cta.loaded{ display:inline; }
221
+ .star-cta:hover .star-count-cta{ background:rgba(255,255,255,.15); color:var(--paper); }
222
+
223
+ /* ── Section header ─────────────────────────────────────── */
224
+ .section{ padding:80px 0 40px; border-bottom:1px solid var(--rule); }
225
+ .section.tight{ padding-top:48px; }
226
+ .section-head{
227
+ display:grid;
228
+ grid-template-columns: 1fr auto;
229
+ gap:32px;
230
+ align-items:baseline;
231
+ margin-bottom:36px;
232
+ }
233
+ .section-head .label{
234
+ font-family:var(--mono);
235
+ font-size:11px;
236
+ letter-spacing:.18em;
237
+ text-transform:uppercase;
238
+ color:var(--ink-mute);
239
+ margin-bottom:6px;
240
+ }
241
+ .section-head h2{
242
+ font-family:var(--serif-display);
243
+ font-weight:400;
244
+ font-size: clamp(34px, 4.4vw, 56px);
245
+ line-height:1;
246
+ letter-spacing:-.01em;
247
+ margin:0;
248
+ }
249
+ .section-head h2 .it{ font-style:italic; }
250
+ .section-head .right{
251
+ font-family:var(--mono);
252
+ font-size:11px;
253
+ text-transform:uppercase;
254
+ letter-spacing:.1em;
255
+ color:var(--ink-mute);
256
+ text-align:right;
257
+ line-height:1.6;
258
+ }
259
+
260
+ /* ── Filter bar ─────────────────────────────────────────── */
261
+ .filters{
262
+ display:grid;
263
+ grid-template-columns: 1fr;
264
+ gap:20px;
265
+ margin-bottom:8px;
266
+ }
267
+ .filter-row{
268
+ display:flex; gap:8px; align-items:center;
269
+ flex-wrap:wrap;
270
+ }
271
+ .filter-row .lbl{
272
+ font-family:var(--mono);
273
+ font-size:10px;
274
+ text-transform:uppercase;
275
+ letter-spacing:.14em;
276
+ color:var(--ink-mute);
277
+ margin-right:6px;
278
+ min-width:80px;
279
+ flex-shrink:0;
280
+ }
281
+ /* on narrow screens, chip rows scroll horizontally */
282
+ @media(max-width:640px){
283
+ .filter-row{ flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch; padding-bottom:4px; }
284
+ .filter-row::-webkit-scrollbar{ height:2px; }
285
+ .filter-row::-webkit-scrollbar-thumb{ background:var(--rule-soft); }
286
+ }
287
+
288
+ .chip{
289
+ font-family:var(--mono);
290
+ font-size:11px;
291
+ text-transform:uppercase;
292
+ letter-spacing:.08em;
293
+ padding:6px 12px 5px;
294
+ border:1px solid var(--rule-soft);
295
+ background:var(--paper);
296
+ color:var(--ink-soft);
297
+ border-radius:0;
298
+ cursor:pointer;
299
+ position:relative;
300
+ white-space:nowrap;
301
+ flex-shrink:0;
302
+ max-width:220px;
303
+ overflow:hidden;
304
+ text-overflow:ellipsis;
305
+ transition:background .12s ease, color .12s ease, border-color .12s ease, box-shadow .12s ease;
306
+ line-height:1;
307
+ }
308
+ .chip:hover{ background:var(--paper-2); color:var(--ink); border-color:var(--rule); }
309
+ .chip[aria-pressed="true"]{
310
+ background:var(--ink);
311
+ color:var(--paper);
312
+ border-color:var(--ink);
313
+ box-shadow: inset 3px 0 0 var(--accent);
314
+ }
315
+ .chip .glyph-wrap{ display:inline-flex; align-items:center; gap:7px; }
316
+ .chip svg{ width:11px; height:11px; }
317
+ .chip[aria-pressed="true"] svg *{ stroke:var(--paper); fill:var(--paper); }
318
+ .chip[data-eco="Claude"][aria-pressed="true"]{
319
+ background:var(--accent); border-color:var(--accent); box-shadow:none;
320
+ }
321
+ .chip[data-eco="Claude"][aria-pressed="true"] svg *{ stroke:var(--paper); fill:var(--paper); }
322
+
323
+ /* Search */
324
+ .search-wrap{
325
+ display:flex; align-items:center; gap:14px;
326
+ min-width:0;
327
+ border:1px solid var(--rule);
328
+ padding:16px 20px;
329
+ background:var(--paper);
330
+ transition: border-color .15s ease, box-shadow .15s ease;
331
+ }
332
+ .search-wrap:focus-within{
333
+ border-color:var(--accent);
334
+ box-shadow: 0 0 0 3px rgba(199,64,30,.12), 4px 4px 0 var(--accent);
335
+ }
336
+ .search-wrap .lbl{
337
+ min-width:auto; margin:0;
338
+ color:var(--accent); font-size:12px; letter-spacing:.12em;
339
+ }
340
+ .search-wrap input{
341
+ flex:1;
342
+ min-width:0;
343
+ font:inherit;
344
+ font-family:var(--serif-body);
345
+ font-size:21px;
346
+ border:0;
347
+ background:transparent;
348
+ outline:0;
349
+ color:var(--ink);
350
+ }
351
+ .search-wrap input::placeholder{ color:var(--ink-mute); font-style:italic; }
352
+ .search-wrap .count{
353
+ font-family:var(--mono);
354
+ font-size:11px;
355
+ text-transform:uppercase;
356
+ letter-spacing:.1em;
357
+ color:var(--ink-mute);
358
+ white-space:nowrap;
359
+ }
360
+ .search-wrap .count b{ color:var(--accent); font-weight:600; }
361
+ @media(max-width:640px){
362
+ .hero-top{
363
+ align-items:flex-start;
364
+ flex-direction:column;
365
+ gap:12px;
366
+ }
367
+ .hero-nav{
368
+ width:100%;
369
+ min-width:0;
370
+ flex-wrap:wrap;
371
+ justify-content:flex-start;
372
+ gap:5px;
373
+ }
374
+ .hero-nav > a,
375
+ .hero-nav > button{
376
+ min-width:44px;
377
+ min-height:44px;
378
+ display:inline-flex;
379
+ align-items:center;
380
+ justify-content:center;
381
+ }
382
+ .hero-nav > a:not(.star-btn),
383
+ .hero-nav > .dark-toggle{
384
+ padding:0 10px;
385
+ border:1px solid var(--rule-soft);
386
+ }
387
+ .search-wrap{ gap:10px; padding:14px; }
388
+ .search-wrap .count{ font-size:10px; }
389
+ .contribute-grid{ grid-template-columns:1fr !important; }
390
+ .contribute-grid > a:first-child{
391
+ border-right:0 !important;
392
+ border-bottom:1px solid var(--rule-soft);
393
+ }
394
+ }
395
+
396
+ /* ── Skill grid (asymmetric) ────────────────────────────── */
397
+ .grid{
398
+ display:grid;
399
+ grid-template-columns: repeat(12, 1fr);
400
+ gap:0;
401
+ border-top:1px solid var(--rule);
402
+ }
403
+ /* Per-ecosystem card accent colour β€” generated by data.js ECO registry */
404
+
405
+ .card{
406
+ grid-column: span 4;
407
+ border-right:1px solid var(--rule-soft);
408
+ border-bottom:1px solid var(--rule);
409
+ padding:24px 24px 26px;
410
+ background:var(--paper);
411
+ position:relative;
412
+ cursor:pointer;
413
+ z-index:0;
414
+ transition: background .15s ease, box-shadow .18s ease, z-index 0s;
415
+ display:flex; flex-direction:column;
416
+ min-height:248px;
417
+ }
418
+ .card:hover{
419
+ background:var(--paper-2);
420
+ box-shadow: inset 4px 0 0 var(--eco-color, var(--accent));
421
+ z-index:1;
422
+ }
423
+ .card:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
424
+ .card.span-6{ grid-column: span 6; }
425
+ .card.span-8{ grid-column: span 8; }
426
+ .card.span-12{ grid-column: span 12; }
427
+ .card.featured{
428
+ background: linear-gradient(150deg, var(--ink) 0%, #201C15 100%);
429
+ color:var(--paper);
430
+ }
431
+ .card.featured .id, .card.featured .cat, .card.featured .status, .card.featured .desc, .card.featured .eco-label{ color:var(--paper); }
432
+ .card.featured .id{ color:var(--paper); opacity:.5; }
433
+ .card.featured .glyph svg *{ stroke:var(--paper); }
434
+ .card.featured:hover{
435
+ background: linear-gradient(150deg, #201C15 0%, #2A2418 100%);
436
+ box-shadow: inset 4px 0 0 var(--highlight);
437
+ }
438
+ .card.featured .name .it{ color:var(--highlight); }
439
+
440
+ .card:nth-child(12n+0){ border-right:0; }
441
+ @media (max-width: 900px){
442
+ .card, .card.span-6, .card.span-8, .card.span-12{ grid-column: span 12; border-right:0; }
443
+ }
444
+
445
+ .card .top{
446
+ display:flex; justify-content:space-between; align-items:flex-start;
447
+ gap:12px;
448
+ margin-bottom:18px;
449
+ }
450
+ .card .glyph{
451
+ width:32px; height:32px;
452
+ display:flex; align-items:center; justify-content:center;
453
+ flex-shrink:0;
454
+ }
455
+ .card .glyph svg{ width:100%; height:100%; }
456
+ .card .id{
457
+ font-family:var(--mono);
458
+ font-size:10px;
459
+ letter-spacing:.06em;
460
+ color:var(--ink-mute);
461
+ text-transform:uppercase;
462
+ }
463
+ .card .meta-top{
464
+ text-align:right;
465
+ font-family:var(--mono);
466
+ font-size:10.5px;
467
+ letter-spacing:.08em;
468
+ text-transform:uppercase;
469
+ color:var(--ink-mute);
470
+ line-height:1.5;
471
+ }
472
+ .card .meta-top .eco-label{ display:block; color:var(--ink-2); word-break:break-word; max-width:130px; }
473
+
474
+ .card .name{
475
+ font-family:var(--serif-display);
476
+ font-weight:400;
477
+ font-size: clamp(28px, 2.6vw, 38px);
478
+ line-height:1;
479
+ letter-spacing:-.01em;
480
+ margin:0 0 14px;
481
+ text-wrap:balance;
482
+ }
483
+ .card.span-8 .name, .card.span-12 .name{ font-size: clamp(40px, 4vw, 62px); }
484
+ .card .name .it{ font-style:italic; color:var(--accent); }
485
+
486
+ .card .desc{
487
+ font-size: 16px;
488
+ line-height:1.45;
489
+ color:var(--ink-2);
490
+ margin:0 0 18px;
491
+ max-width:42ch;
492
+ text-wrap:pretty;
493
+ }
494
+ .card.span-8 .desc, .card.span-12 .desc{ font-size:19px; max-width:58ch; }
495
+
496
+ .card .bottom{
497
+ margin-top:auto;
498
+ display:flex; justify-content:space-between; align-items:flex-end;
499
+ gap:14px;
500
+ padding-top:14px;
501
+ border-top:1px dotted var(--rule-soft);
502
+ }
503
+ .card .cat{
504
+ font-family:var(--mono);
505
+ font-size:10.5px;
506
+ letter-spacing:.1em;
507
+ text-transform:uppercase;
508
+ color:var(--ink-soft);
509
+ }
510
+ .card .cat::before{ content:"# "; opacity:.4; }
511
+ .card .status{
512
+ font-family:var(--mono);
513
+ font-size:10.5px;
514
+ letter-spacing:.1em;
515
+ text-transform:uppercase;
516
+ color:var(--ink-soft);
517
+ display:inline-flex; align-items:center; gap:6px;
518
+ }
519
+ .status-dot{
520
+ width:6px; height:6px; display:inline-block;
521
+ background:var(--accent);
522
+ }
523
+ .status[data-status="Stable"] .status-dot{ background:var(--ink); }
524
+ .status[data-status="Beta"] .status-dot{
525
+ background:transparent;
526
+ border:1px solid var(--accent);
527
+ box-shadow: inset 0 0 0 2px var(--paper);
528
+ width:8px; height:8px;
529
+ transform:translateY(1px);
530
+ }
531
+ .status[data-status="Deprecated"] .status-dot{ background:var(--ink-mute); }
532
+ .card.featured .status-dot{ background:var(--paper); }
533
+
534
+ /* fade-in stagger */
535
+ .card{ opacity:0; transform:translateY(6px); transition: opacity .28s ease, transform .28s ease, background .15s ease, box-shadow .18s ease; }
536
+ .card.in{ opacity:1; transform:none; }
537
+
538
+ .no-results{
539
+ grid-column: span 12;
540
+ padding:80px 24px;
541
+ text-align:center;
542
+ font-family:var(--serif-display);
543
+ font-style:italic;
544
+ font-size:32px;
545
+ color:var(--ink-mute);
546
+ border-bottom:1px solid var(--rule);
547
+ }
548
+ .no-results small{
549
+ display:block;
550
+ font-family:var(--mono);
551
+ font-style:normal;
552
+ font-size:11px;
553
+ text-transform:uppercase;
554
+ letter-spacing:.12em;
555
+ margin-top:14px;
556
+ color:var(--ink-mute);
557
+ }
558
+
559
+ /* ── Detail / modal ─────────────────────────────────────── */
560
+ .scrim{
561
+ position:fixed; inset:0; background:rgba(21,19,14,.62);
562
+ z-index:50;
563
+ display:none;
564
+ align-items:flex-start;
565
+ justify-content:center;
566
+ padding:48px 24px;
567
+ overflow-y:auto;
568
+ }
569
+ .scrim.open{
570
+ display:flex;
571
+ animation: scrim-in .22s ease-out both;
572
+ }
573
+ @keyframes scrim-in{ from{ opacity:0; } to{ opacity:1; } }
574
+
575
+ .sheet{
576
+ background:var(--paper);
577
+ width:100%;
578
+ max-width:880px;
579
+ padding:48px 56px 48px;
580
+ position:relative;
581
+ border:1px solid var(--ink);
582
+ box-shadow: 8px 8px 0 var(--accent), 16px 16px 0 -2px var(--ink);
583
+ animation: sheet-in .26s cubic-bezier(0.16,1,0.3,1) both;
584
+ }
585
+ @keyframes sheet-in{
586
+ from{ opacity:0; transform: translateY(-18px) scale(.975); }
587
+ to{ opacity:1; transform: none; }
588
+ }
589
+ @media (max-width: 720px){
590
+ .sheet{ padding:36px 24px; box-shadow: 4px 4px 0 var(--accent), 8px 8px 0 -2px var(--ink); }
591
+ }
592
+ .sheet .close{
593
+ position:absolute; top:18px; right:18px;
594
+ font-family:var(--mono);
595
+ font-size:11px;
596
+ text-transform:uppercase;
597
+ letter-spacing:.12em;
598
+ border:1px solid var(--rule);
599
+ padding:6px 10px;
600
+ }
601
+ .sheet .close:hover{ background:var(--ink); color:var(--paper); }
602
+ .sheet .sheet-top{
603
+ display:flex; align-items:flex-start; gap:18px; margin-bottom:24px;
604
+ padding-bottom:18px; border-bottom:1px solid var(--rule);
605
+ }
606
+ .sheet .glyph-lg{ width:54px; height:54px; flex-shrink:0; border:1px solid var(--rule); display:flex; align-items:center; justify-content:center; }
607
+ .sheet .glyph-lg svg{ width:32px; height:32px; }
608
+ .sheet .top-meta{
609
+ font-family:var(--mono);
610
+ font-size:11px;
611
+ letter-spacing:.08em;
612
+ text-transform:uppercase;
613
+ color:var(--ink-mute);
614
+ line-height:1.7;
615
+ }
616
+ .sheet .top-meta b{ color:var(--ink); font-weight:500; }
617
+ .sheet h3{
618
+ font-family:var(--serif-display);
619
+ font-weight:400;
620
+ font-size:64px;
621
+ line-height:1;
622
+ letter-spacing:-.015em;
623
+ margin:0 0 8px;
624
+ }
625
+ .sheet h3 .it{ font-style:italic; color:var(--accent); }
626
+ .sheet .lede{
627
+ font-family:var(--serif-body);
628
+ font-size:21px;
629
+ line-height:1.45;
630
+ color:var(--ink-2);
631
+ margin:0 0 28px;
632
+ max-width:60ch;
633
+ }
634
+ .sheet h4{
635
+ font-family:var(--mono);
636
+ font-size:10.5px;
637
+ letter-spacing:.16em;
638
+ text-transform:uppercase;
639
+ color:var(--ink-mute);
640
+ margin:28px 0 10px;
641
+ }
642
+ .sheet .field{
643
+ border-left:1px solid var(--accent);
644
+ padding:4px 0 4px 16px;
645
+ margin:0;
646
+ font-family:var(--mono);
647
+ font-size:14px;
648
+ line-height:1.55;
649
+ color:var(--ink);
650
+ background: linear-gradient(transparent, transparent);
651
+ overflow-wrap:break-word;
652
+ word-break:break-word;
653
+ }
654
+ .sheet .field.serif{
655
+ font-family:var(--serif-body);
656
+ font-size:18px;
657
+ border-left-color:var(--ink);
658
+ }
659
+ .sheet .src{
660
+ font-family:var(--mono);
661
+ font-size:12px;
662
+ border:1px solid var(--rule);
663
+ padding:10px 14px;
664
+ display:inline-flex;
665
+ align-items:center;
666
+ gap:10px;
667
+ margin-top:6px;
668
+ }
669
+ .sheet .src:hover{ background:var(--ink); color:var(--paper); }
670
+ .sheet .src::before{ content:"β†—"; color:var(--accent); }
671
+ .sheet .src:hover::before{ color:var(--highlight); }
672
+
673
+ /* ── Comparison matrix ─────────────────────────────────── */
674
+ .matrix-wrap{
675
+ max-width:100%;
676
+ border:1px solid var(--rule);
677
+ background:var(--paper);
678
+ overflow-x:auto;
679
+ }
680
+ table.matrix{
681
+ width:100%;
682
+ min-width:1680px;
683
+ border-collapse:collapse;
684
+ font-family:var(--serif-body);
685
+ }
686
+ table.matrix caption{
687
+ text-align:left;
688
+ font-family:var(--mono);
689
+ font-size:11px;
690
+ letter-spacing:.14em;
691
+ text-transform:uppercase;
692
+ color:var(--ink-soft);
693
+ padding:14px 18px;
694
+ border-bottom:1px solid var(--rule);
695
+ background:var(--paper-2);
696
+ }
697
+ table.matrix caption b{ color:var(--ink); font-weight:500; }
698
+ table.matrix thead th{
699
+ font-family:var(--mono);
700
+ font-size:11px;
701
+ letter-spacing:.06em;
702
+ text-transform:uppercase;
703
+ color:var(--ink);
704
+ text-align:center;
705
+ padding:18px 14px 16px;
706
+ border-bottom:1px solid var(--rule);
707
+ background:var(--paper);
708
+ vertical-align:bottom;
709
+ font-weight:500;
710
+ }
711
+ table.matrix thead th:first-child,
712
+ table.matrix tbody th{
713
+ position:sticky;
714
+ left:0;
715
+ width:360px;
716
+ min-width:360px;
717
+ background:var(--paper);
718
+ z-index:1;
719
+ }
720
+ table.matrix thead th:first-child{
721
+ z-index:2;
722
+ text-align:left;
723
+ padding-left:22px;
724
+ }
725
+ table.matrix thead .head-glyph{
726
+ display:flex; flex-direction:column; align-items:center; gap:8px;
727
+ }
728
+ table.matrix thead .head-glyph svg{ width:22px; height:22px; }
729
+ table.matrix tbody td{
730
+ font-size:16px;
731
+ padding:16px 14px;
732
+ text-align:center;
733
+ border-bottom:1px solid var(--rule-soft);
734
+ vertical-align:middle;
735
+ }
736
+ table.matrix tbody th{
737
+ padding-left:22px;
738
+ text-align:left;
739
+ font-family:var(--serif-body);
740
+ font-size:18px;
741
+ font-weight:400;
742
+ color:var(--ink);
743
+ border-bottom:1px solid var(--rule-soft);
744
+ vertical-align:middle;
745
+ }
746
+ table.matrix tbody th .row-num{
747
+ display:inline-block;
748
+ min-width:24px;
749
+ margin-right:14px;
750
+ font-family:var(--mono);
751
+ font-size:10px;
752
+ letter-spacing:.1em;
753
+ color:var(--ink-soft);
754
+ }
755
+ table.matrix tbody tr:hover{ background:var(--paper-2); }
756
+ table.matrix tbody tr:hover th{ background:var(--paper-2); }
757
+ table.matrix tbody tr:last-child td,
758
+ table.matrix tbody tr:last-child th{ border-bottom:0; }
759
+ .mark{
760
+ display:inline-flex; align-items:center; justify-content:center;
761
+ width:34px; height:34px;
762
+ font-family:var(--mono);
763
+ font-size:14px;
764
+ font-weight:500;
765
+ }
766
+ .mark.yes{ color:var(--accent-deep); font-size:22px; font-family:var(--serif-display); }
767
+ .mark.no{ color:var(--ink-soft); font-size:18px; }
768
+ .mark.partial{
769
+ width:auto; height:auto;
770
+ padding:4px 8px;
771
+ border:1px solid var(--rule);
772
+ background:var(--paper-2);
773
+ color:var(--ink);
774
+ font-size:10px;
775
+ text-transform:uppercase;
776
+ letter-spacing:.08em;
777
+ }
778
+ [data-theme="dark"] .mark.yes{ color:#F07855; }
779
+ @media(max-width:700px){
780
+ table.matrix thead th:first-child,
781
+ table.matrix tbody th{
782
+ width:210px;
783
+ min-width:210px;
784
+ }
785
+ }
786
+
787
+ /* ── Colophon / footer ───────────────────────��──────────── */
788
+ .colophon{
789
+ padding:64px 0 80px;
790
+ display:grid;
791
+ grid-template-columns: 1fr 1fr 1fr;
792
+ gap:48px;
793
+ border-top:1px solid var(--rule);
794
+ margin-top:0;
795
+ }
796
+ @media (max-width: 800px){ .colophon{ grid-template-columns:1fr; gap:32px; } }
797
+ .colophon h5{
798
+ font-family:var(--mono);
799
+ font-size:10.5px;
800
+ letter-spacing:.18em;
801
+ text-transform:uppercase;
802
+ color:var(--ink-mute);
803
+ margin:0 0 12px;
804
+ font-weight:500;
805
+ }
806
+ .colophon p, .colophon ul{
807
+ font-family:var(--serif-body);
808
+ font-size:15px;
809
+ line-height:1.55;
810
+ color:var(--ink-2);
811
+ margin:0;
812
+ padding:0;
813
+ list-style:none;
814
+ }
815
+ .colophon ul li{ padding:4px 0; }
816
+ .colophon ul li a{ border-bottom:1px solid var(--rule-soft); }
817
+ .colophon ul li a:hover{ color:var(--accent); border-bottom-color:var(--accent); }
818
+ .colophon .end{
819
+ grid-column:1/-1;
820
+ margin-top:24px;
821
+ padding-top:18px;
822
+ border-top:1px solid var(--rule);
823
+ display:flex; justify-content:space-between; gap:24px; flex-wrap:wrap;
824
+ font-family:var(--mono);
825
+ font-size:10.5px;
826
+ letter-spacing:.1em;
827
+ text-transform:uppercase;
828
+ color:var(--ink-mute);
829
+ }
830
+
831
+ /* focus */
832
+ :focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
833
+
834
+ /* ── Card trigger line + deep-link anchor (v1.1) ─── */
835
+ .card .trigger{
836
+ font-family:var(--mono);
837
+ font-size:11px;
838
+ line-height:1.4;
839
+ color:var(--ink-mute);
840
+ margin:0 0 14px;
841
+ white-space:nowrap;
842
+ overflow:hidden;
843
+ text-overflow:ellipsis;
844
+ max-width:100%;
845
+ padding:6px 10px;
846
+ background: var(--paper-edge);
847
+ margin-left:-4px;
848
+ transition: background .14s ease, color .14s ease;
849
+ }
850
+ .card .trigger::before{ content:"β†’ "; color:var(--accent); }
851
+ .card:hover .trigger{ background:var(--paper-2); }
852
+ .card.featured .trigger{
853
+ color:rgba(242,239,230,.55);
854
+ background:rgba(255,255,255,.06);
855
+ }
856
+ .card.featured:hover .trigger{ background:rgba(255,255,255,.1); }
857
+
858
+ /* Copy-hint badge that appears on trigger hover */
859
+ .copy-hint{
860
+ float:right; font-family:var(--mono); font-size:9px; letter-spacing:.06em;
861
+ text-transform:uppercase; color:var(--accent); opacity:0;
862
+ transition:opacity .15s ease; pointer-events:none; margin-left:8px;
863
+ white-space:nowrap;
864
+ }
865
+ .card .trigger:hover .copy-hint{ opacity:1; }
866
+
867
+ .card .top .id-wrap{ display:flex; align-items:baseline; gap:6px; }
868
+ .card .anchor{
869
+ font-family:var(--mono);
870
+ font-size:13px;
871
+ line-height:1;
872
+ color:var(--ink-mute);
873
+ padding:2px 4px;
874
+ border:1px solid transparent;
875
+ cursor:pointer;
876
+ opacity:0;
877
+ transition: opacity .14s ease, color .14s ease, background .14s ease;
878
+ }
879
+ .card:hover .anchor, .card:focus-within .anchor{ opacity:1; }
880
+ .card .anchor:hover{ color:var(--accent); border-color:var(--rule-soft); }
881
+ .card.featured .anchor{ color:rgba(242,239,230,.55); }
882
+ .card.featured .anchor:hover{ color:var(--highlight); border-color:rgba(242,239,230,.25); }
883
+
884
+ /* ── Toast (deep-link copied) ─── */
885
+ .toast{
886
+ position:fixed; left:50%; bottom:36px; transform:translateX(-50%) translateY(20px);
887
+ background:var(--ink); color:var(--paper);
888
+ font-family:var(--mono); font-size:11px;
889
+ letter-spacing:.12em; text-transform:uppercase;
890
+ padding:12px 18px;
891
+ border:1px solid var(--ink);
892
+ box-shadow: 6px 6px 0 -1px var(--accent);
893
+ z-index:80;
894
+ opacity:0;
895
+ pointer-events:none;
896
+ transition: opacity .2s ease, transform .25s ease;
897
+ }
898
+ .toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
899
+
900
+ /* ── Ecosystem deep-dive strip (v1.1) ─── */
901
+ .eco-strip{
902
+ display:grid;
903
+ grid-template-columns: repeat(4, 1fr);
904
+ border-top:1px solid var(--rule);
905
+ border-bottom:1px solid var(--rule);
906
+ margin-bottom:0;
907
+ }
908
+ @media (max-width: 900px){ .eco-strip{ grid-template-columns: repeat(2, 1fr); } }
909
+ @media (max-width: 540px){ .eco-strip{ grid-template-columns: 1fr; } }
910
+
911
+ /* Per-ecosystem colours on cells β€” generated by data.js ECO registry */
912
+
913
+ .eco-cell{
914
+ padding:26px 22px 22px;
915
+ border-right:1px solid var(--rule-soft);
916
+ background:var(--paper);
917
+ cursor:pointer;
918
+ display:flex; flex-direction:column;
919
+ min-height:200px;
920
+ transition: background .15s ease, box-shadow .18s ease;
921
+ position:relative;
922
+ box-shadow: inset 0 3px 0 var(--eco-color, var(--rule-soft));
923
+ }
924
+ .eco-cell:last-child{ border-right:0; }
925
+ .eco-cell:hover{
926
+ background:var(--paper-2);
927
+ box-shadow: inset 0 4px 0 var(--eco-color, var(--accent));
928
+ }
929
+ .eco-cell .eco-head{
930
+ display:flex; justify-content:space-between; align-items:flex-start;
931
+ margin-bottom:18px;
932
+ }
933
+ .eco-cell .eco-glyph{ width:44px; height:44px; }
934
+ .eco-cell .eco-glyph svg{ width:100%; height:100%; }
935
+ .eco-cell .eco-tag{
936
+ font-family:var(--mono);
937
+ font-size:10px;
938
+ letter-spacing:.12em;
939
+ text-transform:uppercase;
940
+ color:var(--ink-mute);
941
+ text-align:right;
942
+ line-height:1.5;
943
+ }
944
+ .eco-cell .eco-tag b{ color:var(--accent); font-weight:500; font-family:var(--serif-display); font-size:34px; letter-spacing:-.02em; display:block; line-height:.9; margin-bottom:4px; }
945
+ .eco-cell .eco-name{
946
+ font-family:var(--serif-display);
947
+ font-weight:400;
948
+ font-size: clamp(22px, 2.8vw, 34px);
949
+ line-height:1.05;
950
+ letter-spacing:-.01em;
951
+ margin:0 0 8px;
952
+ overflow-wrap:break-word;
953
+ hyphens:auto;
954
+ }
955
+ .eco-cell .eco-name .it{ font-style:italic; color:var(--accent); }
956
+ .eco-cell .eco-blurb{
957
+ font-family:var(--serif-body);
958
+ font-size:14.5px;
959
+ line-height:1.45;
960
+ color:var(--ink-2);
961
+ margin:0 0 16px;
962
+ text-wrap:pretty;
963
+ }
964
+ .eco-cell .eco-link{
965
+ margin-top:auto;
966
+ font-family:var(--mono);
967
+ font-size:10.5px;
968
+ letter-spacing:.1em;
969
+ text-transform:uppercase;
970
+ color:var(--ink);
971
+ padding-top:12px;
972
+ border-top:1px dotted var(--rule-soft);
973
+ }
974
+ .eco-cell:hover .eco-link{ color:var(--accent); }
975
+
976
+ /* inline code / backtick styling */
977
+ .inline-code{
978
+ font-family:var(--mono);
979
+ font-size:.88em;
980
+ background:var(--paper-2);
981
+ padding:1px 6px;
982
+ border-radius:3px;
983
+ color:var(--ink);
984
+ }
985
+ .trigger-sep{ color:var(--ink-mute); }
986
+
987
+ /* ── Sort toggle (v1.1) ─── */
988
+ .sort-toggle{
989
+ display:inline-flex; gap:6px; align-items:center;
990
+ font-family:var(--mono);
991
+ font-size:10.5px;
992
+ letter-spacing:.1em;
993
+ text-transform:uppercase;
994
+ color:var(--ink-mute);
995
+ }
996
+ .sort-toggle a{
997
+ padding:2px 6px;
998
+ border:1px solid transparent;
999
+ cursor:pointer;
1000
+ color:var(--ink-mute);
1001
+ }
1002
+ .sort-toggle a.on{
1003
+ color:var(--accent);
1004
+ border-color:var(--rule-soft);
1005
+ }
1006
+ .sort-toggle a:hover{ color:var(--ink); }
1007
+ .sort-toggle a.on:hover{ color:var(--accent); }
1008
+
1009
+ /* ── Shortcut modal (v1.1) β€” styled like a footnote card ─── */
1010
+ .shortcuts{
1011
+ position:fixed; right:24px; bottom:60px;
1012
+ z-index:60;
1013
+ width:340px; max-width:calc(100vw - 48px);
1014
+ background:var(--paper);
1015
+ border:1px solid var(--ink);
1016
+ box-shadow: 6px 6px 0 var(--accent), 10px 10px 0 -2px var(--ink);
1017
+ padding:22px 24px 20px;
1018
+ display:none;
1019
+ transform-origin: bottom right;
1020
+ }
1021
+ .shortcuts.open{ display:block; animation: pop .2s cubic-bezier(0.16,1,0.3,1); }
1022
+ @keyframes pop{ from{ opacity:0; transform: translateY(10px) scale(.96); } to{ opacity:1; transform:none; } }
1023
+ .shortcuts h6{
1024
+ font-family:var(--mono);
1025
+ font-size:10.5px;
1026
+ letter-spacing:.16em;
1027
+ text-transform:uppercase;
1028
+ color:var(--ink-mute);
1029
+ margin:0 0 4px;
1030
+ font-weight:500;
1031
+ }
1032
+ .shortcuts .ftitle{
1033
+ font-family:var(--serif-display);
1034
+ font-style:italic;
1035
+ font-size:24px;
1036
+ line-height:1;
1037
+ margin:0 0 16px;
1038
+ color:var(--ink);
1039
+ padding-bottom:12px;
1040
+ border-bottom:1px solid var(--rule);
1041
+ }
1042
+ .shortcuts .ftitle::before{ content:"ΒΉ "; color:var(--accent); }
1043
+ .shortcuts dl{ margin:0; display:grid; grid-template-columns: auto 1fr; gap:10px 14px; font-family:var(--serif-body); font-size:14.5px; color:var(--ink-2); }
1044
+ .shortcuts dt{ font-family:var(--mono); font-size:11px; border:1px solid var(--rule); padding:2px 8px; color:var(--ink); align-self:start; white-space:nowrap; }
1045
+ .shortcuts dd{ margin:0; line-height:1.35; align-self:center; }
1046
+ .shortcuts .closeFn{
1047
+ position:absolute; top:8px; right:10px;
1048
+ font-family:var(--mono); font-size:11px;
1049
+ color:var(--ink-mute);
1050
+ padding:4px 6px;
1051
+ }
1052
+ .shortcuts .closeFn:hover{ color:var(--accent); }
1053
+ .hint-key{
1054
+ position:fixed; right:18px; bottom:18px;
1055
+ z-index:55;
1056
+ font-family:var(--mono);
1057
+ font-size:11px;
1058
+ letter-spacing:.1em;
1059
+ text-transform:uppercase;
1060
+ color:var(--ink-mute);
1061
+ border:1px solid var(--rule);
1062
+ background:var(--paper);
1063
+ padding:6px 10px;
1064
+ cursor:pointer;
1065
+ }
1066
+ .hint-key:hover{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
1067
+ .hint-key kbd{
1068
+ font:inherit;
1069
+ color:var(--accent);
1070
+ margin-right:4px;
1071
+ }
1072
+ .hint-key:hover kbd{ color:var(--highlight); }
1073
+
1074
+ /* ── Hash-targeted card highlight ─── */
1075
+ .card.hashed{
1076
+ outline:2px solid var(--accent);
1077
+ outline-offset:-2px;
1078
+ }
1079
+
1080
+ /* ── Print stylesheet (v1.1) ─── */
1081
+ @media print {
1082
+ @page { size: A4; margin: 18mm 16mm 20mm; }
1083
+ @page :left { @bottom-left { content: "SkillsAllYouNeed"; font-family: "JetBrains Mono", monospace; font-size:9pt; color:#666; } @bottom-right { content: counter(page) " / " counter(pages); font-family: "JetBrains Mono", monospace; font-size:9pt; color:#666; } }
1084
+ @page :right { @bottom-left { content: "kishormorol.github.io/SkillsAllYouNeed"; font-family: "JetBrains Mono", monospace; font-size:9pt; color:#666; } @bottom-right { content: counter(page) " / " counter(pages); font-family: "JetBrains Mono", monospace; font-size:9pt; color:#666; } }
1085
+
1086
+ html, body{ background:#fff !important; color:#111 !important; }
1087
+ body::before, body{ background-image: none !important; }
1088
+ .hero-nav, .filters, .hint-key, .shortcuts, .scrim, .toast, .eco-strip, .sort-toggle, .grid-toolbar, .active-filters{ display:none !important; }
1089
+ .section{ border-bottom:1px solid #111 !important; padding:18pt 0 !important; }
1090
+ .section-head{ break-after:avoid; }
1091
+ .grid{ display:block !important; border:0 !important; }
1092
+ .card{
1093
+ grid-column: 1 / -1 !important;
1094
+ display:block !important;
1095
+ break-inside: avoid;
1096
+ page-break-inside: avoid;
1097
+ border:0 !important;
1098
+ border-bottom:1px solid #111 !important;
1099
+ padding:14pt 0 !important;
1100
+ background:#fff !important;
1101
+ color:#111 !important;
1102
+ min-height:0 !important;
1103
+ opacity:1 !important; transform:none !important;
1104
+ }
1105
+ .card.featured, .card.featured *{ background:#fff !important; color:#111 !important; }
1106
+ .card .top{ margin-bottom:8pt; }
1107
+ .card .name{ font-size:24pt !important; margin-bottom:6pt; }
1108
+ .card .desc{ font-size:11pt !important; max-width:none !important; color:#222 !important; }
1109
+ .card .trigger{ white-space:normal !important; overflow:visible !important; font-size:10pt !important; color:#444 !important; margin-bottom:8pt; }
1110
+ .card::after{
1111
+ content: "Trigger: " attr(data-trigger) "\A Example: " attr(data-example) "\A Source: " attr(data-source);
1112
+ white-space: pre-line;
1113
+ display:block;
1114
+ font-family:"JetBrains Mono", monospace;
1115
+ font-size:9pt;
1116
+ line-height:1.45;
1117
+ color:#222;
1118
+ margin-top:8pt;
1119
+ padding-top:6pt;
1120
+ border-top:1px dotted #999;
1121
+ }
1122
+ .card .anchor, .card .bottom .status-dot{ display:none !important; }
1123
+ table.matrix{ min-width:0; table-layout:fixed; break-inside:avoid; font-size:6pt; }
1124
+ table.matrix thead th:first-child,
1125
+ table.matrix tbody th{ position:static; width:28%; min-width:0; }
1126
+ table.matrix thead th{ padding:4pt 2pt !important; font-size:5pt !important; }
1127
+ table.matrix tbody td,
1128
+ table.matrix tbody th{ padding:4pt 2pt !important; font-size:6pt !important; }
1129
+ table.matrix .mark.partial{ padding:1pt 2pt; font-size:5pt; }
1130
+ a{ color:#111 !important; }
1131
+ a[href]:after{ content: " (" attr(href) ")"; font-family:"JetBrains Mono", monospace; font-size:8pt; color:#666; }
1132
+ .colophon{ break-before: page; }
1133
+ }
1134
+
1135
+ @media (max-width: 800px){
1136
+ .section-head{ grid-template-columns: 1fr; }
1137
+ .section-head .right{ display:none; }
1138
+ .hero-stats{ gap:28px; }
1139
+ }
1140
+
1141
+ /* ── v1.2: Favorites Β· Counts Β· Nav Β· Export Β· Scroll-top ─── */
1142
+
1143
+ /* Chip count badge */
1144
+ .chip .badge{
1145
+ font-size:9px; font-family:var(--mono); color:var(--ink-mute);
1146
+ margin-left:5px; letter-spacing:0; vertical-align:middle; opacity:.75;
1147
+ }
1148
+ .chip[aria-pressed="true"] .badge{ color:rgba(242,239,230,.6); }
1149
+
1150
+ /* Favorite button on card */
1151
+ .card .fav-btn{
1152
+ font-size:15px; color:var(--ink-mute); padding:0 2px; line-height:1;
1153
+ flex-shrink:0; background:none; border:0; cursor:pointer;
1154
+ transition: color .14s ease, transform .15s ease;
1155
+ }
1156
+ .card .fav-btn:hover{ color:var(--accent); transform:scale(1.2); }
1157
+ .card .fav-btn.saved{ color:var(--accent); }
1158
+ .card.featured .fav-btn{ color:rgba(242,239,230,.4); }
1159
+ .card.featured .fav-btn.saved{ color:var(--highlight); }
1160
+
1161
+ /* Trigger line: click-to-copy hint */
1162
+ .card .trigger{ cursor:pointer; }
1163
+ .card .trigger:active{ opacity:.6; }
1164
+
1165
+ /* Active filters bar */
1166
+ .active-filters{
1167
+ display:flex; flex-wrap:wrap; gap:6px; align-items:center;
1168
+ padding:10px 0 0; font-family:var(--mono); font-size:10.5px;
1169
+ }
1170
+ .active-filters .af-label{
1171
+ color:var(--ink-mute); text-transform:uppercase; letter-spacing:.1em;
1172
+ white-space:nowrap; flex-shrink:0;
1173
+ }
1174
+ .active-filter-tag{
1175
+ display:inline-flex; align-items:center; gap:4px;
1176
+ padding:3px 9px 3px 10px; border:1px solid var(--accent); color:var(--accent);
1177
+ font-family:var(--mono); font-size:10px; letter-spacing:.06em;
1178
+ text-transform:uppercase; cursor:pointer; background:transparent;
1179
+ transition: background .12s ease, color .12s ease;
1180
+ }
1181
+ .active-filter-tag:hover{ background:var(--accent); color:var(--paper); }
1182
+
1183
+ /* Grid toolbar (surprise Β· saved Β· export) */
1184
+ .grid-toolbar{
1185
+ display:flex; gap:8px; align-items:center; flex-wrap:wrap;
1186
+ padding:14px 0; border-bottom:1px solid var(--rule-soft);
1187
+ }
1188
+ .grid-toolbar .spacer{ flex:1; min-width:8px; }
1189
+
1190
+ /* Sheet action buttons */
1191
+ .sheet-actions{
1192
+ display:flex; gap:8px; flex-wrap:wrap; margin-top:8px;
1193
+ }
1194
+ .sheet-actions button{
1195
+ font-family:var(--mono); font-size:10.5px; letter-spacing:.1em;
1196
+ text-transform:uppercase; border:1px solid var(--rule);
1197
+ padding:5px 11px; color:var(--ink-soft); cursor:pointer;
1198
+ background:transparent; transition: background .12s ease, color .12s ease;
1199
+ }
1200
+ .sheet-actions button:hover{ background:var(--ink); color:var(--paper); }
1201
+ .sheet-actions button.is-saved{ color:var(--accent); border-color:var(--accent); }
1202
+ .sheet-actions button.is-saved:hover{ background:var(--accent); color:var(--paper); }
1203
+
1204
+ /* Sheet prev/next navigation */
1205
+ .sheet-nav{
1206
+ display:flex; gap:8px; align-items:center;
1207
+ margin-top:24px; padding-top:14px; border-top:1px solid var(--rule);
1208
+ }
1209
+ .sheet-nav button{
1210
+ font-family:var(--mono); font-size:10.5px; letter-spacing:.1em;
1211
+ text-transform:uppercase; border:1px solid var(--rule);
1212
+ padding:5px 11px; color:var(--ink); cursor:pointer; background:transparent;
1213
+ transition: background .12s ease, color .12s ease;
1214
+ }
1215
+ .sheet-nav button:hover:not(:disabled){ background:var(--ink); color:var(--paper); }
1216
+ .sheet-nav button:disabled{ opacity:.25; cursor:not-allowed; }
1217
+ .sheet-nav .nav-pos{
1218
+ flex:1; text-align:center; font-family:var(--mono); font-size:10px;
1219
+ text-transform:uppercase; letter-spacing:.08em; color:var(--ink-mute);
1220
+ }
1221
+
1222
+ /* Related skills chips */
1223
+ .related-list{
1224
+ display:flex; flex-wrap:wrap; gap:8px; margin-top:6px;
1225
+ }
1226
+ .related-chip{
1227
+ font-family:var(--mono); font-size:10px; text-transform:uppercase;
1228
+ letter-spacing:.06em; padding:5px 10px;
1229
+ border:1px solid var(--rule); background:transparent;
1230
+ color:var(--ink-mute); cursor:pointer; transition:all .15s ease;
1231
+ border-radius:2px;
1232
+ }
1233
+ .related-chip:hover{
1234
+ background:var(--ink); color:var(--paper); border-color:var(--ink);
1235
+ }
1236
+
1237
+ /* Scroll-to-top (bottom-left, mirrors hint-key) */
1238
+ .scroll-top{
1239
+ position:fixed; left:18px; bottom:18px; z-index:55;
1240
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em;
1241
+ text-transform:uppercase; color:var(--ink-mute);
1242
+ border:1px solid var(--rule); background:var(--paper);
1243
+ padding:6px 10px; cursor:pointer;
1244
+ opacity:0; pointer-events:none; transition: opacity .2s ease;
1245
+ }
1246
+ .scroll-top.visible{ opacity:1; pointer-events:auto; }
1247
+ .scroll-top:hover{ background:var(--ink); color:var(--paper); border-color:var(--ink); }
1248
+
1249
+ @media print{
1250
+ .grid-toolbar, .scroll-top, .active-filters,
1251
+ .sheet-nav, .sheet-actions{ display:none !important; }
1252
+ }
1253
+
1254
+ /* ══════════════════════════════════════════════════════════
1255
+ DARK MODE
1256
+ ══════════════════════════════════════════════════════════ */
1257
+ [data-theme="dark"]{
1258
+ --paper: #17140F;
1259
+ --paper-rgb: 23, 20, 15;
1260
+ --paper-2: #1F1C14;
1261
+ --paper-edge:#252019;
1262
+ --ink: #F0EDE3;
1263
+ --ink-2: #D5D0C4;
1264
+ --ink-soft: #A8A296;
1265
+ --ink-mute: #6E6860;
1266
+ --rule: rgba(240,237,227,.28);
1267
+ --rule-soft: rgba(240,237,227,.10);
1268
+ --accent: #E05830;
1269
+ --highlight: #F5D77A;
1270
+ --eco-claude-code: #d97757;
1271
+ --eco-chatgpt: #3AAA6A;
1272
+ --eco-gemini: #7A5AE0;
1273
+ --eco-perplexity: #2DC4A8;
1274
+ --eco-copilot: #3B9FE8;
1275
+ --eco-opencode: #38C96E;
1276
+ --eco-cursor: #EDECEC;
1277
+ --eco-codex: #F59E0B;
1278
+ --eco-github-copilot: #8B949E;
1279
+ --eco-antigravity: #8AB4F8;
1280
+ --eco-kiro: #B76BFF;
1281
+ --eco-pi: #9BA4AE;
1282
+ }
1283
+ [data-theme="dark"] .card.featured{
1284
+ background: linear-gradient(150deg, #1F1C14 0%, #0D0B08 100%);
1285
+ color: var(--ink);
1286
+ }
1287
+ [data-theme="dark"] .card.featured .desc,
1288
+ [data-theme="dark"] .card.featured .id,
1289
+ [data-theme="dark"] .card.featured .eco-label{ color: var(--ink); }
1290
+ [data-theme="dark"] .card.featured .id{ opacity: .55; }
1291
+ [data-theme="dark"] .card.featured .trigger{ color: rgba(240,237,227,.55); }
1292
+ [data-theme="dark"] .card.featured .anchor{ color: rgba(240,237,227,.55); }
1293
+ [data-theme="dark"] .card.featured .meta-top{ color: var(--ink-soft); }
1294
+ [data-theme="dark"] .card.featured .cat{ color: rgba(240,237,227,.6); }
1295
+ [data-theme="dark"] .card.featured .status{ color: var(--ink-soft); }
1296
+ [data-theme="dark"] .card.featured .fav-btn{ color: rgba(240,237,227,.4); }
1297
+ [data-theme="dark"] .card.featured .glyph svg *{ stroke: var(--ink); }
1298
+ [data-theme="dark"] .search-wrap:focus-within{
1299
+ box-shadow: 0 0 0 3px rgba(224,88,48,.18), 4px 4px 0 var(--accent);
1300
+ }
1301
+ [data-theme="dark"] body{
1302
+ background-image: radial-gradient(rgba(240,237,227,.025) 1px, transparent 1px);
1303
+ }
1304
+ [data-theme="dark"] body::before{
1305
+ background:
1306
+ repeating-linear-gradient(0deg, rgba(240,237,227,.01) 0 1px, transparent 1px 3px),
1307
+ repeating-linear-gradient(90deg, rgba(240,237,227,.007) 0 1px, transparent 1px 4px);
1308
+ }
1309
+ [data-theme="dark"] .hero-body::before{
1310
+ background: radial-gradient(ellipse at 30% 50%, rgba(217,119,87,.12) 0%, transparent 70%);
1311
+ }
1312
+ /* dark-mode toggle button */
1313
+ .dark-toggle{
1314
+ font-size:15px; line-height:1; letter-spacing:0;
1315
+ color:var(--ink-soft); padding:1px 4px;
1316
+ border:none; background:none; cursor:pointer;
1317
+ transition:color .15s ease, transform .2s ease;
1318
+ font-family:inherit;
1319
+ }
1320
+ .dark-toggle:hover{ color:var(--accent); transform:rotate(18deg); }
1321
+
1322
+ /* ══════════════════════════════════════════════════════════
1323
+ ONBOARDING STRIP
1324
+ ══════════════════════════════════════════════════════════ */
1325
+ .onboarding-strip{
1326
+ background:var(--ink); color:var(--paper);
1327
+ border-bottom:1px solid var(--rule);
1328
+ display:none; /* shown by JS the first visit */
1329
+ position:relative; overflow:hidden;
1330
+ transition: opacity 180ms cubic-bezier(0.4, 0, 0.2, 1);
1331
+ }
1332
+ .onboarding-strip.is-leaving{ opacity:0; }
1333
+ /* vermillion hairline on the top edge of the dark band */
1334
+ .onboarding-strip::before{
1335
+ content:""; position:absolute; inset:0 0 auto 0; height:2px;
1336
+ background:var(--accent); opacity:.9;
1337
+ }
1338
+ .onboarding-inner{
1339
+ display:flex; align-items:center; gap:28px; flex-wrap:wrap;
1340
+ max-width:var(--grid-max); margin:0 auto;
1341
+ padding:20px var(--gutter);
1342
+ }
1343
+ .onboarding-title{
1344
+ font-family:var(--serif-display); font-style:italic;
1345
+ font-size:19px; white-space:nowrap; flex-shrink:0; line-height:1;
1346
+ }
1347
+ .onboarding-steps{
1348
+ display:flex; gap:22px; flex-wrap:wrap; flex:1;
1349
+ align-items:flex-start; min-width:0;
1350
+ }
1351
+ .onboarding-step{
1352
+ display:flex; align-items:baseline; gap:8px;
1353
+ font-family:var(--serif-body); font-size:14px;
1354
+ line-height:1.5; max-width:340px;
1355
+ color:rgba(var(--paper-rgb), .74);
1356
+ animation: onboarding-fade-up 360ms cubic-bezier(0.22, 1, 0.36, 1) both;
1357
+ }
1358
+ .onboarding-step:nth-child(1){ animation-delay:60ms; }
1359
+ .onboarding-step:nth-child(2){ animation-delay:130ms; }
1360
+ .onboarding-step:nth-child(3){ animation-delay:200ms; }
1361
+ .step-num{
1362
+ font-family:var(--mono); font-size:10px; font-weight:600;
1363
+ background:var(--accent); color:var(--paper);
1364
+ width:18px; height:18px; flex-shrink:0;
1365
+ display:inline-flex; align-items:center; justify-content:center;
1366
+ transition: transform 220ms cubic-bezier(0.34, 1.45, 0.64, 1);
1367
+ }
1368
+ .onboarding-step:hover .step-num{ transform: scale(1.1) rotate(-4deg); }
1369
+ .onboarding-dismiss{
1370
+ font-family:var(--mono); font-size:10px; letter-spacing:.1em;
1371
+ text-transform:uppercase; color:rgba(var(--paper-rgb), .5);
1372
+ border:1px solid rgba(var(--paper-rgb), .22); padding:6px 12px;
1373
+ background:transparent; cursor:pointer; white-space:nowrap; flex-shrink:0;
1374
+ transition: color 180ms cubic-bezier(0.22, 1, 0.36, 1),
1375
+ border-color 180ms cubic-bezier(0.22, 1, 0.36, 1),
1376
+ background 180ms cubic-bezier(0.22, 1, 0.36, 1),
1377
+ transform 120ms cubic-bezier(0.22, 1, 0.36, 1);
1378
+ }
1379
+ .onboarding-dismiss:hover{
1380
+ color:rgba(var(--paper-rgb), .95);
1381
+ border-color:rgba(var(--paper-rgb), .55);
1382
+ background:rgba(var(--paper-rgb), .08);
1383
+ }
1384
+ .onboarding-dismiss:active{ transform: scale(0.96); }
1385
+ .onboarding-dismiss:focus-visible{
1386
+ outline:1px solid var(--accent); outline-offset:2px;
1387
+ }
1388
+ @keyframes onboarding-fade-up{
1389
+ from{ opacity:0; transform:translateY(6px); }
1390
+ to { opacity:1; transform:translateY(0); }
1391
+ }
1392
+ @media (prefers-reduced-motion: reduce){
1393
+ .onboarding-strip{ transition:none; }
1394
+ .onboarding-step{ animation:none !important; }
1395
+ .onboarding-step:hover .step-num{ transform:none; }
1396
+ .onboarding-dismiss:active{ transform:none; }
1397
+ }
1398
+ @media(max-width:700px){
1399
+ .onboarding-inner{
1400
+ align-items:stretch;
1401
+ flex-direction:column;
1402
+ gap:16px;
1403
+ }
1404
+ .onboarding-steps{
1405
+ flex-direction:column;
1406
+ gap:12px;
1407
+ }
1408
+ .onboarding-step{ max-width:none; }
1409
+ .onboarding-steps .onboarding-step:last-child{ display:none; }
1410
+ .onboarding-dismiss{ align-self:flex-start; }
1411
+ }
1412
+
1413
+ /* ══════════════════════════════════════════════════════════
1414
+ QUICK PRESET FILTERS
1415
+ ══════════════════════════════════════════════════════════ */
1416
+ .preset-row{
1417
+ padding-bottom:14px;
1418
+ border-bottom:1px solid var(--rule-soft);
1419
+ }
1420
+ .preset-btn{
1421
+ font-family:var(--mono); font-size:10.5px; letter-spacing:.08em;
1422
+ text-transform:uppercase; padding:5px 13px;
1423
+ border:1px solid var(--accent); color:var(--accent);
1424
+ background:transparent; cursor:pointer; border-radius:0;
1425
+ transition:background .12s ease, color .12s ease;
1426
+ }
1427
+ .preset-btn:hover, .preset-btn.active{
1428
+ background:var(--accent); color:var(--paper);
1429
+ }
1430
+ .preset-btn.preset-clear{
1431
+ border-color:var(--rule); color:var(--ink-mute);
1432
+ }
1433
+ .preset-btn.preset-clear:hover{
1434
+ background:var(--ink); color:var(--paper); border-color:var(--ink);
1435
+ }
1436
+
1437
+ /* ══════════════════════════════════════════════════════════
1438
+ NEW BADGE
1439
+ ══════════════════════════════════════════════════════════ */
1440
+ .new-badge{
1441
+ font-family:var(--mono); font-size:8.5px; text-transform:uppercase;
1442
+ letter-spacing:.05em; color:var(--paper); background:var(--accent);
1443
+ padding:1px 5px; margin-left:5px; vertical-align:middle;
1444
+ animation:pulse-new 2.6s ease-in-out infinite;
1445
+ }
1446
+ .card.featured .new-badge{ background:var(--highlight); color:var(--ink); }
1447
+ @keyframes pulse-new{
1448
+ 0%,100%{ opacity:1; } 50%{ opacity:.6; }
1449
+ }
1450
+
1451
+ /* ══════════════════════════════════════════════════════════
1452
+ CATEGORY COLOR ACCENTS
1453
+ ══════════════════════════════════════════════════════════ */
1454
+ .cat[data-cat="Code"] { color:#2D7A4F; }
1455
+ .cat[data-cat="Web"] { color:#1A5F96; }
1456
+ .cat[data-cat="Visual"] { color:#7030A0; }
1457
+ .cat[data-cat="Agentic"] { color:var(--accent); }
1458
+ .cat[data-cat="Integration"] { color:#A0600A; }
1459
+ .cat[data-cat="API"] { color:#0A7A7A; }
1460
+ .cat[data-cat="Data"] { color:#1A6090; }
1461
+ .card.featured .cat{ color:rgba(242,239,230,.6) !important; }
1462
+ [data-theme="dark"] .cat[data-cat="Code"] { color:#5CB880; }
1463
+ [data-theme="dark"] .cat[data-cat="Web"] { color:#5AA8DC; }
1464
+ [data-theme="dark"] .cat[data-cat="Visual"] { color:#B07ACC; }
1465
+ [data-theme="dark"] .cat[data-cat="Integration"] { color:#CC9840; }
1466
+ [data-theme="dark"] .cat[data-cat="API"] { color:#40C0C0; }
1467
+ [data-theme="dark"] .cat[data-cat="Data"] { color:#5AAAD0; }
1468
+
1469
+ /* ══════════════════════════════════════════════════════════
1470
+ IMPROVED EMPTY STATE
1471
+ ══════════════════════════════════════════════════════════ */
1472
+ .no-results{
1473
+ grid-column:span 12; padding:72px 24px;
1474
+ text-align:center;
1475
+ font-family:var(--serif-display); font-style:italic;
1476
+ font-size:32px; color:var(--ink-mute);
1477
+ border-bottom:1px solid var(--rule);
1478
+ }
1479
+ .no-results small{
1480
+ display:block; font-family:var(--mono); font-style:normal;
1481
+ font-size:11px; text-transform:uppercase; letter-spacing:.12em;
1482
+ margin-top:12px; color:var(--ink-mute);
1483
+ }
1484
+ .no-results .nr-reset{
1485
+ display:inline-block; margin-top:20px;
1486
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em;
1487
+ text-transform:uppercase; padding:9px 18px;
1488
+ border:1px solid var(--rule); color:var(--ink-soft);
1489
+ background:transparent; cursor:pointer;
1490
+ transition:background .12s ease, color .12s ease;
1491
+ }
1492
+ .no-results .nr-reset:hover{ background:var(--ink); color:var(--paper); }
1493
+
1494
+ /* ══════════════════════════════════════════════════════════
1495
+ CARD HOVER GLYPH ANIMATION
1496
+ ══════════════════════════════════════════════════════════ */
1497
+ .card .glyph{ transition: transform .18s ease; }
1498
+ .card:hover .glyph{ transform: scale(1.12); }
1499
+ .card.featured:hover .glyph{ transform: scale(1.12); }
1500
+
1501
+ /* ── Add Skill form ─── */
1502
+ .add-scrim{
1503
+ position:fixed; inset:0; background:rgba(21,19,14,.62);
1504
+ z-index:50; display:none; align-items:flex-start;
1505
+ justify-content:center; padding:48px 24px; overflow-y:auto;
1506
+ }
1507
+ .add-scrim.open{
1508
+ display:flex;
1509
+ animation: scrim-in .22s ease-out both;
1510
+ }
1511
+ .add-sheet{
1512
+ background:var(--paper); width:100%; max-width:680px;
1513
+ padding:48px 56px 48px; position:relative;
1514
+ border:1px solid var(--ink);
1515
+ box-shadow:8px 8px 0 var(--accent), 16px 16px 0 -2px var(--ink);
1516
+ animation: sheet-in .26s cubic-bezier(0.16,1,0.3,1) both;
1517
+ }
1518
+ @media(max-width:720px){ .add-sheet{ padding:36px 24px; box-shadow:4px 4px 0 var(--accent), 8px 8px 0 -2px var(--ink); } }
1519
+ .add-sheet h3{
1520
+ font-family:var(--serif-display); font-weight:400;
1521
+ font-size:clamp(36px,4vw,56px); line-height:1; letter-spacing:-.015em;
1522
+ margin:0 0 6px;
1523
+ }
1524
+ .add-sheet h3 .it{ font-style:italic; color:var(--accent); }
1525
+ .add-sheet .form-sub{
1526
+ font-family:var(--mono); font-size:11px; text-transform:uppercase;
1527
+ letter-spacing:.1em; color:var(--ink-mute); margin-bottom:28px;
1528
+ padding-bottom:18px; border-bottom:1px solid var(--rule);
1529
+ }
1530
+ .form-row{ display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
1531
+ .form-row.full{ grid-template-columns:1fr; }
1532
+ @media(max-width:540px){ .form-row{ grid-template-columns:1fr; } }
1533
+ .form-group{ display:flex; flex-direction:column; gap:5px; }
1534
+ .form-group label{
1535
+ font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
1536
+ letter-spacing:.12em; color:var(--ink-mute);
1537
+ }
1538
+ .form-group input,.form-group select,.form-group textarea{
1539
+ font-family:var(--serif-body); font-size:16px;
1540
+ border:1px solid var(--rule); background:var(--paper);
1541
+ color:var(--ink); padding:10px 12px; outline:none;
1542
+ border-radius:0; -webkit-appearance:none; resize:vertical;
1543
+ }
1544
+ .form-group input:focus,.form-group select:focus,.form-group textarea:focus{
1545
+ border-color:var(--ink); box-shadow:3px 3px 0 -1px var(--ink);
1546
+ }
1547
+ .form-group .id-preview{
1548
+ font-family:var(--mono); font-size:11px; color:var(--ink-mute); margin-top:2px;
1549
+ }
1550
+ .form-group .id-preview b{ color:var(--accent); }
1551
+ .form-actions{ display:flex; gap:10px; margin-top:24px; padding-top:18px; border-top:1px solid var(--rule); flex-wrap:wrap; }
1552
+ .btn-primary{
1553
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em;
1554
+ text-transform:uppercase; padding:10px 20px;
1555
+ background:var(--ink); color:var(--paper); border:1px solid var(--ink);
1556
+ cursor:pointer; transition:background .12s ease, border-color .12s ease;
1557
+ }
1558
+ .btn-primary:hover{ background:var(--accent); border-color:var(--accent); }
1559
+ .btn-ghost{
1560
+ font-family:var(--mono); font-size:11px; letter-spacing:.1em;
1561
+ text-transform:uppercase; padding:10px 20px;
1562
+ background:transparent; color:var(--ink-soft); border:1px solid var(--rule);
1563
+ cursor:pointer; transition:background .12s ease, color .12s ease;
1564
+ }
1565
+ .btn-ghost:hover{ background:var(--ink); color:var(--paper); }
1566
+ .custom-badge{
1567
+ font-family:var(--mono); font-size:9px; text-transform:uppercase;
1568
+ letter-spacing:.06em; color:var(--paper); background:var(--accent);
1569
+ padding:1px 5px; margin-left:6px; vertical-align:middle;
1570
+ }
1571
+ .card.featured .custom-badge{ background:var(--highlight); color:var(--ink); }
1572
+ .sheet-actions button.is-delete{ color:var(--ink-mute); }
1573
+ .sheet-actions button.is-delete:hover{ background:var(--accent); color:var(--paper); border-color:var(--accent); }