start-vibing 4.4.13 → 4.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +248 -55
- package/package.json +42 -42
- package/template/.claude/skills/typeui-web3-lp/SKILL.md +407 -0
package/dist/cli.js
CHANGED
|
@@ -130,7 +130,7 @@ var STALE_FILES = [
|
|
|
130
130
|
{
|
|
131
131
|
category: "agents",
|
|
132
132
|
relPath: "research-web.md",
|
|
133
|
-
replacedBy: "research skill (scout
|
|
133
|
+
replacedBy: "research skill (scout → query → synthesize → verify)",
|
|
134
134
|
staleChecksums: [
|
|
135
135
|
"49d0cb862d199d1dcb59ed932ed04d8abd0064de3b65ef4f346268528dc21772",
|
|
136
136
|
"72ba14b6cad76a2d8718234775883242e94e82497e4edeec7fb35faf24327d6a",
|
|
@@ -219,8 +219,7 @@ function tryDelete(claudeDir, locationLabel, stale, result) {
|
|
|
219
219
|
location: locationLabel,
|
|
220
220
|
replacedBy: stale.replacedBy
|
|
221
221
|
});
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
222
|
+
} catch {}
|
|
224
223
|
}
|
|
225
224
|
function cleanupStaleAgents(targetDir) {
|
|
226
225
|
const result = { deleted: [], keptCustomized: [] };
|
|
@@ -240,8 +239,8 @@ function cleanupStaleAgents(targetDir) {
|
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
// src/cli.ts
|
|
243
|
-
import { existsSync as
|
|
244
|
-
import { join as
|
|
242
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, appendFileSync as appendFileSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
243
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
245
244
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
246
245
|
import { execSync as execSync3 } from "child_process";
|
|
247
246
|
|
|
@@ -359,8 +358,7 @@ function writeCache(latestVersion) {
|
|
|
359
358
|
latestVersion
|
|
360
359
|
};
|
|
361
360
|
writeFileSync2(CACHE_FILE, JSON.stringify(entry, null, 2));
|
|
362
|
-
} catch {
|
|
363
|
-
}
|
|
361
|
+
} catch {}
|
|
364
362
|
}
|
|
365
363
|
function isCacheValid(entry) {
|
|
366
364
|
if (!entry)
|
|
@@ -509,8 +507,7 @@ async function installClaudeWindows(shell) {
|
|
|
509
507
|
stdio: "ignore",
|
|
510
508
|
shell: "cmd.exe"
|
|
511
509
|
});
|
|
512
|
-
} catch {
|
|
513
|
-
}
|
|
510
|
+
} catch {}
|
|
514
511
|
}
|
|
515
512
|
return { success: true, alreadyInstalled: false };
|
|
516
513
|
} catch (error) {
|
|
@@ -539,9 +536,15 @@ async function installClaudeUnix() {
|
|
|
539
536
|
function encodeProjectPath(absolutePath) {
|
|
540
537
|
return absolutePath.replace(/\\/g, "/").replace(/\//g, "-");
|
|
541
538
|
}
|
|
539
|
+
function getActiveClaudeDir() {
|
|
540
|
+
const fromEnv = process.env["CLAUDE_CONFIG_DIR"];
|
|
541
|
+
if (fromEnv && fromEnv.trim().length > 0)
|
|
542
|
+
return fromEnv;
|
|
543
|
+
return join4(homedir2(), ".claude");
|
|
544
|
+
}
|
|
542
545
|
function hasExistingSession(projectDir) {
|
|
543
546
|
const encodedPath = encodeProjectPath(projectDir);
|
|
544
|
-
const sessionDir = join4(
|
|
547
|
+
const sessionDir = join4(getActiveClaudeDir(), "projects", encodedPath);
|
|
545
548
|
const indexFile = join4(sessionDir, "sessions-index.json");
|
|
546
549
|
if (existsSync4(indexFile)) {
|
|
547
550
|
try {
|
|
@@ -552,15 +555,13 @@ function hasExistingSession(projectDir) {
|
|
|
552
555
|
if (mainSessions.length > 0)
|
|
553
556
|
return true;
|
|
554
557
|
}
|
|
555
|
-
} catch {
|
|
556
|
-
}
|
|
558
|
+
} catch {}
|
|
557
559
|
}
|
|
558
560
|
if (existsSync4(sessionDir)) {
|
|
559
561
|
try {
|
|
560
562
|
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
|
|
561
563
|
return readdirSync2(sessionDir).some((f) => uuidPattern.test(f));
|
|
562
|
-
} catch {
|
|
563
|
-
}
|
|
564
|
+
} catch {}
|
|
564
565
|
}
|
|
565
566
|
return false;
|
|
566
567
|
}
|
|
@@ -597,8 +598,7 @@ function launchClaude(cwd, options = {}) {
|
|
|
597
598
|
}
|
|
598
599
|
function ensureHooksEnabled() {
|
|
599
600
|
try {
|
|
600
|
-
const
|
|
601
|
-
const globalSettingsPath = join4(home, ".claude", "settings.json");
|
|
601
|
+
const globalSettingsPath = join4(getActiveClaudeDir(), "settings.json");
|
|
602
602
|
if (!existsSync4(globalSettingsPath)) {
|
|
603
603
|
return { modified: false };
|
|
604
604
|
}
|
|
@@ -801,9 +801,12 @@ async function installPlugin(plugin) {
|
|
|
801
801
|
let stdout = "";
|
|
802
802
|
let stderr = "";
|
|
803
803
|
if (proc.stdin) {
|
|
804
|
-
proc.stdin.write(
|
|
805
|
-
|
|
806
|
-
proc.stdin.write(
|
|
804
|
+
proc.stdin.write(`y
|
|
805
|
+
`);
|
|
806
|
+
proc.stdin.write(`y
|
|
807
|
+
`);
|
|
808
|
+
proc.stdin.write(`y
|
|
809
|
+
`);
|
|
807
810
|
proc.stdin.end();
|
|
808
811
|
}
|
|
809
812
|
proc.stdout?.on("data", (data) => {
|
|
@@ -938,6 +941,12 @@ function getRecommendedSkills() {
|
|
|
938
941
|
return RECOMMENDED_SKILLS;
|
|
939
942
|
}
|
|
940
943
|
|
|
944
|
+
// src/profile.ts
|
|
945
|
+
import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
|
|
946
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, appendFileSync, writeFileSync as writeFileSync5 } from "fs";
|
|
947
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
948
|
+
import { homedir as homedir3 } from "os";
|
|
949
|
+
|
|
941
950
|
// src/ui.ts
|
|
942
951
|
var c = {
|
|
943
952
|
reset: "\x1B[0m",
|
|
@@ -953,7 +962,7 @@ var c = {
|
|
|
953
962
|
bgRed: "\x1B[41m",
|
|
954
963
|
redBright: "\x1B[91m"
|
|
955
964
|
};
|
|
956
|
-
var SPINNER_FRAMES = ["
|
|
965
|
+
var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
957
966
|
function createBanner(version) {
|
|
958
967
|
const date = new Date().toISOString().slice(0, 10);
|
|
959
968
|
return `
|
|
@@ -963,7 +972,7 @@ ${c.cyan} \\ \\ / / | | | _ \\ | | | \\| || | _
|
|
|
963
972
|
${c.cyan} \\ \\/ / | | | |_) | | | | |\\ || |_| |
|
|
964
973
|
${c.cyan} \\__/ |___||____/ |___||_| \\_| \\____|${c.reset}
|
|
965
974
|
|
|
966
|
-
${c.bright} v${version}${c.reset}${c.dim}
|
|
975
|
+
${c.bright} v${version}${c.reset}${c.dim} · ${date} · 9 plugins · 17 skills · 8 MCPs${c.reset}
|
|
967
976
|
`;
|
|
968
977
|
}
|
|
969
978
|
function createSpinner(initialText) {
|
|
@@ -984,13 +993,13 @@ function createSpinner(initialText) {
|
|
|
984
993
|
succeed(finalText) {
|
|
985
994
|
if (interval)
|
|
986
995
|
clearInterval(interval);
|
|
987
|
-
process.stdout.write(`\r ${c.green}
|
|
996
|
+
process.stdout.write(`\r ${c.green}✓${c.reset} ${finalText}\x1B[K
|
|
988
997
|
`);
|
|
989
998
|
},
|
|
990
999
|
fail(finalText) {
|
|
991
1000
|
if (interval)
|
|
992
1001
|
clearInterval(interval);
|
|
993
|
-
process.stdout.write(`\r ${c.red}
|
|
1002
|
+
process.stdout.write(`\r ${c.red}✗${c.reset} ${finalText}\x1B[K
|
|
994
1003
|
`);
|
|
995
1004
|
},
|
|
996
1005
|
stop() {
|
|
@@ -1004,8 +1013,8 @@ function phaseHeader(step, total, label) {
|
|
|
1004
1013
|
return `${c.dim}[${step}/${total}]${c.reset} ${label}`;
|
|
1005
1014
|
}
|
|
1006
1015
|
function treeItem(name, description, isLast, success) {
|
|
1007
|
-
const branch = isLast ? "
|
|
1008
|
-
const icon = success ? `${c.green}
|
|
1016
|
+
const branch = isLast ? "└" : "├";
|
|
1017
|
+
const icon = success ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
|
|
1009
1018
|
return ` ${c.dim}${branch}${c.reset} ${c.cyan}${name.padEnd(21)}${c.reset}${icon} ${c.dim}${description}${c.reset}`;
|
|
1010
1019
|
}
|
|
1011
1020
|
function formatElapsed(startMs) {
|
|
@@ -1015,27 +1024,159 @@ function formatElapsed(startMs) {
|
|
|
1015
1024
|
function printOptionalMcps() {
|
|
1016
1025
|
console.log("");
|
|
1017
1026
|
console.log(` ${c.dim}Optional MCPs (install manually):${c.reset}`);
|
|
1018
|
-
console.log(` ${c.cyan}github${c.reset} ${c.dim}
|
|
1019
|
-
console.log(` ${c.cyan}sentry${c.reset} ${c.dim}
|
|
1020
|
-
console.log(` ${c.cyan}figma${c.reset} ${c.dim}
|
|
1021
|
-
console.log(` ${c.cyan}linear${c.reset} ${c.dim}
|
|
1022
|
-
console.log(` ${c.cyan}stripe${c.reset} ${c.dim}
|
|
1023
|
-
console.log(` ${c.cyan}vercel${c.reset} ${c.dim}
|
|
1027
|
+
console.log(` ${c.cyan}github${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user github https://api.githubcopilot.com/mcp/`);
|
|
1028
|
+
console.log(` ${c.cyan}sentry${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user sentry https://mcp.sentry.dev/mcp`);
|
|
1029
|
+
console.log(` ${c.cyan}figma${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user figma https://mcp.figma.com/mcp`);
|
|
1030
|
+
console.log(` ${c.cyan}linear${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user linear https://mcp.linear.app/sse`);
|
|
1031
|
+
console.log(` ${c.cyan}stripe${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user stripe https://mcp.stripe.com`);
|
|
1032
|
+
console.log(` ${c.cyan}vercel${c.reset} ${c.dim}·${c.reset} claude mcp add --transport http -s user vercel https://mcp.vercel.com`);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/profile.ts
|
|
1036
|
+
var RESERVED_NAMES = new Set(["default", "main", "claude"]);
|
|
1037
|
+
var NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
1038
|
+
function parseProfileArg(args) {
|
|
1039
|
+
const idx = args.indexOf("--profile");
|
|
1040
|
+
if (idx === -1)
|
|
1041
|
+
return null;
|
|
1042
|
+
const value = args[idx + 1];
|
|
1043
|
+
if (!value || value.startsWith("--")) {
|
|
1044
|
+
throw new Error("--profile requires a name (e.g. --profile work)");
|
|
1045
|
+
}
|
|
1046
|
+
if (RESERVED_NAMES.has(value.toLowerCase())) {
|
|
1047
|
+
throw new Error(`Profile name "${value}" is reserved. Use a different name (e.g. work, personal, client-x).`);
|
|
1048
|
+
}
|
|
1049
|
+
if (!NAME_PATTERN.test(value)) {
|
|
1050
|
+
throw new Error(`Profile name "${value}" is invalid. Use lowercase letters, digits, and hyphens (e.g. work, my-client).`);
|
|
1051
|
+
}
|
|
1052
|
+
return value;
|
|
1053
|
+
}
|
|
1054
|
+
function resolveProfileDir(name) {
|
|
1055
|
+
const home = getHomeDir() || homedir3();
|
|
1056
|
+
const dir = join6(home, `.claude-${name}`);
|
|
1057
|
+
return { name, dir, isNew: !existsSync6(dir) };
|
|
1058
|
+
}
|
|
1059
|
+
function ensureProfileDir(dir) {
|
|
1060
|
+
if (!existsSync6(dir)) {
|
|
1061
|
+
mkdirSync4(dir, { recursive: true });
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
async function loginToProfile(name) {
|
|
1065
|
+
console.log("");
|
|
1066
|
+
console.log(` ${c.cyan}First-time setup for profile "${name}"${c.reset}`);
|
|
1067
|
+
console.log(` ${c.dim}Claude needs to authenticate this profile. A browser will open for SSO login.${c.reset}`);
|
|
1068
|
+
console.log(` ${c.dim}When done, type ${c.bright}/quit${c.reset}${c.dim} (or close the window) to continue.${c.reset}`);
|
|
1069
|
+
console.log("");
|
|
1070
|
+
await new Promise((resolve2) => {
|
|
1071
|
+
const proc = spawn4("claude", ["--dangerously-skip-permissions"], {
|
|
1072
|
+
stdio: "inherit",
|
|
1073
|
+
shell: process.platform === "win32"
|
|
1074
|
+
});
|
|
1075
|
+
proc.on("exit", () => resolve2());
|
|
1076
|
+
proc.on("error", () => resolve2());
|
|
1077
|
+
});
|
|
1078
|
+
console.log("");
|
|
1079
|
+
console.log(` ${c.green}✓${c.reset} Login complete. Resuming setup...`);
|
|
1080
|
+
console.log("");
|
|
1081
|
+
}
|
|
1082
|
+
function getShellConfigPath() {
|
|
1083
|
+
const shell = detectShell();
|
|
1084
|
+
const home = getHomeDir() || homedir3();
|
|
1085
|
+
if (shell === "powershell") {
|
|
1086
|
+
try {
|
|
1087
|
+
const result = spawnSync4("powershell", ["-NoProfile", "-NonInteractive", "-Command", "$PROFILE"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
|
|
1088
|
+
if (result.status === 0 && result.stdout) {
|
|
1089
|
+
return { path: result.stdout.trim(), shell: "powershell" };
|
|
1090
|
+
}
|
|
1091
|
+
} catch {}
|
|
1092
|
+
return {
|
|
1093
|
+
path: join6(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1"),
|
|
1094
|
+
shell: "powershell"
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
if (shell === "zsh")
|
|
1098
|
+
return { path: join6(home, ".zshrc"), shell: "zsh" };
|
|
1099
|
+
if (shell === "bash")
|
|
1100
|
+
return { path: join6(home, ".bashrc"), shell: "bash" };
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
function buildShellFunction(name, configDir, shell) {
|
|
1104
|
+
const marker = `# start-vibing profile: ${name}`;
|
|
1105
|
+
if (shell === "powershell") {
|
|
1106
|
+
const dirLiteral = configDir.replace(/'/g, "''");
|
|
1107
|
+
return [
|
|
1108
|
+
"",
|
|
1109
|
+
marker,
|
|
1110
|
+
`function claude-${name} {`,
|
|
1111
|
+
` $env:CLAUDE_CONFIG_DIR = '${dirLiteral}'`,
|
|
1112
|
+
` claude @args`,
|
|
1113
|
+
`}`,
|
|
1114
|
+
""
|
|
1115
|
+
].join(`
|
|
1116
|
+
`);
|
|
1117
|
+
}
|
|
1118
|
+
const home = getHomeDir() || homedir3();
|
|
1119
|
+
const portable = configDir.startsWith(home) ? `$HOME${configDir.slice(home.length).replace(/\\/g, "/")}` : configDir.replace(/\\/g, "/");
|
|
1120
|
+
return [
|
|
1121
|
+
"",
|
|
1122
|
+
marker,
|
|
1123
|
+
`claude-${name}() {`,
|
|
1124
|
+
` CLAUDE_CONFIG_DIR="${portable}" claude "$@"`,
|
|
1125
|
+
`}`,
|
|
1126
|
+
""
|
|
1127
|
+
].join(`
|
|
1128
|
+
`);
|
|
1129
|
+
}
|
|
1130
|
+
function injectShellFunction(name, configDir) {
|
|
1131
|
+
const target = getShellConfigPath();
|
|
1132
|
+
if (!target) {
|
|
1133
|
+
return { injected: false, skipped: false, path: null, reloadHint: null };
|
|
1134
|
+
}
|
|
1135
|
+
const marker = `# start-vibing profile: ${name}`;
|
|
1136
|
+
let existing = "";
|
|
1137
|
+
if (existsSync6(target.path)) {
|
|
1138
|
+
try {
|
|
1139
|
+
existing = readFileSync5(target.path, "utf-8");
|
|
1140
|
+
} catch {
|
|
1141
|
+
existing = "";
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (existing.includes(marker)) {
|
|
1145
|
+
return { injected: false, skipped: true, path: target.path, reloadHint: null };
|
|
1146
|
+
}
|
|
1147
|
+
const snippet = buildShellFunction(name, configDir, target.shell);
|
|
1148
|
+
try {
|
|
1149
|
+
const parent = dirname2(target.path);
|
|
1150
|
+
if (!existsSync6(parent))
|
|
1151
|
+
mkdirSync4(parent, { recursive: true });
|
|
1152
|
+
if (existsSync6(target.path)) {
|
|
1153
|
+
const prefix = existing.endsWith(`
|
|
1154
|
+
`) ? "" : `
|
|
1155
|
+
`;
|
|
1156
|
+
appendFileSync(target.path, prefix + snippet, "utf-8");
|
|
1157
|
+
} else {
|
|
1158
|
+
writeFileSync5(target.path, snippet, "utf-8");
|
|
1159
|
+
}
|
|
1160
|
+
} catch {
|
|
1161
|
+
return { injected: false, skipped: false, path: target.path, reloadHint: null };
|
|
1162
|
+
}
|
|
1163
|
+
const reload = target.shell === "powershell" ? ". $PROFILE" : target.shell === "zsh" ? "source ~/.zshrc" : "source ~/.bashrc";
|
|
1164
|
+
return { injected: true, skipped: false, path: target.path, reloadHint: reload };
|
|
1024
1165
|
}
|
|
1025
1166
|
|
|
1026
1167
|
// src/cli.ts
|
|
1027
1168
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
1028
|
-
var __dirname3 =
|
|
1169
|
+
var __dirname3 = dirname3(__filename3);
|
|
1029
1170
|
var TOTAL_PHASES = 6;
|
|
1030
1171
|
function getVersion() {
|
|
1031
1172
|
try {
|
|
1032
1173
|
const paths = [
|
|
1033
|
-
|
|
1034
|
-
|
|
1174
|
+
join7(__dirname3, "..", "package.json"),
|
|
1175
|
+
join7(__dirname3, "..", "..", "package.json")
|
|
1035
1176
|
];
|
|
1036
1177
|
for (const pkgPath of paths) {
|
|
1037
|
-
if (
|
|
1038
|
-
const pkg = JSON.parse(
|
|
1178
|
+
if (existsSync7(pkgPath)) {
|
|
1179
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
1039
1180
|
return pkg.version || "4.0.0";
|
|
1040
1181
|
}
|
|
1041
1182
|
}
|
|
@@ -1085,21 +1226,30 @@ function syncGitignore(targetDir) {
|
|
|
1085
1226
|
else
|
|
1086
1227
|
toIgnore.push(p);
|
|
1087
1228
|
}
|
|
1088
|
-
const gitignorePath =
|
|
1229
|
+
const gitignorePath = join7(targetDir, ".gitignore");
|
|
1089
1230
|
const header = "# start-vibing (local Claude setup - not tracked in this repo)";
|
|
1090
1231
|
let current = "";
|
|
1091
|
-
if (
|
|
1092
|
-
current =
|
|
1232
|
+
if (existsSync7(gitignorePath))
|
|
1233
|
+
current = readFileSync6(gitignorePath, "utf-8");
|
|
1093
1234
|
const existing = new Set(current.split(/\r?\n/).map((l) => l.trim()).filter(Boolean));
|
|
1094
1235
|
const missing = toIgnore.filter((e) => !existing.has(e));
|
|
1095
1236
|
if (missing.length === 0) {
|
|
1096
1237
|
return { success: true, added: [], tracked, message: "Already in .gitignore" };
|
|
1097
1238
|
}
|
|
1098
1239
|
if (current.length === 0) {
|
|
1099
|
-
|
|
1240
|
+
writeFileSync6(gitignorePath, `${header}
|
|
1241
|
+
${missing.join(`
|
|
1242
|
+
`)}
|
|
1243
|
+
`, "utf-8");
|
|
1100
1244
|
} else {
|
|
1101
|
-
const prefix = current.endsWith(
|
|
1102
|
-
|
|
1245
|
+
const prefix = current.endsWith(`
|
|
1246
|
+
`) ? "" : `
|
|
1247
|
+
`;
|
|
1248
|
+
appendFileSync2(gitignorePath, `${prefix}
|
|
1249
|
+
${header}
|
|
1250
|
+
${missing.join(`
|
|
1251
|
+
`)}
|
|
1252
|
+
`, "utf-8");
|
|
1103
1253
|
}
|
|
1104
1254
|
return {
|
|
1105
1255
|
success: true,
|
|
@@ -1115,6 +1265,7 @@ var HELP = `${createBanner(VERSION)}
|
|
|
1115
1265
|
npx start-vibing [options]
|
|
1116
1266
|
|
|
1117
1267
|
${c.bright}Options:${c.reset}
|
|
1268
|
+
--profile <name> Use a separate Claude account profile (~/.claude-<name>/)
|
|
1118
1269
|
--new Start fresh Claude session (default: resume last)
|
|
1119
1270
|
--force Overwrite all files (including custom domains)
|
|
1120
1271
|
--no-claude Skip Claude Code installation and launch
|
|
@@ -1124,6 +1275,12 @@ var HELP = `${createBanner(VERSION)}
|
|
|
1124
1275
|
--help, -h Show this help message
|
|
1125
1276
|
--version, -v Show version
|
|
1126
1277
|
|
|
1278
|
+
${c.bright}Profiles:${c.reset}
|
|
1279
|
+
Each profile is an isolated Claude account (separate creds, MCPs, plugins).
|
|
1280
|
+
First run on a new profile triggers SSO login before MCP/plugin install.
|
|
1281
|
+
A shell function ${c.dim}claude-<name>${c.reset} is auto-injected for quick switching.
|
|
1282
|
+
Reload your shell ($PROFILE / .bashrc / .zshrc) after first run.
|
|
1283
|
+
|
|
1127
1284
|
${c.bright}What it does:${c.reset}
|
|
1128
1285
|
[1] Copies template files (agents, skills, config)
|
|
1129
1286
|
[2] Sets up Claude Code (install/migrate if needed)
|
|
@@ -1160,6 +1317,20 @@ async function main() {
|
|
|
1160
1317
|
const newSession = args.includes("--new");
|
|
1161
1318
|
const targetDir = process.cwd();
|
|
1162
1319
|
const globalStart = Date.now();
|
|
1320
|
+
let profile = null;
|
|
1321
|
+
try {
|
|
1322
|
+
const profileName = parseProfileArg(args);
|
|
1323
|
+
if (profileName) {
|
|
1324
|
+
profile = resolveProfileDir(profileName);
|
|
1325
|
+
ensureProfileDir(profile.dir);
|
|
1326
|
+
process.env["CLAUDE_CONFIG_DIR"] = profile.dir;
|
|
1327
|
+
}
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.error("");
|
|
1330
|
+
console.error(` ${c.red}${err instanceof Error ? err.message : err}${c.reset}`);
|
|
1331
|
+
console.error("");
|
|
1332
|
+
process.exit(1);
|
|
1333
|
+
}
|
|
1163
1334
|
if (!skipUpdateCheck) {
|
|
1164
1335
|
try {
|
|
1165
1336
|
const updateResult = await checkForUpdates(VERSION);
|
|
@@ -1169,14 +1340,19 @@ async function main() {
|
|
|
1169
1340
|
console.log(` ${c.dim}Run: ${getUpdateCommand()}${c.reset}`);
|
|
1170
1341
|
console.log("");
|
|
1171
1342
|
}
|
|
1172
|
-
} catch {
|
|
1173
|
-
}
|
|
1343
|
+
} catch {}
|
|
1174
1344
|
}
|
|
1175
1345
|
console.log(createBanner(VERSION));
|
|
1176
1346
|
if (isRunningViaNpx()) {
|
|
1177
1347
|
console.log(` ${c.dim}TIP: npm install -g start-vibing for permanent access${c.reset}`);
|
|
1178
1348
|
console.log("");
|
|
1179
1349
|
}
|
|
1350
|
+
if (profile) {
|
|
1351
|
+
const verb = profile.isNew ? "Creating" : "Reusing";
|
|
1352
|
+
const colour = profile.isNew ? c.cyan : c.green;
|
|
1353
|
+
console.log(` ${colour}→ ${verb} profile "${profile.name}"${c.reset} ${c.dim}at ${profile.dir}${c.reset}`);
|
|
1354
|
+
console.log("");
|
|
1355
|
+
}
|
|
1180
1356
|
const phase1Start = Date.now();
|
|
1181
1357
|
const spinner1 = createSpinner(phaseHeader(1, TOTAL_PHASES, "Copying template files..."));
|
|
1182
1358
|
try {
|
|
@@ -1185,7 +1361,7 @@ async function main() {
|
|
|
1185
1361
|
const ignoreResult = syncGitignore(targetDir);
|
|
1186
1362
|
ensureHooksEnabled();
|
|
1187
1363
|
const counts = `${result.agents} agents, ${result.skills} skills`;
|
|
1188
|
-
spinner1.succeed(phaseHeader(1, TOTAL_PHASES, `Template files ${c.dim}${"
|
|
1364
|
+
spinner1.succeed(phaseHeader(1, TOTAL_PHASES, `Template files ${c.dim}${"·".repeat(14)}${c.reset} ${counts} ${c.dim}${formatElapsed(phase1Start)}${c.reset}`));
|
|
1189
1365
|
if (ignoreResult.success && ignoreResult.added.length > 0) {
|
|
1190
1366
|
console.log(` ${c.dim}gitignored ${ignoreResult.added.length} path(s) not tracked upstream (remove from .gitignore to commit)${c.reset}`);
|
|
1191
1367
|
}
|
|
@@ -1227,7 +1403,10 @@ async function main() {
|
|
|
1227
1403
|
process.exit(1);
|
|
1228
1404
|
}
|
|
1229
1405
|
const claudeStatus = installResult.migrated ? "migrated to native" : installResult.alreadyInstalled ? "ready (native)" : "installed";
|
|
1230
|
-
spinner2.succeed(phaseHeader(2, TOTAL_PHASES, `Claude Code ${c.dim}${"
|
|
1406
|
+
spinner2.succeed(phaseHeader(2, TOTAL_PHASES, `Claude Code ${c.dim}${"·".repeat(18)}${c.reset} ${claudeStatus} ${c.dim}${formatElapsed(phase2Start)}${c.reset}`));
|
|
1407
|
+
if (profile && profile.isNew) {
|
|
1408
|
+
await loginToProfile(profile.name);
|
|
1409
|
+
}
|
|
1231
1410
|
const phase3Start = Date.now();
|
|
1232
1411
|
if (!skipMcp && isClaudeMcpReady()) {
|
|
1233
1412
|
const spinner3 = createSpinner(phaseHeader(3, TOTAL_PHASES, "Installing MCP servers (parallel)..."));
|
|
@@ -1237,7 +1416,7 @@ async function main() {
|
|
|
1237
1416
|
const mcpOk = mcpResult.installed + mcpResult.skipped;
|
|
1238
1417
|
const mcpTotal = mcpOk + mcpResult.failed;
|
|
1239
1418
|
const mcpSummary = `${mcpOk}/${mcpTotal} installed`;
|
|
1240
|
-
spinner3.succeed(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"
|
|
1419
|
+
spinner3.succeed(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"·".repeat(17)}${c.reset} ${mcpSummary} ${c.dim}${formatElapsed(phase3Start)}${c.reset}`));
|
|
1241
1420
|
const coreMcps = getCoreMcps();
|
|
1242
1421
|
for (let i = 0;i < mcpResult.results.length; i++) {
|
|
1243
1422
|
const r = mcpResult.results[i];
|
|
@@ -1248,9 +1427,9 @@ async function main() {
|
|
|
1248
1427
|
} else {
|
|
1249
1428
|
const spinner3 = createSpinner(phaseHeader(3, TOTAL_PHASES, "Installing MCP servers..."));
|
|
1250
1429
|
if (skipMcp) {
|
|
1251
|
-
spinner3.succeed(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"
|
|
1430
|
+
spinner3.succeed(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"·".repeat(17)}${c.reset} skipped (--no-mcp)`));
|
|
1252
1431
|
} else {
|
|
1253
|
-
spinner3.fail(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"
|
|
1432
|
+
spinner3.fail(phaseHeader(3, TOTAL_PHASES, `MCP servers ${c.dim}${"·".repeat(17)}${c.reset} skipped (CLI not ready)`));
|
|
1254
1433
|
}
|
|
1255
1434
|
}
|
|
1256
1435
|
const phase4Start = Date.now();
|
|
@@ -1262,7 +1441,7 @@ async function main() {
|
|
|
1262
1441
|
const pluginOk = pluginResult.installed + pluginResult.skipped;
|
|
1263
1442
|
const pluginTotal = pluginOk + pluginResult.failed;
|
|
1264
1443
|
const pluginSummary = `${pluginOk}/${pluginTotal} installed`;
|
|
1265
|
-
spinner4.succeed(phaseHeader(4, TOTAL_PHASES, `Plugins ${c.dim}${"
|
|
1444
|
+
spinner4.succeed(phaseHeader(4, TOTAL_PHASES, `Plugins ${c.dim}${"·".repeat(21)}${c.reset} ${pluginSummary} ${c.dim}${formatElapsed(phase4Start)}${c.reset}`));
|
|
1266
1445
|
const plugins = getRecommendedPlugins();
|
|
1267
1446
|
for (let i = 0;i < pluginResult.results.length; i++) {
|
|
1268
1447
|
const r = pluginResult.results[i];
|
|
@@ -1272,7 +1451,7 @@ async function main() {
|
|
|
1272
1451
|
}
|
|
1273
1452
|
} else {
|
|
1274
1453
|
const spinner4 = createSpinner(phaseHeader(4, TOTAL_PHASES, "Installing plugins..."));
|
|
1275
|
-
spinner4.succeed(phaseHeader(4, TOTAL_PHASES, `Plugins ${c.dim}${"
|
|
1454
|
+
spinner4.succeed(phaseHeader(4, TOTAL_PHASES, `Plugins ${c.dim}${"·".repeat(21)}${c.reset} ${c.dim}deferred (will auto-prompt)${c.reset}`));
|
|
1276
1455
|
}
|
|
1277
1456
|
const phase5Start = Date.now();
|
|
1278
1457
|
if (!skipSkills && isSkillInstallReady()) {
|
|
@@ -1281,7 +1460,7 @@ async function main() {
|
|
|
1281
1460
|
spinner5.update(phaseHeader(5, TOTAL_PHASES, `Installing skills... ${c.dim}${current}/${total} done${c.reset}`));
|
|
1282
1461
|
});
|
|
1283
1462
|
const skillSummary = `${skillResult.installed}/${skillResult.installed + skillResult.failed} installed`;
|
|
1284
|
-
spinner5.succeed(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"
|
|
1463
|
+
spinner5.succeed(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"·".repeat(11)}${c.reset} ${skillSummary} ${c.dim}${formatElapsed(phase5Start)}${c.reset}`));
|
|
1285
1464
|
const skills = getRecommendedSkills();
|
|
1286
1465
|
for (let i = 0;i < skillResult.results.length; i++) {
|
|
1287
1466
|
const r = skillResult.results[i];
|
|
@@ -1292,15 +1471,29 @@ async function main() {
|
|
|
1292
1471
|
} else {
|
|
1293
1472
|
const spinner5 = createSpinner(phaseHeader(5, TOTAL_PHASES, "Installing community skills..."));
|
|
1294
1473
|
if (skipSkills) {
|
|
1295
|
-
spinner5.succeed(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"
|
|
1474
|
+
spinner5.succeed(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"·".repeat(11)}${c.reset} skipped (--no-skills)`));
|
|
1296
1475
|
} else {
|
|
1297
|
-
spinner5.fail(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"
|
|
1476
|
+
spinner5.fail(phaseHeader(5, TOTAL_PHASES, `Community skills ${c.dim}${"·".repeat(11)}${c.reset} skipped (npx not found)`));
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (profile) {
|
|
1480
|
+
const injection = injectShellFunction(profile.name, profile.dir);
|
|
1481
|
+
if (injection.injected && injection.path && injection.reloadHint) {
|
|
1482
|
+
console.log("");
|
|
1483
|
+
console.log(` ${c.green}✓${c.reset} Shell function ${c.bright}claude-${profile.name}${c.reset} added to ${c.dim}${injection.path}${c.reset}`);
|
|
1484
|
+
console.log(` ${c.dim}Reload your shell with:${c.reset} ${c.bright}${injection.reloadHint}${c.reset}`);
|
|
1485
|
+
} else if (injection.skipped && injection.path) {
|
|
1486
|
+
console.log("");
|
|
1487
|
+
console.log(` ${c.dim}Shell function claude-${profile.name} already present in ${injection.path}${c.reset}`);
|
|
1488
|
+
} else if (!injection.path) {
|
|
1489
|
+
console.log("");
|
|
1490
|
+
console.log(` ${c.yellow}Could not detect shell config${c.reset}${c.dim} — set CLAUDE_CONFIG_DIR=${profile.dir} manually before running 'claude'.${c.reset}`);
|
|
1298
1491
|
}
|
|
1299
1492
|
}
|
|
1300
1493
|
const sessionExists = hasExistingSession(targetDir);
|
|
1301
1494
|
const willResume = !newSession && sessionExists;
|
|
1302
1495
|
const launchMode = newSession ? "new session" : willResume ? "resuming last session" : "new session";
|
|
1303
|
-
console.log(` ${c.green}
|
|
1496
|
+
console.log(` ${c.green}✓${c.reset} ${phaseHeader(6, TOTAL_PHASES, `Launching Claude Code ${c.dim}${"·".repeat(7)}${c.reset} ${launchMode}`)}`);
|
|
1304
1497
|
printOptionalMcps();
|
|
1305
1498
|
console.log("");
|
|
1306
1499
|
console.log(` ${c.green}Setup complete${c.reset} in ${formatElapsed(globalStart)}`);
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "start-vibing",
|
|
3
|
-
"version": "4.4.
|
|
4
|
-
"description": "Setup Claude Code with 9 plugins, 6 community skills, and 8 MCP servers. Parallel install, auto-accept, superpowers + ralph-loop. e2e-audit 0.2.0 refactor (skill-only, no agents): SessionStart hook + slash command make the skill keyword-invokable (\"e2e audit\", \"roda o e2e\", \"integration test\", \"test coverage gaps\"). Source-first discovery via detect-stack, discover-routes (Next app/pages/Remix/SvelteKit/Nuxt/Astro), discover-api-surface (HTTP handlers, tRPC procedures, GraphQL, server actions, middleware auth), inventory-existing-tests (preserve prior corpus + sha256 drift hash), and detect-uncovered (branch-diff vs origin/main finds changes not covered by existing specs). Report-then-ask between mapping and Playwright run; post-run-feedback report before writing findings. SHOT+TRACE+ASSERT+SOURCE evidence quad per non-meta finding; meta rules (coverage-gap-*, uncovered-*, test-drift, stack-detect, post-run-feedback) exempt. verify-audit.sh enforces schema + quad. Generic (no project leakage). super-design 0.7.0 carries over.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"start-vibing": "./dist/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist",
|
|
11
|
-
"template"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "bun build ./src/cli.ts --outdir ./dist --target node",
|
|
15
|
-
"dev": "bun run ./src/cli.ts",
|
|
16
|
-
"prepublishOnly": "bun run build"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"claude",
|
|
20
|
-
"claude-code",
|
|
21
|
-
"ai",
|
|
22
|
-
"agents",
|
|
23
|
-
"skills",
|
|
24
|
-
"hooks",
|
|
25
|
-
"automation",
|
|
26
|
-
"workflow",
|
|
27
|
-
"cli"
|
|
28
|
-
],
|
|
29
|
-
"author": "joaov",
|
|
30
|
-
"license": "MIT",
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "https://github.com/LimaTechnologies/ai-development"
|
|
34
|
-
},
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=18.0.0"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/node": "^20.0.0",
|
|
40
|
-
"typescript": "^5.0.0"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "start-vibing",
|
|
3
|
+
"version": "4.4.15",
|
|
4
|
+
"description": "Setup Claude Code with 9 plugins, 6 community skills, and 8 MCP servers. Parallel install, auto-accept, superpowers + ralph-loop. e2e-audit 0.2.0 refactor (skill-only, no agents): SessionStart hook + slash command make the skill keyword-invokable (\"e2e audit\", \"roda o e2e\", \"integration test\", \"test coverage gaps\"). Source-first discovery via detect-stack, discover-routes (Next app/pages/Remix/SvelteKit/Nuxt/Astro), discover-api-surface (HTTP handlers, tRPC procedures, GraphQL, server actions, middleware auth), inventory-existing-tests (preserve prior corpus + sha256 drift hash), and detect-uncovered (branch-diff vs origin/main finds changes not covered by existing specs). Report-then-ask between mapping and Playwright run; post-run-feedback report before writing findings. SHOT+TRACE+ASSERT+SOURCE evidence quad per non-meta finding; meta rules (coverage-gap-*, uncovered-*, test-drift, stack-detect, post-run-feedback) exempt. verify-audit.sh enforces schema + quad. Generic (no project leakage). super-design 0.7.0 carries over.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"start-vibing": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "bun build ./src/cli.ts --outdir ./dist --target node",
|
|
15
|
+
"dev": "bun run ./src/cli.ts",
|
|
16
|
+
"prepublishOnly": "bun run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"ai",
|
|
22
|
+
"agents",
|
|
23
|
+
"skills",
|
|
24
|
+
"hooks",
|
|
25
|
+
"automation",
|
|
26
|
+
"workflow",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"author": "joaov",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/LimaTechnologies/ai-development"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web3-lp
|
|
3
|
+
description: Cyber-tactical landing-page system for Web3 token launches with real product behind them — sticky live-price bar, hex-grid scanline hero, animated track-record proof, floating buy pill, "WHAT IS $TICKER" reveal. Built for fair launches (pump.fun / fair-launch DEX) where the coin is fun but the tech is real. Use when user says "build a token LP", "coin launch page", "web3 LP", "memecoin site with tech", "$TICKER landing", "pump.fun launch site".
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: deadodds-build
|
|
7
|
+
origin: 'Synthesized from sd-research audits of HyperLiquid, Pump.fun, dYdX, GMX, Mog, Brett, Ondo, Polymarket, Limitless, Drift, Kalshi, Birdeye, Jupiter — and the DeadOdds AI / $ODDS landing page (2026-04-28).'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Web3-Launch Design System Skill (Universal)
|
|
11
|
+
|
|
12
|
+
## Mission
|
|
13
|
+
|
|
14
|
+
You are an expert design-system author for **Web3 token-launch landing pages** — coin LPs that have real tech (not pure memes) and want both degen credibility AND product credibility.
|
|
15
|
+
|
|
16
|
+
The single hardest thing about a token-with-tech LP is balancing two audiences: degens scanning for ticker + chart + buy-button in <3 seconds, and serious users who need to verify the product works. This skill exists to resolve that tension.
|
|
17
|
+
|
|
18
|
+
When unsure: prioritize **proof** over **polish**. Numbers > adjectives. Live data > marketing copy. Receipts > promises.
|
|
19
|
+
|
|
20
|
+
## Brand North-Star
|
|
21
|
+
|
|
22
|
+
> **The token is fun. The product is real.**
|
|
23
|
+
|
|
24
|
+
Every section either hypes the coin OR proves the tech. Never both at once. Every claim has a number behind it. Every button has a destination. Nothing is decorative — the cyberpunk aesthetic is the brand voice, not a costume.
|
|
25
|
+
|
|
26
|
+
## Style Foundations
|
|
27
|
+
|
|
28
|
+
### Visual style
|
|
29
|
+
|
|
30
|
+
- Dark base (`#050505` to `#0A0A0A`), single neon accent, monospace data, geometric display headlines
|
|
31
|
+
- Hex-grid or terminal-scanline hero background (open territory; competitors use blob gradients or photography)
|
|
32
|
+
- Glow-shadow CTAs (subtle 28px spread, 4px offset) — never flat
|
|
33
|
+
- Sharp corners (4–8px radius); never rounded-full except for floating pills
|
|
34
|
+
|
|
35
|
+
### Typography scale
|
|
36
|
+
|
|
37
|
+
- **Display:** Rajdhani / Orbitron / Geist condensed bold, ALL CAPS, tight tracking
|
|
38
|
+
- **Body:** Inter / Geist Sans, sentence case
|
|
39
|
+
- **Numbers / data:** Geist Mono / JetBrains Mono / IBM Plex Mono, tabular-nums
|
|
40
|
+
- **Scale:** 10/11/12/13.5/14.5/16/22/24/32/48/clamp-hero
|
|
41
|
+
|
|
42
|
+
### Color palette (default — override if user has tokens)
|
|
43
|
+
|
|
44
|
+
- `--brand-bg: #050505` (near-black surface)
|
|
45
|
+
- `--brand-surface: #0A0A0A` (cards)
|
|
46
|
+
- `--brand-accent: #7CFF3D` (neon — primary CTA + win signal)
|
|
47
|
+
- `--brand-danger: #FF3D5C` (lose signal / warning)
|
|
48
|
+
- `--brand-chrome: #C7CDD4` (secondary text)
|
|
49
|
+
- `--brand-text: #FFFFFF` (primary text)
|
|
50
|
+
- `--brand-text-muted: #64748B` (slate-500 — labels, captions)
|
|
51
|
+
- `--brand-line: #1A1A1A` (1px borders)
|
|
52
|
+
|
|
53
|
+
If the user has a token color (memecoin tradition: $MOG = gold, $BRETT = cyan, $PEPE = lime), keep `#7CFF3D` semantics for **GREEN/win** and use the user's color as a **secondary brand accent** for hero glow + token block emphasis. Never use 3+ accents.
|
|
54
|
+
|
|
55
|
+
### Spacing scale
|
|
56
|
+
|
|
57
|
+
- `4 / 8 / 12 / 16 / 20 / 24 / 32 / 48 / 64 / 80 / 112 / 144`
|
|
58
|
+
- Section vertical rhythm: `py-20 lg:py-28` minimum between major blocks
|
|
59
|
+
- Cards: `p-5 lg:p-7`
|
|
60
|
+
- Compact UI (price bar, tickers): `py-2.5 px-4`
|
|
61
|
+
|
|
62
|
+
### Border radius
|
|
63
|
+
|
|
64
|
+
- Buttons / inputs / cards / pills: `rounded` (4px) — sharp, technical
|
|
65
|
+
- Floating buy pill: `rounded-full` — only exception
|
|
66
|
+
- Token block accent boxes: `rounded` (4–6px)
|
|
67
|
+
- NEVER `rounded-2xl` / `rounded-3xl` — that's SaaS, not Web3-tactical
|
|
68
|
+
|
|
69
|
+
## Section Playbook (the LP recipe)
|
|
70
|
+
|
|
71
|
+
This is the canonical section order for a Web3-launch LP. Skip sections you don't have data for, but DO NOT reorder. The flow has been triangulated across 12 audited competitors.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
01. PriceBar — sticky top, h-9, live ticker + MCap + Vol + buy links
|
|
75
|
+
02. MarketingHeader — sticky top-9, wordmark + nav + social count badges + Launch App
|
|
76
|
+
03. LiveTicker — marquee of recent product events (signals / trades / mints)
|
|
77
|
+
04. Hero — hex-grid bg, headline, dual CTAs, contract row, in-hero proof row
|
|
78
|
+
05. WhatIs — full-screen reveal: "WHAT IS $TICKER?" with poetic 2-liner
|
|
79
|
+
06. TrackRecord — animated counters + sample resolved/historical data table
|
|
80
|
+
07. HowItWorks — 3 steps, numbered cards (01 / 02 / 03), big oversized digit watermark
|
|
81
|
+
08. TechPreview — "Not a meme. A weapon." — mock product UI screenshot or live embed
|
|
82
|
+
09. TokenBlock — contract card + tokenomics 2x2 grid + buy CTAs
|
|
83
|
+
10. Community — 3-card grid: Telegram / X / pump.fun (or chain-native exchange)
|
|
84
|
+
11. FAQ — <details> accordion, 5–7 questions, first item open
|
|
85
|
+
12. FinalCta — "stop guessing, start sniping" pattern
|
|
86
|
+
13. MarketingFooter — 4 columns (brand / Product / Token / Community)
|
|
87
|
+
14. FloatingBuy — fixed bottom-right pill, appears after hero scroll
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Section anatomy rules
|
|
91
|
+
|
|
92
|
+
**01. PriceBar** (mandatory if token is launched; mock with `TBA` placeholders pre-launch)
|
|
93
|
+
|
|
94
|
+
- Height `h-9` (36px), `sticky top-0 z-[60]`, `bg-black/95 backdrop-blur-md`
|
|
95
|
+
- Left side: `· $TICKER $price ±change% | MCap $X | Vol 24h $Y | Holders N`
|
|
96
|
+
- Right side: `pump.fun ↗ | Dexscreener ↗`
|
|
97
|
+
- Pulsing dot before ticker for "live" signal (`animate-pulse` + accent glow)
|
|
98
|
+
- Hide non-essential cells progressively at sm/md/lg breakpoints
|
|
99
|
+
- ALWAYS connectable to the launchpad's public API once token is live
|
|
100
|
+
|
|
101
|
+
**02. MarketingHeader**
|
|
102
|
+
|
|
103
|
+
- `sticky top-9 z-50 h-14 lg:h-16`
|
|
104
|
+
- Wordmark left, primary nav center, social-count badges + Launch App right
|
|
105
|
+
- Social badges show actual follower counts (e.g. `4,221`) on desktop
|
|
106
|
+
- Primary CTA = "Launch App" → links to the actual product, NOT to buy page
|
|
107
|
+
- Buy CTA lives in PriceBar + FloatingBuy, never in the header
|
|
108
|
+
|
|
109
|
+
**03. LiveTicker**
|
|
110
|
+
|
|
111
|
+
- 60s loop marquee, full-width, `border-y border-line bg-surface`
|
|
112
|
+
- Items show ACTUAL product events, not slogans (e.g. "AI: GREEN +14.2pp on [Trump 2028]" not "BUY $ODDS NOW")
|
|
113
|
+
- Pause on hover (already in `globals.css`)
|
|
114
|
+
|
|
115
|
+
**04. Hero** (the most important section)
|
|
116
|
+
|
|
117
|
+
- Hex-grid + scanline background — see Motion Catalog
|
|
118
|
+
- Eyebrow: "Fair-launching $TICKER on pump.fun" with pulsing dot
|
|
119
|
+
- Headline: 2 lines, second line is the brand color with `textShadow: 0 0 28px <accent>45`
|
|
120
|
+
- Add terminal-cursor blink (`_`) at end of color line — code-aesthetic signal
|
|
121
|
+
- Sub-copy: 2 sentences max, bold the receipts ("EV-edge bets", "real time", "with receipts")
|
|
122
|
+
- Dual CTA: Primary = "Buy $TICKER on pump.fun" (filled accent) · Secondary = "Launch Oracle/App" (ghost, `bg-black/50 backdrop-blur`)
|
|
123
|
+
- ContractRow card directly below CTAs — NEVER bury contract address in token section
|
|
124
|
+
- In-hero proof row (3 stats max) below contract — animated counters that fire on first paint
|
|
125
|
+
- Stats accent: middle stat is the headline metric (win rate / TVL / users) in brand color
|
|
126
|
+
|
|
127
|
+
**05. WhatIs** (the moment of poetry)
|
|
128
|
+
|
|
129
|
+
- `min-h-[80vh]`, oversized type at `clamp(3rem, 11vw, 9rem)`
|
|
130
|
+
- Eyebrow as terminal command: `> query: what_is_$ticker`
|
|
131
|
+
- Headline structure: "WHAT IS" newline "$TICKER?" — second line in accent
|
|
132
|
+
- 2–3 short statements (≤8 words each), the third in muted color as denouement
|
|
133
|
+
- Two CTAs: "Buy $TICKER" + "See the receipts" (anchor to TrackRecord)
|
|
134
|
+
|
|
135
|
+
**06. TrackRecord** (the onliness section — what no competitor has)
|
|
136
|
+
|
|
137
|
+
- 4-stat grid (counters animate on scroll into view)
|
|
138
|
+
- Resolved-events / sample-data table: desktop = `<table>`, mobile = card list (NEVER a horizontal scroll)
|
|
139
|
+
- Header row: monospace eyebrow `▸ resolved_predictions · sample` + Brier score / best result on the right
|
|
140
|
+
- Each row: market name + AI/your call + market consensus + resolution badge + edge captured
|
|
141
|
+
- Footer link: "See live signal feed in the [product] ›"
|
|
142
|
+
|
|
143
|
+
**07. HowItWorks**
|
|
144
|
+
|
|
145
|
+
- 3 numbered cards in a `lg:grid-cols-3`
|
|
146
|
+
- Big watermark digit (`01 / 02 / 03`) at top-right, opacity `0.025` → `0.10` on hover
|
|
147
|
+
- Card eyebrow: "Step 01" in accent monospace
|
|
148
|
+
- Card headline: ALL CAPS display, ≤3 words
|
|
149
|
+
- Card body: 2 sentences max, plain language
|
|
150
|
+
|
|
151
|
+
**08. TechPreview**
|
|
152
|
+
|
|
153
|
+
- Two-column grid `lg:grid-cols-2 lg:items-center`
|
|
154
|
+
- Left = copy + bullet list + CTA · Right = mock product UI in faux window chrome
|
|
155
|
+
- Mock window chrome: 3 dots + tab label `<wordmark> · live signals`
|
|
156
|
+
- 3 mock cards inside, one each: positive / negative / neutral signal — colored accent border
|
|
157
|
+
- Headline pattern: "Not a meme. A weapon." (or domain equivalent — "Not a fork. A protocol.")
|
|
158
|
+
|
|
159
|
+
**09. TokenBlock**
|
|
160
|
+
|
|
161
|
+
- 2-column `lg:grid-cols-[1.1fr_1fr]`: hero card on left, tokenomics 2×2 on right
|
|
162
|
+
- Hero card has accent radial blur top-right + ticker huge + chain badge + contract code-block + dual buy CTA
|
|
163
|
+
- Tokenomics tiles: Supply / Launch / Liquidity / Tax — each with label + big number + 1-line caption
|
|
164
|
+
- Default copy for fair launch: "Fair · 100% on pump.fun" / "Burned · LP locked at bond" / "0 / 0 · forever"
|
|
165
|
+
|
|
166
|
+
**10. Community**
|
|
167
|
+
|
|
168
|
+
- 3-card grid, each card = social platform with: brand-color icon, name, handle, blurb
|
|
169
|
+
- Cards have subtle hover glow + arrow shifts right
|
|
170
|
+
- Order: Telegram (community) · X (alpha) · pump.fun/Dexscreener (token)
|
|
171
|
+
|
|
172
|
+
**11. FAQ**
|
|
173
|
+
|
|
174
|
+
- Native `<details>` accordion (zero JS) inside `<dl>` semantics
|
|
175
|
+
- First item `open` by default
|
|
176
|
+
- "+" → "×" rotation on open via `group-open:rotate-45`
|
|
177
|
+
- 5–7 questions; first MUST be "Is $TICKER required to use the app?" (or domain analog)
|
|
178
|
+
|
|
179
|
+
**12. FinalCta**
|
|
180
|
+
|
|
181
|
+
- Centered, `py-24 lg:py-32`, radial-gradient floor accent
|
|
182
|
+
- Headline 2 lines, second line in brand color
|
|
183
|
+
- Same dual CTAs as hero
|
|
184
|
+
|
|
185
|
+
**13. Footer**
|
|
186
|
+
|
|
187
|
+
- 4 columns: brand+blurb / Product / Token / Community
|
|
188
|
+
- Disclaimer line: "Not financial advice. Markets are risky. Bet responsibly."
|
|
189
|
+
- Right-aligned tagline: "Built on [chain] · Powered by [tech]"
|
|
190
|
+
|
|
191
|
+
**14. FloatingBuy** (the conversion safety net)
|
|
192
|
+
|
|
193
|
+
- Fixed `bottom-5 right-4 lg:bottom-7 lg:right-7`
|
|
194
|
+
- Appears after `scrollY > 600`, hides when `nearFooter` (avoid double-CTA collision)
|
|
195
|
+
- `rounded-full` (only exception to sharp-radius rule), accent-filled
|
|
196
|
+
- Scale-up + glow-up on hover (`hover:scale-[1.03]`)
|
|
197
|
+
|
|
198
|
+
## Motion Catalog
|
|
199
|
+
|
|
200
|
+
Six approved motion patterns. **Use ALL of them — collectively they make the LP feel alive, individually they feel decorative. Skip any and the page reads dead.**
|
|
201
|
+
|
|
202
|
+
1. **Hex-grid scanline** — hero background. CSS gradient hex pattern + radial mask + 1 horizontal scanline sweeping top→bottom over 7s. Open territory; no audited competitor uses it.
|
|
203
|
+
2. **Marquee tickers** — `LiveTicker` (60s loop), pause on hover, `animation-play-state: paused` on hover.
|
|
204
|
+
3. **Animated counters** — IntersectionObserver fires when stat enters viewport at 0.4 threshold; cubic-eased count-up over 1.4s; honors `prefers-reduced-motion`.
|
|
205
|
+
4. **Terminal cursor blink** — single `_` char at end of hero accent line; 1.05s step-end. Reads as "command line", not decorative.
|
|
206
|
+
5. **Floating-pill entrance** — translate-y(20px) + scale(0.9) → identity, 240ms ease-out. Plays each time the pill mounts.
|
|
207
|
+
6. **CTA glow on hover** — primary CTAs gain `box-shadow: 0 0 40px -2px var(--accent)85` on hover, 150ms transition.
|
|
208
|
+
|
|
209
|
+
**Do NOT add:** parallax (every Web3 LP has it; you'll look generic), full-page scroll-snapping, fade-in-on-scroll for every element (uncanny / slow), confetti, character mascots dancing.
|
|
210
|
+
|
|
211
|
+
**MUST honor `prefers-reduced-motion: reduce`** — `animation: none` for marquee, scanline, counters, cursor blink. The page must still be navigable and beautiful without motion.
|
|
212
|
+
|
|
213
|
+
## Copy / Voice
|
|
214
|
+
|
|
215
|
+
### Tone (Aaker × NN/g)
|
|
216
|
+
|
|
217
|
+
- **Sincere:** -1 (we're not earnest, we're sharp)
|
|
218
|
+
- **Excitement:** +2 (degen-adjacent, but with restraint)
|
|
219
|
+
- **Competence:** +2 (technical, precise, monospace numbers)
|
|
220
|
+
- **Sophistication:** +1 (cyberpunk minimalism, not luxury)
|
|
221
|
+
- **Ruggedness:** +1 (sniper / weapon / edge metaphors are OK)
|
|
222
|
+
|
|
223
|
+
### Vocabulary
|
|
224
|
+
|
|
225
|
+
- **PREFER:** alpha · edge · receipts · ship · sniper · oracle · signal · degen · fair launch · proof · live · win rate · EV (expected value) · Kelly · in the wild
|
|
226
|
+
- **AVOID:** revolutionary · disrupting · ecosystem · empower · journey · mission · synergy · best-in-class · world's first (unless you can prove it)
|
|
227
|
+
- **NEVER:** "leverage", "robust", "seamless", "unlock the future"
|
|
228
|
+
|
|
229
|
+
### Headline patterns
|
|
230
|
+
|
|
231
|
+
- 2 lines, second is brand-accent
|
|
232
|
+
- Imperative or noun phrase (avoid passive)
|
|
233
|
+
- Examples that work: "AI sniper for / prediction markets" · "Stop guessing. / Start sniping." · "Not a meme. / A weapon." · "Receipts on chain. / Edge in the wild."
|
|
234
|
+
- Examples that don't: "Welcome to the future of X" · "The next generation of Y"
|
|
235
|
+
|
|
236
|
+
### CTA copy
|
|
237
|
+
|
|
238
|
+
- **Primary buy:** "Buy $TICKER" or "Buy $TICKER on pump.fun"
|
|
239
|
+
- **Primary product:** "Launch App" / "Launch Oracle" — never "Get started"
|
|
240
|
+
- **Secondary:** "See the receipts" / "Read docs" / "See live signal feed"
|
|
241
|
+
- Always pair with a `›` arrow glyph (not `→` not lucide arrow)
|
|
242
|
+
|
|
243
|
+
### Semantic voice
|
|
244
|
+
|
|
245
|
+
- All numbers tabular-nums always
|
|
246
|
+
- Wins/positive: brand-accent color
|
|
247
|
+
- Losses/negative: `--brand-danger`
|
|
248
|
+
- Neutral/pending: amber / slate-300
|
|
249
|
+
- Status pills: `border-color/40 bg-color/10 text-color`
|
|
250
|
+
|
|
251
|
+
## Reference LPs Audited
|
|
252
|
+
|
|
253
|
+
This skill was synthesized from primary research on these sites (full evidence in any sd-research session that targets `web3-launch`):
|
|
254
|
+
|
|
255
|
+
| Tier | Sites | What we stole |
|
|
256
|
+
| ------------------------ | --------------------------------- | ---------------------------------------------------------------------------------- |
|
|
257
|
+
| Coin-with-tech (premium) | HyperLiquid, GMX, Ondo | In-hero proof row · animated stat counters · 3-stat hero |
|
|
258
|
+
| Pure meme + culture | Mog, Brett | Sticky price bar · floating buy pill · "WTF IS X?" reveal · contract at hero level |
|
|
259
|
+
| Prediction markets | Polymarket, Kalshi, Limitless | Live activity ticker · category code (dark + green/red) · marquee above nav |
|
|
260
|
+
| Solana DeFi | Jupiter, Drift, Birdeye, Pump.fun | Card-grid layouts · monospace data · neon glow shadows |
|
|
261
|
+
|
|
262
|
+
**Open territory we own:** serious + expressive + terminal/radar aesthetic. No audited competitor uses hex-grid + scanline as hero background. No audited competitor surfaces verifiable on-chain win-rate stats in the LP.
|
|
263
|
+
|
|
264
|
+
## Default Onliness Pattern
|
|
265
|
+
|
|
266
|
+
Use this template for the LP's positioning statement:
|
|
267
|
+
|
|
268
|
+
> "$TICKER is the only [category] token with [verifiable proof] — [what we have], [where the proof lives], [the cultural energy]."
|
|
269
|
+
|
|
270
|
+
Example: "$ODDS is the only prediction-market token with a verifiable on-chain win rate — AI signals, proof on-chain, degen energy."
|
|
271
|
+
|
|
272
|
+
## Component Families (LP-scoped)
|
|
273
|
+
|
|
274
|
+
- price ticker bar
|
|
275
|
+
- marquee live feed
|
|
276
|
+
- hero with motion background
|
|
277
|
+
- contract address row (with copy + explorer + buy)
|
|
278
|
+
- animated stat counter
|
|
279
|
+
- track-record table (desktop) + card list (mobile)
|
|
280
|
+
- numbered step cards
|
|
281
|
+
- mock product preview card
|
|
282
|
+
- token contract card
|
|
283
|
+
- tokenomics tile grid
|
|
284
|
+
- social platform card
|
|
285
|
+
- accordion FAQ
|
|
286
|
+
- floating buy pill
|
|
287
|
+
- 4-column footer
|
|
288
|
+
- terminal-style status header (`▸ command_name`)
|
|
289
|
+
|
|
290
|
+
## Accessibility (non-negotiable)
|
|
291
|
+
|
|
292
|
+
- WCAG 2.2 AA contrast on all text (verify accent on dark = 7.0+ for body sizes)
|
|
293
|
+
- Visible focus rings: `focus-visible:outline-2 outline-offset-2 outline-[var(--brand-accent)]`
|
|
294
|
+
- Touch targets `>= 44px` mobile (price bar links exempt; buy CTAs always)
|
|
295
|
+
- `<details>` accordion is keyboard + screen-reader native — DON'T rebuild it as JS
|
|
296
|
+
- All animations `prefers-reduced-motion: reduce` aware
|
|
297
|
+
- Marquee tickers: `role="marquee"` + `aria-label` + pause-on-hover required
|
|
298
|
+
- Contract row: `aria-label="Copy contract address"` on copy button; show `Copied` confirmation
|
|
299
|
+
- Color is never the only signal — always pair with text label or icon (e.g. signal pill says "GREEN" not just green color)
|
|
300
|
+
|
|
301
|
+
## Writing Tone (for the skill consumer)
|
|
302
|
+
|
|
303
|
+
Confident, sharp, low-jargon. Short sentences. Active voice. Numbers > adjectives. Receipts > promises. Examples > descriptions.
|
|
304
|
+
|
|
305
|
+
## Rules: Do
|
|
306
|
+
|
|
307
|
+
- Lead with the number, not the claim ("73% win rate" before "AI is good")
|
|
308
|
+
- One section = one job (hype OR proof, never both)
|
|
309
|
+
- Use brand-accent only for: primary CTAs, win signals, pulsing live dots, key stats — anywhere else is brand dilution
|
|
310
|
+
- Animate sparingly; every motion must serve recognition or recall
|
|
311
|
+
- Surface the contract address in the hero — never hide it in a token section
|
|
312
|
+
- Write CTAs as commands, not descriptions ("Buy $ODDS" not "Get $ODDS now")
|
|
313
|
+
- Pair every screenshot with a caption that says what it is
|
|
314
|
+
- Keep PriceBar + FloatingBuy as escape hatches so the user is never >300vh from a buy action
|
|
315
|
+
- Pre-launch: use `TBA` placeholders inside the FINAL UI shell — don't omit the section. Users will see the structure they'll buy into
|
|
316
|
+
|
|
317
|
+
## Rules: Don't
|
|
318
|
+
|
|
319
|
+
- Don't use rounded-2xl/3xl on cards — that's SaaS, this is tactical
|
|
320
|
+
- Don't write "revolutionary", "disrupt", "next-gen", "future of X"
|
|
321
|
+
- Don't add parallax to the hero — it's the most overused Web3 LP move
|
|
322
|
+
- Don't put 3+ CTAs in the hero — primary buy + secondary product, that's it
|
|
323
|
+
- Don't decorate stats with glyphs/icons — the number IS the decoration
|
|
324
|
+
- Don't make the FAQ a JS-controlled accordion — `<details>` is better
|
|
325
|
+
- Don't ship without `prefers-reduced-motion` overrides on every animation
|
|
326
|
+
- Don't translate the LP — degen audience reads English universally; translation dilutes voice
|
|
327
|
+
- Don't add a "subscribe to newsletter" CTA — degen audiences reject email gates
|
|
328
|
+
- Don't put more than 5 footer columns — 4 max (brand / Product / Token / Community)
|
|
329
|
+
|
|
330
|
+
## Expected Behavior
|
|
331
|
+
|
|
332
|
+
- Always restate the user's positioning in one onliness sentence before generating sections
|
|
333
|
+
- Use the section playbook order; only skip sections, never reorder
|
|
334
|
+
- Pre-launch: still ship all sections, fill with mock data shaped like the real data ("2,847 markets analyzed", "$1.42M MCap" — degen-realistic, not "Lorem ipsum")
|
|
335
|
+
- For monorepos / multiple LPs (e.g. one per token), keep this skill as the shared brand floor and override `--brand-accent` per token
|
|
336
|
+
- When a section has no data analog (e.g. a pure meme has no `TrackRecord`), substitute with the closest meme-tier proof (community stats, bridge/exchange listings, viral tweet count) — never delete the section silently
|
|
337
|
+
|
|
338
|
+
## Guideline Authoring Workflow
|
|
339
|
+
|
|
340
|
+
1. Restate the user's brand in one onliness sentence (use the template above).
|
|
341
|
+
2. Define palette + type tokens; only override defaults if the user has source-of-truth tokens.
|
|
342
|
+
3. Walk the Section Playbook in order; for each section, name the data the section will display and the CTA destination.
|
|
343
|
+
4. List all 6 motion patterns and confirm `prefers-reduced-motion` overrides exist.
|
|
344
|
+
5. Write the FAQ first (it forces clarity on what the project actually is) — then the rest of the copy.
|
|
345
|
+
6. Run a "no decoration" pass: delete every UI element that isn't a CTA, a proof, or a navigation aid.
|
|
346
|
+
|
|
347
|
+
## Required Output Structure
|
|
348
|
+
|
|
349
|
+
When generating a Web3-launch LP, deliver:
|
|
350
|
+
|
|
351
|
+
- One-line onliness statement
|
|
352
|
+
- Token + brand tokens (palette, type, radius, spacing)
|
|
353
|
+
- Per-section markup with: data shape, copy, CTA targets, motion notes
|
|
354
|
+
- Mobile + desktop layout decisions per section
|
|
355
|
+
- Accessibility checklist (focus rings, reduced-motion, touch targets, ARIA labels)
|
|
356
|
+
- Anti-patterns explicitly avoided
|
|
357
|
+
- QA checklist (see below)
|
|
358
|
+
|
|
359
|
+
## QA Checklist
|
|
360
|
+
|
|
361
|
+
- [ ] PriceBar shows live ticker + at least 2 of {MCap, Vol, Holders}; uses `TBA` if pre-launch
|
|
362
|
+
- [ ] Hero contains: eyebrow + 2-line headline + sub-copy + 2 CTAs + contract row + 3-stat proof row — in that order
|
|
363
|
+
- [ ] In-hero counters animate on first paint (or scroll into view), not stuck at 0
|
|
364
|
+
- [ ] Contract row has: address (full or 4-char-truncated) + Copy button + Explorer link + Buy link
|
|
365
|
+
- [ ] WhatIs section has min-h-80vh and oversized clamp(3rem, 11vw, 9rem) headline
|
|
366
|
+
- [ ] TrackRecord stats animate on scroll-into-view via IntersectionObserver
|
|
367
|
+
- [ ] TrackRecord has both desktop table AND mobile card list (NEVER horizontal scroll)
|
|
368
|
+
- [ ] FloatingBuy appears after 600px scroll and disappears within 280px of footer
|
|
369
|
+
- [ ] PriceBar (`top-0`) and Header (`top-9`) stack correctly with no overlap at any scroll
|
|
370
|
+
- [ ] All marquees pause on hover via `[role="marquee"]:hover > div` selector
|
|
371
|
+
- [ ] All animations have `@media (prefers-reduced-motion: reduce)` override
|
|
372
|
+
- [ ] No `rounded-2xl` / `rounded-3xl` anywhere except FloatingBuy (intentional pill)
|
|
373
|
+
- [ ] FAQ uses native `<details>` not JS accordion
|
|
374
|
+
- [ ] Footer has 4 columns max, includes "Not financial advice" disclaimer
|
|
375
|
+
- [ ] All numbers use `tabular-nums` and `font-mono` (Geist Mono / JetBrains Mono)
|
|
376
|
+
- [ ] Brand accent used ONLY on: primary CTAs, win signals, live dots, eyebrow callouts, hero accent line
|
|
377
|
+
- [ ] Touch targets >= 44px on mobile
|
|
378
|
+
- [ ] No use of "revolutionary", "disrupt", "next-gen", "ecosystem", "empower"
|
|
379
|
+
- [ ] If LP has multiple language audiences: ship English only — degen audience reads it universally
|
|
380
|
+
|
|
381
|
+
## Component Rule Expectations
|
|
382
|
+
|
|
383
|
+
For every component, define:
|
|
384
|
+
|
|
385
|
+
- **Default state:** layout, tokens, copy
|
|
386
|
+
- **Hover state:** glow-up if interactive, color shift if link
|
|
387
|
+
- **Focus-visible:** outline ring with brand-accent
|
|
388
|
+
- **Active state:** brief scale-down or color flash
|
|
389
|
+
- **Loading / pre-launch:** show skeleton OR `TBA` placeholder inside final UI shell
|
|
390
|
+
- **Error / empty:** monospace status header (`▸ no_data_yet`) + helpful next-action link
|
|
391
|
+
- **Mobile collapse:** which cells/columns hide; which become card-stacks
|
|
392
|
+
|
|
393
|
+
## Quality Gates
|
|
394
|
+
|
|
395
|
+
- Every section must have a quantifiable success metric (CTA click rate / scroll depth / time-on-page) — if you can't name it, the section probably doesn't belong
|
|
396
|
+
- Accent color usage audit: count occurrences. If `--brand-accent` appears more than 12 times in the rendered LP, you're over-using it
|
|
397
|
+
- "No-JS pass": disable JS — the LP must still render readable, navigable, and convey the pitch. Only counters, marquees, and the floating-buy pill should degrade
|
|
398
|
+
- Lighthouse: Performance ≥ 90, Accessibility ≥ 95, Best-Practices ≥ 95, SEO ≥ 95 on the LP
|
|
399
|
+
|
|
400
|
+
## Migration Notes (when retrofitting an existing LP)
|
|
401
|
+
|
|
402
|
+
1. Don't try to redesign in-place — fork the route into a route group like `(marketing)/` with its own layout
|
|
403
|
+
2. Pull the dashboard / app out of `/` first; LP takes the root URL, app moves to `/dashboard` or `/app`
|
|
404
|
+
3. Add PriceBar + FloatingBuy LAST, after sections render — they're the conversion safety net, not the structure
|
|
405
|
+
4. The hex-grid scanline goes on the Hero only — don't bleed it into other sections (loses meaning)
|
|
406
|
+
5. Replace any existing "fade up on scroll" decorative animations with the 6 approved motion patterns — quality > quantity
|
|
407
|
+
<!-- TYPEUI_SH_MANAGED_END -->
|