vue-kaspa-cli 0.1.0
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/bin/index.mjs +79 -0
- package/package.json +15 -0
- package/templates/nuxt/_gitignore +5 -0
- package/templates/nuxt/_package.json +15 -0
- package/templates/nuxt/components/KaspaStatus.vue +69 -0
- package/templates/nuxt/nuxt.config.ts +19 -0
- package/templates/nuxt/tsconfig.json +3 -0
- package/templates/vue/_gitignore +5 -0
- package/templates/vue/_package.json +24 -0
- package/templates/vue/index.html +12 -0
- package/templates/vue/src/App.vue +21 -0
- package/templates/vue/src/components/KaspaStatus.vue +123 -0
- package/templates/vue/src/main.ts +8 -0
- package/templates/vue/src/style.css +41 -0
- package/templates/vue/tsconfig.app.json +17 -0
- package/templates/vue/tsconfig.json +4 -0
- package/templates/vue/vite.config.ts +22 -0
package/bin/index.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline/promises'
|
|
3
|
+
import { stdin as input, stdout as output } from 'node:process'
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { join, dirname } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates')
|
|
10
|
+
|
|
11
|
+
// npm strips .gitignore and treats package.json as the package manifest,
|
|
12
|
+
// so templates use prefixed names that get renamed on copy.
|
|
13
|
+
const RENAMES = {
|
|
14
|
+
'_gitignore': '.gitignore',
|
|
15
|
+
'_package.json': 'package.json',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rl = createInterface({ input, output })
|
|
19
|
+
|
|
20
|
+
async function ask(question, fallback) {
|
|
21
|
+
const answer = (await rl.question(question)).trim()
|
|
22
|
+
return answer || fallback
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function choose(question, choices) {
|
|
26
|
+
output.write(`${question}\n${choices.map((c, i) => ` ${i + 1}) ${c}`).join('\n')}\n`)
|
|
27
|
+
while (true) {
|
|
28
|
+
const n = parseInt((await rl.question(' Enter number: ')).trim(), 10) - 1
|
|
29
|
+
if (n >= 0 && n < choices.length) return choices[n]
|
|
30
|
+
output.write(` Please enter 1–${choices.length}.\n`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function copyDir(src, dest) {
|
|
35
|
+
mkdirSync(dest, { recursive: true })
|
|
36
|
+
for (const entry of readdirSync(src)) {
|
|
37
|
+
const srcPath = join(src, entry)
|
|
38
|
+
const destPath = join(dest, RENAMES[entry] ?? entry)
|
|
39
|
+
statSync(srcPath).isDirectory()
|
|
40
|
+
? copyDir(srcPath, destPath)
|
|
41
|
+
: writeFileSync(destPath, readFileSync(srcPath))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
output.write('\n vue-kaspa-cli — scaffold a Kaspa-connected Vue/Nuxt app\n\n')
|
|
47
|
+
|
|
48
|
+
const name = await ask(' Project name (kaspa-app): ', 'kaspa-app')
|
|
49
|
+
const framework = await choose('\n Framework:', ['Vue', 'Nuxt'])
|
|
50
|
+
rl.close()
|
|
51
|
+
|
|
52
|
+
const targetDir = join(process.cwd(), name)
|
|
53
|
+
|
|
54
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
55
|
+
output.write(`\n "${name}" already exists and is not empty.\n\n`)
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
copyDir(join(TEMPLATES_DIR, framework.toLowerCase()), targetDir)
|
|
60
|
+
|
|
61
|
+
const pkgPath = join(targetDir, 'package.json')
|
|
62
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
63
|
+
pkg.name = name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '')
|
|
64
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
65
|
+
|
|
66
|
+
output.write(`
|
|
67
|
+
Done!
|
|
68
|
+
|
|
69
|
+
cd ${name}
|
|
70
|
+
npm install
|
|
71
|
+
npm run dev
|
|
72
|
+
|
|
73
|
+
Edit src/components/KaspaStatus.vue to start building.
|
|
74
|
+
Docs → https://furatamasensei.github.io/vue-kaspa
|
|
75
|
+
|
|
76
|
+
`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch(e => { console.error(e); process.exit(1) })
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-kaspa-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Scaffold a Vue 3 or Nuxt app wired to the Kaspa blockchain",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/furatamasensei/vue-kaspa.git",
|
|
10
|
+
"directory": "packages/vue-kaspa-cli"
|
|
11
|
+
},
|
|
12
|
+
"bin": { "vue-kaspa-cli": "bin/index.mjs" },
|
|
13
|
+
"files": ["bin", "templates"],
|
|
14
|
+
"engines": { "node": ">=18.0.0" }
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-kaspa-nuxt-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "nuxt dev",
|
|
7
|
+
"build": "nuxt build",
|
|
8
|
+
"preview": "nuxt preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@vue-kaspa/kaspa-wasm": "^1.2.0",
|
|
12
|
+
"nuxt": "^3.17.0",
|
|
13
|
+
"vue-kaspa": "^0.1.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// useKaspa, useRpc, and computed are auto-imported by Nuxt.
|
|
3
|
+
// The nuxt module's client plugin handles init + connect via autoConnect: true.
|
|
4
|
+
|
|
5
|
+
const kaspa = useKaspa()
|
|
6
|
+
const rpc = useRpc()
|
|
7
|
+
|
|
8
|
+
const stateLabel = computed(() => {
|
|
9
|
+
if (kaspa.wasmStatus.value === 'loading') return 'Loading WASM…'
|
|
10
|
+
if (kaspa.wasmStatus.value === 'error') return 'WASM Error'
|
|
11
|
+
return ({
|
|
12
|
+
disconnected: 'Disconnected',
|
|
13
|
+
connecting: 'Connecting…',
|
|
14
|
+
connected: 'Connected',
|
|
15
|
+
reconnecting: 'Reconnecting…',
|
|
16
|
+
error: 'Connection Error',
|
|
17
|
+
} as Record<string, string>)[rpc.connectionState.value] ?? rpc.connectionState.value
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const badgeColor = computed(() => {
|
|
21
|
+
if (kaspa.wasmStatus.value !== 'ready') return '#9ca3af'
|
|
22
|
+
if (rpc.connectionState.value === 'connected') return '#4caf50'
|
|
23
|
+
if (rpc.connectionState.value === 'error') return '#f44336'
|
|
24
|
+
return '#9ca3af'
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const daaScore = computed(() =>
|
|
28
|
+
rpc.virtualDaaScore.value > 0n ? rpc.virtualDaaScore.value.toLocaleString() : '—'
|
|
29
|
+
)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div style="width:100%;max-width:480px;padding:2rem;border:1px solid #e2e8f0;border-radius:10px;background:#f9f9f9;display:flex;flex-direction:column;gap:1.5rem;font-family:Inter,system-ui,sans-serif;">
|
|
34
|
+
<header style="display:flex;align-items:center;gap:.75rem;">
|
|
35
|
+
<span style="font-size:1.75rem;color:#49c5a3;line-height:1;">⬡</span>
|
|
36
|
+
<h1 style="font-size:1.25rem;font-weight:600;color:#1a1a1a;flex:1;margin:0;">vue-kaspa</h1>
|
|
37
|
+
<span :style="`font-size:.75rem;font-weight:500;padding:.2em .65em;border-radius:999px;border:1px solid ${badgeColor};color:${badgeColor};white-space:nowrap;`">
|
|
38
|
+
{{ stateLabel }}
|
|
39
|
+
</span>
|
|
40
|
+
</header>
|
|
41
|
+
|
|
42
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;">
|
|
43
|
+
<div style="display:flex;flex-direction:column;gap:.25rem;">
|
|
44
|
+
<span style="font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;">Network</span>
|
|
45
|
+
<span style="font-size:.95rem;color:#1a1a1a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{ rpc.networkId.value ?? '—' }}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div style="display:flex;flex-direction:column;gap:.25rem;">
|
|
48
|
+
<span style="font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;">Server version</span>
|
|
49
|
+
<span style="font-size:.95rem;color:#1a1a1a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{ rpc.serverVersion.value ?? '—' }}</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div style="display:flex;flex-direction:column;gap:.25rem;">
|
|
52
|
+
<span style="font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;">DAA Score</span>
|
|
53
|
+
<span style="font-size:.95rem;color:#1a1a1a;font-family:monospace;">{{ daaScore }}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div style="display:flex;flex-direction:column;gap:.25rem;">
|
|
56
|
+
<span style="font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;">Synced</span>
|
|
57
|
+
<span :style="`font-size:.95rem;color:${rpc.isConnected.value ? (rpc.isSynced.value ? '#4caf50' : '#1a1a1a') : '#9ca3af'};`">
|
|
58
|
+
{{ rpc.isConnected.value ? (rpc.isSynced.value ? 'Yes' : 'Syncing…') : '—' }}
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<footer style="display:flex;align-items:center;gap:.5rem;font-size:.85rem;border-top:1px solid #e2e8f0;padding-top:1rem;">
|
|
64
|
+
<a href="https://furatamasensei.github.io/vue-kaspa" target="_blank" rel="noopener" style="color:#374151;text-decoration:none;">Docs</a>
|
|
65
|
+
<span>·</span>
|
|
66
|
+
<a href="https://github.com/furatamasensei/vue-kaspa" target="_blank" rel="noopener" style="color:#374151;text-decoration:none;">GitHub</a>
|
|
67
|
+
</footer>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default defineNuxtConfig({
|
|
2
|
+
compatibilityDate: '2025-01-01',
|
|
3
|
+
modules: ['vue-kaspa/nuxt'],
|
|
4
|
+
kaspa: {
|
|
5
|
+
network: 'mainnet',
|
|
6
|
+
autoConnect: true,
|
|
7
|
+
},
|
|
8
|
+
// SharedArrayBuffer requires COOP/COEP headers in both dev and production.
|
|
9
|
+
nitro: {
|
|
10
|
+
routeRules: {
|
|
11
|
+
'/**': {
|
|
12
|
+
headers: {
|
|
13
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
14
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-kaspa-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"typecheck": "vue-tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@vue-kaspa/kaspa-wasm": "^1.2.0",
|
|
14
|
+
"vue": "^3.5.0",
|
|
15
|
+
"vue-kaspa": "^0.1.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@vitejs/plugin-vue": "^6.0.0",
|
|
19
|
+
"typescript": "^6.0.0",
|
|
20
|
+
"vite": "^8.0.0",
|
|
21
|
+
"vite-plugin-wasm": "^3.6.0",
|
|
22
|
+
"vue-tsc": "^2.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Kaspa App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted } from 'vue'
|
|
3
|
+
import { useKaspa, useRpc } from 'vue-kaspa'
|
|
4
|
+
import KaspaStatus from './components/KaspaStatus.vue'
|
|
5
|
+
|
|
6
|
+
const kaspa = useKaspa()
|
|
7
|
+
const rpc = useRpc()
|
|
8
|
+
|
|
9
|
+
// KaspaPlugin doesn't call init/connect automatically — we do it here.
|
|
10
|
+
// The promise is not awaited so the app renders immediately while WASM
|
|
11
|
+
// loads and the RPC connection establishes in the background.
|
|
12
|
+
onMounted(() => {
|
|
13
|
+
kaspa.init().then(() => rpc.connect()).catch(() => {})
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<main>
|
|
19
|
+
<KaspaStatus />
|
|
20
|
+
</main>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useKaspa, useRpc } from 'vue-kaspa'
|
|
4
|
+
|
|
5
|
+
const kaspa = useKaspa()
|
|
6
|
+
const rpc = useRpc()
|
|
7
|
+
|
|
8
|
+
const stateLabel = computed(() => {
|
|
9
|
+
if (kaspa.wasmStatus.value === 'loading') return 'Loading WASM…'
|
|
10
|
+
if (kaspa.wasmStatus.value === 'error') return 'WASM Error'
|
|
11
|
+
return ({
|
|
12
|
+
disconnected: 'Disconnected',
|
|
13
|
+
connecting: 'Connecting…',
|
|
14
|
+
connected: 'Connected',
|
|
15
|
+
reconnecting: 'Reconnecting…',
|
|
16
|
+
error: 'Connection Error',
|
|
17
|
+
} as Record<string, string>)[rpc.connectionState.value] ?? rpc.connectionState.value
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const stateClass = computed(() => {
|
|
21
|
+
if (kaspa.wasmStatus.value !== 'ready') return 'badge--pending'
|
|
22
|
+
if (rpc.connectionState.value === 'connected') return 'badge--ok'
|
|
23
|
+
if (rpc.connectionState.value === 'error') return 'badge--error'
|
|
24
|
+
return 'badge--pending'
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const daaScore = computed(() =>
|
|
28
|
+
rpc.virtualDaaScore.value > 0n ? rpc.virtualDaaScore.value.toLocaleString() : '—'
|
|
29
|
+
)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="card">
|
|
34
|
+
<header class="card__header">
|
|
35
|
+
<span class="logo">⬡</span>
|
|
36
|
+
<h1 class="card__title">vue-kaspa</h1>
|
|
37
|
+
<span class="badge" :class="stateClass">{{ stateLabel }}</span>
|
|
38
|
+
</header>
|
|
39
|
+
|
|
40
|
+
<div class="grid">
|
|
41
|
+
<div class="stat">
|
|
42
|
+
<span class="stat__label">Network</span>
|
|
43
|
+
<span class="stat__value">{{ rpc.networkId.value ?? '—' }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="stat">
|
|
46
|
+
<span class="stat__label">Server version</span>
|
|
47
|
+
<span class="stat__value">{{ rpc.serverVersion.value ?? '—' }}</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="stat">
|
|
50
|
+
<span class="stat__label">DAA Score</span>
|
|
51
|
+
<span class="stat__value mono">{{ daaScore }}</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="stat">
|
|
54
|
+
<span class="stat__label">Synced</span>
|
|
55
|
+
<span class="stat__value" :class="{ ok: rpc.isSynced.value, muted: !rpc.isConnected.value }">
|
|
56
|
+
{{ rpc.isConnected.value ? (rpc.isSynced.value ? 'Yes' : 'Syncing…') : '—' }}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<footer class="card__footer">
|
|
62
|
+
<a href="https://furatamasensei.github.io/vue-kaspa" target="_blank" rel="noopener">Docs</a>
|
|
63
|
+
<span>·</span>
|
|
64
|
+
<a href="https://github.com/furatamasensei/vue-kaspa" target="_blank" rel="noopener">GitHub</a>
|
|
65
|
+
</footer>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<style scoped>
|
|
70
|
+
.card {
|
|
71
|
+
width: 100%;
|
|
72
|
+
max-width: 480px;
|
|
73
|
+
padding: 2rem;
|
|
74
|
+
border: 1px solid var(--color-border);
|
|
75
|
+
border-radius: 10px;
|
|
76
|
+
background: var(--color-background-soft);
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
gap: 1.5rem;
|
|
80
|
+
}
|
|
81
|
+
.card__header { display: flex; align-items: center; gap: .75rem; }
|
|
82
|
+
.logo { font-size: 1.75rem; color: #49c5a3; line-height: 1; }
|
|
83
|
+
.card__title { font-size: 1.25rem; font-weight: 600; color: var(--color-heading); flex: 1; margin: 0; }
|
|
84
|
+
.badge {
|
|
85
|
+
font-size: .75rem;
|
|
86
|
+
font-weight: 500;
|
|
87
|
+
padding: .2em .65em;
|
|
88
|
+
border-radius: 999px;
|
|
89
|
+
border: 1px solid currentColor;
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
}
|
|
92
|
+
.badge--ok { color: #4caf50; }
|
|
93
|
+
.badge--pending { color: var(--color-muted, #888); }
|
|
94
|
+
.badge--error { color: #f44336; }
|
|
95
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
96
|
+
.stat { display: flex; flex-direction: column; gap: .25rem; }
|
|
97
|
+
.stat__label {
|
|
98
|
+
font-size: .7rem;
|
|
99
|
+
text-transform: uppercase;
|
|
100
|
+
letter-spacing: .05em;
|
|
101
|
+
color: var(--color-muted, #888);
|
|
102
|
+
}
|
|
103
|
+
.stat__value {
|
|
104
|
+
font-size: .95rem;
|
|
105
|
+
color: var(--color-heading);
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
text-overflow: ellipsis;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
.stat__value.mono { font-family: monospace; }
|
|
111
|
+
.stat__value.ok { color: #4caf50; }
|
|
112
|
+
.stat__value.muted { color: var(--color-muted, #888); }
|
|
113
|
+
.card__footer {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: .5rem;
|
|
117
|
+
font-size: .85rem;
|
|
118
|
+
border-top: 1px solid var(--color-border);
|
|
119
|
+
padding-top: 1rem;
|
|
120
|
+
}
|
|
121
|
+
.card__footer a { color: var(--color-text); text-decoration: none; }
|
|
122
|
+
.card__footer a:hover { color: var(--color-heading); }
|
|
123
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
*, *::before, *::after {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--color-background: #ffffff;
|
|
7
|
+
--color-background-soft: #f9f9f9;
|
|
8
|
+
--color-border: #e2e8f0;
|
|
9
|
+
--color-heading: #1a1a1a;
|
|
10
|
+
--color-text: #374151;
|
|
11
|
+
--color-muted: #9ca3af;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--color-background: #1a1a1a;
|
|
17
|
+
--color-background-soft: #242424;
|
|
18
|
+
--color-border: #2d2d2d;
|
|
19
|
+
--color-heading: #ffffff;
|
|
20
|
+
--color-text: #d1d5db;
|
|
21
|
+
--color-muted: #6b7280;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
background: var(--color-background);
|
|
32
|
+
color: var(--color-text);
|
|
33
|
+
font-family: Inter, system-ui, -apple-system, sans-serif;
|
|
34
|
+
font-size: 16px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#app {
|
|
38
|
+
width: 100%;
|
|
39
|
+
max-width: 600px;
|
|
40
|
+
padding: 1.5rem;
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noUnusedLocals": true,
|
|
10
|
+
"noUnusedParameters": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"jsx": "preserve",
|
|
13
|
+
"jsxImportSource": "vue"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts", "src/**/*.vue"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import vue from '@vitejs/plugin-vue'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import wasm from 'vite-plugin-wasm'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [vue(), wasm()],
|
|
7
|
+
optimizeDeps: { exclude: ['@vue-kaspa/kaspa-wasm'] },
|
|
8
|
+
server: {
|
|
9
|
+
headers: {
|
|
10
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
11
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
build: {
|
|
15
|
+
rollupOptions: {
|
|
16
|
+
output: {
|
|
17
|
+
// kaspa-wasm must live in one chunk — class identity breaks across chunks.
|
|
18
|
+
manualChunks: id => id.includes('@vue-kaspa/kaspa-wasm') ? 'kaspa-wasm' : undefined,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
})
|