website changes
This commit is contained in:
24
.idea/workspace.xml
generated
24
.idea/workspace.xml
generated
@@ -11,12 +11,11 @@
|
|||||||
</cargoProject>
|
</cargoProject>
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b9851397-889b-45d6-bfac-85598560e053" name="Changes" comment="init rust stuff">
|
<list default="true" id="b9851397-889b-45d6-bfac-85598560e053" name="Changes" comment="worker fixes">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/mis-interpreter.iml" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/wrangler.toml" beforeDir="false" afterPath="$PROJECT_DIR$/wrangler.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/wrangler.toml" beforeDir="false" afterPath="$PROJECT_DIR$/wrangler.toml" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@@ -91,7 +90,7 @@
|
|||||||
<option name="number" value="Default" />
|
<option name="number" value="Default" />
|
||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1761613870150</updated>
|
<updated>1761613870150</updated>
|
||||||
<workItem from="1761613871160" duration="2196000" />
|
<workItem from="1761613871160" duration="6548000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="init rust stuff">
|
<task id="LOCAL-00001" summary="init rust stuff">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -101,7 +100,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1761613910534</updated>
|
<updated>1761613910534</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="2" />
|
<task id="LOCAL-00002" summary="worker fixes">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1761616087329</created>
|
||||||
|
<option name="number" value="00002" />
|
||||||
|
<option name="presentableId" value="LOCAL-00002" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1761616087329</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="3" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -109,6 +116,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<MESSAGE value="init rust stuff" />
|
<MESSAGE value="init rust stuff" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="init rust stuff" />
|
<MESSAGE value="worker fixes" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="worker fixes" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
77
Cargo.lock
generated
77
Cargo.lock
generated
@@ -26,7 +26,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
|
checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
"axum-macros",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"form_urlencoded",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
@@ -38,6 +40,8 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
@@ -62,6 +66,17 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-macros"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.0"
|
version = "3.19.0"
|
||||||
@@ -353,6 +368,28 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"maud_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud_macros"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"proc-macro2-diagnostics",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.7.6"
|
||||||
@@ -370,7 +407,12 @@ name = "mis-interpreter"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"futures-util",
|
||||||
|
"maud",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"urlencoding",
|
||||||
"worker",
|
"worker",
|
||||||
"worker-macros",
|
"worker-macros",
|
||||||
]
|
]
|
||||||
@@ -446,6 +488,18 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2-diagnostics"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.41"
|
version = "1.0.41"
|
||||||
@@ -521,6 +575,17 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_path_to_error"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -642,12 +707,24 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.105"
|
version = "0.2.105"
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -7,13 +7,25 @@ authors = ["Rivulet <cadenream@cadencoaster.com>"]
|
|||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
# (Wrangler will ignore these; fine to keep or remove)
|
||||||
[package.metadata.wasm-pack]
|
[package.metadata.wasm-pack]
|
||||||
target = "web"
|
target = "web"
|
||||||
out-dir = "build"
|
out-dir = "build"
|
||||||
out-name = "index"
|
out-name = "index"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
worker = { version = "0.6", features = ['http', 'axum'] }
|
# Cloudflare Workers (HTTP + Axum adapter)
|
||||||
worker-macros = { version = "0.6", features = ['http'] }
|
worker = { version = "0.6", features = ["http", "axum"] }
|
||||||
axum = { version = "0.8", default-features = false }
|
worker-macros = { version = "0.6", features = ["http"] }
|
||||||
|
|
||||||
|
# Axum *without* tokio/hyper; we don't use its WS, so don't enable it.
|
||||||
|
axum = { version = "0.8", default-features = false, features = ["form", "macros"] }
|
||||||
|
|
||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
|
|
||||||
|
maud = { version = "0.27.0", default-features = false }
|
||||||
|
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
urlencoding = "2.1.3"
|
||||||
|
futures-util = "0.3.31"
|
||||||
|
|||||||
339
src/lib.rs
339
src/lib.rs
@@ -1,20 +1,339 @@
|
|||||||
use axum::{routing::get, Router};
|
use axum::{
|
||||||
|
body::{to_bytes, Body},
|
||||||
|
http::{Response as AxumResponse, StatusCode},
|
||||||
|
response::Html,
|
||||||
|
routing::{get, post},
|
||||||
|
Form, Router,
|
||||||
|
};
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use maud::{html, Markup};
|
||||||
|
use serde::Deserialize;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
use worker::*;
|
use worker::*;
|
||||||
|
|
||||||
|
/* ===================== Router ===================== */
|
||||||
|
|
||||||
fn router() -> Router {
|
fn router() -> Router {
|
||||||
Router::new().route("/", get(root))
|
Router::new()
|
||||||
|
.route("/", get(root))
|
||||||
|
.route("/create", post(create_game))
|
||||||
|
// /ws is handled in fetch() using CF WebSocketPair.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================== Cloudflare entry ===================== */
|
||||||
|
|
||||||
#[event(fetch)]
|
#[event(fetch)]
|
||||||
async fn fetch(
|
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<Response> {
|
||||||
req: HttpRequest,
|
// Intercept WebSocket before handing off to Axum
|
||||||
_env: Env,
|
let is_ws = req
|
||||||
_ctx: Context,
|
.headers()
|
||||||
) -> Result<axum::http::Response<axum::body::Body>> {
|
.get("Upgrade")
|
||||||
Ok(router().call(req).await?)
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(|v| v.eq_ignore_ascii_case("websocket"))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if req.uri().path() == "/ws" && is_ws {
|
||||||
|
return handle_ws_upgrade(req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn root() -> &'static str {
|
// Otherwise hand off to Axum and adapt Axum -> Worker Response
|
||||||
"Hello Axum!"
|
let axum_resp: AxumResponse<Body> = router().call(req).await?;
|
||||||
|
axum_to_worker(axum_resp).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Convert an Axum response into a worker::Response */
|
||||||
|
async fn axum_to_worker(ax: AxumResponse<Body>) -> Result<Response> {
|
||||||
|
let (parts, body) = ax.into_parts();
|
||||||
|
let bytes = to_bytes(body, usize::MAX).await.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut resp = Response::from_bytes(bytes.to_vec())?;
|
||||||
|
resp = resp.with_status(parts.status.as_u16());
|
||||||
|
|
||||||
|
for (name, value) in parts.headers.iter() {
|
||||||
|
if let Ok(val) = value.to_str() {
|
||||||
|
resp.headers_mut().set(name.as_str(), val)?; // <- note: no ? after headers_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== Pages ===================== */
|
||||||
|
|
||||||
|
/// GET /
|
||||||
|
pub async fn root() -> Html<String> {
|
||||||
|
// If you want to read cookies here, switch to a custom Response and pass headers in via extractors.
|
||||||
|
let page = render_page(None);
|
||||||
|
Html::from(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /create (classic PRG with cookie flash)
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreateForm {
|
||||||
|
game_name: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
player_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_game(Form(form): Form<CreateForm>) -> AxumResponse<Body> {
|
||||||
|
let msg = flash_msg(&form);
|
||||||
|
AxumResponse::builder()
|
||||||
|
.status(StatusCode::SEE_OTHER)
|
||||||
|
.header("Location", "/")
|
||||||
|
.header(
|
||||||
|
"Set-Cookie",
|
||||||
|
format!(
|
||||||
|
"flash={}; Path=/; Max-Age=30; HttpOnly; SameSite=Lax",
|
||||||
|
urlencoding::encode(&msg)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== WebSocket (HTMX ws-send) ===================== */
|
||||||
|
|
||||||
|
async fn handle_ws_upgrade(_req: HttpRequest) -> Result<Response> {
|
||||||
|
// Create the CF WebSocketPair at the worker layer
|
||||||
|
let pair = WebSocketPair::new()?;
|
||||||
|
let server = pair.server;
|
||||||
|
let client = pair.client;
|
||||||
|
|
||||||
|
server.accept()?;
|
||||||
|
|
||||||
|
// Spawn a local task to read messages and push OOB HTML back.
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
// Create the borrow-bound event stream *inside* the task and keep the clone alive.
|
||||||
|
let server_clone = server.clone();
|
||||||
|
let mut events = match server_clone.events() {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(Ok(evt)) = events.next().await {
|
||||||
|
if let WebsocketEvent::Message(m) = evt {
|
||||||
|
if let Some(txt) = m.text() {
|
||||||
|
if let Ok(parsed) = serde_json::from_str::<CreateForm>(&*txt) {
|
||||||
|
let msg = flash_msg(&parsed);
|
||||||
|
let oob = render_flash_oob(&msg);
|
||||||
|
let _ = server_clone.send_with_str(oob);
|
||||||
|
} else {
|
||||||
|
let _ = server_clone.send_with_str(render_log_oob("Unrecognized message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the upgrade response (must be worker::Response)
|
||||||
|
Response::from_websocket(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== View / HTML ===================== */
|
||||||
|
|
||||||
|
fn render_page(flash: Option<&str>) -> String {
|
||||||
|
(html! {
|
||||||
|
(maud::DOCTYPE)
|
||||||
|
html lang="en" {
|
||||||
|
head {
|
||||||
|
meta charset="utf-8";
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
|
title { "Mis-Interpreter" }
|
||||||
|
|
||||||
|
// HTMX + WS Extension
|
||||||
|
script src="https://unpkg.com/htmx.org@2.0.4" {}
|
||||||
|
script src="https://unpkg.com/htmx-ext-ws@2.0.3/ws.js" {}
|
||||||
|
|
||||||
|
style { (PASTEL_CSS) }
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
main class="shell" {
|
||||||
|
section class="card cartoon" {
|
||||||
|
h1 class="title" { "Mis-Interpreter" }
|
||||||
|
p class="subtitle" { "Create a game?" }
|
||||||
|
|
||||||
|
// Flash target
|
||||||
|
div id="flash" {
|
||||||
|
@if let Some(msg) = flash {
|
||||||
|
(render_flash_inline(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live form using htmx WebSocket
|
||||||
|
div hx-ext="ws" ws-connect="/ws" {
|
||||||
|
form id="create-form" class="form" ws-send {
|
||||||
|
label class="sr" for="game_name" { "Game Name" }
|
||||||
|
input id="game_name" type="text" name="game_name"
|
||||||
|
placeholder="Game Name (Optional)" class="input";
|
||||||
|
|
||||||
|
label class="sr" for="password" { "Password" }
|
||||||
|
input id="password" type="password" name="password"
|
||||||
|
placeholder="Password (Optional)" class="input";
|
||||||
|
|
||||||
|
label class="sr" for="player_name" { "Player Name" }
|
||||||
|
input id="player_name" type="text" name="player_name"
|
||||||
|
value="John Wick" class="input";
|
||||||
|
|
||||||
|
button type="submit" class="btn" { "Create (Live)" }
|
||||||
|
}
|
||||||
|
div id="ws-log" class="wslog" {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).into_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_flash_inline(msg: &str) -> Markup {
|
||||||
|
html! { div class="flash cartoon" { (escape(msg)) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_flash_oob(msg: &str) -> String {
|
||||||
|
format!(
|
||||||
|
r#"<div id="flash" hx-swap-oob="true"><div class="flash cartoon">{}</div></div>"#,
|
||||||
|
escape(msg)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_log_oob(text: &str) -> String {
|
||||||
|
format!(r#"<div id="ws-log" hx-swap-oob="true">{}</div>"#, escape(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== Utilities ===================== */
|
||||||
|
|
||||||
|
fn flash_msg(form: &CreateForm) -> String {
|
||||||
|
let game = form.game_name.as_deref().unwrap_or("Untitled");
|
||||||
|
let player = form.player_name.as_deref().unwrap_or("Anonymous");
|
||||||
|
let priv_tag = if form.password.as_deref().unwrap_or("").is_empty() { "" } else { " (private)" };
|
||||||
|
format!("Created game '{}' for '{}'{priv_tag}", escape(game), escape(player))
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimal safe escaping for text nodes
|
||||||
|
fn escape(s: &str) -> String {
|
||||||
|
s.replace('&', "&").replace('<', "<").replace('>', ">")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== Theme ===================== */
|
||||||
|
|
||||||
|
const PASTEL_CSS: &str = r#"
|
||||||
|
/* ===== Pastel Blues, Cartoon Borders ===== */
|
||||||
|
:root{
|
||||||
|
/* palette */
|
||||||
|
--sky-50:#eff6ff; --sky-100:#dbeafe; --sky-200:#bfdbfe; --sky-300:#93c5fd;
|
||||||
|
--sky-400:#60a5fa; --sky-500:#3b82f6; --indigo-600:#4f46e5; --teal-300:#99f6e4;
|
||||||
|
|
||||||
|
--bg-top: color-mix(in oklab, var(--sky-100) 70%, white);
|
||||||
|
--bg-bot: color-mix(in oklab, var(--teal-300) 35%, white);
|
||||||
|
|
||||||
|
--card: #f2f7ff;
|
||||||
|
--ink: #0b1220;
|
||||||
|
--muted:#44506a;
|
||||||
|
|
||||||
|
/* cartoon outlines */
|
||||||
|
--outline: #133a84; /* deep blue stroke */
|
||||||
|
--shadow: #0e2250; /* offset shadow for sticker look */
|
||||||
|
|
||||||
|
--ring: var(--sky-400);
|
||||||
|
--field-a: #e9f2ff;
|
||||||
|
--field-b: #e6fffb;
|
||||||
|
|
||||||
|
--btn-a: #a5b4ff; /* indigo pastel */
|
||||||
|
--btn-b: #7dd3fc; /* sky pastel */
|
||||||
|
|
||||||
|
--flash-bg:#c4f1f9; /* cyan-200 */
|
||||||
|
--flash-border:#0891b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
html,body{height:100%}
|
||||||
|
body{
|
||||||
|
margin:0;
|
||||||
|
font:17px/1.55 ui-sans-serif, system-ui, -apple-system, Segoe UI, Inter, Roboto, Arial;
|
||||||
|
color:var(--ink);
|
||||||
|
background:
|
||||||
|
radial-gradient(1000px 700px at 15% -10%, var(--sky-200) 0%, transparent 60%),
|
||||||
|
radial-gradient(1100px 800px at 110% 0%, var(--teal-300) 0%, transparent 60%),
|
||||||
|
linear-gradient(180deg, var(--bg-top), var(--bg-bot));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ——— cartoon helpers ——— */
|
||||||
|
.cartoon{
|
||||||
|
border:4px solid var(--outline);
|
||||||
|
box-shadow: 8px 8px 0 var(--shadow);
|
||||||
|
border-radius: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* layout */
|
||||||
|
.shell{min-height:100%; display:grid; place-items:center; padding:3rem 1rem}
|
||||||
|
.card{
|
||||||
|
width:min(740px, 94vw);
|
||||||
|
background: var(--card);
|
||||||
|
padding:3rem 2.5rem;
|
||||||
|
}
|
||||||
|
.card.cartoon{} /* (keep class for specificity) */
|
||||||
|
|
||||||
|
.title{
|
||||||
|
text-align:center; margin:0 0 .5rem; font-weight:900; letter-spacing:.3px;
|
||||||
|
font-size: clamp(2.1rem, 3.4vw + 1rem, 3.4rem);
|
||||||
|
/* shiny blue gradient text */
|
||||||
|
background: linear-gradient(90deg, var(--sky-500), var(--indigo-600));
|
||||||
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||||||
|
text-shadow: 0 2px 0 #ffffff66;
|
||||||
|
}
|
||||||
|
.subtitle{ text-align:center; color:var(--muted); margin:0 0 2rem; font-weight:700 }
|
||||||
|
|
||||||
|
.flash{
|
||||||
|
background: var(--flash-bg);
|
||||||
|
border:4px solid var(--flash-border);
|
||||||
|
color:#0c4a6e;
|
||||||
|
padding:.85rem 1rem; border-radius:16px; margin:0 0 1.25rem;
|
||||||
|
box-shadow: 6px 6px 0 var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* form */
|
||||||
|
.form{ display:grid; grid-template-columns:1fr auto; gap:16px 16px; align-items:center }
|
||||||
|
.input{
|
||||||
|
width:100%;
|
||||||
|
padding:1rem 1.1rem;
|
||||||
|
border:4px solid var(--outline);
|
||||||
|
border-radius:16px;
|
||||||
|
background:
|
||||||
|
linear-gradient(0deg, #ffffff, #ffffff) padding-box,
|
||||||
|
radial-gradient(120% 120% at 0% 0%, var(--field-a) 0%, var(--field-b) 100%) border-box;
|
||||||
|
color:var(--ink);
|
||||||
|
box-shadow: 4px 4px 0 var(--shadow);
|
||||||
|
transition: transform .06s ease, box-shadow .15s ease, border-color .15s ease;
|
||||||
|
}
|
||||||
|
.input::placeholder{color:#6b7280}
|
||||||
|
.input:focus{
|
||||||
|
outline:none; transform: translateY(-1px);
|
||||||
|
border-color: var(--ring);
|
||||||
|
box-shadow: 6px 6px 0 var(--shadow), 0 0 0 6px color-mix(in oklab, var(--ring) 40%, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* button: chunky pill with dual-tone blue */
|
||||||
|
.btn{
|
||||||
|
padding:1rem 1.35rem;
|
||||||
|
font-weight:900;
|
||||||
|
border:4px solid var(--outline);
|
||||||
|
border-radius:999px;
|
||||||
|
color:#06223a;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
color-mix(in oklab, var(--btn-b) 75%, white),
|
||||||
|
color-mix(in oklab, var(--btn-a) 85%, white));
|
||||||
|
cursor:pointer;
|
||||||
|
box-shadow: 6px 6px 0 var(--shadow);
|
||||||
|
transition: transform .07s ease, filter .15s ease, box-shadow .15s ease;
|
||||||
|
}
|
||||||
|
.btn:hover{ filter:brightness(1.06) saturate(1.05) }
|
||||||
|
.btn:active{ transform: translateY(2px); box-shadow: 4px 4px 0 var(--shadow) }
|
||||||
|
|
||||||
|
/* accessibility */
|
||||||
|
.sr{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0 }
|
||||||
|
|
||||||
|
/* responsive */
|
||||||
|
@media (max-width: 560px){
|
||||||
|
.form{ grid-template-columns: 1fr }
|
||||||
|
.btn{ width:100% }
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
name = "mis-interpreter"
|
name = "mis-interpreter"
|
||||||
workers_dev = true
|
workers_dev = true
|
||||||
compatibility_date = "2025-10-28"
|
compatibility_date = "2025-10-27"
|
||||||
main = "build/worker/shim.mjs"
|
main = "build/worker/shim.mjs"
|
||||||
|
|
||||||
|
|
||||||
[[rules]]
|
|
||||||
globs = [ "**/*.wasm" ]
|
|
||||||
type = "CompiledWasm"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
command = "cargo install -q worker-build --version ^0.0.8 && worker-build --release" # required
|
command = "cargo install -q worker-build && worker-build --release" # required
|
||||||
Reference in New Issue
Block a user