Resolve "WIP: UTXO Selection and Storage"

This commit is contained in:
Jim Hamill
2025-01-11 08:39:24 +00:00
parent f934216d68
commit 33fd9bac4d
156 changed files with 13304 additions and 4595 deletions
@@ -0,0 +1,54 @@
<!-- .vitepress/theme/MyLayout.vue -->
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
<Layout>
<!-- Override the home-hero-image slot with the Stack, BCH, and Apps icons along with gradient arrows -->
<template #home-hero-image>
<div class="icon-container">
<!-- Stack Icon -->
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="stack-icon">
<path d="m2.394 15.759s7.554 4.246 9.09 5.109c.165.093.333.132.492.132.178 0 .344-.049.484-.127 1.546-.863 9.155-5.113 9.155-5.113.246-.138.385-.393.385-.656 0-.566-.614-.934-1.116-.654 0 0-7.052 3.958-8.539 4.77-.211.115-.444.161-.722.006-1.649-.928-8.494-4.775-8.494-4.775-.502-.282-1.117.085-1.117.653 0 .262.137.517.382.655zm0-3.113s7.554 4.246 9.09 5.109c.165.093.333.132.492.132.178 0 .344-.049.484-.127 1.546-.863 9.155-5.113 9.155-5.113.246-.138.385-.393.385-.656 0-.566-.614-.934-1.116-.654 0 0-7.052 3.958-8.539 4.77-.211.115-.444.161-.722.006-1.649-.928-8.494-4.775-8.494-4.775-.502-.282-1.117.085-1.117.653 0 .262.137.517.382.655zm10.271-9.455c-.246-.128-.471-.191-.692-.191-.223 0-.443.065-.675.191l-8.884 5.005c-.276.183-.414.444-.414.698 0 .256.139.505.414.664l8.884 5.006c.221.133.447.203.678.203.223 0 .452-.065.689-.203l8.884-5.006c.295-.166.451-.421.451-.68 0-.25-.145-.503-.451-.682z" fill-rule="nonzero"/>
</svg>
<!-- Right Arrow with gradient -->
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="url(#gradient)" fill-rule="evenodd" clip-rule="evenodd" class="right-arrow" stroke="url(#gradient)" stroke-width="2">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#5271ff;stop-opacity:1" />
<stop offset="100%" style="stop-color:#38b6ff;stop-opacity:1" />
</linearGradient>
</defs>
<path d="M4 .755l14.374 11.245-14.374 11.219.619.781 15.381-12-15.391-12-.609.755z"/>
</svg>
<!-- BCH Icon -->
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="currentColor" class="bch-icon">
<path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-.5 12.497v2.503c1.984 0 3.344-.188 3.344-1.258 0-1.148-1.469-1.245-3.344-1.245zm2.789-2.247c0-1-1.039-1.25-2.789-1.25v2.5c1.105 0 2.789-.078 2.789-1.25zm-2.289-7.25c-4.971 0-9 4.029-9 9s4.029 9 9 9 9-4.029 9-9-4.029-9-9-9zm1 13.5v1.5h-1v-1.511h-.5v1.511h-1v-1.511h-2.484l.25-1.489h.539c.442 0 .695-.425.695-.854v-4.444c0-.416-.242-.702-.683-.702h-.817v-1.5h2.5v-1.5h1v1.5h.5v-1.5h1v1.526c2.158.073 3.012.891 3.257 1.812.29 1.09-.429 2.005-1.046 2.228.75.192 1.789.746 1.789 2.026 0 1.741-1.344 2.908-4 2.908z"/>
</svg>
<!-- Left Arrow with gradient -->
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="url(#gradient)" fill-rule="evenodd" clip-rule="evenodd" class="left-arrow" stroke="url(#gradient)" stroke-width="2">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#5271ff;stop-opacity:1" />
<stop offset="100%" style="stop-color:#38b6ff;stop-opacity:1" />
</linearGradient>
</defs>
<path d="M20 .755l-14.374 11.245 14.374 11.219-.619.781-15.381-12 15.391-12 .609.755z"/>
</svg>
<!-- Apps Icon -->
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="apps-icon">
<path d="m21 4c0-.478-.379-1-1-1h-16c-.62 0-1 .519-1 1v16c0 .621.52 1 1 1h16c.478 0 1-.379 1-1zm-16.5.5h15v15h-15zm6.75 9.25v3.25c0 .53-.47 1-1 1h-3.25c-.53 0-1-.47-1-1v-3.25c0-.53.47-1 1-1h3.25c.53 0 1 .47 1 1zm0-6.75v3.25c0 .53-.47 1-1 1h-3.25c-.53 0-1-.47-1-1v-3.25c0-.53.47-1 1-1h3.25c.53 0 1 .47 1 1zm6.75 0v3.25c0 .53-.47 1-1 1h-3.25c-.53 0-1-.47-1-1v-3.25c0-.53.47-1 1-1h3.25c.53 0 1 .47 1 1z" fill-rule="nonzero"/>
</svg>
</div>
</template>
</Layout>
</template>
@@ -0,0 +1,107 @@
<template>
<Tooltip :show="showTooltip && !isCopied">
<button
class="copy-button"
:class="{ 'copied': isCopied }"
@click="handleCopy"
@mouseenter="showTooltip = true"
@mouseleave="showTooltip = false"
>
<Transition mode="out-in">
<img v-if="!isCopied" key="icon" src="../icons/copy.svg" alt="Copy" class="copy-icon" />
<span v-else key="text" class="copied-text">Copied!</span>
</Transition>
</button>
<template #content>Copy</template>
</Tooltip>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Tooltip from './Tooltip.vue';
type Props = {
content: string;
}
const props = withDefaults(defineProps<Props>(), {
content: ''
});
const isCopied = ref(false);
const showTooltip = ref(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(props.content);
isCopied.value = true;
showTooltip.value = false;
setTimeout(() => {
isCopied.value = false;
}, 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
};
</script>
<style scoped>
.tooltip-container {
position: absolute !important;
top: 5px;
right: 5px;
}
.copy-button {
position: relative;
padding: 5px 10px;
font-size: 1rem;
font-weight: bold;
color: white;
background: var(--vp-home-hero-name-background);
border: none;
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
opacity: 0.9;
width: 2.2em;
height: 2.2em;
overflow: hidden;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
}
.copy-button:hover {
opacity: 1;
background: var(--primary);
}
.copy-button.copied {
width: 70px;
}
.copy-icon {
width: 28px;
height: 28px;
filter: invert(1);
}
.copied-text {
font-weight: 600;
white-space: nowrap;
font-size: 0.8rem;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.2s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
@@ -0,0 +1,74 @@
<template>
<div class="labelled-section-container">
<div class="labelled-section">
{{ content || ' ' }}
</div>
<label class="section-label" :class="{ 'is-floating': hasContent }">
{{ label }}
</label>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
export interface Props {
content?: string
label?: string
}
const props = withDefaults(defineProps<Props>(), {
content: '',
label: ''
})
const hasContent = computed(() => props.content.length > 0)
</script>
<style scoped>
.labelled-section-container {
position: relative;
width: 100%;
margin-bottom: 10px;
}
.labelled-section {
box-sizing: content-box;
padding: 10px 8px;
font-size: 1rem;
border: 1px dashed #ccc;
border-radius: 4px;
background: var(--vp-c-bg);
line-height: 1.5;
min-height: calc(1.5em);
font-family: monospace;
max-width: 100%;
text-wrap: wrap;
overflow-wrap: anywhere;
white-space: pre-wrap;
display: flex;
align-items: center;
}
.section-label {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
background: var(--vp-c-bg);
padding: 0 4px;
font-size: 1em;
font-family: monospace;
transition: all 0.2s ease;
opacity: 0.5;
}
.section-label.is-floating {
top: 0;
font-size: 0.8em;
transform: translateY(-50%);
font-style: normal;
color: var(--vp-c-text-2);
opacity: 1.0;
}
</style>
@@ -0,0 +1,238 @@
<template>
<div class="input-wrapper">
<h2>Input</h2>
<div class="form-group-row" :class="{ 'extended': state.selectedFromFunction?.extendedInput }">
<div class="form-group select-group">
<label for="functionSelect">Select Function</label>
<select v-model="state.selectedFromFunction">
<option v-for="fromFunction of props.fromFunctions" :key="fromFunction.label" :value="fromFunction">{{ fromFunction.label }}</option>
</select>
</div>
<div class="form-group input-group" :class="{ 'extended': state.selectedFromFunction?.extendedInput }">
<template v-for="(_input, i) of state.selectedFromFunction.inputs" :key="i">
<label for="field">{{ state.selectedFromFunction.inputs[i] }}</label>
<div>
<textarea
v-if="state.selectedFromFunction?.extendedInput"
v-model="state.selectedFromFunction.models[i]"
class="extended-input"
@input="autoGrow($event.target)"
></textarea>
<input
v-else
type="text"
v-model="state.selectedFromFunction.models[i]"
/>
<div v-if="state.error" class="text-error">{{ state.error }}</div>
</div>
</template>
</div>
</div>
<div>
<button type="submit" class="gradient-button" @click="() => setPrimitive(() => state.selectedFromFunction.callback(state.selectedFromFunction.models))">Create Output</button>
</div>
</div>
<div class="section output-section">
<h2>Output</h2>
<template v-for="(toFunction, i) of toFunctions" :key="i">
<div>
<div class="output-box-container">
<LabelledSection :label="toFunction.label" :content="getPrimitive(() => toFunction.callback(state.primitive))" />
<CopyButton :content="getPrimitive(() => toFunction.callback(state.primitive))" />
</div>
</div>
</template>
<template v-if="state.methods.length && state.primitive">
<h2>Methods</h2>
<div class="form-group-row" :class="{ 'extended': state.selectedMethod?.extendedInput }">
<div class="form-group select-group">
<label for="functionSelect">Select Function</label>
<select v-model="state.selectedMethod">
<option v-for="method of props.methods" :key="method.label" :value="method">{{ method.label }}</option>
</select>
</div>
<div class="form-group input-group" :class="{ 'extended': state.selectedMethod?.extendedInput }">
<template v-for="(_input, i) of state.selectedMethod.inputs" :key="i">
<label for="field">{{ state.selectedMethod.inputs[i] }}</label>
<div>
<textarea
v-if="state.selectedMethod?.extendedInput"
v-model="state.selectedMethod.models[i]"
class="extended-input"
@input="autoGrow($event.target)"
></textarea>
<input
v-else
type="text"
v-model="state.selectedMethod.models[i]"
/>
<div v-if="state.error" class="text-error">{{ state.error }}</div>
</div>
</template>
</div>
</div>
<div>
<button type="submit" class="gradient-button" @click="() => executeMethod(state.selectedMethod.callback(state.primitive, state.selectedMethod.models))">Execute Method</button>
</div>
<!-- Result -->
<div>
<div class="output-box-container">
<LabelledSection label="Result" :content="state.selectedMethod.result" />
<CopyButton :content="state.selectedMethod.result" />
</div>
</div>
</template>
</div>
</template>
<script setup generic="Primitive" lang="ts">
import { ref } from 'vue';
import type { Ref } from 'vue';
import LabelledSection from './LabelledSection.vue';
import CopyButton from './CopyButton.vue';
export interface FromFunction<Primitive> {
// The label of the function
label: string,
// Array of inputs that the function takes
inputs: Array<string>,
// Array of models that the input stores its value in
models: Array<any>,
// Function that takes the models and returns the primitive
callback: (value: Array<any>) => Primitive,
// Whether the input is extended onto a new line
extendedInput?: boolean
}
export interface ToFunction<Primitive> {
label: string;
callback: (primitive: Primitive) => string;
result?: string;
}
export interface Method<Primitive> {
// The label of the function
label: string;
// Array of inputs that the function takes
inputs: Array<string>,
// Array of models that the input stores its value in
models: Array<any>,
// Result of method execution,
result?: string;
// Function that takes the models and returns the primitive
callback: (primitive: Primitive, value: Array<any>) => string;
// Whether the input is extended onto a new line
extendedInput?: boolean
}
export interface Props<Primitive> {
fromFunctions: Array<FromFunction<Primitive>>,
toFunctions: Array<ToFunction<Primitive>>,
methods?: Array<Method<Primitive>>,
}
const props = withDefaults(defineProps<Props<Primitive>>(), {
fromFunctions: () => [],
toFunctions: () => [],
methods: () => [],
});
const state: Ref<{
fromFunctions: Array<FromFunction<Primitive>>,
toFunctions: Array<ToFunction<Primitive>>,
methods: Array<Method<Primitive>>,
// Selected In Function
selectedFromFunction: FromFunction<Primitive>,
// Selected Method
selectedMethod?: Method<Primitive>,
// The Primitive.
primitive?: Primitive,
// Error to display if an invalid input was given.
error?: string;
}> = ref({
// From functions, to functions and methods.
fromFunctions: [ ...props.fromFunctions ],
toFunctions: [ ...props.toFunctions ],
methods: [ ...props.methods ],
// The selected function.
selectedFromFunction: props.fromFunctions[0],
// The selected method.
selectedMethod: props.methods[0] || undefined,
// The Primitive.
primitive: undefined,
})
async function setPrimitive(functionToExecute: () => Primitive | Promise<Primitive>): void {
try {
state.value.primitive = await functionToExecute();
state.value.error = undefined;
state.value.toFunctions.forEach((wrappedMethod, i) => {
state.value.toFunctions[i].result = wrappedMethod.callback(state.value.primitive)
});
} catch(error: any) {
state.value.error = `${error}`;
}
}
function getPrimitive(functionToExecute: () => string): string {
try {
if(!state.value.primitive) {
return '';
}
return functionToExecute();
} catch(error: any) {
return error.message;
}
}
async function executeMethod(wrappedMethod: () => string | Promise<string>): string {
try {
if(!state.value.primitive) {
return '';
}
state.value.selectedMethod.result = await wrappedMethod;
} catch(error: any) {
return error.message;
}
};
// Select the first "from" function.
state.value.selectedFromFunction = state.value.fromFunctions[0];
const autoGrow = (element: HTMLTextAreaElement) => {
element.style.height = 'auto'
element.style.height = `${Math.min(element.scrollHeight, 300)}px`
}
</script>
<style>
.text-error {
color: #f46666;
overflow-wrap: break-word;
}
</style>
@@ -0,0 +1,76 @@
<template>
<div class="tooltip-container">
<slot></slot>
<Transition>
<div v-if="show" class="tooltip" :class="position">
<slot name="content"></slot>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
type Props = {
show: boolean;
position?: 'top' | 'bottom' | 'left' | 'right';
}
withDefaults(defineProps<Props>(), {
position: 'top',
show: false
});
</script>
<style scoped>
.tooltip-container {
position: relative;
display: inline-block;
}
.tooltip {
position: absolute;
background: var(--vp-c-text-1);
color: var(--vp-c-bg);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
white-space: nowrap;
pointer-events: none;
z-index: 100;
}
.tooltip.top {
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(-6px);
}
.tooltip.bottom {
top: 100%;
left: 50%;
transform: translateX(-50%) translateY(6px);
}
.tooltip.left {
right: 100%;
top: 50%;
transform: translateY(-50%) translateX(-6px);
}
.tooltip.right {
left: 100%;
top: 50%;
transform: translateY(-50%) translateX(6px);
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(0);
}
</style>
@@ -0,0 +1,164 @@
:root {
--primary: #5271ff;
--vp-nav-logo-height: 48px;
}
html.dark {
--primary: #38b6ff;
}
body {
font-size: 14px;
}
.vp-nav .title {
font-size: 24px !important;
}
.vp-doc h1 {
font-size: 24px;
}
.vp-doc h2 {
font-size: 20px;
}
.vp-doc h3 {
font-size: 16px;
}
.vp-doc h4 {
font-size: 14px;
opacity: 0.75;
}
.VPSidebarItem .text {
font-size: 12px;
}
ul.VPDocOutlineItem.root {
font-weight: bold;
}
.VPSidebarItem.level-2.is-active > .item > .indicator,
.VPSidebarItem.level-3.is-active > .item > .indicator,
.VPSidebarItem.level-4.is-active > .item > .indicator,
.VPSidebarItem.level-5.is-active > .item > .indicator {
background-color: var(--primary) !important;
}
.VPSidebarItem.level-0.is-active > .item .link > .text,
.VPSidebarItem.level-1.is-active > .item .link > .text,
.VPSidebarItem.level-2.is-active > .item .link > .text,
.VPSidebarItem.level-3.is-active > .item .link > .text,
.VPSidebarItem.level-4.is-active > .item .link > .text,
.VPSidebarItem.level-5.is-active > .item .link > .text {
color: var(--primary) !important;
}
.VPSidebarItem.level-0.is-link > .item > .link:hover .text,
.VPSidebarItem.level-1.is-link > .item > .link:hover .text,
.VPSidebarItem.level-2.is-link > .item > .link:hover .text,
.VPSidebarItem.level-3.is-link > .item > .link:hover .text,
.VPSidebarItem.level-4.is-link > .item > .link:hover .text,
.VPSidebarItem.level-5.is-link > .item > .link:hover .text {
color: var(--primary) !important;
}
.outline-title {
font-size: 12px !important;
}
.outline-link {
font-size: 12px !important;
}
/* Customization of VitePress CSS */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#5271ff,
#38b6ff
);
}
.VPButton.brand {
background: linear-gradient(-45deg, #5271ff, #38b6ff) !important;
}
.VPButton.brand:hover {
background: linear-gradient(45deg, #5271ff, #5271ff) !important;
}
/* .vitepress/theme/custom.css */
.image-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%; /* Ensure it takes up the full height of the parent */
width: 100%; /* Ensure it takes up the full width of the parent */
}
/* .vitepress/theme/custom.css */
/* Container for all icons and arrows */
.icon-container {
display: flex;
align-items: center; /* Align the icons and arrows vertically */
justify-content: center; /* Center the icons and arrows horizontally */
gap: 20px; /* Space between the icons and arrows */
}
/* BCH icon styling */
.bch-icon {
width: 64px;
height: 64px;
transform: rotate(-15deg); /* Rotate the BCH icon by 15 degrees to the left */
fill: currentColor;
}
/* Stack icon styling */
.stack-icon {
width: 64px;
height: 64px;
fill: currentColor;
}
/* Apps icon styling */
.apps-icon {
width: 64px;
height: 64px;
fill: currentColor;
}
/* Arrow between Stack and BCH */
.right-arrow {
width: 24px;
height: 24px;
}
/* Arrow between BCH and Apps */
.left-arrow {
width: 24px;
height: 24px;
}
/* Light / dark mode logos */
:root:not(.dark) .dark-only {
display: none;
}
:root:is(.dark) .light-only {
display: none;
}
/* Text colors */
.text-error {
color: #f46666;
overflow-wrap: break-word;
}
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g id="Text-files" stroke="#000000" stroke-width="2" stroke-linejoin="round">
<path fill-rule="evenodd" d="M53.9791489,9.1429005H50.010849c-0.0826988,0-0.1562004,0.0283995-0.2331009,0.0469999V5.0228
C49.7777481,2.253,47.4731483,0,44.6398468,0h-34.422596C7.3839517,0,5.0793519,2.253,5.0793519,5.0228v46.8432999
c0,2.7697983,2.3045998,5.0228004,5.1378999,5.0228004h6.0367002v2.2678986C16.253952,61.8274002,18.4702511,64,21.1954517,64
h32.783699c2.7252007,0,4.9414978-2.1725998,4.9414978-4.8432007V13.9861002
C58.9206467,11.3155003,56.7043495,9.1429005,53.9791489,9.1429005z M7.1110516,51.8661003V5.0228
c0-1.6487999,1.3938999-2.9909999,3.1062002-2.9909999h34.422596c1.7123032,0,3.1062012,1.3422,3.1062012,2.9909999v46.8432999
c0,1.6487999-1.393898,2.9911003-3.1062012,2.9911003h-34.422596C8.5049515,54.8572006,7.1110516,53.5149002,7.1110516,51.8661003z
M56.8888474,59.1567993c0,1.550602-1.3055,2.8115005-2.9096985,2.8115005h-32.783699
c-1.6042004,0-2.9097996-1.2608986-2.9097996-2.8115005v-2.2678986h26.3541946
c2.8333015,0,5.1379013-2.2530022,5.1379013-5.0228004V11.1275997c0.0769005,0.0186005,0.1504021,0.0469999,0.2331009,0.0469999
h3.9682999c1.6041985,0,2.9096985,1.2609005,2.9096985,2.8115005V59.1567993z"/>
<path fill-rule="evenodd" d="M38.6031494,13.2063999H16.253952c-0.5615005,0-1.0159006,0.4542999-1.0159006,1.0158005
c0,0.5615997,0.4544001,1.0158997,1.0159006,1.0158997h22.3491974c0.5615005,0,1.0158997-0.4542999,1.0158997-1.0158997
C39.6190491,13.6606998,39.16465,13.2063999,38.6031494,13.2063999z"/>
<path fill-rule="evenodd" d="M38.6031494,21.3334007H16.253952c-0.5615005,0-1.0159006,0.4542999-1.0159006,1.0157986
c0,0.5615005,0.4544001,1.0159016,1.0159006,1.0159016h22.3491974c0.5615005,0,1.0158997-0.454401,1.0158997-1.0159016
C39.6190491,21.7877007,39.16465,21.3334007,38.6031494,21.3334007z"/>
<path fill-rule="evenodd" d="M38.6031494,29.4603004H16.253952c-0.5615005,0-1.0159006,0.4543991-1.0159006,1.0158997
s0.4544001,1.0158997,1.0159006,1.0158997h22.3491974c0.5615005,0,1.0158997-0.4543991,1.0158997-1.0158997
S39.16465,29.4603004,38.6031494,29.4603004z"/>
<path fill-rule="evenodd" d="M28.4444485,37.5872993H16.253952c-0.5615005,0-1.0159006,0.4543991-1.0159006,1.0158997
s0.4544001,1.0158997,1.0159006,1.0158997h12.1904964c0.5615025,0,1.0158005-0.4543991,1.0158005-1.0158997
S29.0059509,37.5872993,28.4444485,37.5872993z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,15 @@
// https://vitepress.dev/guide/custom-theme
import type { Theme } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import Layout from './Layout.vue';
import './style.css';
import './custom.css';
import './tools.css';
export default {
extends: DefaultTheme,
Layout,
enhanceApp({ app, router, siteData }) {
// ...
},
} satisfies Theme;
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 80 KiB

@@ -0,0 +1,560 @@
/**
* Customize default theme styling by overriding CSS variables:
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
*/
/**
* Colors
*
* Each colors have exact same color scale system with 3 levels of solid
* colors with different brightness, and 1 soft color.
*
* - `XXX-1`: The most solid color used mainly for colored text. It must
* satisfy the contrast ratio against when used on top of `XXX-soft`.
*
* - `XXX-2`: The color used mainly for hover state of the button.
*
* - `XXX-3`: The color for solid background, such as bg color of the button.
* It must satisfy the contrast ratio with pure white (#ffffff) text on
* top of it.
*
* - `XXX-soft`: The color used for subtle background such as custom container
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
* on top of it.
*
* The soft color must be semi transparent alpha channel. This is crucial
* because it allows adding multiple "soft" colors on top of each other
* to create a accent, such as when having inline code block inside
* custom containers.
*
* - `default`: The color used purely for subtle indication without any
* special meanings attched to it such as bg color for menu hover state.
*
* - `brand`: Used for primary brand colors, such as link text, button with
* brand theme, etc.
*
* - `tip`: Used to indicate useful information. The default theme uses the
* brand color for this by default.
*
* - `warning`: Used to indicate warning to the users. Used in custom
* container, badges, etc.
*
* - `danger`: Used to show error, or dangerous message to the users. Used
* in custom container, badges, etc.
* -------------------------------------------------------------------------- */
:root {
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-brand-1: var(--vp-c-indigo-1);
--vp-c-brand-2: var(--vp-c-indigo-2);
--vp-c-brand-3: var(--vp-c-indigo-3);
--vp-c-brand-soft: var(--vp-c-indigo-soft);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-brand-1);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#38b6ff 30%,
#5271ff
);
/*
--vp-home-hero-image-background-image: linear-gradient(
-45deg,
#5271ff 50%,
#38b6ff 50%
);
--vp-home-hero-image-filter: blur(44px);
*/
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1) !important;
}
/**
* Overrides
*/
:root {
--primary: #5271ff;
--vp-nav-logo-height: 48px;
}
html.dark {
--primary: #38b6ff;
}
body {
font-size: 14px;
}
.vp-nav .title {
font-size: 24px !important;
}
.vp-doc h1 {
font-size: 24px;
}
.vp-doc h2 {
font-size: 22px;
color: var(--primary);
font-weight: bold;
margin-top: 15px;
margin-bottom: 5px;
border: none !important;
line-height: 1.2;
}
.vp-doc h3 {
font-size: 16px;
}
.vp-doc h4 {
font-size: 14px;
opacity: 0.75;
}
.VPSidebarItem .text {
font-size: 12px;
}
.VPSidebarItem.level-2.is-active > .item > .indicator,
.VPSidebarItem.level-3.is-active > .item > .indicator,
.VPSidebarItem.level-4.is-active > .item > .indicator,
.VPSidebarItem.level-5.is-active > .item > .indicator {
background-color: var(--primary) !important;
}
.VPSidebarItem.level-0.is-active > .item .link > .text,
.VPSidebarItem.level-1.is-active > .item .link > .text,
.VPSidebarItem.level-2.is-active > .item .link > .text,
.VPSidebarItem.level-3.is-active > .item .link > .text,
.VPSidebarItem.level-4.is-active > .item .link > .text,
.VPSidebarItem.level-5.is-active > .item .link > .text {
color: var(--primary) !important;
}
.VPSidebarItem.level-0.is-link > .item > .link:hover .text,
.VPSidebarItem.level-1.is-link > .item > .link:hover .text,
.VPSidebarItem.level-2.is-link > .item > .link:hover .text,
.VPSidebarItem.level-3.is-link > .item > .link:hover .text,
.VPSidebarItem.level-4.is-link > .item > .link:hover .text,
.VPSidebarItem.level-5.is-link > .item > .link:hover .text {
color: var(--primary) !important;
}
.outline-title {
font-size: 12px !important;
}
.outline-link {
font-size: 12px !important;
}
/* Customization of VitePress CSS */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#5271ff,
#38b6ff
);
}
.VPButton.brand {
background: linear-gradient(-45deg, #5271ff, #38b6ff) !important;
}
.VPButton.brand:hover {
background: linear-gradient(45deg, #5271ff, #5271ff) !important;
}
/* .vitepress/theme/custom.css */
.image-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%; /* Ensure it takes up the full height of the parent */
width: 100%; /* Ensure it takes up the full width of the parent */
}
/* .vitepress/theme/custom.css */
/* Container for all icons and arrows */
.icon-container {
display: flex;
align-items: center; /* Align the icons and arrows vertically */
justify-content: center; /* Center the icons and arrows horizontally */
gap: 20px; /* Space between the icons and arrows */
}
/* BCH icon styling */
.bch-icon {
width: 64px;
height: 64px;
transform: rotate(-15deg); /* Rotate the BCH icon by 15 degrees to the left */
fill: currentColor;
}
/* Stack icon styling */
.stack-icon {
width: 64px;
height: 64px;
fill: currentColor;
}
/* Apps icon styling */
.apps-icon {
width: 64px;
height: 64px;
fill: currentColor;
}
/* Arrow between Stack and BCH */
.right-arrow {
width: 24px;
height: 24px;
}
/* Arrow between BCH and Apps */
.left-arrow {
width: 24px;
height: 24px;
}
/* Light / dark mode logos */
:root:not(.dark) .dark-only {
display: none;
}
:root:is(.dark) .light-only {
display: none;
}
/* Feature box override */
.feature-box-or {
flex-direction: row !important;
text-align: center !important;
}
.feature-box-or h2 {
border: none !important;
padding-top: 10px !important;
margin: 0 !important;
}
.feature-box-or img {
margin: auto !important;
}
/* Promo on front page */
.promo {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
padding: 20px;
}
.promo-text {
flex: 2; /* Adjusted flex value for more text space */
min-width: 250px; /* Ensures text is responsive */
}
.promo-text h1 {
font-size: 2rem; /* Adjust font size as needed */
margin: 0;
}
.promo-text p {
font-size: 1.1rem; /* Slightly larger text for better readability */
margin-top: 10px;
}
.promo-text .clip {
background: var(--vp-home-hero-name-background);
-webkit-background-clip: text;
background-clip: text;
color: var(
--vp-home-hero-name-color
); /* Make the text color transparent to reveal background */
font-weight: bold;
padding: 0 5px;
}
.promo-image {
position: relative; /* To position the animated elements inside */
flex: 1; /* Reduce the image's width */
min-width: 150px; /* Ensures the image is smaller */
max-width: 250px; /* Set a max width for the image */
text-align: right;
}
.promo-image img {
max-width: 100%;
height: auto;
position: relative; /* Image is above the animated icons */
z-index: 1; /* Ensure image is in front of the icons */
}
/* X and O animation styles behind promo-image */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* Ignore background interactions */
z-index: 0; /* Ensure the animated elements are behind the image */
}
.xo {
position: absolute;
font-size: 1.5rem;
}
.xo-1,
.xo-3 {
color: var(--primary); /* X elements using --primary color */
}
.xo-2,
.xo-4 {
color: var(--vp-c-text-1); /* O elements using --vp-c-text-1 color */
}
/* Unique positions and animations for each XO element */
.xo-1 {
top: 10%;
left: 15%;
animation:
rotate-small 6s infinite linear,
move-elliptical 10s infinite ease-in-out;
}
.xo-2 {
top: 40%;
left: 60%;
animation:
rotate-large 8s infinite linear,
move-circular 12s infinite ease-in-out;
}
.xo-3 {
top: 70%;
left: 30%;
animation:
rotate-reverse 5s infinite linear,
move-diagonal 14s infinite ease-in-out;
}
.xo-4 {
top: 20%;
left: 80%;
animation:
rotate-small-reverse 7s infinite linear,
move-random 16s infinite ease-in-out;
}
/* Keyframe for self-rotation: Small */
@keyframes rotate-small {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Keyframe for self-rotation: Large */
@keyframes rotate-large {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(720deg);
}
}
/* Keyframe for reverse self-rotation */
@keyframes rotate-reverse {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-360deg);
}
}
@keyframes rotate-small-reverse {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-720deg);
}
}
/* Keyframe for elliptical movement */
@keyframes move-elliptical {
0% {
transform: translate(0, 0);
}
25% {
transform: translate(60px, 40px);
}
50% {
transform: translate(120px, 0);
}
75% {
transform: translate(60px, -40px);
}
100% {
transform: translate(0, 0);
}
}
/* Keyframe for circular movement */
@keyframes move-circular {
0% {
transform: translate(0, 0);
}
25% {
transform: translate(50px, 50px);
}
50% {
transform: translate(100px, 0);
}
75% {
transform: translate(50px, -50px);
}
100% {
transform: translate(0, 0);
}
}
/* Keyframe for diagonal movement */
@keyframes move-diagonal {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(100px, 100px);
}
100% {
transform: translate(0, 0);
}
}
/* Keyframe for random, unpredictable movement */
@keyframes move-random {
0% {
transform: translate(0, 0);
}
20% {
transform: translate(40px, -30px);
}
40% {
transform: translate(80px, 50px);
}
60% {
transform: translate(-50px, -40px);
}
80% {
transform: translate(30px, 70px);
}
100% {
transform: translate(0, 0);
}
}
@media (max-width: 768px) {
.promo {
flex-direction: column;
text-align: center;
}
.promo-image {
margin-top: 20px;
}
}
@@ -0,0 +1,145 @@
.tool h2 {
font-size: 16px;
}
/* Wrapper for input section */
.input-wrapper {
margin-bottom: 20px;
}
/* Flex container for dropdown and input */
.form-group-row {
display: flex;
flex-wrap: wrap; /* Ensure responsiveness */
gap: 15px; /* Space between dropdown and input */
margin-bottom: 15px;
}
.form-group-row.extended {
flex-direction: column;
}
/* Form group styling */
.form-group {
flex: 1;
min-width: 200px;
}
.select-group {
flex: 1;
min-width: 200px;
max-width: 50%;
}
.input-group {
flex: 1;
min-width: 200px;
}
.input-group.extended {
max-width: 100%;
width: 100%;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group select,
.form-group input,
.form-group button {
width: 100%; /* Makes fields responsive */
padding: 10px;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
/* Extended input styling */
.extended-input {
width: 100%;
min-height: 2.5rem;
max-height: 300px;
resize: none;
padding: 0.5rem;
line-height: 1.5;
font-family: inherit;
font-size: inherit;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg-alt);
}
.form-group button {
background-color: #333;
cursor: pointer;
}
.form-group button:hover {
background-color: #555;
}
/* Output section */
.output-section {
margin-top: 20px;
margin-bottom: 50px;
}
.output-box-container {
position: relative;
margin-top: 15px;
}
.output-box-label {
font-size: 0.8em;
}
.output-box {
box-sizing: content-box;
border: 1px dashed #ccc;
padding: 10px;
min-height: 24px;
overflow-wrap: break-word;
white-space: pre-wrap;
font-family: monospace;
}
.placeholder {
text-align: center;
color: #888;
font-style: italic;
margin-top: 20px;
}
/* Gradient Button Styling */
.gradient-button {
padding: 10px 20px;
font-size: 1rem;
font-weight: bold;
color: white;
background: var(--vp-home-hero-name-background);
border: none;
width: 100%;
margin: auto;
border-radius: 10px; /* Full-rounded button */
cursor: pointer;
text-transform: uppercase;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); /* Soft shadow */
}
.gradient-button:hover {
background: var(--primary);
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.select-group {
max-width: 100%;
}
.input-group {
width: 100%;
}
}