feat: add projects showcase and Lux-powered static file server

- Add website/serve.lux: static file server using HttpServer effect
  - Demonstrates serving the Lux website with Lux itself
  - Handles index files, clean URLs, and 404 responses

- Add website/projects/index.html: example projects showcase
  - Features 6 real project cards (REST API, Todo App, JSON Parser, etc.)
  - Highlights Task Manager API demonstrating all 3 killer features
  - Links to full source code in the repository

- Update examples sidebar with Projects section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 23:34:17 -05:00
parent 7e76acab18
commit 52e3876b81
3 changed files with 530 additions and 0 deletions

View File

@@ -194,6 +194,14 @@
<li><a href="middleware.html">Middleware</a></li>
<li><a href="routing.html">Routing</a></li>
</ul>
<h2>Projects</h2>
<ul>
<li><a href="/projects/">All Projects</a></li>
<li><a href="/projects/#rest-api">REST API</a></li>
<li><a href="/projects/#todo-app">Todo App</a></li>
<li><a href="/projects/#json-parser">JSON Parser</a></li>
</ul>
</aside>
<div class="examples-content">

456
website/projects/index.html Normal file
View File

@@ -0,0 +1,456 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projects - Lux</title>
<meta name="description" content="Real-world example projects demonstrating Lux's capabilities.">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>&#10024;</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Playfair+Display:wght@400;600;700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../static/style.css">
<style>
.projects-container {
max-width: 1100px;
margin: 0 auto;
padding: var(--space-xl) var(--space-lg);
}
.projects-header {
text-align: center;
margin-bottom: var(--space-2xl);
}
.projects-header p {
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--space-lg);
}
.project-card {
background: var(--bg-glass);
border: 1px solid var(--border-subtle);
border-radius: 12px;
padding: var(--space-lg);
transition: border-color 0.3s ease, transform 0.2s ease;
}
.project-card:hover {
border-color: var(--border-gold);
transform: translateY(-2px);
}
.project-card h2 {
font-size: 1.25rem;
margin-bottom: var(--space-sm);
text-align: left;
display: flex;
align-items: center;
gap: var(--space-sm);
}
.project-card h2 .icon {
font-size: 1.5rem;
}
.project-card .description {
color: var(--text-secondary);
margin-bottom: var(--space-md);
line-height: 1.6;
}
.project-card .features {
margin-bottom: var(--space-md);
}
.project-card .features h3 {
font-size: 0.85rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--space-xs);
}
.project-card .features ul {
list-style: none;
padding: 0;
}
.project-card .features li {
color: var(--text-secondary);
font-size: 0.9rem;
padding: 0.2rem 0;
}
.project-card .features li::before {
content: "\2022";
color: var(--gold);
margin-right: var(--space-xs);
}
.project-card .code-preview {
background: var(--code-bg);
border: 1px solid var(--code-border);
border-radius: 6px;
padding: var(--space-md);
margin-bottom: var(--space-md);
overflow-x: auto;
}
.project-card .code-preview pre {
font-family: var(--font-code);
font-size: 0.85rem;
line-height: 1.5;
margin: 0;
}
.project-card .actions {
display: flex;
gap: var(--space-sm);
}
.project-card .actions a {
padding: var(--space-xs) var(--space-md);
font-size: 0.9rem;
}
.showcase-section {
margin-top: var(--space-3xl);
padding-top: var(--space-2xl);
border-top: 1px solid var(--border-subtle);
}
.showcase-section h2 {
text-align: center;
margin-bottom: var(--space-xl);
}
.showcase-card {
background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
border: 1px solid var(--border-gold);
border-radius: 16px;
padding: var(--space-xl);
margin-bottom: var(--space-xl);
}
.showcase-card h3 {
font-size: 1.5rem;
color: var(--gold);
margin-bottom: var(--space-md);
text-align: left;
}
.showcase-card p {
color: var(--text-secondary);
margin-bottom: var(--space-lg);
font-size: 1.1rem;
}
.showcase-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--space-lg);
margin-bottom: var(--space-xl);
}
.showcase-feature {
background: var(--bg-glass);
border-radius: 8px;
padding: var(--space-md);
}
.showcase-feature h4 {
color: var(--gold);
font-size: 1rem;
margin-bottom: var(--space-xs);
}
.showcase-feature p {
color: var(--text-muted);
font-size: 0.9rem;
margin: 0;
}
.showcase-code {
background: var(--code-bg);
border: 1px solid var(--code-border);
border-radius: 8px;
padding: var(--space-lg);
overflow-x: auto;
}
.showcase-code pre {
font-family: var(--font-code);
font-size: 0.9rem;
line-height: 1.6;
margin: 0;
}
</style>
</head>
<body>
<nav>
<a href="/" class="logo">Lux</a>
<ul class="nav-links" id="nav-links">
<li><a href="/install">Install</a></li>
<li><a href="/tour/">Tour</a></li>
<li><a href="/examples/">Examples</a></li>
<li><a href="/docs/">Docs</a></li>
<li><a href="/play">Play</a></li>
<li><a href="https://git.qrty.ink/blu/lux" class="nav-source">Source</a></li>
</ul>
</nav>
<main class="projects-container">
<header class="projects-header">
<h1>Example Projects</h1>
<p>Real-world applications demonstrating Lux's unique capabilities. Clone these to learn by doing.</p>
</header>
<div class="projects-grid">
<div class="project-card">
<h2><span class="icon">API</span> REST API</h2>
<p class="description">
A full REST API for task management with JSON responses, routing, and CRUD operations.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>HttpServer effect</li>
<li>Pattern matching for routing</li>
<li>JSON serialization</li>
<li>Effect tracking</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">fn</span> <span class="fn">router</span>(req): Response <span class="kw">with</span> {<span class="ef">Http</span>} =
<span class="kw">match</span> (req.method, req.path) {
(<span class="st">"GET"</span>, <span class="st">"/"</span>) => httpOk(welcome),
(<span class="st">"GET"</span>, <span class="st">"/tasks"</span>) => httpOk(tasks),
_ => httpNotFound(<span class="st">"404"</span>)
}</code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/rest-api" class="btn btn-primary">View Code</a>
</div>
</div>
<div class="project-card">
<h2><span class="icon">TODO</span> Todo App</h2>
<p class="description">
A command-line todo application showcasing algebraic data types and pattern matching.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>ADTs for data modeling</li>
<li>Pattern matching</li>
<li>Recursive list operations</li>
<li>Console effect</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">type</span> <span class="ty">Priority</span> =
| <span class="ty">Low</span>
| <span class="ty">Medium</span>
| <span class="ty">High</span>
<span class="kw">type</span> <span class="ty">TodoItem</span> =
| <span class="ty">TodoItem</span>(Int, String, Bool, Priority)</code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/todo-app" class="btn btn-primary">View Code</a>
</div>
</div>
<div class="project-card">
<h2><span class="icon">JSON</span> JSON Parser</h2>
<p class="description">
A recursive descent JSON parser demonstrating ADTs for AST representation.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>ADTs for AST</li>
<li>Recursive parsing</li>
<li>String manipulation</li>
<li>Pure functions</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">type</span> <span class="ty">JsonValue</span> =
| <span class="ty">JsonNull</span>
| <span class="ty">JsonBool</span>(Bool)
| <span class="ty">JsonNumber</span>(Int)
| <span class="ty">JsonString</span>(String)
| <span class="ty">JsonArray</span>(List&lt;JsonValue&gt;)
| <span class="ty">JsonObject</span>(List&lt;(String, JsonValue)&gt;)</code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/json-parser" class="btn btn-primary">View Code</a>
</div>
</div>
<div class="project-card">
<h2><span class="icon">GAME</span> Guessing Game</h2>
<p class="description">
A simple number guessing game demonstrating effects for randomness and I/O.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>Random effect</li>
<li>Console I/O</li>
<li>Recursive game loop</li>
<li>User input handling</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">fn</span> <span class="fn">playGame</span>(): Unit
<span class="kw">with</span> {<span class="ef">Console</span>, <span class="ef">Random</span>} = {
<span class="kw">let</span> secret = Random.int(<span class="num">1</span>, <span class="num">100</span>)
Console.print(<span class="st">"Guess a number!"</span>)
guessLoop(secret, <span class="num">0</span>)
}</code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/guessing-game" class="btn btn-primary">View Code</a>
</div>
</div>
<div class="project-card">
<h2><span class="icon">MD</span> Markdown Converter</h2>
<p class="description">
Convert Markdown to HTML using pattern matching and string processing.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>String manipulation</li>
<li>Pattern matching</li>
<li>Pure transformation</li>
<li>List processing</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">fn</span> <span class="fn">convertLine</span>(line: String): String =
<span class="kw">if</span> startsWith(line, <span class="st">"# "</span>) <span class="kw">then</span>
<span class="st">"&lt;h1&gt;"</span> + rest(line, <span class="num">2</span>) + <span class="st">"&lt;/h1&gt;"</span>
<span class="kw">else if</span> startsWith(line, <span class="st">"- "</span>) <span class="kw">then</span>
<span class="st">"&lt;li&gt;"</span> + rest(line, <span class="num">2</span>) + <span class="st">"&lt;/li&gt;"</span>
<span class="kw">else</span> <span class="st">"&lt;p&gt;"</span> + line + <span class="st">"&lt;/p&gt;"</span></code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/markdown-converter" class="btn btn-primary">View Code</a>
</div>
</div>
<div class="project-card">
<h2><span class="icon">CALC</span> Mini Interpreter</h2>
<p class="description">
A tiny expression interpreter demonstrating language implementation patterns.
</p>
<div class="features">
<h3>Demonstrates</h3>
<ul>
<li>ADTs for AST</li>
<li>Recursive evaluation</li>
<li>Environment handling</li>
<li>Interpreter patterns</li>
</ul>
</div>
<div class="code-preview">
<pre><code><span class="kw">type</span> <span class="ty">Expr</span> =
| <span class="ty">Num</span>(Int)
| <span class="ty">Add</span>(Expr, Expr)
| <span class="ty">Mul</span>(Expr, Expr)
| <span class="ty">Var</span>(String)
| <span class="ty">Let</span>(String, Expr, Expr)</code></pre>
</div>
<div class="actions">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/projects/mini-interpreter" class="btn btn-primary">View Code</a>
</div>
</div>
</div>
<!-- Featured Showcase -->
<section class="showcase-section">
<h2>Featured: Task Manager API</h2>
<div class="showcase-card">
<h3>A Complete Showcase of Lux's Unique Features</h3>
<p>This comprehensive example demonstrates all three of Lux's killer features working together.</p>
<div class="showcase-features">
<div class="showcase-feature">
<h4>1. Algebraic Effects</h4>
<p>Every side effect is explicit in function signatures. No hidden I/O.</p>
</div>
<div class="showcase-feature">
<h4>2. Behavioral Types</h4>
<p>Compile-time guarantees: <code>is pure</code>, <code>is total</code>, <code>is idempotent</code>.</p>
</div>
<div class="showcase-feature">
<h4>3. Schema Evolution</h4>
<p>Versioned types with automatic migration. Data structures evolve safely.</p>
</div>
</div>
<div class="showcase-code">
<pre><code><span class="cm">// Task v1: Original data model</span>
<span class="kw">type</span> <span class="ty">Task</span> <span class="cm">@v1</span> {
id: String,
title: String,
done: Bool
}
<span class="cm">// Task v2: Added priority field with migration</span>
<span class="kw">type</span> <span class="ty">Task</span> <span class="cm">@v2</span> {
id: String,
title: String,
done: Bool,
priority: String,
<span class="cm">// Old tasks get "medium" priority by default</span>
<span class="kw">from</span> <span class="cm">@v1</span> = {
id: old.id,
title: old.title,
done: old.done,
priority: <span class="st">"medium"</span>
}
}
<span class="cm">// Pure, total, idempotent business logic</span>
<span class="kw">fn</span> <span class="fn">validateTitle</span>(title: String): <span class="ty">Result</span>&lt;String, String&gt;
<span class="kw">is</span> <span class="ty">pure</span>, <span class="ty">total</span> =
<span class="kw">if</span> String.length(title) == <span class="num">0</span> <span class="kw">then</span>
Err(<span class="st">"Title cannot be empty"</span>)
<span class="kw">else</span>
Ok(title)</code></pre>
</div>
<div class="actions" style="margin-top: var(--space-lg);">
<a href="https://git.qrty.ink/blu/lux/src/branch/master/examples/showcase/task_manager.lux" class="btn btn-primary">View Full Code</a>
<a href="/tour/06-effects-intro.html" class="btn btn-secondary">Learn About Effects</a>
</div>
</div>
</section>
<!-- Get Started -->
<section class="showcase-section" style="text-align: center;">
<h2>Build Your Own</h2>
<p style="margin-bottom: var(--space-lg); color: var(--text-secondary);">Ready to start building with Lux?</p>
<div style="display: flex; gap: var(--space-md); justify-content: center; flex-wrap: wrap;">
<a href="/install" class="btn btn-primary">Install Lux</a>
<a href="/tour/" class="btn btn-secondary">Take the Tour</a>
<a href="/examples/" class="btn btn-tertiary">Browse Examples</a>
</div>
</section>
</main>
</body>
</html>

66
website/serve.lux Normal file
View File

@@ -0,0 +1,66 @@
// Static File Server for Lux Website
//
// Usage: lux website/serve.lux
// Then open http://localhost:8080
//
// This demonstrates serving the Lux website using Lux's own HTTP server!
fn main(): Unit with {HttpServer, Console, File} = {
let port = 8080
let root = "website"
HttpServer.listen(port)
Console.print("Lux website server running at http://localhost:" + toString(port))
Console.print("Serving files from: " + root)
Console.print("Press Ctrl+C to stop")
Console.print("")
serverLoop(root)
}
fn serverLoop(root: String): Unit with {HttpServer, Console, File} = {
let req = HttpServer.accept()
// Log request
Console.print(req.method + " " + req.path)
// Only handle GET requests
if req.method != "GET" then {
HttpServer.respond(405, "Method Not Allowed")
serverLoop(root)
} else {
serveFile(root, req.path)
serverLoop(root)
}
}
fn serveFile(root: String, reqPath: String): Unit with {HttpServer, Console, File} = {
// Determine file path
let path = if reqPath == "/" then "/index.html" else reqPath
let filePath = root + path
// Try to serve the file
if File.exists(filePath) then {
let content = File.read(filePath)
HttpServer.respond(200, content)
} else {
// Try with .html extension for clean URLs
let htmlPath = filePath + ".html"
if File.exists(htmlPath) then {
let content = File.read(htmlPath)
HttpServer.respond(200, content)
} else {
// Try index.html for directory paths
let indexPath = filePath + "/index.html"
if File.exists(indexPath) then {
let content = File.read(indexPath)
HttpServer.respond(200, content)
} else {
Console.print(" -> 404 Not Found: " + filePath)
HttpServer.respond(404, "Not Found: " + reqPath)
}
}
}
}
let output = run main() with {}