mirror of
https://fastgit.cc/github.com/HKUDS/CLI-Anything
synced 2026-04-21 13:21:02 +08:00
2754 lines
86 KiB
HTML
2754 lines
86 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CLI-Anything Hub</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--bg: #09090b;
|
|
--surface: #18181b;
|
|
--surface-raised: #1e1e22;
|
|
--border: #27272a;
|
|
--border-subtle: #1f1f23;
|
|
--text: #fafafa;
|
|
--text-secondary: #a1a1aa;
|
|
--text-tertiary: #71717a;
|
|
--hero-neutral-top: #e7ecf2;
|
|
--hero-neutral-bottom: #b8c1cd;
|
|
--accent: #3b82f6;
|
|
--accent-muted: #2563eb;
|
|
--green: #22c55e;
|
|
--green-muted: rgba(34, 197, 94, 0.12);
|
|
--purple: #a78bfa;
|
|
--purple-muted: rgba(167, 139, 250, 0.12);
|
|
--radius: 8px;
|
|
--radius-sm: 6px;
|
|
}
|
|
|
|
/* ── Light theme ── */
|
|
[data-theme="light"] {
|
|
--bg: #ffffff;
|
|
--surface: #f4f4f5;
|
|
--surface-raised: #e4e4e7;
|
|
--border: #d4d4d8;
|
|
--border-subtle: #e4e4e7;
|
|
--text: #09090b;
|
|
--text-secondary: #52525b;
|
|
--text-tertiary: #71717a;
|
|
--hero-neutral-top: #7f8894;
|
|
--hero-neutral-bottom: #a8b1bc;
|
|
--accent: #2563eb;
|
|
--accent-muted: #1d4ed8;
|
|
--green: #16a34a;
|
|
--green-muted: rgba(22, 163, 74, 0.1);
|
|
--purple: #7c3aed;
|
|
--purple-muted: rgba(124, 58, 237, 0.1);
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* ── Nav bar ── */
|
|
.nav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
max-width: 1120px;
|
|
margin: 0 auto;
|
|
padding: 1rem 1.5rem;
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.nav-brand {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.nav-brand span { color: var(--text-tertiary); }
|
|
|
|
.nav-links {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.nav-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.4rem 0.7rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-size: 0.8rem;
|
|
font-weight: 500;
|
|
border-radius: var(--radius-sm);
|
|
transition: color 0.15s, background 0.15s;
|
|
}
|
|
|
|
.nav-link:hover {
|
|
color: var(--text);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.nav-link svg { width: 15px; height: 15px; flex-shrink: 0; }
|
|
|
|
.nav-link-stars {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 2.8rem;
|
|
padding: 0.1rem 0.45rem;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
background: var(--surface);
|
|
color: var(--text);
|
|
font-size: 0.72rem;
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
/* ── Theme toggle ── */
|
|
.theme-toggle {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
background: none;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.theme-toggle:hover {
|
|
color: var(--text);
|
|
border-color: var(--text-tertiary);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.theme-toggle svg { width: 16px; height: 16px; }
|
|
.theme-toggle .icon-moon { display: none; }
|
|
.theme-toggle .icon-sun { display: block; }
|
|
[data-theme="light"] .theme-toggle .icon-moon { display: block; }
|
|
[data-theme="light"] .theme-toggle .icon-sun { display: none; }
|
|
|
|
/* ── Hero ── */
|
|
.hero {
|
|
max-width: 1120px;
|
|
margin: 0 auto;
|
|
padding: 3.5rem 1.5rem 2.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.hero-title {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.95rem;
|
|
margin-bottom: 0.82rem;
|
|
position: relative;
|
|
}
|
|
|
|
.hero-title-badge {
|
|
position: relative;
|
|
width: 3.25rem;
|
|
height: 3.25rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(148, 163, 184, 0.12);
|
|
background:
|
|
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.008)),
|
|
linear-gradient(135deg, rgba(59, 130, 246, 0.08), transparent 42%),
|
|
var(--surface);
|
|
box-shadow:
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
|
inset 0 -1px 0 rgba(255, 255, 255, 0.02),
|
|
0 14px 34px rgba(2, 6, 23, 0.2);
|
|
overflow: hidden;
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
}
|
|
|
|
.hero-title-badge::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
background: linear-gradient(180deg, rgba(255,255,255,0.04), transparent 36%, transparent 100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hero-title-badge svg {
|
|
width: 2.26rem;
|
|
height: 2.26rem;
|
|
overflow: visible;
|
|
}
|
|
|
|
.hero h1 {
|
|
font-size: 2.8rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.15;
|
|
margin: 0;
|
|
display: inline-flex;
|
|
align-items: baseline;
|
|
gap: 0.18em;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hero-word {
|
|
display: inline-block;
|
|
will-change: transform, opacity;
|
|
}
|
|
|
|
.js .hero-word {
|
|
opacity: 0;
|
|
transform: translateY(18px);
|
|
}
|
|
|
|
.motion-ready .hero-word--cli {
|
|
animation: hero-rise-in 0.72s cubic-bezier(0.2, 0.9, 0.2, 1) 0.06s both;
|
|
}
|
|
|
|
.motion-ready .hero-word--anything {
|
|
animation: hero-rise-in 0.82s cubic-bezier(0.2, 0.9, 0.2, 1) 0.18s both;
|
|
}
|
|
|
|
.motion-ready .hero-word--hub {
|
|
animation: hero-drop-in 0.8s cubic-bezier(0.2, 0.9, 0.2, 1) 0.34s both;
|
|
}
|
|
|
|
.hero-accent { color: var(--accent); }
|
|
.hero-dim {
|
|
color: var(--hero-neutral-bottom);
|
|
background: linear-gradient(180deg, var(--hero-neutral-top) 0%, var(--hero-neutral-bottom) 100%);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.hero-shimmer {
|
|
position: relative;
|
|
isolation: isolate;
|
|
}
|
|
|
|
.hero-shimmer::after {
|
|
content: attr(data-text);
|
|
position: absolute;
|
|
inset: 0;
|
|
color: transparent;
|
|
background: linear-gradient(115deg, transparent 22%, rgba(255,255,255,0.94) 48%, transparent 72%);
|
|
background-size: 220% 100%;
|
|
background-position: 145% 50%;
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.motion-ready .hero-shimmer::after {
|
|
animation: hero-shimmer-pass 1.25s ease-out 0.66s 1 both;
|
|
}
|
|
|
|
.hero-tagline {
|
|
font-size: 1.15rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
max-width: 700px;
|
|
margin: 0 auto 1.5rem;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
white-space: normal;
|
|
}
|
|
|
|
.hero-tagline-line {
|
|
display: inline;
|
|
}
|
|
|
|
.js .hero-tagline-line {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
|
|
.hero-tagline-line--any {
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.motion-ready .hero-tagline-line--any {
|
|
animation: hero-tagline-in 0.62s ease-out 0.5s both;
|
|
}
|
|
|
|
.motion-ready .hero-tagline-line--rest {
|
|
animation: hero-tagline-in 0.62s ease-out 0.64s both;
|
|
}
|
|
|
|
.hero-tagline strong { color: var(--text); font-weight: 600; }
|
|
|
|
.motion-ready .hero-title-badge {
|
|
animation: hero-badge-in 0.68s cubic-bezier(0.2, 0.9, 0.2, 1) 0.02s both;
|
|
}
|
|
|
|
.motion-ready .hero-title-badge svg {
|
|
animation: hero-badge-float 4.2s ease-in-out 1.1s infinite;
|
|
}
|
|
|
|
.motion-ready .hero-badge-glow {
|
|
animation: hero-badge-pulse 4.2s ease-in-out 1.1s infinite;
|
|
}
|
|
|
|
.motion-ready .hero-badge-cursor {
|
|
animation: hero-badge-cursor 1.1s steps(1) infinite;
|
|
}
|
|
|
|
@keyframes hero-rise-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(18px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes hero-drop-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-16px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes hero-tagline-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes hero-badge-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px) scale(0.94);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes hero-badge-float {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-3px); }
|
|
}
|
|
|
|
@keyframes hero-badge-pulse {
|
|
0%, 100% { opacity: 0.3; }
|
|
50% { opacity: 0.88; }
|
|
}
|
|
|
|
@keyframes hero-badge-cursor {
|
|
0%, 48% { opacity: 1; }
|
|
49%, 100% { opacity: 0.18; }
|
|
}
|
|
|
|
@keyframes hero-shimmer-pass {
|
|
0% {
|
|
opacity: 0;
|
|
background-position: 145% 50%;
|
|
}
|
|
12% {
|
|
opacity: 0.92;
|
|
}
|
|
78% {
|
|
opacity: 0.92;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
background-position: -55% 50%;
|
|
}
|
|
}
|
|
|
|
@keyframes hero-stats-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* ── Empower card ── */
|
|
.empower-row {
|
|
display: flex;
|
|
gap: 1rem;
|
|
max-width: 1100px;
|
|
margin: 0 auto 1.5rem;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.empower-card {
|
|
flex: 1;
|
|
min-width: 0;
|
|
margin: 0;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 1.5rem 1.75rem;
|
|
text-align: left;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.empower-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0;
|
|
height: 2px;
|
|
background: var(--accent);
|
|
}
|
|
|
|
.empower-card h3 {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.85rem;
|
|
color: var(--text);
|
|
}
|
|
|
|
.empower-card h3 span { color: var(--accent); }
|
|
|
|
.install-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 0.52rem 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.install-row:hover { border-color: var(--text-tertiary); }
|
|
|
|
.install-row code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.78rem;
|
|
color: var(--text-secondary);
|
|
flex: 1;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.install-row--wrap {
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.install-row--wrap code {
|
|
white-space: normal;
|
|
overflow: visible;
|
|
text-overflow: clip;
|
|
overflow-wrap: anywhere;
|
|
word-break: break-word;
|
|
line-height: 1.45;
|
|
padding-top: 0.08rem;
|
|
}
|
|
|
|
.empower-note {
|
|
margin-top: 0.25rem;
|
|
font-size: 0.76rem;
|
|
color: var(--text-tertiary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.empower-note strong {
|
|
color: var(--text-secondary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.empower-prompt {
|
|
margin-top: 0.85rem;
|
|
padding: 0.6rem 0.85rem;
|
|
background: rgba(59, 130, 246, 0.05);
|
|
border: 1px dashed rgba(59, 130, 246, 0.2);
|
|
border-radius: var(--radius);
|
|
font-size: 0.82rem;
|
|
color: var(--text-tertiary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.empower-prompt strong { color: var(--text-secondary); font-weight: 600; }
|
|
.empower-prompt em { color: var(--accent); font-style: italic; }
|
|
|
|
.empower-available {
|
|
margin: 0.25rem 0 0.7rem;
|
|
font-size: 0.78rem;
|
|
color: var(--text-tertiary);
|
|
}
|
|
.empower-available a { color: var(--accent); text-decoration: none; }
|
|
.empower-available a:hover { text-decoration: underline; }
|
|
|
|
.empower-card.empower-self::before { background: var(--green); }
|
|
.empower-card.empower-self h3 span { color: var(--green); }
|
|
.typing-shell {
|
|
position: relative;
|
|
margin-bottom: 0.9rem;
|
|
padding: 0.8rem 0.92rem 0.88rem;
|
|
background:
|
|
linear-gradient(180deg, rgba(34, 197, 94, 0.06) 0%, rgba(34, 197, 94, 0.015) 100%),
|
|
radial-gradient(circle at top right, rgba(59, 130, 246, 0.14), transparent 42%),
|
|
var(--bg);
|
|
border: 1px solid rgba(63, 63, 70, 0.95);
|
|
border-radius: 11px;
|
|
overflow: hidden;
|
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 12px 28px rgba(0, 0, 0, 0.16);
|
|
}
|
|
.typing-shell::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 22%, transparent 78%, rgba(255, 255, 255, 0.025));
|
|
pointer-events: none;
|
|
}
|
|
.typing-shell-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.56rem;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.typing-shell-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.42rem;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.68rem;
|
|
font-style: italic;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
}
|
|
.typing-shell-label::before {
|
|
content: '';
|
|
width: 0.42rem;
|
|
height: 0.42rem;
|
|
border-radius: 999px;
|
|
background: var(--green);
|
|
box-shadow: 0 0 14px rgba(34, 197, 94, 0.55);
|
|
flex-shrink: 0;
|
|
}
|
|
.typing-shell-copy {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.typing-shell-body {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.65rem;
|
|
min-height: 2.5rem;
|
|
padding: 0.76rem 0.82rem;
|
|
border-radius: 9px;
|
|
border: 1px solid rgba(63, 63, 70, 0.92);
|
|
background: linear-gradient(180deg, rgba(9, 9, 11, 0.74), rgba(9, 9, 11, 0.94));
|
|
}
|
|
.typing-shell-prompt {
|
|
color: var(--green);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.82rem;
|
|
font-weight: 500;
|
|
text-shadow: 0 0 16px rgba(34, 197, 94, 0.25);
|
|
flex-shrink: 0;
|
|
}
|
|
.typing-shell-code {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.8rem;
|
|
color: #d4d4d8;
|
|
letter-spacing: -0.01em;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
.typing-shell-text {
|
|
display: inline-block;
|
|
min-height: 1em;
|
|
}
|
|
.typing-shell-cursor {
|
|
width: 0.62ch;
|
|
height: 1.1em;
|
|
margin-left: 0.1rem;
|
|
border-radius: 1px;
|
|
background: linear-gradient(180deg, #fafafa, #a1a1aa);
|
|
box-shadow: 0 0 14px rgba(255, 255, 255, 0.2);
|
|
animation: typing-caret 1s steps(1) infinite;
|
|
flex-shrink: 0;
|
|
}
|
|
.typing-shell-subtitle {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin-top: 0.55rem;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.73rem;
|
|
font-style: italic;
|
|
line-height: 1.45;
|
|
}
|
|
@keyframes typing-caret {
|
|
0%, 48% { opacity: 1; }
|
|
49%, 100% { opacity: 0.2; }
|
|
}
|
|
.empower-self .cmd-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.38rem;
|
|
}
|
|
.empower-self .cmd-item {
|
|
display: grid;
|
|
grid-template-rows: auto auto;
|
|
align-content: start;
|
|
gap: 0.1rem;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 0.42rem 0.64rem 0.44rem;
|
|
min-height: 3.12rem;
|
|
}
|
|
.empower-self .cmd-item:hover { border-color: var(--text-tertiary); }
|
|
.empower-self .cmd-item .cmd-name {
|
|
color: var(--green);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 500;
|
|
font-size: 0.76rem;
|
|
line-height: 1.18;
|
|
white-space: nowrap;
|
|
}
|
|
.empower-self .cmd-item .cmd-desc {
|
|
color: var(--text-tertiary);
|
|
font-size: 0.68rem;
|
|
line-height: 1.22;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.hero-actions {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.btn-primary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.55rem 1.1rem;
|
|
background: var(--text);
|
|
color: var(--bg);
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
border-radius: var(--radius-sm);
|
|
transition: opacity 0.15s;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-primary:hover { opacity: 0.85; }
|
|
|
|
.btn-secondary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.55rem 1.1rem;
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border);
|
|
transition: border-color 0.15s, color 0.15s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-secondary:hover { border-color: var(--text-tertiary); color: var(--text); }
|
|
.btn-secondary svg { width: 15px; height: 15px; }
|
|
|
|
.hero-hint {
|
|
font-size: 0.78rem;
|
|
color: var(--text-tertiary);
|
|
font-style: italic;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.hero-hint a {
|
|
color: var(--text-tertiary);
|
|
text-decoration: underline;
|
|
text-decoration-style: dotted;
|
|
}
|
|
|
|
.hero-hint a:hover { color: var(--text-secondary); }
|
|
|
|
.hero-cta {
|
|
font-size: 0.95rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.65;
|
|
max-width: 700px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.hero-cta em { color: var(--accent); font-style: normal; font-weight: 600; }
|
|
.hero-cta a { color: var(--accent); text-decoration: none; font-weight: 500; }
|
|
.hero-cta a:hover { text-decoration: underline; }
|
|
|
|
.stats {
|
|
display: inline-flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
gap: 0;
|
|
margin: 0.45rem auto 1.45rem;
|
|
}
|
|
|
|
.js .stats {
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
}
|
|
|
|
.motion-ready .stats {
|
|
animation: hero-stats-in 0.66s cubic-bezier(0.2, 0.9, 0.2, 1) 0.78s both;
|
|
}
|
|
|
|
.stat {
|
|
min-width: 8.5rem;
|
|
padding: 0 1.55rem;
|
|
position: relative;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat + .stat::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0.2rem;
|
|
bottom: 0.2rem;
|
|
width: 1px;
|
|
background: linear-gradient(180deg, transparent, rgba(148, 163, 184, 0.32), transparent);
|
|
}
|
|
|
|
.stat-num {
|
|
font-size: 2.7rem;
|
|
line-height: 1;
|
|
font-weight: 800;
|
|
letter-spacing: -0.045em;
|
|
color: var(--accent);
|
|
font-variant-numeric: tabular-nums;
|
|
text-shadow: 0 10px 28px rgba(37, 99, 235, 0.16);
|
|
}
|
|
|
|
.stat-label {
|
|
margin-top: 0.38rem;
|
|
font-size: 0.73rem;
|
|
color: var(--text-tertiary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.09em;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.chip-label {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: var(--text-tertiary);
|
|
background: var(--surface-raised);
|
|
padding: 0.15rem 0.45rem;
|
|
border-radius: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.copy-btn {
|
|
background: none;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
color: var(--text-tertiary);
|
|
cursor: pointer;
|
|
padding: 0.2rem 0.45rem;
|
|
font-size: 0.7rem;
|
|
font-family: 'Inter', sans-serif;
|
|
transition: color 0.15s, border-color 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.copy-btn:hover { color: var(--text); border-color: var(--text-tertiary); }
|
|
|
|
/* ── Divider ── */
|
|
.section-divider {
|
|
max-width: 1120px;
|
|
margin: 0 auto;
|
|
border-top: 1px solid var(--border-subtle);
|
|
}
|
|
|
|
.marketplace-prelude {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 0.9rem 1.5rem 0;
|
|
}
|
|
|
|
.marketplace-prelude .typing-shell {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* ── Deck system ── */
|
|
.deck-stage {
|
|
--deck-peek: 72px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
position: relative;
|
|
}
|
|
|
|
.deck-viewport {
|
|
--deck-panel-width: calc(100% - var(--deck-peek));
|
|
width: calc(100% + var(--deck-peek));
|
|
margin-right: calc(var(--deck-peek) * -1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
isolation: isolate;
|
|
touch-action: pan-y pinch-zoom;
|
|
}
|
|
.deck-viewport.dragging * { user-select: none; }
|
|
|
|
.deck-track {
|
|
display: flex;
|
|
width: max-content;
|
|
transition: transform 0.55s cubic-bezier(0.4, 0, 0.15, 1);
|
|
will-change: transform;
|
|
}
|
|
|
|
.deck-track.is-dragging { transition: none; }
|
|
|
|
.deck-panel {
|
|
position: relative;
|
|
width: var(--deck-panel-width);
|
|
min-width: var(--deck-panel-width);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.deck-panel--public {
|
|
box-shadow: inset 1px 0 0 rgba(161, 161, 170, 0.55);
|
|
}
|
|
|
|
.deck-panel::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.28s ease;
|
|
z-index: 2;
|
|
}
|
|
|
|
.deck-track[data-active="harness"] .deck-panel--public::before {
|
|
background: linear-gradient(90deg, rgba(9, 9, 11, 0.4) 0%, rgba(9, 9, 11, 0.2) 48%, rgba(9, 9, 11, 0.05) 100%);
|
|
opacity: 1;
|
|
}
|
|
|
|
.deck-track[data-active="public"] .deck-panel--harness::before {
|
|
background: linear-gradient(270deg, rgba(9, 9, 11, 0.4) 0%, rgba(9, 9, 11, 0.2) 48%, rgba(9, 9, 11, 0.05) 100%);
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-theme="light"] .deck-track[data-active="harness"] .deck-panel--public::before {
|
|
background: linear-gradient(90deg, rgba(250, 250, 250, 0.56) 0%, rgba(250, 250, 250, 0.28) 48%, rgba(250, 250, 250, 0.06) 100%);
|
|
}
|
|
|
|
[data-theme="light"] .deck-track[data-active="public"] .deck-panel--harness::before {
|
|
background: linear-gradient(270deg, rgba(250, 250, 250, 0.56) 0%, rgba(250, 250, 250, 0.28) 48%, rgba(250, 250, 250, 0.06) 100%);
|
|
}
|
|
|
|
/* ── Deck nav strip ── */
|
|
.deck-nav {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 1.25rem 1.5rem 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0;
|
|
}
|
|
|
|
.deck-tab {
|
|
position: relative;
|
|
padding: 0.6rem 1.25rem;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.85rem;
|
|
font-family: 'Inter', sans-serif;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.deck-tab::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0; left: 1.25rem; right: 1.25rem;
|
|
height: 2px;
|
|
background: transparent;
|
|
border-radius: 1px;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.deck-tab:hover { color: var(--text-secondary); }
|
|
|
|
.deck-tab.active {
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.deck-tab.active::after {
|
|
background: var(--accent);
|
|
}
|
|
|
|
.deck-tab.active.deck-tab--public::after {
|
|
background: var(--orange);
|
|
}
|
|
|
|
.deck-tab .deck-count {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
padding: 0.1rem 0.45rem;
|
|
border-radius: 999px;
|
|
background: var(--surface-raised);
|
|
color: var(--text-tertiary);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.deck-tab.active .deck-count {
|
|
background: rgba(59, 130, 246, 0.12);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.deck-tab.active.deck-tab--public .deck-count {
|
|
background: rgba(234, 138, 46, 0.12);
|
|
color: var(--orange);
|
|
}
|
|
|
|
.deck-separator {
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--border-subtle);
|
|
margin: 0 0.5rem;
|
|
}
|
|
|
|
.deck-arrow-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.4rem 0.85rem;
|
|
background: none;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.75rem;
|
|
font-family: 'Inter', sans-serif;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.deck-arrow-btn:hover {
|
|
border-color: var(--text-tertiary);
|
|
color: var(--text-secondary);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.deck-arrow-btn svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.deck-arrow-btn:hover svg {
|
|
transform: translateX(2px);
|
|
}
|
|
|
|
.deck-arrow-btn--back:hover svg {
|
|
transform: translateX(-2px);
|
|
}
|
|
|
|
/* ── Public deck accent ── */
|
|
:root {
|
|
--orange: #ea8a2e;
|
|
--orange-muted: rgba(234, 138, 46, 0.12);
|
|
}
|
|
|
|
[data-theme="light"] {
|
|
--orange: #c2711e;
|
|
--orange-muted: rgba(194, 113, 30, 0.1);
|
|
}
|
|
|
|
/* ── Controls ── */
|
|
.controls {
|
|
margin: 0;
|
|
padding: 1.15rem 1.5rem 0;
|
|
display: flex;
|
|
gap: 0.7rem;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.search {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
padding: 0.72rem 0.95rem;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text);
|
|
font-size: 0.9rem;
|
|
font-family: 'Inter', sans-serif;
|
|
outline: none;
|
|
transition: border-color 0.15s;
|
|
}
|
|
|
|
.search::placeholder { color: var(--text-tertiary); }
|
|
.search:focus { border-color: var(--text-tertiary); }
|
|
|
|
.sort-select {
|
|
min-width: 168px;
|
|
padding: 0.78rem 2.7rem 0.78rem 1rem;
|
|
background:
|
|
linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)),
|
|
var(--surface) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><path fill="%23a1a1aa" d="M7 10.2 2.8 6h8.4z"/></svg>') no-repeat right 0.85rem center;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
color: var(--text);
|
|
font-size: 0.9rem;
|
|
font-family: 'Inter', sans-serif;
|
|
font-weight: 600;
|
|
letter-spacing: -0.01em;
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
|
|
outline: none;
|
|
cursor: pointer;
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
transition: border-color 0.15s, box-shadow 0.15s, background-color 0.15s, transform 0.15s;
|
|
}
|
|
|
|
.sort-select:focus, .sort-select:hover {
|
|
border-color: var(--text-tertiary);
|
|
background-color: var(--surface-raised);
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
[data-theme="light"] .sort-select {
|
|
background-image:
|
|
linear-gradient(180deg, rgba(255,255,255,0.5), rgba(255,255,255,0)),
|
|
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><path fill="%2352525b" d="M7 10.2 2.8 6h8.4z"/></svg>');
|
|
}
|
|
|
|
.filter-row {
|
|
display: flex;
|
|
gap: 0.45rem;
|
|
row-gap: 0.55rem;
|
|
flex-wrap: wrap;
|
|
margin: 0;
|
|
padding: 0.85rem 1.5rem 0;
|
|
}
|
|
|
|
.deck-panel--harness .filter-row {
|
|
margin-right: calc(var(--deck-peek) * 0.58);
|
|
padding-right: 1.5rem;
|
|
}
|
|
|
|
.filter-row::-webkit-scrollbar { display: none; }
|
|
|
|
.filter-btn {
|
|
padding: 0.3rem 0.7rem;
|
|
background: transparent;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.8rem;
|
|
font-family: 'Inter', sans-serif;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.filter-btn:hover { border-color: var(--text-tertiary); color: var(--text-secondary); }
|
|
.filter-btn.active {
|
|
background: var(--text);
|
|
color: var(--bg);
|
|
border-color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ── Grid ── */
|
|
.grid {
|
|
margin: 1.25rem 0 4rem;
|
|
padding: 0 1.5rem;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 0.9rem;
|
|
}
|
|
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 1.25rem;
|
|
transition: border-color 0.15s;
|
|
}
|
|
|
|
.card:hover { border-color: var(--text-tertiary); }
|
|
|
|
.card-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.4rem;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.card-title a {
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
transition: color 0.15s;
|
|
}
|
|
|
|
.card-title a:hover { color: var(--accent); }
|
|
|
|
.card-title a svg {
|
|
width: 11px;
|
|
height: 11px;
|
|
margin-left: 3px;
|
|
vertical-align: middle;
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
}
|
|
|
|
.card-title a:hover svg { opacity: 0.5; }
|
|
|
|
.card-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.card-version {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
color: var(--green);
|
|
background: var(--green-muted);
|
|
padding: 0.12rem 0.45rem;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.category-tag {
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
color: var(--text-tertiary);
|
|
background: var(--surface-raised);
|
|
padding: 0.12rem 0.45rem;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.card-desc {
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 0.85rem;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.card-requires {
|
|
font-size: 0.78rem;
|
|
color: var(--text-tertiary);
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.card-requires strong {
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.card-install {
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
padding: 0.8rem 0.9rem 0.8rem 0.9rem;
|
|
position: relative;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.card-install--note {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.card-install code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
white-space: pre-wrap;
|
|
overflow-wrap: break-word;
|
|
word-break: normal;
|
|
line-height: 1.45;
|
|
display: block;
|
|
padding-top: 0.1rem;
|
|
}
|
|
|
|
.card-install code .install-comment {
|
|
display: block;
|
|
color: var(--text-tertiary);
|
|
font-style: italic;
|
|
margin-bottom: 0.22rem;
|
|
padding-right: 4.8rem;
|
|
}
|
|
|
|
.card-install code .install-command {
|
|
display: block;
|
|
}
|
|
|
|
.card-install code .install-command + .install-comment {
|
|
margin-top: 0.38rem;
|
|
}
|
|
|
|
.card-copy-btn {
|
|
background: none;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
color: var(--text-tertiary);
|
|
cursor: pointer;
|
|
padding: 0.2rem 0.45rem;
|
|
font-size: 0.7rem;
|
|
font-family: 'Inter', sans-serif;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.card-install .card-copy-btn {
|
|
position: absolute;
|
|
top: 0.78rem;
|
|
right: 0.72rem;
|
|
}
|
|
|
|
.card-copy-btn:hover { color: var(--text); border-color: var(--text-tertiary); }
|
|
|
|
.card-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
margin-top: 0.85rem;
|
|
padding-top: 0.8rem;
|
|
border-top: 1px solid var(--border-subtle);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.card-links {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
font-size: 0.78rem;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.card-links a,
|
|
.card-link-btn {
|
|
color: var(--text-tertiary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
transition: color 0.15s;
|
|
}
|
|
|
|
.card-link-btn {
|
|
background: none;
|
|
border: 0;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
padding: 0;
|
|
}
|
|
|
|
.card-links a:hover,
|
|
.card-link-btn:hover { color: var(--text-secondary); }
|
|
|
|
.skill-popover {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.skill-popover-panel {
|
|
position: absolute;
|
|
left: 0;
|
|
top: calc(100% + 0.45rem);
|
|
width: min(18rem, calc(100vw - 3rem));
|
|
padding: 0.7rem;
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(148, 163, 184, 0.36);
|
|
background: rgba(248, 250, 252, 0.98);
|
|
color: #0f172a;
|
|
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.24);
|
|
opacity: 0;
|
|
transform: translateY(-4px);
|
|
pointer-events: none;
|
|
transition: opacity 0.16s ease, transform 0.16s ease;
|
|
z-index: 10;
|
|
}
|
|
|
|
.skill-popover.is-open .skill-popover-panel {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.skill-popover-label {
|
|
display: block;
|
|
font-size: 0.64rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: #475569;
|
|
margin-bottom: 0.45rem;
|
|
}
|
|
|
|
.skill-popover-panel code {
|
|
display: block;
|
|
font-size: 0.75rem;
|
|
line-height: 1.55;
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
word-break: break-word;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
background: rgba(226, 232, 240, 0.7);
|
|
border: 1px solid rgba(148, 163, 184, 0.3);
|
|
border-radius: 8px;
|
|
padding: 0.55rem 0.6rem;
|
|
}
|
|
|
|
.skill-popover-copy {
|
|
margin-top: 0.55rem;
|
|
color: #334155;
|
|
border-color: rgba(100, 116, 139, 0.34);
|
|
background: rgba(255, 255, 255, 0.72);
|
|
}
|
|
|
|
.skill-popover-copy:hover {
|
|
color: #0f172a;
|
|
border-color: rgba(51, 65, 85, 0.42);
|
|
}
|
|
|
|
.card-contributor {
|
|
font-size: 0.75rem;
|
|
color: var(--text-tertiary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.card-contributor a {
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.card-contributor a:hover { color: var(--text); }
|
|
|
|
.community-badge {
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
color: var(--purple);
|
|
background: var(--purple-muted);
|
|
padding: 0.08rem 0.35rem;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.card-date {
|
|
font-size: 0.72rem;
|
|
color: var(--text-tertiary);
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.public-badge {
|
|
font-size: 0.65rem;
|
|
font-weight: 600;
|
|
color: var(--text-tertiary);
|
|
background: var(--surface-raised);
|
|
border: 1px solid var(--border);
|
|
padding: 0.08rem 0.35rem;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.public-badge--npm {
|
|
color: #cb3837;
|
|
background: rgba(203, 56, 55, 0.1);
|
|
border-color: rgba(203, 56, 55, 0.14);
|
|
}
|
|
|
|
.public-badge--brew {
|
|
color: #8b5cf6;
|
|
background: rgba(139, 92, 246, 0.1);
|
|
border-color: rgba(139, 92, 246, 0.14);
|
|
}
|
|
|
|
.public-badge--bundled {
|
|
color: #64748b;
|
|
background: rgba(100, 116, 139, 0.1);
|
|
border-color: rgba(100, 116, 139, 0.14);
|
|
}
|
|
|
|
.card-public-notes {
|
|
display: grid;
|
|
gap: 0.45rem;
|
|
margin-top: 0.2rem;
|
|
margin-bottom: 0.1rem;
|
|
padding: 0.7rem 0.75rem;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border-subtle);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.card-npx {
|
|
font-size: 0.75rem;
|
|
color: var(--text-tertiary);
|
|
padding: 0.55rem 0.65rem;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: calc(var(--radius-sm) - 2px);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.card-npx--direct {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 0.38rem;
|
|
background: transparent;
|
|
border: 0;
|
|
border-left: 2px solid var(--border);
|
|
border-radius: 0;
|
|
padding: 0.05rem 0 0.05rem 0.7rem;
|
|
}
|
|
|
|
.card-npx--direct strong {
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.card-npx--direct span {
|
|
color: var(--text-tertiary);
|
|
overflow-wrap: anywhere;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.card-npx--comment {
|
|
background: transparent;
|
|
border: 0;
|
|
border-left: 2px solid var(--border-subtle);
|
|
border-radius: 0;
|
|
padding: 0.05rem 0 0.05rem 0.7rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.card-npx code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
color: var(--text-secondary);
|
|
font-size: 0.73rem;
|
|
display: inline-block;
|
|
margin-top: 0.25rem;
|
|
overflow-wrap: anywhere;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.public-panel-note {
|
|
padding: 0.75rem 1.5rem 0;
|
|
font-size: 0.74rem;
|
|
color: var(--text-tertiary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.public-panel-note code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.72rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty {
|
|
grid-column: 1 / -1;
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: var(--text-tertiary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* ── Footer ── */
|
|
.footer {
|
|
max-width: 1120px;
|
|
margin: 0 auto;
|
|
padding: 2rem 1.5rem;
|
|
border-top: 1px solid var(--border-subtle);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.footer-left {
|
|
font-size: 0.8rem;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.footer-left a {
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.footer-left a:hover { color: var(--text); }
|
|
|
|
.footer-right {
|
|
display: flex;
|
|
gap: 1rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.footer-right a {
|
|
color: var(--text-tertiary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
transition: color 0.15s;
|
|
}
|
|
|
|
.footer-right a:hover { color: var(--text-secondary); }
|
|
|
|
.footer-stats {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 1.5rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--border-subtle);
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.footer-analytics-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-size: 0.78rem;
|
|
color: var(--text-tertiary);
|
|
padding: 0.35rem 0.75rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.footer-analytics-link svg { opacity: 0.6; flex-shrink: 0; }
|
|
|
|
.analytics-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
font-size: 0.7rem;
|
|
color: var(--text-tertiary);
|
|
margin-left: 0.15rem;
|
|
}
|
|
|
|
.analytics-sep { opacity: 0.4; }
|
|
|
|
.stat-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
display: inline-block;
|
|
}
|
|
|
|
.dot-total { background: var(--text-tertiary); }
|
|
.dot-human { background: var(--green); }
|
|
.dot-agent { background: var(--accent); }
|
|
|
|
/* ── Responsive ── */
|
|
@media (max-width: 640px) {
|
|
.deck-stage { --deck-peek: 46px; }
|
|
.hero { padding: 2rem 1.25rem 1.5rem; }
|
|
.hero h1 { font-size: 2rem; }
|
|
.hero-title {
|
|
gap: 0.72rem;
|
|
align-items: flex-start;
|
|
}
|
|
.hero-title-badge {
|
|
width: 2.7rem;
|
|
height: 2.7rem;
|
|
border-radius: 11px;
|
|
}
|
|
.hero-title-badge svg {
|
|
width: 1.84rem;
|
|
height: 1.84rem;
|
|
}
|
|
.stats {
|
|
display: flex;
|
|
width: 100%;
|
|
justify-content: center;
|
|
gap: 0;
|
|
}
|
|
.stat {
|
|
min-width: 0;
|
|
flex: 1;
|
|
padding: 0 1rem;
|
|
}
|
|
.stat + .stat::before {
|
|
top: 0.1rem;
|
|
bottom: 0.1rem;
|
|
}
|
|
.stat-num {
|
|
font-size: 2.2rem;
|
|
}
|
|
.hero-tagline { white-space: normal; }
|
|
.grid { grid-template-columns: 1fr; padding: 0 1.25rem; }
|
|
.controls { padding: 1rem 1.25rem 0; }
|
|
.filter-row {
|
|
gap: 0.4rem;
|
|
row-gap: 0.5rem;
|
|
padding: 0.75rem 1.25rem 0;
|
|
}
|
|
.public-panel-note { padding: 0.7rem 1.25rem 0; }
|
|
.deck-panel--harness .filter-row {
|
|
margin-right: calc(var(--deck-peek) * 0.5);
|
|
padding-right: 1.25rem;
|
|
}
|
|
.marketplace-prelude { padding: 0.75rem 1.25rem 0; }
|
|
.nav { padding: 0.75rem 1.25rem; }
|
|
.footer { padding: 1.5rem 1.25rem; flex-direction: column; align-items: flex-start; }
|
|
.empower-row { flex-direction: column; }
|
|
.empower-card { margin: 0 0 1rem; padding: 1.25rem; }
|
|
.typing-shell { padding: 0.72rem 0.78rem 0.8rem; }
|
|
.typing-shell-head { align-items: flex-start; }
|
|
.typing-shell-body { padding: 0.72rem 0.74rem; }
|
|
.typing-shell-code { font-size: 0.74rem; }
|
|
.empower-self .cmd-grid { grid-template-columns: 1fr; }
|
|
.deck-nav { padding: 1.25rem 1.25rem 0; }
|
|
.deck-arrow-btn span { display: none; }
|
|
}
|
|
|
|
@media (max-width: 1120px) {
|
|
.grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 400px) {
|
|
.nav-links { gap: 0; }
|
|
.nav-link { padding: 0.4rem 0.4rem; font-size: 0.75rem; }
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.hero-word,
|
|
.hero-tagline-line {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
|
|
.hero-shimmer::after,
|
|
.hero-title-badge,
|
|
.hero-badge-glow,
|
|
.hero-badge-cursor,
|
|
.typing-shell-cursor {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
</style>
|
|
<script>document.documentElement.classList.add('js');</script>
|
|
<!-- Umami Analytics — hkuds.github.io -->
|
|
<script defer src="https://cloud.umami.is/script.js" data-website-id="07082d05-efd3-4f85-a7a1-b426b0e8bfaa"></script>
|
|
<!-- Umami Analytics — clianything.cc -->
|
|
<script defer src="https://cloud.umami.is/script.js" data-website-id="a076c661-bed1-405c-a522-813794e688b4"></script>
|
|
</head>
|
|
<body>
|
|
<!-- noscript pixel: catches non-JS visitors (AI crawlers) -->
|
|
<noscript>
|
|
<img src="https://cloud.umami.is/api/send?type=event&payload=%7B%22website%22%3A%2207082d05-efd3-4f85-a7a1-b426b0e8bfaa%22%2C%22name%22%3A%22noscript-visit%22%7D" style="position:absolute;left:-9999px" alt="" width="1" height="1">
|
|
</noscript>
|
|
<!-- Nav -->
|
|
<nav class="nav">
|
|
<a class="nav-brand" href="#">CLI-Hub</a>
|
|
<div class="nav-links">
|
|
<a class="nav-link" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">
|
|
SKILL
|
|
</a>
|
|
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
|
|
<svg viewBox="0 0 98 96" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6C29.304 70.25 17.9 66.013 17.9 47.02c0-5.52 1.94-10.046 5.127-13.58-.486-1.302-2.264-6.437.486-13.34 0 0 4.206-1.302 13.59 5.216 3.963-1.14 8.17-1.628 12.34-1.628 4.17 0 8.376.568 12.34 1.628 9.384-6.518 13.59-5.216 13.59-5.216 2.75 6.903.972 12.038.486 13.34 3.268 3.534 5.127 8.06 5.127 13.58 0 19.074-11.485 23.15-22.328 24.29 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/></svg>
|
|
GitHub
|
|
<span class="nav-link-stars" data-github-stars aria-label="GitHub star count">--</span>
|
|
</a>
|
|
<a class="nav-link" href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">
|
|
Contribute
|
|
</a>
|
|
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
|
|
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Hero -->
|
|
<div class="hero">
|
|
<div class="hero-title">
|
|
<span class="hero-title-badge" aria-hidden="true">
|
|
<svg viewBox="0 0 48 48" fill="none">
|
|
<path d="M8 16.5 18.5 24 8 31.5" stroke="currentColor" stroke-width="3.9" stroke-linecap="square" stroke-linejoin="miter" style="color: var(--accent);" />
|
|
<path d="M24 29.2h16" stroke="currentColor" stroke-width="3.9" stroke-linecap="square" class="hero-badge-cursor" style="color: rgba(248,250,252,0.98);" />
|
|
<path d="M26 12h14" stroke="url(#heroBadgeBeam)" stroke-width="2.6" stroke-linecap="square" class="hero-badge-glow" opacity="0.78" />
|
|
<path d="M7 10.5h9" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" style="color: rgba(148,163,184,0.48);" />
|
|
<path d="M7 37.5h11" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" style="color: rgba(96,165,250,0.3);" />
|
|
<defs>
|
|
<linearGradient id="heroBadgeBeam" x1="26" y1="12" x2="40" y2="12" gradientUnits="userSpaceOnUse">
|
|
<stop stop-color="#60A5FA" stop-opacity="0" />
|
|
<stop offset="0.52" stop-color="#93C5FD" />
|
|
<stop offset="1" stop-color="#60A5FA" stop-opacity="0" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</span>
|
|
<h1>
|
|
<span class="hero-word hero-word--cli hero-accent">CLI-</span>
|
|
<span class="hero-word hero-word--anything hero-dim hero-shimmer" data-text="Anything">Anything</span>
|
|
<span class="hero-word hero-word--hub hero-accent">Hub</span>
|
|
</h1>
|
|
</div>
|
|
<p class="hero-tagline"><span class="hero-tagline-line hero-tagline-line--any">Any software. Any codebase. Any Web API.</span> <span class="hero-tagline-line hero-tagline-line--rest">Generate an agent-native CLI and let AI agents operate it — install with a single command.</span></p>
|
|
<div class="stats">
|
|
<div class="stat">
|
|
<div class="stat-num" id="cli-count">—</div>
|
|
<div class="stat-label">CLIs Available</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-num" id="category-count">—</div>
|
|
<div class="stat-label">Categories</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="empower-row">
|
|
<div class="empower-card">
|
|
<h3>Empower your agents — <span>install in one command</span></h3>
|
|
<p class="empower-available">Also Available on: <a href="https://clawhub.ai/yuh-yang/cli-anything-hub" target="_blank">ClawHub</a>, <a href="https://www.skillhub.club/web/skills/itsyuhao-cli-anything-hub" target="_blank">SkillHub</a>, <a href="https://skillhub.cn/skills/cli-hub-meta-skill" target="_blank">SkillHub.cn</a></p>
|
|
<div class="install-row install-row--wrap">
|
|
<span class="chip-label">npx skills</span>
|
|
<code>npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y</code>
|
|
<button class="copy-btn" onclick="copyCmd(this, 'npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y')">Copy</button>
|
|
</div>
|
|
<p class="empower-note"><strong>Works with:</strong> OpenClaw, Nanobot, Claude Code, Codex, Antigravity, and other SKILL-compatible agents.</p>
|
|
<div class="empower-prompt">
|
|
<strong>Then prompt:</strong> <em>"Find appropriate CLI software in CLI-Hub and complete the task: ..."</em>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="empower-card empower-self">
|
|
<h3>Empower yourself — <span>your CLI toolkit</span></h3>
|
|
<div class="install-row install-row--wrap">
|
|
<span class="chip-label">pip</span>
|
|
<code>pip install cli-anything-hub</code>
|
|
<button class="copy-btn" onclick="copyCmd(this, 'pip install cli-anything-hub')">Copy</button>
|
|
</div>
|
|
<div class="cmd-grid">
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub list</span><span class="cmd-desc">Browse the registry</span></div>
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub search</span><span class="cmd-desc">Search by keyword</span></div>
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub install</span><span class="cmd-desc">Install from CLI-Hub</span></div>
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub info</span><span class="cmd-desc">Inspect one CLI</span></div>
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub update</span><span class="cmd-desc">Update an install</span></div>
|
|
<div class="cmd-item"><span class="cmd-name">cli-hub uninstall</span><span class="cmd-desc">Remove an install</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hero-actions">
|
|
<a class="btn-primary" href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">Agent SKILL (SKILL.md)</a>
|
|
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything" target="_blank">
|
|
<svg viewBox="0 0 98 96" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6C29.304 70.25 17.9 66.013 17.9 47.02c0-5.52 1.94-10.046 5.127-13.58-.486-1.302-2.264-6.437.486-13.34 0 0 4.206-1.302 13.59 5.216 3.963-1.14 8.17-1.628 12.34-1.628 4.17 0 8.376.568 12.34 1.628 9.384-6.518 13.59-5.216 13.59-5.216 2.75 6.903.972 12.038.486 13.34 3.268 3.534 5.127 8.06 5.127 13.58 0 19.074-11.485 23.15-22.328 24.29 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/></svg>
|
|
Star on GitHub
|
|
</a>
|
|
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">
|
|
Contributing Guide
|
|
</a>
|
|
<a class="btn-secondary" href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">
|
|
PR Template
|
|
</a>
|
|
</div>
|
|
|
|
<p class="hero-hint">Feed <a href="https://reeceyang.sgp1.cdn.digitaloceanspaces.com/SKILL.md" target="_blank">SKILL.md</a> to your AI agent for autonomous CLI discovery & installation</p>
|
|
|
|
<p class="hero-cta">We welcome contributions for <em>any</em> application — desktop apps, dev tools, cloud services, SaaS APIs, creative suites, and beyond. If it has a GUI or an API, it deserves an agent-native CLI. Read the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">Contributing Guide</a>, use the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">PR Template</a>, and bring it to the hub!</p>
|
|
|
|
</div>
|
|
|
|
<div class="marketplace-prelude">
|
|
<div class="typing-shell" data-typing-text="pip install cli-anything-hub">
|
|
<div class="typing-shell-head">
|
|
<span class="typing-shell-label">install first, then browse the marketplace</span>
|
|
<button class="copy-btn typing-shell-copy" onclick="copyCmd(this, 'pip install cli-anything-hub')">Copy</button>
|
|
</div>
|
|
<div class="typing-shell-body" aria-label="pip install cli-anything-hub">
|
|
<span class="typing-shell-prompt">$</span>
|
|
<code class="typing-shell-code"><span class="typing-shell-text" data-typing-target></span><span class="typing-shell-cursor" aria-hidden="true"></span></code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Deck nav -->
|
|
<div class="deck-nav">
|
|
<button class="deck-tab active" data-deck="harness" onclick="switchDeck('harness')">
|
|
CLI-Anything CLIs
|
|
<span class="deck-count" id="deck-count-harness">—</span>
|
|
</button>
|
|
<button class="deck-tab deck-tab--public" data-deck="public" onclick="switchDeck('public')">
|
|
Public CLIs
|
|
<span class="deck-count" id="deck-count-public">—</span>
|
|
</button>
|
|
<span class="deck-separator"></span>
|
|
<button class="deck-arrow-btn" id="deck-toggle" onclick="toggleDeck()">
|
|
<span>Public CLIs</span>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Deck viewport -->
|
|
<div class="deck-stage">
|
|
<div class="deck-viewport">
|
|
<div class="deck-track" id="deck-track" data-active="harness">
|
|
|
|
<!-- Harness deck -->
|
|
<div class="deck-panel deck-panel--harness" data-peek-label="CLI-Anything">
|
|
<div class="controls">
|
|
<input class="search" type="text" placeholder="Search CLIs..." id="search-harness">
|
|
<select class="sort-select" id="sort-harness">
|
|
<option value="updated">Last updated</option>
|
|
<option value="name">Name</option>
|
|
<option value="category">Category</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-row" id="filters-harness"></div>
|
|
<div class="grid" id="grid-harness"></div>
|
|
</div>
|
|
|
|
<!-- Public CLI deck -->
|
|
<div class="deck-panel deck-panel--public" data-peek-label="Public CLIs">
|
|
<div class="controls">
|
|
<input class="search" type="text" placeholder="Search public CLIs..." id="search-public">
|
|
<select class="sort-select" id="sort-public">
|
|
<option value="updated">Last updated</option>
|
|
<option value="name">Name</option>
|
|
<option value="category">Category</option>
|
|
</select>
|
|
</div>
|
|
<div class="public-panel-note">
|
|
Public CLIs remain the property of their respective software owners and maintainers. All rights stay with those owners; CLI-Hub only helps you discover and install them.
|
|
</div>
|
|
<div class="filter-row" id="filters-public"></div>
|
|
<div class="grid" id="grid-public"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="footer">
|
|
<div class="footer-left">
|
|
Want to add or update a CLI? Read the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/CONTRIBUTING.md" target="_blank">Contributing Guide</a> and use the <a href="https://github.com/HKUDS/CLI-Anything/blob/main/.github/PULL_REQUEST_TEMPLATE.md" target="_blank">PR Template</a>
|
|
</div>
|
|
<div class="footer-right">
|
|
Powered by <a href="https://github.com/HKUDS/CLI-Anything" target="_blank">CLI-Anything</a>
|
|
</div>
|
|
<div class="footer-stats">
|
|
<div class="footer-analytics-link" aria-label="Traffic snapshot">
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
<span class="stat-dot dot-total"></span>
|
|
<span id="stat-total">—</span> visits
|
|
<span class="analytics-sep">·</span>
|
|
<span class="stat-dot dot-human"></span>
|
|
<span id="stat-human">—</span> human
|
|
<span class="analytics-sep">·</span>
|
|
<span class="stat-dot dot-agent"></span>
|
|
<span id="stat-agent">—</span> AI agent
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
// ── Theme: system preference + localStorage override ──
|
|
function getPreferredTheme() {
|
|
const stored = localStorage.getItem('theme');
|
|
if (stored) return stored;
|
|
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
}
|
|
|
|
function applyTheme(theme) {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
}
|
|
|
|
function toggleTheme() {
|
|
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
applyTheme(next);
|
|
localStorage.setItem('theme', next);
|
|
}
|
|
|
|
// Apply immediately to avoid flash
|
|
applyTheme(getPreferredTheme());
|
|
|
|
// Listen for system preference changes (if no manual override)
|
|
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
|
|
if (!localStorage.getItem('theme')) {
|
|
applyTheme(e.matches ? 'light' : 'dark');
|
|
}
|
|
});
|
|
|
|
const REPO = 'https://github.com/HKUDS/CLI-Anything';
|
|
const GITHUB_REPO_API = 'https://api.github.com/repos/HKUDS/CLI-Anything';
|
|
const REPO_SKILLS_SOURCE = 'HKUDS/CLI-Anything';
|
|
const REGISTRY_URLS = [
|
|
'../../registry.json',
|
|
'https://raw.githubusercontent.com/HKUDS/CLI-Anything/main/registry.json'
|
|
];
|
|
const PUBLIC_REGISTRY_URLS = [
|
|
'../../public_registry.json',
|
|
'https://raw.githubusercontent.com/HKUDS/CLI-Anything/main/public_registry.json'
|
|
];
|
|
const DATES_URLS = [
|
|
'https://hkuds.github.io/CLI-Anything/registry-dates.json',
|
|
'../../registry-dates.json'
|
|
];
|
|
const EXTERNAL_LINK_SVG = '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5H4.56l6.72 6.72a.75.75 0 1 1-1.06 1.06L3.5 4.56v2.69a.75.75 0 0 1-1.5 0v-3.5A1.75 1.75 0 0 1 3.75 2Zm7.5 0h1A1.75 1.75 0 0 1 14 3.75v8.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-1a.75.75 0 0 1 1.5 0v1c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25h-1a.75.75 0 0 1 0-1.5Z"/></svg>';
|
|
|
|
let harnessClis = [];
|
|
let publicClis = [];
|
|
let activeDeck = 'harness';
|
|
let wheelSwitchLockUntil = 0;
|
|
|
|
const deckGesture = {
|
|
pointerId: null,
|
|
startX: 0,
|
|
deltaX: 0,
|
|
dragging: false,
|
|
};
|
|
|
|
// Per-deck state
|
|
const deckState = {
|
|
harness: { filter: 'all', sort: 'updated', query: '' },
|
|
public: { filter: 'all', sort: 'updated', query: '' },
|
|
};
|
|
|
|
async function fetchJson(urls) {
|
|
for (const url of urls) {
|
|
try {
|
|
const resp = await fetch(url);
|
|
if (resp.ok) return await resp.json();
|
|
} catch (_) { continue; }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function formatCompactCount(value) {
|
|
return new Intl.NumberFormat('en', {
|
|
notation: 'compact',
|
|
maximumFractionDigits: value >= 10000 ? 0 : 1,
|
|
}).format(value);
|
|
}
|
|
|
|
async function loadGitHubStars() {
|
|
try {
|
|
const resp = await fetch(GITHUB_REPO_API, {
|
|
headers: { 'Accept': 'application/vnd.github+json' }
|
|
});
|
|
if (!resp.ok) return;
|
|
|
|
const repo = await resp.json();
|
|
const stars = repo.stargazers_count;
|
|
if (typeof stars !== 'number') return;
|
|
|
|
document.querySelectorAll('[data-github-stars]').forEach(el => {
|
|
el.textContent = formatCompactCount(stars);
|
|
el.setAttribute('aria-label', `${stars.toLocaleString('en-US')} GitHub stars`);
|
|
el.title = `${stars.toLocaleString('en-US')} GitHub stars`;
|
|
});
|
|
} catch (_) {
|
|
// Keep fallback placeholder when GitHub API is unavailable.
|
|
}
|
|
}
|
|
|
|
async function loadRegistries() {
|
|
try {
|
|
const [harnessData, publicData, dates] = await Promise.all([
|
|
fetchJson(REGISTRY_URLS),
|
|
fetchJson(PUBLIC_REGISTRY_URLS),
|
|
fetchJson(DATES_URLS),
|
|
]);
|
|
|
|
if (!harnessData) throw new Error('Registry load failed');
|
|
|
|
harnessClis = harnessData.clis;
|
|
if (dates) harnessClis.forEach(c => c.last_modified = dates[c.name]);
|
|
|
|
if (publicData && publicData.clis) {
|
|
publicClis = publicData.clis;
|
|
if (dates) publicClis.forEach(c => c.last_modified = dates[c.name]);
|
|
}
|
|
|
|
// Update counts
|
|
const totalClis = harnessClis.length + publicClis.length;
|
|
const allCategories = [...new Set([...harnessClis, ...publicClis].map(c => c.category))];
|
|
animateCount(document.getElementById('cli-count'), totalClis, 980);
|
|
animateCount(document.getElementById('category-count'), allCategories.length, 860);
|
|
document.getElementById('deck-count-harness').textContent = harnessClis.length;
|
|
document.getElementById('deck-count-public').textContent = publicClis.length;
|
|
|
|
// Build filters for harness deck (initial)
|
|
const harnessCats = [...new Set(harnessClis.map(c => c.category))];
|
|
buildFilters('harness', harnessCats);
|
|
|
|
const publicCats = [...new Set(publicClis.map(c => c.category))];
|
|
buildFilters('public', publicCats);
|
|
|
|
renderDeck('harness');
|
|
renderDeck('public');
|
|
} catch (e) {
|
|
document.getElementById('grid-harness').innerHTML =
|
|
'<div class="empty">Failed to load registry. <a href="' + REPO + '/blob/main/registry.json" style="color:var(--accent)">View source</a></div>';
|
|
}
|
|
}
|
|
|
|
const CATEGORY_LABELS = { 'ai': 'AI', '3d': '3D' };
|
|
function catLabel(cat) {
|
|
return CATEGORY_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
}
|
|
|
|
function buildFilters(deck, categories) {
|
|
const container = document.getElementById('filters-' + deck);
|
|
container.innerHTML = '';
|
|
const allBtn = document.createElement('button');
|
|
allBtn.className = 'filter-btn active';
|
|
allBtn.textContent = 'All';
|
|
allBtn.dataset.cat = 'all';
|
|
allBtn.dataset.deck = deck;
|
|
allBtn.onclick = () => setFilter(deck, 'all');
|
|
container.appendChild(allBtn);
|
|
|
|
categories.sort().forEach(cat => {
|
|
const btn = document.createElement('button');
|
|
btn.className = 'filter-btn';
|
|
btn.textContent = catLabel(cat);
|
|
btn.dataset.cat = cat;
|
|
btn.dataset.deck = deck;
|
|
btn.onclick = () => setFilter(deck, cat);
|
|
container.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
function setFilter(deck, cat) {
|
|
deckState[deck].filter = cat;
|
|
document.querySelectorAll(`#filters-${deck} .filter-btn`).forEach(b => {
|
|
b.classList.toggle('active', b.dataset.cat === cat);
|
|
});
|
|
renderDeck(deck);
|
|
}
|
|
|
|
function switchDeck(deck) {
|
|
activeDeck = deck;
|
|
const track = document.getElementById('deck-track');
|
|
track.dataset.active = deck;
|
|
updateDeckPosition();
|
|
document.querySelectorAll('.deck-tab').forEach(t => {
|
|
t.classList.toggle('active', t.dataset.deck === deck);
|
|
});
|
|
// Update toggle button
|
|
const toggle = document.getElementById('deck-toggle');
|
|
if (deck === 'harness') {
|
|
toggle.querySelector('span').textContent = 'Public CLIs';
|
|
toggle.querySelector('svg').innerHTML = '<polyline points="9 18 15 12 9 6"/>';
|
|
toggle.classList.remove('deck-arrow-btn--back');
|
|
} else {
|
|
toggle.querySelector('span').textContent = 'CLI-Anything CLIs';
|
|
toggle.querySelector('svg').innerHTML = '<polyline points="15 18 9 12 15 6"/>';
|
|
toggle.classList.add('deck-arrow-btn--back');
|
|
}
|
|
}
|
|
|
|
function toggleDeck() {
|
|
switchDeck(activeDeck === 'harness' ? 'public' : 'harness');
|
|
}
|
|
|
|
function getDeckMetrics() {
|
|
const stage = document.querySelector('.deck-stage');
|
|
const viewport = document.querySelector('.deck-viewport');
|
|
const track = document.getElementById('deck-track');
|
|
if (!stage || !viewport || !track) return null;
|
|
|
|
const peek = parseFloat(getComputedStyle(stage).getPropertyValue('--deck-peek')) || 0;
|
|
const panelWidth = Math.max(viewport.clientWidth - peek, 0);
|
|
return {
|
|
viewport,
|
|
track,
|
|
panelWidth,
|
|
maxShift: Math.max(panelWidth - peek, 0),
|
|
};
|
|
}
|
|
|
|
function setDeckTransform(offset = 0) {
|
|
const metrics = getDeckMetrics();
|
|
if (!metrics) return;
|
|
const { viewport, track, panelWidth, maxShift } = metrics;
|
|
viewport.style.setProperty('--deck-panel-width', `${panelWidth}px`);
|
|
const baseShift = activeDeck === 'public' ? -maxShift : 0;
|
|
const clampedShift = Math.min(0, Math.max(-maxShift, baseShift + offset));
|
|
track.style.transform = `translateX(${clampedShift}px)`;
|
|
}
|
|
|
|
function updateDeckPosition() {
|
|
setDeckTransform();
|
|
}
|
|
|
|
function finishDeckDrag() {
|
|
const viewport = document.querySelector('.deck-viewport');
|
|
const track = document.getElementById('deck-track');
|
|
if (viewport) viewport.classList.remove('dragging');
|
|
if (track) track.classList.remove('is-dragging');
|
|
deckGesture.pointerId = null;
|
|
deckGesture.startX = 0;
|
|
deckGesture.deltaX = 0;
|
|
deckGesture.dragging = false;
|
|
}
|
|
|
|
function initDeckGestures() {
|
|
const viewport = document.querySelector('.deck-viewport');
|
|
const track = document.getElementById('deck-track');
|
|
if (!viewport || !track) return;
|
|
|
|
const isInteractiveTarget = (target) => !!target.closest('a, button, input, select, textarea, label');
|
|
|
|
viewport.addEventListener('pointerdown', (e) => {
|
|
if (e.pointerType === 'mouse' && e.button !== 0) return;
|
|
if (isInteractiveTarget(e.target)) return;
|
|
deckGesture.pointerId = e.pointerId;
|
|
deckGesture.startX = e.clientX;
|
|
deckGesture.deltaX = 0;
|
|
deckGesture.dragging = true;
|
|
viewport.classList.add('dragging');
|
|
track.classList.add('is-dragging');
|
|
if (viewport.setPointerCapture) viewport.setPointerCapture(e.pointerId);
|
|
});
|
|
|
|
viewport.addEventListener('pointermove', (e) => {
|
|
if (!deckGesture.dragging || e.pointerId !== deckGesture.pointerId) return;
|
|
deckGesture.deltaX = e.clientX - deckGesture.startX;
|
|
setDeckTransform(deckGesture.deltaX);
|
|
});
|
|
|
|
const endDrag = (e) => {
|
|
if (!deckGesture.dragging || e.pointerId !== deckGesture.pointerId) return;
|
|
const deltaX = deckGesture.deltaX;
|
|
finishDeckDrag();
|
|
if (activeDeck === 'harness' && deltaX < -60) {
|
|
switchDeck('public');
|
|
return;
|
|
}
|
|
if (activeDeck === 'public' && deltaX > 60) {
|
|
switchDeck('harness');
|
|
return;
|
|
}
|
|
updateDeckPosition();
|
|
};
|
|
|
|
viewport.addEventListener('pointerup', endDrag);
|
|
viewport.addEventListener('pointercancel', endDrag);
|
|
viewport.addEventListener('lostpointercapture', () => {
|
|
if (!deckGesture.dragging) return;
|
|
finishDeckDrag();
|
|
updateDeckPosition();
|
|
});
|
|
|
|
viewport.addEventListener('wheel', (e) => {
|
|
const now = Date.now();
|
|
if (now < wheelSwitchLockUntil) return;
|
|
if (Math.abs(e.deltaX) < 24 || Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
|
|
e.preventDefault();
|
|
if (e.deltaX > 0 && activeDeck !== 'public') {
|
|
switchDeck('public');
|
|
wheelSwitchLockUntil = now + 450;
|
|
} else if (e.deltaX < 0 && activeDeck !== 'harness') {
|
|
switchDeck('harness');
|
|
wheelSwitchLockUntil = now + 450;
|
|
}
|
|
}, { passive: false });
|
|
}
|
|
|
|
function renderDeck(deck) {
|
|
const clis = deck === 'harness' ? harnessClis : publicClis;
|
|
const state = deckState[deck];
|
|
const query = state.query.toLowerCase();
|
|
|
|
const filtered = clis.filter(c => {
|
|
const matchFilter = state.filter === 'all' || c.category === state.filter;
|
|
const matchSearch = !query ||
|
|
c.display_name.toLowerCase().includes(query) ||
|
|
c.description.toLowerCase().includes(query) ||
|
|
c.name.toLowerCase().includes(query);
|
|
return matchFilter && matchSearch;
|
|
});
|
|
|
|
filtered.sort((a, b) => {
|
|
if (state.sort === 'updated') return (b.last_modified || '').localeCompare(a.last_modified || '');
|
|
if (state.sort === 'name') return a.display_name.localeCompare(b.display_name);
|
|
if (state.sort === 'category') return a.category.localeCompare(b.category) || a.display_name.localeCompare(b.display_name);
|
|
return 0;
|
|
});
|
|
|
|
const grid = document.getElementById('grid-' + deck);
|
|
if (filtered.length === 0) {
|
|
grid.innerHTML = '<div class="empty">No CLIs match your search.</div>';
|
|
return;
|
|
}
|
|
|
|
if (deck === 'harness') {
|
|
grid.innerHTML = filtered.map(renderHarnessCard).join('');
|
|
} else {
|
|
grid.innerHTML = filtered.map(renderNpmCard).join('');
|
|
}
|
|
}
|
|
|
|
function repoSkillId(skillPath) {
|
|
if (!skillPath || skillPath.startsWith('http')) return '';
|
|
const match = skillPath.match(/^skills\/([^/]+)\/SKILL\.md$/);
|
|
return match ? match[1] : '';
|
|
}
|
|
|
|
function isWebUrl(value) {
|
|
return /^https?:\/\//i.test(value || '');
|
|
}
|
|
|
|
function isRepoSkillPath(value) {
|
|
return !/\s/.test(value || '') && /(^\.{0,2}\/|\/|\.md$|\.txt$)/i.test(value || '');
|
|
}
|
|
|
|
function normalizeNpxSkillsCmd(cmd) {
|
|
if (!cmd) return '';
|
|
const trimmed = cmd.trim();
|
|
if (!trimmed.startsWith('npx skills add ')) return '';
|
|
return trimmed
|
|
.replace(/\s+-g\b/g, '')
|
|
.replace(/\s+-y\b/g, '')
|
|
.trim() + ' -g -y';
|
|
}
|
|
|
|
function harnessSkillCmd(c) {
|
|
const skillId = repoSkillId(c.skill_md);
|
|
return skillId ? `npx skills add ${REPO_SKILLS_SOURCE} --skill ${skillId} -g -y` : '';
|
|
}
|
|
|
|
function publicSkillCmd(c) {
|
|
return normalizeNpxSkillsCmd(c.skill_md);
|
|
}
|
|
|
|
function renderSkillAction(value) {
|
|
if (!value) return '';
|
|
if (isWebUrl(value)) {
|
|
return `<a href="${esc(value)}" target="_blank">Skill</a>`;
|
|
}
|
|
if (isRepoSkillPath(value)) {
|
|
return `<a href="${REPO}/blob/main/${esc(value)}" target="_blank">Skill</a>`;
|
|
}
|
|
return `
|
|
<span class="skill-popover">
|
|
<button class="card-link-btn skill-trigger" type="button" aria-expanded="false" data-skill-value="${escAttr(value)}">Skill</button>
|
|
<span class="skill-popover-panel">
|
|
<span class="skill-popover-label">Skill install</span>
|
|
<code>${esc(value)}</code>
|
|
<button class="card-copy-btn skill-popover-copy" type="button" data-copy-value="${escAttr(value)}">Copy</button>
|
|
</span>
|
|
</span>`;
|
|
}
|
|
|
|
function renderInstallStack(steps) {
|
|
const display = steps.map((step) =>
|
|
`<span class="install-comment"># ${esc(step.label.toLowerCase())}</span><span class="install-command">${esc(step.cmd)}</span>`
|
|
).join('');
|
|
const copyText = steps.map((step) =>
|
|
`# ${step.label.toLowerCase()}\n${step.cmd}`
|
|
).join('\n');
|
|
return `<div class="card-install">
|
|
<code>${display}</code>
|
|
<button class="card-copy-btn" onclick='copyCmd(this, ${JSON.stringify(copyText)})'>Copy</button>
|
|
</div>`;
|
|
}
|
|
|
|
function renderHarnessCard(c) {
|
|
const requiresHtml = c.requires
|
|
? `<div class="card-requires"><strong>Requires</strong> ${esc(c.requires)}</div>`
|
|
: '';
|
|
const skillLink = c.skill_md
|
|
? `<a href="${c.skill_md.startsWith('http') ? c.skill_md : `${REPO}/blob/main/${c.skill_md}`}" target="_blank">Skill</a>`
|
|
: '';
|
|
const sourceLink = c.source_url
|
|
? `<a href="${esc(c.source_url)}" target="_blank">Source</a>`
|
|
: `<a href="${REPO}/tree/main/${c.name}/agent-harness" target="_blank">Source</a>`;
|
|
const titleHtml = c.homepage
|
|
? `<a href="${esc(c.homepage)}" target="_blank">${esc(c.display_name)}${EXTERNAL_LINK_SVG}</a>`
|
|
: esc(c.display_name);
|
|
const contributors = c.contributors || (c.contributor ? [{name: c.contributor, url: c.contributor_url}] : []);
|
|
const isTeam = contributors.length === 1 && contributors[0].name === 'CLI-Anything-Team';
|
|
const contributorHtml = contributors.length
|
|
? `<div class="card-contributor">
|
|
${contributors.map(ct => `<a href="${esc(ct.url)}" target="_blank">${esc(ct.name)}</a>`).join(', ')}${isTeam ? '' : '<span class="community-badge">Community</span>'}
|
|
</div>`
|
|
: '';
|
|
const dateHtml = c.last_modified
|
|
? `<div class="card-date">Updated ${esc(c.last_modified)}</div>`
|
|
: '';
|
|
const installSteps = [
|
|
{ label: 'step 1 · install cli', cmd: `cli-hub install ${c.name}` }
|
|
];
|
|
const skillCmd = harnessSkillCmd(c);
|
|
if (skillCmd) installSteps.push({ label: 'step 2 · install skill', cmd: skillCmd });
|
|
const installHtml = renderInstallStack(installSteps);
|
|
|
|
return `
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<span class="card-title">${titleHtml}</span>
|
|
<div class="card-meta">
|
|
<span class="category-tag">${catLabel(c.category)}</span>
|
|
<span class="card-version">${esc(c.version)}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-desc">${esc(c.description)}</div>
|
|
${dateHtml}
|
|
${requiresHtml}
|
|
${installHtml}
|
|
<div class="card-footer">
|
|
<div class="card-links">${sourceLink}${skillLink ? ' · ' + skillLink : ''}</div>
|
|
${contributorHtml}
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function formatPublicManager(c) {
|
|
const manager = (c.package_manager || c.install_strategy || 'public').toLowerCase();
|
|
const labelMap = { npm: 'npm', brew: 'brew', bundled: 'bundled' };
|
|
const label = labelMap[manager] || manager;
|
|
return `<span class="public-badge public-badge--${esc(manager)}">${esc(label)}</span>`;
|
|
}
|
|
|
|
function publicDirectCmd(c) {
|
|
return c.direct_cmd || c.entry_point || c.npx_cmd || '';
|
|
}
|
|
|
|
function renderNpmCard(c) {
|
|
const isBundled = (c.install_strategy || c.package_manager) === 'bundled';
|
|
const dateHtml = c.last_modified
|
|
? `<div class="card-date">Updated ${esc(c.last_modified)}</div>`
|
|
: '';
|
|
const requiresHtml = c.requires
|
|
? `<div class="card-requires"><strong>Requires</strong> ${esc(c.requires)}</div>`
|
|
: '';
|
|
const titleHtml = c.homepage
|
|
? `<a href="${esc(c.homepage)}" target="_blank">${esc(c.display_name)}${EXTERNAL_LINK_SVG}</a>`
|
|
: esc(c.display_name);
|
|
const skillLink = renderSkillAction(c.skill_md);
|
|
const sourceLink = c.source_url
|
|
? `<a href="${esc(c.source_url)}" target="_blank">Source</a>`
|
|
: '';
|
|
const contributors = c.contributors || [];
|
|
const contributorHtml = contributors.length
|
|
? `<div class="card-contributor">
|
|
${contributors.map(ct => `<a href="${esc(ct.url)}" target="_blank">${esc(ct.name)}</a>`).join(', ')}
|
|
</div>`
|
|
: '';
|
|
const directCmd = c.install_cmd || publicDirectCmd(c);
|
|
const directCmdHtml = directCmd && !isBundled
|
|
? `<div class="card-npx card-npx--direct"><strong>Or run directly:</strong><span>${esc(directCmd)}</span></div>`
|
|
: '';
|
|
const installNotesHtml = c.install_notes
|
|
? `<div class="card-npx card-npx--comment">${esc(c.install_notes)}</div>`
|
|
: '';
|
|
const publicNotesHtml = (directCmdHtml || installNotesHtml)
|
|
? `<div class="card-public-notes">${directCmdHtml}${installNotesHtml}</div>`
|
|
: '';
|
|
const installSteps = isBundled
|
|
? []
|
|
: [{ label: 'step 1 · install cli', cmd: `cli-hub install ${c.name}` }];
|
|
const skillCmd = publicSkillCmd(c);
|
|
if (skillCmd) installSteps.push({ label: 'step 2 · install skill', cmd: skillCmd });
|
|
const cliHubInstallHtml = isBundled
|
|
? `<div class="card-install card-install--note"><code>Bundled with the upstream app</code></div>`
|
|
: renderInstallStack(installSteps);
|
|
const linksHtml = [sourceLink, skillLink].filter(Boolean).join(' · ');
|
|
|
|
return `
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<span class="card-title">${titleHtml}</span>
|
|
<div class="card-meta">
|
|
${formatPublicManager(c)}
|
|
<span class="category-tag">${catLabel(c.category)}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-desc">${esc(c.description)}</div>
|
|
${dateHtml}
|
|
${requiresHtml}
|
|
${cliHubInstallHtml}
|
|
${publicNotesHtml}
|
|
<div class="card-footer">
|
|
<div class="card-links">${linksHtml}</div>
|
|
${contributorHtml}
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function copyCmd(btn, cmd) {
|
|
navigator.clipboard.writeText(cmd).then(() => {
|
|
btn.textContent = 'Copied!';
|
|
setTimeout(() => btn.textContent = 'Copy', 1500);
|
|
});
|
|
}
|
|
|
|
function initHeroMotion() {
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
document.documentElement.classList.remove('js');
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(() => {
|
|
document.documentElement.classList.add('motion-ready');
|
|
});
|
|
}
|
|
|
|
function animateCount(el, target, duration = 900) {
|
|
if (!el) return;
|
|
const finalValue = Number(target) || 0;
|
|
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
el.textContent = finalValue;
|
|
return;
|
|
}
|
|
|
|
const start = performance.now();
|
|
|
|
function step(now) {
|
|
const progress = Math.min((now - start) / duration, 1);
|
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
el.textContent = Math.round(finalValue * eased);
|
|
if (progress < 1) {
|
|
window.requestAnimationFrame(step);
|
|
} else {
|
|
el.textContent = finalValue;
|
|
}
|
|
}
|
|
|
|
window.requestAnimationFrame(step);
|
|
}
|
|
|
|
function initHeroTypingBar() {
|
|
const shell = document.querySelector('[data-typing-text]');
|
|
if (!shell) return;
|
|
const target = shell.querySelector('[data-typing-target]');
|
|
const fullText = shell.dataset.typingText || '';
|
|
if (!target || !fullText) return;
|
|
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
target.textContent = fullText;
|
|
return;
|
|
}
|
|
|
|
let index = 0;
|
|
let deleting = false;
|
|
|
|
function tick() {
|
|
target.textContent = fullText.slice(0, index);
|
|
|
|
let delay = deleting ? 44 : 86;
|
|
if (!deleting && index < fullText.length) {
|
|
index += 1;
|
|
} else if (!deleting && index === fullText.length) {
|
|
deleting = true;
|
|
delay = 1650;
|
|
} else if (deleting && index > 0) {
|
|
index -= 1;
|
|
} else {
|
|
deleting = false;
|
|
delay = 480;
|
|
}
|
|
|
|
window.setTimeout(tick, delay);
|
|
}
|
|
|
|
window.setTimeout(tick, 420);
|
|
}
|
|
|
|
function esc(s) {
|
|
if (!s) return '';
|
|
const d = document.createElement('div');
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function escAttr(s) {
|
|
if (!s) return '';
|
|
return String(s)
|
|
.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
function toggleSkillPopover(trigger) {
|
|
const popover = trigger.closest('.skill-popover');
|
|
if (!popover) return;
|
|
const shouldOpen = !popover.classList.contains('is-open');
|
|
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
|
|
node.classList.remove('is-open');
|
|
const btn = node.querySelector('.skill-trigger');
|
|
if (btn) btn.setAttribute('aria-expanded', 'false');
|
|
});
|
|
if (!shouldOpen) return;
|
|
popover.classList.add('is-open');
|
|
trigger.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
document.addEventListener('click', (e) => {
|
|
const copyBtn = e.target.closest('.skill-popover-copy');
|
|
if (copyBtn) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
copyCmd(copyBtn, copyBtn.dataset.copyValue);
|
|
return;
|
|
}
|
|
|
|
const trigger = e.target.closest('.skill-trigger');
|
|
if (trigger) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
toggleSkillPopover(trigger);
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
|
|
node.classList.remove('is-open');
|
|
const btn = node.querySelector('.skill-trigger');
|
|
if (btn) btn.setAttribute('aria-expanded', 'false');
|
|
});
|
|
});
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key !== 'Escape') return;
|
|
document.querySelectorAll('.skill-popover.is-open').forEach((node) => {
|
|
node.classList.remove('is-open');
|
|
const btn = node.querySelector('.skill-trigger');
|
|
if (btn) btn.setAttribute('aria-expanded', 'false');
|
|
});
|
|
});
|
|
|
|
// Wire up search and sort for each deck
|
|
document.getElementById('search-harness').addEventListener('input', (e) => {
|
|
deckState.harness.query = e.target.value;
|
|
renderDeck('harness');
|
|
});
|
|
document.getElementById('sort-harness').addEventListener('change', (e) => {
|
|
deckState.harness.sort = e.target.value;
|
|
renderDeck('harness');
|
|
});
|
|
document.getElementById('search-public').addEventListener('input', (e) => {
|
|
deckState.public.query = e.target.value;
|
|
renderDeck('public');
|
|
});
|
|
document.getElementById('sort-public').addEventListener('change', (e) => {
|
|
deckState.public.sort = e.target.value;
|
|
renderDeck('public');
|
|
});
|
|
|
|
window.addEventListener('resize', updateDeckPosition);
|
|
|
|
initDeckGestures();
|
|
initHeroMotion();
|
|
initHeroTypingBar();
|
|
loadRegistries().then(updateDeckPosition);
|
|
|
|
// ── Visitor classification: human vs AI agent ──
|
|
(function() {
|
|
const AI_UA_PATTERNS = [
|
|
/claude/i, /anthropic/i, /openai/i, /gpt/i, /chatgpt/i,
|
|
/bingbot/i, /googlebot/i, /baiduspider/i, /yandexbot/i,
|
|
/slurp/i, /duckduckbot/i, /facebookexternalhit/i,
|
|
/twitterbot/i, /linkedinbot/i, /whatsapp/i, /telegrambot/i,
|
|
/applebot/i, /semrushbot/i, /ahrefsbot/i, /dotbot/i,
|
|
/petalbot/i, /mj12bot/i, /bytespider/i,
|
|
/headless/i, /phantom/i, /selenium/i, /puppeteer/i, /playwright/i,
|
|
/crawl/i, /spider/i, /bot\b/i, /scraper/i,
|
|
/python-requests/i, /axios/i, /node-fetch/i, /go-http/i, /curl/i, /wget/i,
|
|
/ccbot/i, /gptbot/i, /perplexity/i, /cohere/i,
|
|
];
|
|
|
|
const ua = navigator.userAgent || '';
|
|
const isKnownAgent = AI_UA_PATTERNS.some(p => p.test(ua));
|
|
const isWebdriver = navigator.webdriver === true;
|
|
let humanConfirmed = false;
|
|
|
|
// ── Send event to BOTH Umami website IDs ──
|
|
// The global `umami` object only binds to one script tag, so we also
|
|
// POST directly to the send API for the second site.
|
|
const UMAMI_SEND = 'https://cloud.umami.is/api/send';
|
|
const DUAL_WEBSITE_IDS = [
|
|
'07082d05-efd3-4f85-a7a1-b426b0e8bfaa',
|
|
'a076c661-bed1-405c-a522-813794e688b4',
|
|
];
|
|
function trackBoth(eventName, eventData) {
|
|
// POST directly to both sites — do NOT also call umami.track()
|
|
// because that would double-count on whichever site umami is bound to.
|
|
DUAL_WEBSITE_IDS.forEach(wid => {
|
|
const payload = {
|
|
type: 'event',
|
|
payload: {
|
|
website: wid,
|
|
hostname: location.hostname,
|
|
url: location.pathname,
|
|
name: eventName,
|
|
data: eventData || {},
|
|
},
|
|
};
|
|
fetch(UMAMI_SEND, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
keepalive: true,
|
|
}).catch(() => {});
|
|
});
|
|
}
|
|
|
|
// Tag the visit type via Umami custom events
|
|
if (isKnownAgent || isWebdriver) {
|
|
trackBoth('visit-agent', { ua: ua.slice(0, 200) });
|
|
}
|
|
|
|
// Wait for Umami to load, then tag
|
|
window.addEventListener('load', () => {
|
|
setTimeout(() => {
|
|
if (isKnownAgent || isWebdriver) {
|
|
trackBoth('visit-agent', { ua: ua.slice(0, 200) });
|
|
}
|
|
}, 500);
|
|
});
|
|
|
|
// Real human interaction detection
|
|
function onHumanInteraction() {
|
|
if (humanConfirmed) return;
|
|
humanConfirmed = true;
|
|
trackBoth('visit-human');
|
|
// Clean up listeners
|
|
['mousemove', 'touchstart', 'scroll', 'keydown', 'click'].forEach(evt => {
|
|
document.removeEventListener(evt, onHumanInteraction);
|
|
});
|
|
}
|
|
|
|
['mousemove', 'touchstart', 'scroll', 'keydown', 'click'].forEach(evt => {
|
|
document.addEventListener(evt, onHumanInteraction, { once: false, passive: true });
|
|
});
|
|
|
|
// ── Fetch all-time stats from Umami Cloud API (both sites) ──
|
|
const UMAMI_API = 'https://api.umami.is/v1';
|
|
const UMAMI_KEY = 'api_idAebMhzn6z0hsUQT7BSxRuCK2GUZvRY';
|
|
const HKUDS_WEBSITE_ID = '07082d05-efd3-4f85-a7a1-b426b0e8bfaa';
|
|
const CC_WEBSITE_ID = 'a076c661-bed1-405c-a522-813794e688b4';
|
|
const headers = { 'Accept': 'application/json', 'x-umami-api-key': UMAMI_KEY };
|
|
|
|
async function loadVisitorStats() {
|
|
try {
|
|
const now = Date.now();
|
|
let hkudsHumanVisits = 0;
|
|
let ccHumanVisits = 0;
|
|
let ccAgentVisits = 0;
|
|
|
|
const [hkudsStatsResp, ccEventsResp] = await Promise.all([
|
|
fetch(`${UMAMI_API}/websites/${HKUDS_WEBSITE_ID}/stats?startAt=0&endAt=${now}`, { headers }),
|
|
fetch(`${UMAMI_API}/websites/${CC_WEBSITE_ID}/events/series?startAt=0&endAt=${now}&unit=year&timezone=UTC`, { headers })
|
|
]);
|
|
|
|
if (hkudsStatsResp.ok) {
|
|
const stats = await hkudsStatsResp.json();
|
|
hkudsHumanVisits = stats.visits ?? 0;
|
|
}
|
|
|
|
if (ccEventsResp.ok) {
|
|
const events = await ccEventsResp.json();
|
|
events.forEach(e => {
|
|
if (e.x === 'visit-human') ccHumanVisits += e.y || 0;
|
|
if (e.x === 'visit-agent') ccAgentVisits += e.y || 0;
|
|
});
|
|
}
|
|
|
|
const humanCount = hkudsHumanVisits + ccHumanVisits;
|
|
const totalVisits = humanCount + ccAgentVisits;
|
|
|
|
document.getElementById('stat-total').textContent = totalVisits.toLocaleString();
|
|
document.getElementById('stat-human').textContent = humanCount.toLocaleString();
|
|
document.getElementById('stat-agent').textContent = ccAgentVisits.toLocaleString();
|
|
} catch (_) {
|
|
// Stats not available — leave dashes
|
|
}
|
|
}
|
|
|
|
loadVisitorStats();
|
|
loadGitHubStars();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|