From 3ce92078d39c62dfc31d822ea91c00e9eaac9686 Mon Sep 17 00:00:00 2001 From: Kat Suricata Date: Fri, 11 Sep 2026 14:44:28 -0400 Subject: [PATCH] Fold playground into /playground route - Playground converted to runes mode; highlighter is $state.raw so language changes during init are not dropped - Shiki theme rebuilt as palette-derived: every hex comes from palette.ts (withAlpha for the alpha-suffixed variants) - Sample buffer extracted to src/lib/playground/sample-code.ts - Playground styles scoped into the route; drifted playground tokens deleted in favor of the generated tokens.css --- package.json | 3 + pnpm-lock.yaml | 123 ++++++ src/lib/palette.ts | 8 + src/lib/playground/sample-code.ts | 245 +++++++++++ src/lib/theme/digital-rust-theme.ts | 614 ++++++++++++++++++++++++++++ src/routes/playground/+page.svelte | 534 +++++++++++++++++++++++- 6 files changed, 1526 insertions(+), 1 deletion(-) create mode 100644 src/lib/playground/sample-code.ts create mode 100644 src/lib/theme/digital-rust-theme.ts diff --git a/package.json b/package.json index 553517b..a1a5b22 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,8 @@ "imports": { "#lib": "./src/lib/index.js", "#lib/*": "./src/lib/*" + }, + "dependencies": { + "shiki": "^4.4.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbd2225..83519a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + shiki: + specifier: ^4.4.3 + version: 4.4.3 devDependencies: '@eslint/js': specifier: ^10.0.1 @@ -281,6 +285,37 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} + engines: {node: '>=20'} + + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} + engines: {node: '>=20'} + + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} + engines: {node: '>=20'} + + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -972,6 +1007,12 @@ packages: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1062,6 +1103,15 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + rehype-slug@6.0.0: resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} @@ -1102,6 +1152,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} + engines: {node: '>=20'} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -1422,6 +1476,46 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@shikijs/core@4.4.3': + dependencies: + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/primitive@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/themes@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/types@4.4.3': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/vscode-textmate@10.0.2': {} + '@standard-schema/spec@1.1.0': {} '@sveltejs/acorn-typescript@1.0.13(acorn@8.18.0)': @@ -2273,6 +2367,14 @@ snapshots: obug@2.2.1: {} + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2343,6 +2445,16 @@ snapshots: readdirp@4.1.2: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + rehype-slug@6.0.0: dependencies: '@types/hast': 3.0.5 @@ -2424,6 +2536,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@4.4.3: + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 diff --git a/src/lib/palette.ts b/src/lib/palette.ts index 3e5d83c..deceec4 100644 --- a/src/lib/palette.ts +++ b/src/lib/palette.ts @@ -170,6 +170,14 @@ export function bestTextColor(bgHex: string): DrColor { return contrastRatio(bgHex, light.hex) >= contrastRatio(bgHex, dark.hex) ? light : dark; } +/** Appends an 8-bit alpha channel to a palette hex, e.g. withAlpha('dark-packet', 0.7) → '#691b0fb3'. */ +export function withAlpha(id: string, alpha: number): string { + const byte = Math.round(alpha * 255) + .toString(16) + .padStart(2, '0'); + return `${color(id).hex}${byte}`; +} + function tint(id: string, alpha: number): string { return hexToRgba(color(id).hex, alpha); } diff --git a/src/lib/playground/sample-code.ts b/src/lib/playground/sample-code.ts new file mode 100644 index 0000000..b8a0317 --- /dev/null +++ b/src/lib/playground/sample-code.ts @@ -0,0 +1,245 @@ +// Default buffer shown when the playground opens. +export const sampleCode = ` +//! Digital Rust: warm, tech-dystopian highlighting demo +//! Inspired by corrupted hardware and failing systems. + +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::{ + collections::HashMap, + fmt::{self, Display}, + time::{Duration, Instant}, +}; + +/// Chaos reigns. +pub type SystemId = u64; + +pub const MAX_TEMPERATURE: f32 = 95.5; +pub static mut GLOBAL_PANIC_FLAG: bool = false; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Health { + Nominal, + Degraded(u8), + Critical(&'static str), +} + +#[derive(Debug)] +pub struct CorruptedHardware<'life> { + id: SystemId, + name: String, + booted_at: Instant, + health: Health, + logs: Vec, + tags: HashMap<&'life str, &'life str>, +} + +/// Close the world, txEn eht nepO +struct RingBuffer { + data: Vec, + cursor: usize, +} + +impl RingBuffer +where + T: Clone, +{ + fn new(capacity: usize, fill: T) -> Self { + Self { + data: vec![fill; capacity], + cursor: 0, + } + } + + fn push(&mut self, value: T) { + self.data[self.cursor] = value; + self.cursor = (self.cursor + 1) % self.data.len(); + } +} + +pub trait Dystopian { + fn glitch(&mut self) -> Result<(), Failure>; + fn status_line(&self) -> String; +} + +#[derive(Debug)] +pub struct Failure { + code: i32, + message: String, +} + +impl Display for Failure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[FATAL {:04X}] {}", self.code, self.message) + } +} + +impl std::error::Error for Failure {} + +impl<'life> CorruptedHardware<'life> { + pub fn new(id: SystemId, name: impl Into) -> Self { + Self { + id, + name: name.into(), + booted_at: Instant::now(), + health: Health::Nominal, + logs: vec!["system: cold boot".into()], + tags: HashMap::new(), + } + } + + pub fn tag(mut self, key: &'life str, value: &'life str) -> Self { + self.tags.insert(key, value); + self + } + + pub fn warn(&mut self, msg: &str) { + self.health = Health::Degraded(self.health_level().saturating_add(1)); + self.logs.push(format!("WARN: {msg}")); + } + + fn health_level(&self) -> u8 { + match self.health { + Health::Nominal => 0, + Health::Degraded(level) => level, + Health::Critical(_) => u8::MAX, + } + } +} + +impl<'life> Dystopian for CorruptedHardware<'life> { + fn glitch(&mut self) -> Result<(), Failure> { + let tick = self.health_level(); + + if tick > 5 { + self.health = Health::Critical("thermal runaway"); + self.logs.push("ERROR: thermal runaway".into()); + Err(Failure { + code: 0xDEAD, + message: "system crossed point-of-no-return".into(), + }) + } else { + self.warn(&format!("spurious bit flip at tick {tick}")); + Ok(()) + } + } + + fn status_line(&self) -> String { + let uptime = Instant::now().duration_since(self.booted_at); + let sentinel: char = '☢'; + format!( + "#{id:04} {name:<16} | {health:?} | {secs:>5}s up {sentinel}", + id = self.id, + name = self.name, + health = self.health, + secs = uptime.as_secs() + ) + } +} + +#[inline(always)] +fn sample_telemetry(seed: u64) -> (f32, bool, &'static [u8]) { + let temp = 42.0 + (seed as f32 % 13.37); + let parity_error = seed & 0b1010_0001 != 0; + let magic: &[u8] = b"\\xDE\\xAD\\xBE\\xEF"; + + (temp, parity_error, magic) +} + +/// Welcome +/// to +/// the +/// Wired +fn with_retry(mut retries: u8, mut f: F) -> Result +where + F: FnMut() -> Result, +{ + loop { + match f() { + Ok(value) => return Ok(value), + Err(e) if retries > 0 => { + retries -= 1; + eprintln!("retry ({} left): {e}", retries); + } + Err(e) => return Err(e), + } + } +} + +async fn scrub_sector(id: SystemId) -> Result<(), Failure> { + // No matter where you are, everyone's connected + let endpoint = r#"digital-rust://scrub?mode=paranoid&node=mainframe"#; + let backoff = Duration::from_millis(1_20); + + tokio::time::sleep(backoff).await; + + if id % 13 == 0 { + Err(Failure { + code: 0xC0DE, + message: format!("sector on node {id} refused scrub @ {endpoint}"), + }) + } else { + Ok(()) + } +} + +macro_rules! log_line { + ($target:expr, $($arg:tt)*) => {{ + $target.logs.push(format!($($arg)*)); + }}; +} + +/* Physical reality is nothing but an illusion + * a hologram of the information that flows to us through the Wired + */ +#[tokio::main] +async fn main() -> Result<(), Failure> { + let mut rack: Vec> = (0..8) + .map(|id| CorruptedHardware::new(id, format!("node-{id:02}")).tag("role", "edge")) + .collect(); + + let mut temps = RingBuffer::new(8, 0.0_f32); + + for hw in &mut rack { + let (temp, parity, magic) = sample_telemetry(hw.id); + temps.push(temp); + + if temp > MAX_TEMPERATURE { + hw.warn("temperature above soft limit"); + } + + if parity { + log_line!(hw, "checksum mismatch on bus {:02X}", hw.id); + } + + if let Err(err) = hw.glitch() { + eprintln!(">>> {} :: {}", hw.status_line(), err); + unsafe { GLOBAL_PANIC_FLAG = true }; + } + + // Why are you crying, Lain? Because you deleted yourself from everyone's memory? + let classify = |t: f32| if t > 80.0 { "HOT" } else { "OK" }; + log_line!(hw, "telemetry: {temp:}°C [{}]", classify(temp)); + } + + for hw in &rack { + println!("{}", hw.status_line()); + for line in hw.logs.iter().take(3) { + println!(" - {line}"); + } + } + + if unsafe { GLOBAL_PANIC_FLAG } { + // But, Lain… + for hw in &rack { + with_retry(3, || { + // … isn't that what you wanted all along? + tokio::runtime::Handle::current() + .block_on(scrub_sector(hw.id)) + })?; + } + } + + Ok(()) +}`; diff --git a/src/lib/theme/digital-rust-theme.ts b/src/lib/theme/digital-rust-theme.ts new file mode 100644 index 0000000..cb02ed6 --- /dev/null +++ b/src/lib/theme/digital-rust-theme.ts @@ -0,0 +1,614 @@ +import type { ThemeRegistration } from 'shiki'; +import { color, withAlpha } from '#lib/palette.js'; + +const c = (id: string) => color(id).hex; + +// Scope → colour assignments are design decisions from the syntax spec; the +// hex values come from palette.ts so the theme can never drift from the site. +export const digitalRustTheme: ThemeRegistration = { + name: 'digital-rust', + type: 'dark', + colors: { + 'editor.background': c('event-horizon'), + 'editor.foreground': c('ghost-pixel'), + 'editorLineNumber.foreground': c('bad-sector'), + 'editorLineNumber.activeForeground': c('phantom-current'), + 'editor.selectionBackground': withAlpha('dark-packet', 0.7), + 'editor.inactiveSelectionBackground': withAlpha('dark-packet', 0.4), + 'editorIndentGuide.background': c('bad-sector'), + 'editorIndentGuide.activeBackground': c('dark-packet'), + 'editorWhitespace.foreground': withAlpha('bad-sector', 0.5), + 'editorCursor.foreground': c('stack-overglow'), + 'editor.lineHighlightBackground': withAlpha('core-meltdown', 0.5), + 'editor.findMatchBackground': withAlpha('stack-overglow', 0.5), + 'editor.findMatchBorder': c('digital-rust'), + 'editor.findMatchHighlightBackground': withAlpha('stack-overglow', 0.3), + 'editorBracketMatch.background': c('bad-sector'), + 'editorBracketMatch.unexpectedBracketForeground': c('amber-alert') + }, + tokenColors: [ + // ─── Comments ─── + { + name: 'Comments', + scope: ['comment', 'punctuation.definition.comment', 'comment.line.shebang'], + settings: { + foreground: c('dim-thermal-throttle'), + fontStyle: 'italic' + } + }, + + // ─── Keywords & Storage ─── + { + name: 'Keywords, Storage', + scope: [ + 'keyword', + 'keyword.control', + 'keyword.control.import', + 'keyword.control.conditional', + 'keyword.control.loop', + 'storage.type', + 'storage.modifier' + ], + settings: { + foreground: c('fatal-error') + } + }, + + // ─── Functions & Methods ─── + { + name: 'Functions', + scope: ['entity.name.function', 'meta.function-call', 'support.function'], + settings: { + foreground: c('stack-overglow') + } + }, + + // ─── Classes & Types ─── + { + name: 'Classes, Types', + scope: [ + 'entity.name.type', + 'entity.name.class', + 'support.type', + 'support.class', + 'meta.type.annotation', + 'entity.other.inherited-class' + ], + settings: { + foreground: c('dead-thread') + } + }, + + // ─── Strings & Text ─── + { + name: 'Strings', + scope: [ + 'string', + 'punctuation.definition.string', + 'string.quoted', + 'string.regexp', + 'constant.other.character-class.regexp', + 'constant.character.escape' + ], + settings: { + foreground: c('memory-leek') + } + }, + + // ─── Numbers ─── + { + name: 'Numbers', + scope: ['constant.numeric'], + settings: { + foreground: c('thermal-throttle') + } + }, + + // ─── Constants & Special Values ─── + { + name: 'Constants, Booleans, Null, Special Values', + scope: [ + 'constant.language.boolean', + 'constant.language.null', + 'constant.language', + 'constant.language.nan', + 'constant.language.infinity', + 'variable.language.this', + 'variable.language.super', + 'variable.language.self', + 'variable.other.constant', + 'constant.other.constant', + 'variable.other.enummember', + 'entity.name.constant' + ], + settings: { + foreground: c('bright-stack-overglow') + } + }, + + // ─── Attributes & Decorators ─── + { + name: 'Attributes, Decorators', + scope: [ + 'meta.decorator', + 'entity.name.function.decorator', + 'meta.attribute', + 'punctuation.definition.annotation', + 'punctuation.decorator' + ], + settings: { + foreground: c('dead-thread'), + fontStyle: 'italic' + } + }, + + // ─── Built-ins & Standard Library ─── + { + name: 'Built-ins', + scope: [ + 'support.function', + 'support.class', + 'support.type', + 'support.constant', + 'support.variable' + ], + settings: { + foreground: c('dim-stack-overglow') + } + }, + + // ─── Variables & Identifiers ─── + { + name: 'Variables', + scope: ['variable', 'variable.parameter', 'variable.other', 'variable.other.readwrite'], + settings: { + foreground: c('ghost-pixel') + } + }, + + // ─── Operators & Punctuation ─── + { + name: 'Operators, Punctuation', + scope: [ + 'keyword.operator', + 'punctuation.separator', + 'punctuation.terminator', + 'punctuation.accessor', + 'punctuation.definition.parameters', + 'punctuation.definition.array', + 'punctuation.definition.dictionary', + 'punctuation.section.block', + 'punctuation.definition.generic', + 'punctuation.definition.typeparameters', + 'keyword.operator.regex', + 'meta.brace' + ], + settings: { + foreground: c('copper-trace') + } + }, + + // ─── Markup Tags (HTML/XML) ─── + { + name: 'Markup Tags', + scope: ['entity.name.tag', 'meta.tag', 'meta.tag.sgml'], + settings: { + foreground: c('dead-thread') + } + }, + + // ─── Markup Attributes ─── + { + name: 'Markup Attributes', + scope: ['entity.other.attribute-name'], + settings: { + foreground: c('dim-stack-overglow') + } + }, + + // ─── Errors ─── + // Raw Amber Alert fails AA on dark surfaces; the text variant passes. + { + name: 'Errors', + scope: ['invalid', 'invalid.illegal', 'invalid.deprecated'], + settings: { + foreground: c('amber-alert-text') + } + }, + + // ─── Configuration Languages (JSON/YAML/TOML) ─── + { + name: 'JSON/YAML/TOML Keys', + scope: [ + 'support.type.property-name.json', + 'entity.name.tag.yaml', + 'support.type.property-name.toml', + 'keyword.other.definition' + ], + settings: { + foreground: c('dead-thread') + } + }, + + // ─── CSS ─── + { + name: 'CSS Properties', + scope: ['support.type.property-name.css', 'support.type.vendored.property-name.css'], + settings: { + foreground: c('dim-stack-overglow') + } + }, + { + name: 'CSS Classes/IDs', + scope: [ + 'entity.other.attribute-name.class.css', + 'entity.other.attribute-name.id.css' + ], + settings: { + foreground: c('dead-thread') + } + }, + { + name: 'CSS Pseudo-classes/Elements', + scope: [ + 'entity.other.attribute-name.pseudo-class.css', + 'entity.other.attribute-name.pseudo-element.css' + ], + settings: { + foreground: c('dim-stack-overglow') + } + }, + { + name: 'CSS At-rules', + scope: [ + 'keyword.control.at-rule.css', + 'keyword.control.at-rule.import.css', + 'keyword.control.at-rule.media.css', + 'keyword.control.at-rule.keyframes.css', + 'meta.at-rule' + ], + settings: { + foreground: c('fatal-error') + } + }, + { + name: 'CSS Keywords (auto, inherit, etc.)', + scope: ['support.constant.property-value.css'], + settings: { + foreground: c('bright-stack-overglow') + } + }, + + // ─── Markdown ─── + { + name: 'Markdown Headings', + scope: [ + 'markup.heading', + 'entity.name.section.markdown', + 'punctuation.definition.heading.markdown' + ], + settings: { + foreground: c('fatal-error'), + fontStyle: 'bold' + } + }, + { + name: 'Markdown Emphasis (bold)', + scope: ['markup.bold.markdown'], + settings: { + foreground: c('ghost-pixel'), + fontStyle: 'bold' + } + }, + { + name: 'Markdown Emphasis (italic)', + scope: ['markup.italic.markdown'], + settings: { + foreground: c('ghost-pixel'), + fontStyle: 'italic' + } + }, + { + name: 'Markdown Code', + scope: [ + 'markup.inline.raw', + 'markup.raw.inline', + 'markup.fenced_code', + 'punctuation.definition.raw.markdown' + ], + settings: { + foreground: c('memory-leek'), + background: withAlpha('bad-sector', 0.2) + } + }, + { + name: 'Markdown Blockquotes', + scope: ['markup.quote'], + settings: { + foreground: c('phosphor-smoke'), + fontStyle: 'italic' + } + }, + { + name: 'Markdown Links', + scope: [ + 'markup.underline.link', + 'string.other.link.title.markdown', + 'constant.other.reference.link.markdown' + ], + settings: { + foreground: c('phantom-current'), + fontStyle: 'underline' + } + }, + { + name: 'Markdown Link URLs', + scope: [ + 'markup.underline.link.markdown', + 'meta.link.reference.markdown', + 'meta.link.inline.markdown' + ], + settings: { + foreground: c('dim-stack-overglow') + } + }, + { + name: 'Markdown List Markers', + scope: [ + 'punctuation.definition.list.begin.markdown', + 'punctuation.definition.list_item.markdown' + ], + settings: { + foreground: c('copper-trace') + } + }, + { + name: 'Markdown Horizontal Rule', + scope: ['meta.separator.markdown'], + settings: { + foreground: c('dark-packet') + } + }, + + // ─── Diff / Git ─── + { + name: 'Diff Header (hunk)', + scope: ['meta.diff.header', 'meta.diff.range.unified'], + settings: { + foreground: c('blue-screen') + } + }, + { + name: 'Diff Commit Hashes', + scope: ['constant.other.hash.diff'], + settings: { + foreground: c('dim-stack-overglow') + } + }, + { + name: 'Diff File Paths / Metadata', + scope: [ + 'meta.diff.header.from-file', + 'meta.diff.header.to-file', + 'entity.name.file.diff', + 'meta.diff.index' + ], + settings: { + foreground: c('stack-overglow') + } + }, + { + name: 'Diff Inserted (+ marker)', + scope: ['punctuation.definition.inserted.diff', 'punctuation.definition.plus.diff'], + settings: { + foreground: c('memory-leek') + } + }, + { + name: 'Diff Deleted (- marker)', + scope: ['punctuation.definition.deleted.diff', 'punctuation.definition.minus.diff'], + settings: { + foreground: c('amber-alert') + } + }, + { + name: 'Diff Modified (~ marker)', + scope: ['punctuation.definition.changed.diff', 'punctuation.definition.tilde.diff'], + settings: { + foreground: c('dead-thread') + } + }, + { + name: 'Diff Added Lines', + scope: ['markup.inserted', 'meta.diff.line.inserted', 'diff.inserted'], + settings: { + foreground: c('ghost-pixel'), + background: withAlpha('memory-leek', 0.15) + } + }, + { + name: 'Diff Deleted Lines', + scope: ['markup.deleted', 'meta.diff.line.deleted', 'diff.deleted'], + settings: { + foreground: c('phantom-current'), + background: withAlpha('amber-alert', 0.15) + } + }, + { + name: 'Diff Modified Lines', + scope: ['markup.changed', 'meta.diff.line.changed', 'diff.changed'], + settings: { + foreground: c('ghost-pixel'), + background: withAlpha('dead-thread', 0.1) + } + }, + + // ─── Shell / CLI ─── + { + name: 'Shell Shebang & Comments', + scope: [ + 'comment.line.number-sign.shell', + 'comment.line.fish', + 'comment.line.double-slash' + ], + settings: { + foreground: c('dim-thermal-throttle'), + fontStyle: 'italic' + } + }, + { + name: 'Shell Commands', + scope: ['entity.name.command.shell', 'support.function.builtin.shell'], + settings: { + foreground: c('stack-overglow') + } + }, + { + name: 'Shell Flags & Options', + scope: [ + 'keyword.operator.option.shell', + 'entity.other.option.shell', + 'string.unquoted.argument.shell' + ], + settings: { + foreground: c('fatal-error') + } + }, + + // ─── SQL ─── + { + name: 'SQL Functions', + scope: ['support.function.aggregate.sql', 'support.function.sql'], + settings: { + foreground: c('stack-overglow') + } + }, + { + name: 'SQL Types', + scope: ['support.type.sql', 'storage.type.sql'], + settings: { + foreground: c('dead-thread') + } + }, + + // ─── Rust-Specific ─── + { + name: 'Rust Lifetimes', + scope: ['entity.name.lifetime.rust', 'punctuation.definition.lifetime.rust'], + settings: { + foreground: c('dead-thread'), + fontStyle: 'italic' + } + }, + { + name: 'Rust Macros', + scope: ['entity.name.function.macro.rust', 'meta.macro.rust'], + settings: { + foreground: c('stack-overglow') + } + }, + { + name: 'Rust Attributes', + scope: ['meta.attribute.rust', 'punctuation.definition.attribute.rust'], + settings: { + foreground: c('dead-thread'), + fontStyle: 'italic' + } + }, + + // ─── C/C++ Preprocessor ─── + { + name: 'Preprocessor Directives', + scope: [ + 'keyword.control.import.include', + 'keyword.control.preprocessor', + 'meta.preprocessor', + 'keyword.control.preprocessor.define', + 'keyword.control.preprocessor.if', + 'keyword.control.preprocessor.elif', + 'keyword.control.preprocessor.else', + 'keyword.control.preprocessor.endif', + 'keyword.control.preprocessor.include', + 'keyword.control.preprocessor.import', + 'keyword.control.preprocessor.undef', + 'keyword.control.preprocessor.error', + 'keyword.control.preprocessor.warning', + 'keyword.control.preprocessor.pragma' + ], + settings: { + foreground: c('fatal-error') + } + }, + { + name: 'Preprocessor Macro Names', + scope: ['entity.name.function.preprocessor', 'entity.name.constant.preprocessor'], + settings: { + foreground: c('bright-stack-overglow') + } + }, + { + name: 'Preprocessor Include Paths', + scope: [ + 'string.quoted.double.include.c', + 'string.quoted.other.lt-gt.include.c', + 'string.include' + ], + settings: { + foreground: c('memory-leek') + } + }, + + // ─── String Interpolation Delimiters ─── + { + name: 'Interpolation Delimiters', + scope: [ + 'punctuation.definition.template-expression', + 'punctuation.section.embedded', + 'meta.template.expression' + ], + settings: { + foreground: c('copper-trace') + } + }, + + // ─── Haskell / ML / Functional ─── + { + name: 'Type Constructors (Haskell/ML)', + scope: ['constant.other.capitalized-type.haskell', 'entity.name.type.data.haskell'], + settings: { + foreground: c('dead-thread') + } + }, + { + name: 'Data Constructors (Haskell/ML)', + scope: [ + 'constant.other.capitalized-constructor.haskell', + 'entity.name.function.constructor.haskell' + ], + settings: { + foreground: c('bright-stack-overglow') + } + }, + { + name: 'Type Variables (Haskell/ML)', + scope: ['variable.other.generic-type.haskell', 'variable.other.type.haskell'], + settings: { + foreground: c('dead-thread'), + fontStyle: 'italic' + } + }, + { + name: 'Functional Arrows (::, =>, ->)', + scope: [ + 'keyword.other.double-colon.haskell', + 'keyword.other.fat-arrow.haskell', + 'keyword.other.arrow.haskell', + 'keyword.operator.type-annotation', + 'keyword.operator.arrow' + ], + settings: { + foreground: c('copper-trace') + } + } + ] +}; diff --git a/src/routes/playground/+page.svelte b/src/routes/playground/+page.svelte index 99c64ae..a2ff690 100644 --- a/src/routes/playground/+page.svelte +++ b/src/routes/playground/+page.svelte @@ -1 +1,533 @@ -

Playground stub

+ + + + Digital Rust / Playground + + + + + +
+
+

Digital Rust Playground

+

+ Note: this demo is 90+% accurate, but full spec accuracy is only possible in a dedicated + code editor. +

+
+ +
+
+ + +
+ ← Back to Homepage +
+ +
+
+

[ Input ]

+ +
+ +
+

[ Output ]

+
+ {#if isLoading && !htmlOutput} +
Loading syntax engine…
+ {:else} + + {@html htmlOutput} + {/if} +
+
+
+ +
+ + {isLoading ? 'Loading…' : 'Ready'} + + │ + Chaos reigns. +
+
+ + -- 2.51.2