Interactive page comparing 5 unique DreamStack capabilities vs React/Svelte/Solid/Vue: 1. Reactivity as a type (Signal<T> in the type system) 2. Algebraic effects (swappable side-effect handlers) 3. Springs are signals (physics auto-propagates through reactive graph) 4. Compile-time dependency graph (static analysis, dead signal elimination) 5. Constraint-based layout (Cassowary solver, not CSS hacks) Includes live spring physics demo, interactive constraint layout toggle, side-by-side code comparisons, Elm-style error previews, and comparison table.
1202 lines
No EOL
45 KiB
HTML
1202 lines
No EOL
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>DreamStack — What No Other Framework Can Do</title>
|
||
<link
|
||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
||
rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg: #050510;
|
||
--surface: #0c0c1d;
|
||
--surface-2: #12122a;
|
||
--border: #1e1e3a;
|
||
--text: #e8e8f0;
|
||
--text-dim: #6b6b8a;
|
||
--accent: #7c3aed;
|
||
--accent-2: #a855f7;
|
||
--green: #22c55e;
|
||
--red: #ef4444;
|
||
--blue: #3b82f6;
|
||
--yellow: #eab308;
|
||
--pink: #ec4899;
|
||
--orange: #f97316;
|
||
--cyan: #06b6d4;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: 'Inter', -apple-system, system-ui, sans-serif;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
/* ─── Hero ───────────────────────────────────── */
|
||
.hero {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(ellipse 600px 400px at 30% 20%, rgba(124, 58, 237, 0.12) 0%, transparent 70%),
|
||
radial-gradient(ellipse 500px 350px at 70% 60%, rgba(168, 85, 247, 0.08) 0%, transparent 70%),
|
||
radial-gradient(ellipse 400px 300px at 50% 80%, rgba(59, 130, 246, 0.06) 0%, transparent 70%);
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 16px;
|
||
border-radius: 20px;
|
||
background: rgba(124, 58, 237, 0.15);
|
||
border: 1px solid rgba(124, 58, 237, 0.3);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--accent-2);
|
||
margin-bottom: 24px;
|
||
position: relative;
|
||
}
|
||
|
||
.hero h1 {
|
||
font-size: clamp(36px, 5vw, 64px);
|
||
font-weight: 800;
|
||
line-height: 1.1;
|
||
margin-bottom: 20px;
|
||
position: relative;
|
||
background: linear-gradient(135deg, #fff 0%, #c4b5fd 50%, #7c3aed 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.hero p {
|
||
font-size: 18px;
|
||
color: var(--text-dim);
|
||
max-width: 600px;
|
||
line-height: 1.6;
|
||
position: relative;
|
||
}
|
||
|
||
/* ─── Section ────────────────────────────────── */
|
||
.section {
|
||
padding: 80px 20px;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.section-header {
|
||
text-align: center;
|
||
margin-bottom: 60px;
|
||
}
|
||
|
||
.section-num {
|
||
display: inline-block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--accent);
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.section h2 {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.section h2 em {
|
||
font-style: normal;
|
||
color: var(--accent-2);
|
||
}
|
||
|
||
.section .subtitle {
|
||
font-size: 16px;
|
||
color: var(--text-dim);
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ─── Comparison Card ────────────────────────── */
|
||
.comparison {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.code-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
transition: transform 0.3s, box-shadow 0.3s;
|
||
}
|
||
|
||
.code-card:hover {
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.code-card.dreamstack {
|
||
border-color: rgba(124, 58, 237, 0.4);
|
||
box-shadow: 0 0 40px rgba(124, 58, 237, 0.08);
|
||
}
|
||
|
||
.code-card.dreamstack:hover {
|
||
box-shadow: 0 8px 40px rgba(124, 58, 237, 0.15);
|
||
}
|
||
|
||
.code-card.others {
|
||
border-color: rgba(255, 255, 255, 0.06);
|
||
}
|
||
|
||
.code-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 20px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.code-card-header .label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.code-card-header .tag {
|
||
font-size: 10px;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tag-unique {
|
||
background: rgba(124, 58, 237, 0.2);
|
||
color: var(--accent-2);
|
||
}
|
||
|
||
.tag-impossible {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
color: var(--red);
|
||
}
|
||
|
||
.tag-lines {
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-dim);
|
||
}
|
||
|
||
.tag-error {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
color: var(--red);
|
||
}
|
||
|
||
.code-card pre {
|
||
padding: 20px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
overflow-x: auto;
|
||
color: #c9d1d9;
|
||
}
|
||
|
||
/* Syntax highlighting */
|
||
.kw {
|
||
color: #c678dd;
|
||
}
|
||
|
||
.fn {
|
||
color: #61afef;
|
||
}
|
||
|
||
.str {
|
||
color: #98c379;
|
||
}
|
||
|
||
.num {
|
||
color: #d19a66;
|
||
}
|
||
|
||
.op {
|
||
color: #56b6c2;
|
||
}
|
||
|
||
.cm {
|
||
color: #5c6370;
|
||
font-style: italic;
|
||
}
|
||
|
||
.ty {
|
||
color: #e5c07b;
|
||
}
|
||
|
||
.sig {
|
||
color: #c678dd;
|
||
}
|
||
|
||
.eff {
|
||
color: #e06c75;
|
||
}
|
||
|
||
.err {
|
||
color: #ef4444;
|
||
text-decoration: wavy underline;
|
||
text-decoration-color: #ef4444;
|
||
}
|
||
|
||
.dim {
|
||
opacity: 0.4;
|
||
}
|
||
|
||
/* ─── Feature Grid ───────────────────────────── */
|
||
.feature-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.feature-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 28px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.feature-card:hover {
|
||
border-color: rgba(124, 58, 237, 0.3);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.feature-icon {
|
||
font-size: 28px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.feature-card h3 {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.feature-card p {
|
||
font-size: 14px;
|
||
color: var(--text-dim);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.feature-card .frameworks {
|
||
display: flex;
|
||
gap: 6px;
|
||
margin-top: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.fw-badge {
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.fw-no {
|
||
background: rgba(239, 68, 68, 0.12);
|
||
color: rgba(239, 68, 68, 0.7);
|
||
}
|
||
|
||
.fw-partial {
|
||
background: rgba(234, 179, 8, 0.12);
|
||
color: rgba(234, 179, 8, 0.7);
|
||
}
|
||
|
||
.fw-yes {
|
||
background: rgba(34, 197, 94, 0.12);
|
||
color: rgba(34, 197, 94, 0.7);
|
||
}
|
||
|
||
/* ─── Live Demo Pane ─────────────────────────── */
|
||
.demo-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.demo-code {
|
||
background: var(--surface);
|
||
padding: 24px;
|
||
border-right: 1px solid var(--border);
|
||
}
|
||
|
||
.demo-code pre {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.demo-result {
|
||
background: var(--surface-2);
|
||
padding: 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.demo-result-label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--text-dim);
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* ─── Type error display ─────────────────────── */
|
||
.error-display {
|
||
background: #1a0a0a;
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
border-radius: 10px;
|
||
padding: 16px 20px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 12.5px;
|
||
line-height: 1.6;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.error-display .error-header {
|
||
color: var(--red);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.error-display .error-body {
|
||
color: #d4a0a0;
|
||
}
|
||
|
||
.error-display .error-hint {
|
||
color: var(--cyan);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ─── Diagram ────────────────────────────────── */
|
||
.diagram-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.diagram-node {
|
||
padding: 10px 18px;
|
||
border-radius: 10px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.diagram-node.signal {
|
||
background: rgba(124, 58, 237, 0.15);
|
||
border: 1px solid var(--accent);
|
||
color: #c4b5fd;
|
||
}
|
||
|
||
.diagram-node.derived {
|
||
background: rgba(59, 130, 246, 0.15);
|
||
border: 1px solid var(--blue);
|
||
color: #93c5fd;
|
||
}
|
||
|
||
.diagram-node.effect {
|
||
background: rgba(249, 115, 22, 0.15);
|
||
border: 1px solid var(--orange);
|
||
color: #fdba74;
|
||
}
|
||
|
||
.diagram-node.spring {
|
||
background: rgba(236, 72, 153, 0.15);
|
||
border: 1px solid var(--pink);
|
||
color: #f9a8d4;
|
||
}
|
||
|
||
.diagram-node.view {
|
||
background: rgba(34, 197, 94, 0.15);
|
||
border: 1px solid var(--green);
|
||
color: #86efac;
|
||
}
|
||
|
||
.diagram-arrow {
|
||
color: var(--text-dim);
|
||
font-size: 18px;
|
||
}
|
||
|
||
/* ─── Table ──────────────────────────────────── */
|
||
.comparison-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 40px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.comparison-table th {
|
||
padding: 14px 16px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
color: var(--text-dim);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.comparison-table td {
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid rgba(30, 30, 58, 0.5);
|
||
}
|
||
|
||
.comparison-table tr:hover td {
|
||
background: rgba(124, 58, 237, 0.03);
|
||
}
|
||
|
||
.comparison-table .feature-name {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.dot-yes {
|
||
color: var(--green);
|
||
}
|
||
|
||
.dot-no {
|
||
color: var(--red);
|
||
}
|
||
|
||
.dot-partial {
|
||
color: var(--yellow);
|
||
}
|
||
|
||
/* ─── Separator ──────────────────────────────── */
|
||
.sep {
|
||
width: 60px;
|
||
height: 3px;
|
||
background: linear-gradient(90deg, var(--accent), transparent);
|
||
margin: 80px auto;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* ─── Responsive ─────────────────────────────── */
|
||
@media (max-width: 768px) {
|
||
|
||
.comparison,
|
||
.demo-container {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.demo-code {
|
||
border-right: none;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
}
|
||
|
||
/* ─── Animations ─────────────────────────────── */
|
||
@keyframes float {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-6px);
|
||
}
|
||
}
|
||
|
||
.float-1 {
|
||
animation: float 3s ease-in-out infinite;
|
||
}
|
||
|
||
.float-2 {
|
||
animation: float 3s ease-in-out infinite 0.5s;
|
||
}
|
||
|
||
.float-3 {
|
||
animation: float 3s ease-in-out infinite 1s;
|
||
}
|
||
|
||
.float-4 {
|
||
animation: float 3s ease-in-out infinite 1.5s;
|
||
}
|
||
|
||
@keyframes pulse-line {
|
||
0% {
|
||
opacity: 0.3;
|
||
}
|
||
|
||
50% {
|
||
opacity: 1;
|
||
}
|
||
|
||
100% {
|
||
opacity: 0.3;
|
||
}
|
||
}
|
||
|
||
.pulse {
|
||
animation: pulse-line 2s ease-in-out infinite;
|
||
}
|
||
|
||
/* Live demo area */
|
||
.live-spring-demo {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 32px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.spring-track {
|
||
width: 100%;
|
||
height: 80px;
|
||
background: var(--surface-2);
|
||
border-radius: 12px;
|
||
position: relative;
|
||
cursor: pointer;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.spring-ball {
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, var(--accent), var(--pink));
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
box-shadow: 0 0 30px rgba(124, 58, 237, 0.4);
|
||
cursor: grab;
|
||
}
|
||
|
||
.spring-ball:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
.spring-info {
|
||
display: flex;
|
||
gap: 20px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
}
|
||
|
||
.spring-info span {
|
||
color: var(--accent-2);
|
||
}
|
||
|
||
.layout-viz {
|
||
display: grid;
|
||
grid-template-columns: 200px 1fr;
|
||
gap: 2px;
|
||
height: 200px;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
margin: 16px 0;
|
||
transition: grid-template-columns 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
.layout-panel {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
.layout-sidebar {
|
||
background: rgba(124, 58, 237, 0.2);
|
||
color: var(--accent-2);
|
||
}
|
||
|
||
.layout-main {
|
||
background: rgba(59, 130, 246, 0.15);
|
||
color: var(--blue);
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- HERO -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="hero">
|
||
<div class="hero-badge">✦ Not another framework</div>
|
||
<h1>What DreamStack Does<br>That Nothing Else Can</h1>
|
||
<p>Five capabilities that exist nowhere else — not in React, Svelte, Solid, Vue, or any other framework. Each
|
||
one is novel. Together, they're a paradigm shift.</p>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- #1: SIGNALS IN THE TYPE SYSTEM -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<div class="section-num">Differentiator 01</div>
|
||
<h2>Reactivity is a <em>type</em>, not a runtime trick</h2>
|
||
<p class="subtitle">In every other framework, signals are invisible to the type system. You can pass a
|
||
signal where a plain value is expected and it silently breaks. DreamStack catches this <strong>at
|
||
compile time</strong>.</p>
|
||
</div>
|
||
|
||
<div class="comparison">
|
||
<div class="code-card dreamstack">
|
||
<div class="code-card-header">
|
||
<span class="label">⚡ DreamStack</span>
|
||
<span class="tag tag-unique">UNIQUE</span>
|
||
</div>
|
||
<pre><span class="cm">// The compiler KNOWS these are reactive</span>
|
||
<span class="kw">let</span> count = <span class="num">0</span> <span class="cm">// → Signal<Int></span>
|
||
<span class="kw">let</span> name = <span class="str">"Ada"</span> <span class="cm">// → Signal<String></span>
|
||
<span class="kw">let</span> doubled = count * <span class="num">2</span> <span class="cm">// → Derived<Int></span>
|
||
<span class="kw">let</span> hot = count > <span class="num">10</span> <span class="cm">// → Derived<Bool></span>
|
||
|
||
<span class="cm">// ✅ Type-safe: compiler verifies deps</span>
|
||
<span class="cm">// ✅ Auto-tracks what to re-render</span>
|
||
<span class="cm">// ✅ Zero accidental subscriptions</span></pre>
|
||
</div>
|
||
|
||
<div class="code-card others">
|
||
<div class="code-card-header">
|
||
<span class="label">React / Solid / Svelte</span>
|
||
<span class="tag tag-impossible">IMPOSSIBLE</span>
|
||
</div>
|
||
<pre><span class="cm">// TypeScript can't tell these apart:</span>
|
||
<span class="kw">const</span> [count, setCount] = useState(<span class="num">0</span>)
|
||
<span class="cm">// count is... number? Signal? Who knows?</span>
|
||
|
||
<span class="cm">// This compiles but SILENTLY BREAKS:</span>
|
||
<span class="kw">function</span> <span class="fn">add</span>(a: <span class="ty">number</span>, b: <span class="ty">number</span>) {
|
||
<span class="kw">return</span> a + b
|
||
}
|
||
<span class="fn">add</span>(count, <span class="num">5</span>) <span class="cm">// ← loses reactivity!</span>
|
||
<span class="cm">// No framework catches this at compile time</span></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Show an actual compile error -->
|
||
<div class="error-display">
|
||
<div class="error-header">── TYPE MISMATCH ─────────────────────────────────────────</div>
|
||
<div class="error-body">
|
||
7:12
|
||
|
||
add(count, 5)
|
||
|
||
I was expecting:
|
||
|
||
Int
|
||
|
||
but found:
|
||
|
||
<span style="color: var(--accent-2)">Signal<Int></span>
|
||
|
||
<span class="error-hint">Hint: Use `count.value` to unwrap the signal, or make `add` accept
|
||
Signal<Int>
|
||
to preserve reactivity.</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sep"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- #2: ALGEBRAIC EFFECTS -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<div class="section-num">Differentiator 02</div>
|
||
<h2>Effects you can <em>swap at test time</em></h2>
|
||
<p class="subtitle">React has useEffect (untyped, untestable). DreamStack has algebraic effects — declare
|
||
what side-effects a function <em>may</em> perform, and the compiler enforces that every effect is
|
||
handled. Swap handlers for testing without touching business logic.</p>
|
||
</div>
|
||
|
||
<div class="comparison">
|
||
<div class="code-card dreamstack">
|
||
<div class="code-card-header">
|
||
<span class="label">⚡ DreamStack</span>
|
||
<span class="tag tag-unique">UNIQUE</span>
|
||
</div>
|
||
<pre><span class="cm">// Declare what effects exist</span>
|
||
<span class="kw">effect</span> <span class="fn">Http.get</span>(url: <span class="ty">String</span>): <span class="ty">Response</span>
|
||
<span class="kw">effect</span> <span class="fn">Time.delay</span>(ms: <span class="ty">Int</span>): <span class="ty">()</span>
|
||
|
||
<span class="cm">// Use them — the TYPE tracks it</span>
|
||
<span class="kw">let</span> search = (q) <span class="op">-></span> {
|
||
<span class="kw">perform</span> Time.delay(<span class="num">250</span>)
|
||
<span class="kw">perform</span> Http.get(<span class="str">"/api?q="</span> ++ q)
|
||
}
|
||
<span class="cm">// search : (String) -> Response ! Http, Time</span>
|
||
|
||
<span class="cm">// In production:</span>
|
||
<span class="kw">handle</span> { Http.get(u) <span class="op">-></span> fetch(u) }
|
||
|
||
<span class="cm">// In tests — SAME code, DIFFERENT handler:</span>
|
||
<span class="kw">handle</span> { Http.get(u) <span class="op">-></span> mock_data }</pre>
|
||
</div>
|
||
|
||
<div class="code-card others">
|
||
<div class="code-card-header">
|
||
<span class="label">React / Vue / Svelte</span>
|
||
<span class="tag tag-impossible">IMPOSSIBLE</span>
|
||
</div>
|
||
<pre><span class="cm">// Effects are opaque and untraceable</span>
|
||
<span class="fn">useEffect</span>(() <span class="op">=></span> {
|
||
fetch(<span class="str">'/api'</span>) <span class="cm">// ← hidden side-effect</span>
|
||
.then(r <span class="op">=></span> r.json())
|
||
.then(setData)
|
||
}, [query])
|
||
|
||
<span class="cm">// Questions no framework can answer:</span>
|
||
<span class="cm">// • What effects does this component perform?</span>
|
||
<span class="cm">// • Are all effects cleaned up?</span>
|
||
<span class="cm">// • Can I swap fetch for a mock?</span>
|
||
<span class="cm">// • Will this work without network?</span>
|
||
<span class="cm">//</span>
|
||
<span class="cm">// Answer: 🤷 good luck</span></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="error-display" style="border-color: rgba(249, 115, 22, 0.4); background: #1a120a;">
|
||
<div class="error-header" style="color: var(--orange);">── UNHANDLED EFFECT
|
||
──────────────────────────────────────</div>
|
||
<div class="error-body">
|
||
The function `search` performs the `Http` effect, but no handler is installed.
|
||
|
||
<span class="error-hint">Hint: Wrap the call in `handle(() => search(q), { "Http": ... })`</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sep"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- #3: SPRINGS ARE SIGNALS -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<div class="section-num">Differentiator 03</div>
|
||
<h2>Physics values that <em>auto-propagate</em></h2>
|
||
<p class="subtitle">In other frameworks, spring animations are separate from state. In DreamStack, a spring
|
||
IS a signal — set its target and every derived value, every view binding, every layout constraint
|
||
updates automatically through the reactive graph. Physics and reactivity are one system.</p>
|
||
</div>
|
||
|
||
<div class="comparison">
|
||
<div class="code-card dreamstack">
|
||
<div class="code-card-header">
|
||
<span class="label">⚡ DreamStack</span>
|
||
<span class="tag tag-unique">UNIQUE</span>
|
||
</div>
|
||
<pre><span class="cm">// A spring IS a signal — same API, same graph</span>
|
||
<span class="kw">let</span> sidebar_w = <span class="fn">spring</span>(
|
||
target: <span class="num">240</span>,
|
||
stiffness: <span class="num">170</span>,
|
||
damping: <span class="num">26</span>
|
||
) <span class="cm">// → Spring<Float> (is-a Signal<Float>)</span>
|
||
|
||
<span class="cm">// Derived values track spring physics!</span>
|
||
<span class="kw">let</span> main_w = <span class="num">1000</span> - sidebar_w
|
||
<span class="cm">// main_w smoothly animates as sidebar springs</span>
|
||
|
||
<span class="cm">// Layout constraints + springs = magic</span>
|
||
<span class="kw">view</span> main = <span class="kw">row</span> [
|
||
panel { width: sidebar_w } <span class="cm">// springs!</span>
|
||
panel { width: main_w } <span class="cm">// auto-follows!</span>
|
||
]
|
||
|
||
<span class="kw">on</span> toggle <span class="op">-></span> sidebar_w.target = <span class="num">64</span>
|
||
<span class="cm">// Everything animates. Zero manual work.</span></pre>
|
||
</div>
|
||
|
||
<div class="code-card others">
|
||
<div class="code-card-header">
|
||
<span class="label">Framer Motion / React Spring</span>
|
||
<span class="tag tag-impossible">IMPOSSIBLE</span>
|
||
</div>
|
||
<pre><span class="cm">// Animation is separate from state</span>
|
||
<span class="kw">const</span> [sidebar, api] = <span class="fn">useSpring</span>({
|
||
width: expanded ? <span class="num">240</span> : <span class="num">64</span>
|
||
})
|
||
|
||
<span class="cm">// Want main panel to follow? Manual math:</span>
|
||
<span class="kw">const</span> main = sidebar.width.<span class="fn">to</span>(
|
||
w <span class="op">=></span> <span class="num">1000</span> - w
|
||
) <span class="cm">// ← manual interpolation per value</span>
|
||
|
||
<span class="cm">// Want derived-of-derived? More manual work.</span>
|
||
<span class="cm">// Want 5 things to follow? 5× manual.</span>
|
||
<span class="cm">// Physics never feeds back into the</span>
|
||
<span class="cm">// reactive graph. Two separate worlds.</span>
|
||
<span class="cm">//</span>
|
||
<span class="cm">// Spring ≠ Signal. They can't talk.</span></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Interactive spring demo -->
|
||
<div class="live-spring-demo">
|
||
<div style="font-size: 13px; font-weight: 600; margin-bottom: 8px;">Live: click anywhere on the track →
|
||
spring physics</div>
|
||
<div class="spring-track" id="springTrack">
|
||
<div class="spring-ball" id="springBall" style="left: 50%"></div>
|
||
</div>
|
||
<div class="spring-info">
|
||
<div>position: <span id="springPos">50.0</span>%</div>
|
||
<div>velocity: <span id="springVel">0.0</span></div>
|
||
<div>target: <span id="springTarget">50.0</span>%</div>
|
||
<div>stiffness: <span>170</span></div>
|
||
<div>damping: <span>26</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Interactive layout demo -->
|
||
<div class="live-spring-demo" style="margin-top: 16px">
|
||
<div style="font-size: 13px; font-weight: 600; margin-bottom: 8px;">Live: constraint-based layout — click to
|
||
toggle sidebar</div>
|
||
<div class="layout-viz" id="layoutViz">
|
||
<div class="layout-panel layout-sidebar" id="layoutSidebar">sidebar: <span id="sidebarW">200</span>px
|
||
</div>
|
||
<div class="layout-panel layout-main" id="layoutMain">main: <span id="mainW">auto</span></div>
|
||
</div>
|
||
<div class="spring-info">
|
||
<div>constraint: <span>sidebar + main = 100%</span></div>
|
||
<div>sidebar.min: <span>64px</span></div>
|
||
<div>solver: <span>Cassowary</span></div>
|
||
</div>
|
||
<button id="toggleLayout"
|
||
style="margin-top: 12px; padding: 8px 20px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); cursor: pointer; font-family: inherit; font-size: 13px;">Toggle
|
||
sidebar ⟷</button>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sep"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- #4: COMPILE-TIME DEPENDENCY GRAPH -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<div class="section-num">Differentiator 04</div>
|
||
<h2>The compiler <em>sees the whole graph</em></h2>
|
||
<p class="subtitle">In React/Solid, dependencies are discovered at runtime. DreamStack's compiler statically
|
||
analyzes the entire signal dependency graph before a single line runs. Dead signals get eliminated.
|
||
Over-subscription is impossible.</p>
|
||
</div>
|
||
|
||
<div class="diagram-row">
|
||
<div class="diagram-node signal float-1">count: Signal<Int></div>
|
||
<div class="diagram-arrow">→</div>
|
||
<div class="diagram-node derived float-2">doubled: Derived<Int></div>
|
||
<div class="diagram-arrow">→</div>
|
||
<div class="diagram-node spring float-3">anim_x: Spring<Float></div>
|
||
<div class="diagram-arrow">→</div>
|
||
<div class="diagram-node view float-4">view main: View</div>
|
||
</div>
|
||
|
||
<div class="diagram-row" style="margin-top: -10px">
|
||
<div class="diagram-node signal float-2">query: Signal<String></div>
|
||
<div class="diagram-arrow">→</div>
|
||
<div class="diagram-node effect float-3">search: ! Http, Time</div>
|
||
<div class="diagram-arrow">→</div>
|
||
<div class="diagram-node derived float-4">results: Derived<[Item]></div>
|
||
</div>
|
||
|
||
<div class="comparison" style="margin-top: 40px">
|
||
<div class="code-card dreamstack">
|
||
<div class="code-card-header">
|
||
<span class="label">⚡ DreamStack Compiler Output</span>
|
||
<span class="tag tag-unique">STATIC ANALYSIS</span>
|
||
</div>
|
||
<pre><span class="cm">// The compiler produces this at BUILD TIME:</span>
|
||
<span class="ty">Signal Graph</span> {
|
||
<span class="sig">count</span>: Source(Int) → [doubled, is_hot, view]
|
||
<span class="sig">doubled</span>: Derived(Int) ← [count]
|
||
<span class="sig">is_hot</span>: Derived(Bool) ← [count]
|
||
|
||
<span class="cm">// Dead signal detection:</span>
|
||
<span class="eff">⚠ unused_var</span>: Source(String) — <span class="cm">DEAD, eliminated</span>
|
||
|
||
<span class="cm">// Optimal subscription plan:</span>
|
||
view.main subscribes to: [count, doubled, is_hot]
|
||
<span class="cm">// NOT to unused_var — zero wasted work</span>
|
||
}</pre>
|
||
</div>
|
||
|
||
<div class="code-card others">
|
||
<div class="code-card-header">
|
||
<span class="label">React / Solid / Vue</span>
|
||
<span class="tag tag-impossible">RUNTIME ONLY</span>
|
||
</div>
|
||
<pre><span class="cm">// Dependency discovery happens AT RUNTIME</span>
|
||
<span class="cm">// The compiler has NO IDEA what subscribes to what</span>
|
||
|
||
<span class="fn">useEffect</span>(() <span class="op">=></span> {
|
||
<span class="cm">// React: "I'll figure it out when it runs"</span>
|
||
<span class="cm">// Missing dep? Silent stale closure bug.</span>
|
||
<span class="cm">// Extra dep? Runs too often.</span>
|
||
setDoubled(count * <span class="num">2</span>)
|
||
}, [count]) <span class="cm">// ← YOU must list deps manually</span>
|
||
|
||
<span class="cm">// In Solid: auto-tracking is runtime-only.</span>
|
||
<span class="cm">// Unused signals still exist in memory.</span>
|
||
<span class="cm">// Over-subscription? No compiler to warn you.</span></pre>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sep"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- #5: INTEGRATED CONSTRAINT LAYOUT -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<div class="section-num">Differentiator 05</div>
|
||
<h2>Layout is <em>constraints</em>, not CSS hacks</h2>
|
||
<p class="subtitle">CSS was designed for documents, not UIs. DreamStack uses a Cassowary constraint solver —
|
||
the same algorithm Apple uses for Auto Layout. Declare relationships between elements, and the solver
|
||
finds the solution. No <code>flex-grow: 1; min-width: 0; overflow: hidden</code> nightmares.</p>
|
||
</div>
|
||
|
||
<div class="comparison">
|
||
<div class="code-card dreamstack">
|
||
<div class="code-card-header">
|
||
<span class="label">⚡ DreamStack</span>
|
||
<span class="tag tag-unique">CONSTRAINT SOLVER</span>
|
||
</div>
|
||
<pre><span class="cm">// Declare relationships. Solver does the math.</span>
|
||
<span class="kw">constraint</span> sidebar.width == <span class="num">200</span> @ Strong
|
||
<span class="kw">constraint</span> sidebar.width >= <span class="num">64</span> @ Required
|
||
<span class="kw">constraint</span> main.x == sidebar.x + sidebar.width
|
||
<span class="kw">constraint</span> main.x + main.width == <span class="num">1000</span>
|
||
|
||
<span class="cm">// The solver guarantees:</span>
|
||
<span class="cm">// • sidebar = 200, main = 800 (normal)</span>
|
||
<span class="cm">// • sidebar = 64, main = 936 (collapsed)</span>
|
||
<span class="cm">// • NEVER sidebar = -1 or main = NaN</span>
|
||
|
||
<span class="cm">// Bonus: constraint + spring = animated layout</span>
|
||
<span class="kw">constraint</span> sidebar.width == sidebar_spring
|
||
<span class="cm">// Physics drives layout. Solver maintains truth.</span></pre>
|
||
</div>
|
||
|
||
<div class="code-card others">
|
||
<div class="code-card-header">
|
||
<span class="label">CSS / Flexbox / Grid</span>
|
||
<span class="tag tag-impossible">NO SOLVER</span>
|
||
</div>
|
||
<pre><span class="cm">/* Hope and pray this works: */</span>
|
||
<span class="kw">.sidebar</span> {
|
||
width: <span class="num">200</span>px;
|
||
min-width: <span class="num">64</span>px;
|
||
flex-shrink: <span class="num">0</span>;
|
||
transition: width <span class="num">0.3</span>s;
|
||
}
|
||
<span class="kw">.main</span> {
|
||
flex: <span class="num">1</span>;
|
||
min-width: <span class="num">0</span>; <span class="cm">/* ← the classic hack */</span>
|
||
overflow: hidden; <span class="cm">/* ← another hack */</span>
|
||
}
|
||
|
||
<span class="cm">/* What if sidebar + main > viewport? */</span>
|
||
<span class="cm">/* What if content pushes sidebar wider? */</span>
|
||
<span class="cm">/* What if animation causes flash of wrong layout? */</span>
|
||
<span class="cm">/* CSS: ¯\_(ツ)_/¯ */</span></pre>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="sep"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- SUMMARY TABLE -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<section class="section">
|
||
<div class="section-header">
|
||
<h2>The Full Picture</h2>
|
||
<p class="subtitle">Five capabilities. Zero competitors have all five. Most have zero.</p>
|
||
</div>
|
||
|
||
<table class="comparison-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Capability</th>
|
||
<th style="text-align:center">DreamStack</th>
|
||
<th style="text-align:center">React</th>
|
||
<th style="text-align:center">Svelte 5</th>
|
||
<th style="text-align:center">Solid</th>
|
||
<th style="text-align:center">Vue</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="feature-name">Signals in the type system</td>
|
||
<td style="text-align:center"><span class="dot-yes">✓</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="feature-name">Algebraic effects (testable side-effects)</td>
|
||
<td style="text-align:center"><span class="dot-yes">✓</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="feature-name">Springs are signals (unified graph)</td>
|
||
<td style="text-align:center"><span class="dot-yes">✓</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="feature-name">Compile-time dependency graph</td>
|
||
<td style="text-align:center"><span class="dot-yes">✓</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-partial">~</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="feature-name">Constraint-based layout (Cassowary)</td>
|
||
<td style="text-align:center"><span class="dot-yes">✓</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
<td style="text-align:center"><span class="dot-no">✕</span></td>
|
||
</tr>
|
||
<tr style="border-top: 2px solid var(--border)">
|
||
<td class="feature-name" style="font-weight: 700">Score</td>
|
||
<td style="text-align:center; font-weight: 700; color: var(--green)">5/5</td>
|
||
<td style="text-align:center; font-weight: 700; color: var(--red)">0/5</td>
|
||
<td style="text-align:center; font-weight: 700; color: var(--yellow)">~0.5/5</td>
|
||
<td style="text-align:center; font-weight: 700; color: var(--red)">0/5</td>
|
||
<td style="text-align:center; font-weight: 700; color: var(--red)">0/5</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<div style="height: 80px"></div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<!-- SCRIPTS -->
|
||
<!-- ═══════════════════════════════════════════════════ -->
|
||
<script>
|
||
// ─── Spring Physics Engine (live demo) ──────────────
|
||
|
||
class Spring {
|
||
constructor(target, stiffness = 170, damping = 26, mass = 1) {
|
||
this.target = target;
|
||
this.position = target;
|
||
this.velocity = 0;
|
||
this.stiffness = stiffness;
|
||
this.damping = damping;
|
||
this.mass = mass;
|
||
}
|
||
|
||
step(dt) {
|
||
const force = -this.stiffness * (this.position - this.target);
|
||
const dampingForce = -this.damping * this.velocity;
|
||
const acceleration = (force + dampingForce) / this.mass;
|
||
this.velocity += acceleration * dt;
|
||
this.position += this.velocity * dt;
|
||
}
|
||
|
||
isAtRest() {
|
||
return Math.abs(this.velocity) < 0.01 && Math.abs(this.position - this.target) < 0.1;
|
||
}
|
||
}
|
||
|
||
// Spring demo
|
||
const spring = new Spring(50);
|
||
const ball = document.getElementById('springBall');
|
||
const posEl = document.getElementById('springPos');
|
||
const velEl = document.getElementById('springVel');
|
||
const targetEl = document.getElementById('springTarget');
|
||
const track = document.getElementById('springTrack');
|
||
|
||
track.addEventListener('click', (e) => {
|
||
const rect = track.getBoundingClientRect();
|
||
const pct = ((e.clientX - rect.left) / rect.width) * 100;
|
||
spring.target = Math.max(3, Math.min(97, pct));
|
||
targetEl.textContent = spring.target.toFixed(1);
|
||
});
|
||
|
||
function animateSpring() {
|
||
requestAnimationFrame(animateSpring);
|
||
const substeps = 8;
|
||
const dt = 1 / 60 / substeps;
|
||
for (let i = 0; i < substeps; i++) {
|
||
spring.step(dt);
|
||
}
|
||
ball.style.left = spring.position + '%';
|
||
posEl.textContent = spring.position.toFixed(1);
|
||
velEl.textContent = spring.velocity.toFixed(1);
|
||
}
|
||
animateSpring();
|
||
|
||
// ─── Layout constraint demo ─────────────────────────
|
||
|
||
let sidebarExpanded = true;
|
||
const layoutViz = document.getElementById('layoutViz');
|
||
const sidebarWEl = document.getElementById('sidebarW');
|
||
const mainWEl = document.getElementById('mainW');
|
||
|
||
document.getElementById('toggleLayout').addEventListener('click', () => {
|
||
sidebarExpanded = !sidebarExpanded;
|
||
const sidebarW = sidebarExpanded ? 200 : 64;
|
||
layoutViz.style.gridTemplateColumns = `${sidebarW}px 1fr`;
|
||
sidebarWEl.textContent = sidebarW;
|
||
mainWEl.textContent = `${layoutViz.offsetWidth - sidebarW}`;
|
||
});
|
||
|
||
// Update main width on resize
|
||
const resizeObs = new ResizeObserver(() => {
|
||
const sidebarW = sidebarExpanded ? 200 : 64;
|
||
mainWEl.textContent = `${layoutViz.offsetWidth - sidebarW}`;
|
||
});
|
||
resizeObs.observe(layoutViz);
|
||
</script>
|
||
</body>
|
||
|
||
</html> |