Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 16 additions & 31 deletions src/components/Header.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script>
import { page } from '$app/stores';
export let y;

// export let tabs = [
// { name: "Projects ", link: "#projects" },
// { name: "About", link: "#about" },
// { name: "Mission", link: "#mission"},
// { name: "The Team", link: "#team"},
// ];
const tabs = [
{ name: "Home", link: "/" },
{ name: "Erminia", link: "/erminia" },
];
</script>

<header
Expand All @@ -15,34 +14,20 @@
? " py-4 bg-slate-950 border-green-950"
: " py-6 bg-transparent border-transparent")}
>
<h1 class="font-medium">
<span class="">Vertical</span> <b class="font-bold poppins text-green-400">Playground</b>
</h1>
<!-- <div class="sm:flex items-center gap-4 hidden">
{#each tabs as tab, index}
<a href="/" class="font-medium hover:opacity-80 duration-200">
<span class="">Vertical</span> <b class="font-bold poppins text-green-400">Playground</b>
</a>
<nav class="flex items-center gap-1">
{#each tabs as tab}
<a
href={tab.link}
class="duration-200 hover:text-green-400"
target={index === 2 ? "_blank" : ""}
class={"px-3 py-1.5 rounded-lg text-sm duration-200 " +
($page.url.pathname === tab.link
? "text-green-400 bg-green-400/10"
: "text-slate-300 hover:text-green-400 hover:bg-slate-800")}
>
<p>{tab.name}</p>
{tab.name}
</a>
{/each}
<button
class="blueShadow relative overflow-hidden px-5 py-2 group rounded-full bg-green-600 text-white"
>
<div
class="absolute top-0 right-full w-full h-full bg-green-400 group-hover:translate-x-full z-0 duration-200"
/>
<h4 class="relative z-9">Login</h4>
</button>
<button
class="blueShadow relative overflow-hidden px-5 py-2 group rounded-full bg-white text-slate-950"
>
<div
class="absolute top-0 right-full w-full h-full bg-green-400 group-hover:translate-x-full z-0 duration-200"
/>
<h4 class="relative z-9">Get in touch</h4>
</button>
</div> -->
</nav>
</header>
4 changes: 3 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
</button>
</div>
<Header {y} {innerHeight}/>
<slot />
<main class="flex flex-col flex-1 min-h-0">
<slot />
</main>
<Footer />
</div>
<svelte:window bind:scrollY={y} bind:innerHeight bind:innerWidth />
251 changes: 251 additions & 0 deletions src/routes/erminia/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<script>
let swapped = $state(false);
let code = $state(`# Erminia DSL — ARC-AGI Grid Language
# Define a grid with dimensions
grid 5 5

# Set individual cells: cell(row, col) = color_index
cell(0,0) = 1
cell(0,4) = 1
cell(1,1) = 2
cell(1,3) = 2
cell(2,2) = 3
cell(3,1) = 2
cell(3,3) = 2
cell(4,0) = 1
cell(4,4) = 1

# Define a named pattern
pattern diamond {
cell(0,2) = 4
cell(1,2) = 4
cell(2,0) = 4
cell(2,4) = 4
cell(3,2) = 4
cell(4,2) = 4
}

apply diamond
`);

const COLORS = [
'#1e293b', // 0 - background (slate-800)
'#4ade80', // 1 - green
'#60a5fa', // 2 - blue
'#f87171', // 3 - red
'#facc15', // 4 - yellow
'#c084fc', // 5 - purple
'#fb923c', // 6 - orange
'#34d399', // 7 - emerald
'#f472b6', // 8 - pink
'#94a3b8', // 9 - slate
];

const parsed = $derived(parseErminia(code));
const jsonOutput = $derived(JSON.stringify(parsed, null, 2));

function parseErminia(src) {
const lines = src.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
let rows = 0, cols = 0;
/** @type {Record<string, number>} */
let cells = {};
/** @type {Record<string, Record<string, number>>} */
let patterns = {};
/** @type {string | null} */
let currentPattern = null;

for (const line of lines) {
// grid N M
const gridMatch = line.match(/^grid\s+(\d+)\s+(\d+)$/);
if (gridMatch) {
rows = parseInt(gridMatch[1]);
cols = parseInt(gridMatch[2]);
continue;
}

// pattern name {
const patternStart = line.match(/^pattern\s+(\w+)\s*\{$/);
if (patternStart) {
currentPattern = patternStart[1];
patterns[currentPattern] = {};
continue;
}

// closing brace
if (line === '}') {
currentPattern = null;
continue;
}

// cell(r,c) = v
const cellMatch = line.match(/^cell\((\d+),(\d+)\)\s*=\s*(\d+)$/);
if (cellMatch) {
const r = parseInt(cellMatch[1]);
const c = parseInt(cellMatch[2]);
const v = parseInt(cellMatch[3]);
const key = `${r},${c}`;
if (currentPattern) {
patterns[currentPattern][key] = v;
} else {
cells[key] = v;
}
continue;
}

// apply patternName
const applyMatch = line.match(/^apply\s+(\w+)$/);
if (applyMatch) {
const name = applyMatch[1];
if (patterns[name]) {
Object.assign(cells, patterns[name]);
}
continue;
}
}

// Build grid array
const grid = [];
for (let r = 0; r < rows; r++) {
const row = [];
for (let c = 0; c < cols; c++) {
row.push(cells[`${r},${c}`] ?? 0);
}
grid.push(row);
}

return { rows, cols, grid, patterns: Object.keys(patterns) };
}

/** @type {HTMLTextAreaElement} */
let editorEl;
let lineCount = $derived(code.split('\n').length);

/** @param {KeyboardEvent & { target: HTMLTextAreaElement }} e */
function handleTab(e) {
if (e.key === 'Tab') {
e.preventDefault();
const start = e.target.selectionStart;
const end = e.target.selectionEnd;
code = code.substring(0, start) + ' ' + code.substring(end);
// restore cursor after state update
requestAnimationFrame(() => {
if (editorEl) {
editorEl.selectionStart = editorEl.selectionEnd = start + 2;
}
});
}
}

let copied = $state(false);
function copyJson() {
navigator.clipboard.writeText(jsonOutput);
copied = true;
setTimeout(() => copied = false, 1500);
}
</script>

<div class="flex flex-col flex-1 min-h-0 px-4 pb-6 pt-2 gap-4">
<!-- Toolbar -->
<div class="flex items-center justify-between gap-4 flex-wrap">
<div>
<h2 class="poppins font-bold text-green-400 text-lg">Erminia <span class="font-normal text-white">Playground</span></h2>
<p class="text-slate-400 text-xs mt-0.5">DSL for ARC-AGI abstracted image interpretation</p>
</div>
<div class="flex items-center gap-2">
<span class="text-slate-500 text-xs hidden sm:block">
{parsed.rows}×{parsed.cols} grid · {parsed.patterns.length} pattern{parsed.patterns.length === 1 ? '' : 's'}
</span>
<div class="w-px h-4 bg-slate-700 hidden sm:block"></div>
<button
onclick={() => swapped = !swapped}
class="flex items-center gap-2 px-3 py-1.5 rounded-lg border border-slate-700 hover:border-green-700 hover:text-green-400 text-slate-300 duration-200 text-xs cursor-pointer"
title="Swap editor and renderer"
>
<i class="fa-solid fa-arrow-right-arrow-left"></i>
<span class="hidden sm:inline">Swap Panels</span>
</button>
</div>
</div>

<!-- Panels -->
<div class="flex gap-4 flex-1 min-h-0" class:flex-row-reverse={swapped}>
<!-- Editor Panel -->
<div class="flex flex-col flex-1 min-w-0 rounded-xl border border-slate-700 overflow-hidden bg-slate-900/60">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700 bg-slate-900/80">
<div class="flex items-center gap-2 text-xs text-slate-400">
<i class="fa-solid fa-code text-green-400"></i>
<span>Editor</span>
<span class="text-slate-600">·</span>
<span>{lineCount} lines</span>
</div>
<div class="flex gap-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-slate-700"></span>
<span class="w-2.5 h-2.5 rounded-full bg-slate-700"></span>
<span class="w-2.5 h-2.5 rounded-full bg-green-700"></span>
</div>
</div>
<div class="flex flex-1 min-h-0 overflow-hidden font-mono text-sm">
<!-- Line numbers -->
<div class="select-none text-right pr-3 pl-3 pt-4 text-slate-600 text-xs leading-6 overflow-hidden bg-slate-900/40 border-r border-slate-800 min-w-[3rem]">
{#each Array(lineCount) as _, i}
<div>{i + 1}</div>
{/each}
</div>
<!-- Textarea -->
<textarea
bind:this={editorEl}
bind:value={code}
onkeydown={handleTab}
spellcheck={false}
class="flex-1 bg-transparent text-slate-200 resize-none outline-none p-4 leading-6 overflow-auto"
style="tab-size: 2;"
placeholder="Write Erminia code here..."
></textarea>
</div>
</div>

<!-- Renderer Panel -->
<div class="flex flex-col flex-1 min-w-0 rounded-xl border border-slate-700 overflow-hidden bg-slate-900/60">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700 bg-slate-900/80">
<div class="flex items-center gap-2 text-xs text-slate-400">
<i class="fa-solid fa-diagram-project text-green-400"></i>
<span>Renderer</span>
</div>
<button
onclick={copyJson}
class="flex items-center gap-1.5 text-xs text-slate-500 hover:text-green-400 duration-200 cursor-pointer"
title="Copy JSON"
>
<i class="fa-solid {copied ? 'fa-check text-green-400' : 'fa-copy'}"></i>
<span>{copied ? 'Copied!' : 'Copy JSON'}</span>
</button>
</div>

<div class="flex flex-col flex-1 min-h-0 overflow-hidden">
<!-- Visual grid -->
{#if parsed.rows > 0 && parsed.cols > 0}
<div class="p-4 border-b border-slate-800 flex justify-center bg-slate-950/40">
<div class="flex flex-col gap-0.5">
{#each parsed.grid as row}
<div class="flex gap-0.5">
{#each row as cell}
<div
class="w-7 h-7 rounded-sm transition-colors duration-150"
style="background-color: {COLORS[cell] ?? COLORS[0]}"
title="value: {cell}"
></div>
{/each}
</div>
{/each}
</div>
</div>
{/if}

<!-- JSON output -->
<div class="flex-1 overflow-auto p-4 font-mono text-xs text-slate-300 leading-5">
<pre class="whitespace-pre-wrap break-all">{jsonOutput}</pre>
</div>
</div>
</div>
</div>
</div>