feat: styles
This commit is contained in:
@@ -30,20 +30,20 @@ from uframe.themes import BUILTIN_THEMES
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("page", node_class=Page, section="Layout",
|
||||
detail='page "Title" 64', snippet='page "${title}" ${width:64}',
|
||||
detail='page "Title" 64', snippet='page "${1:Title}" ${2:64}',
|
||||
is_container=True)
|
||||
|
||||
register_keyword("box", node_class=Box, section="Layout",
|
||||
detail='box light "Title"', snippet='box ${weight:light} "${title}"',
|
||||
detail='box light "Title"', snippet='box ${1:light} "${2:Title}"',
|
||||
highlight_values=["light", "heavy", "double", "rounded"],
|
||||
is_container=True)
|
||||
|
||||
register_keyword("row", node_class=Row, section="Layout",
|
||||
detail="row [gap]", snippet="row ${gap:2}",
|
||||
detail="row [gap]", snippet="row ${1:2}",
|
||||
is_container=True)
|
||||
|
||||
register_keyword("col", node_class=Col, section="Layout",
|
||||
detail="col [width]", snippet="col ${width}",
|
||||
detail="col [width]", snippet="col ${1}",
|
||||
is_container=True)
|
||||
|
||||
register_keyword("spacer", node_class=Spacer, section="Layout",
|
||||
@@ -51,7 +51,7 @@ register_keyword("spacer", node_class=Spacer, section="Layout",
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("pad", node_class=Pad, section="Layout",
|
||||
detail="pad t r b l", snippet="pad ${top:1} ${right:1} ${bottom:1} ${left:1}",
|
||||
detail="pad t r b l", snippet="pad ${1:1} ${2:1} ${3:1} ${4:1}",
|
||||
is_container=True)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -59,28 +59,28 @@ register_keyword("pad", node_class=Pad, section="Layout",
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("heading", node_class=Heading, section="Content",
|
||||
detail='heading 1 "Text"', snippet='heading ${level:1} "${text}"',
|
||||
detail='heading 1 "Text"', snippet='heading ${1:1} "${2:Text}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("text", node_class=Text, section="Content",
|
||||
detail='text "Content"', snippet='text "${content}"',
|
||||
detail='text "Content"', snippet='text "${1:Content}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("label", node_class=Label, section="Content",
|
||||
detail='label "Key" "Value"', snippet='label "${key}" "${value}"',
|
||||
detail='label "Key" "Value"', snippet='label "${1:Key}" "${2:Value}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("divider", node_class=Divider, section="Content",
|
||||
detail="divider heavy", snippet="divider ${style:light}",
|
||||
detail="divider heavy", snippet="divider ${1:light}",
|
||||
highlight_values=["light", "heavy", "double", "dash", "dot"],
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("link", node_class=Link, section="Content",
|
||||
detail='link "Text" "/path.mu"', snippet='link "${display}" "${dest}"',
|
||||
detail='link "Text" "/path.mu"', snippet='link "${1:Text}" "${2:/page/dest.mu}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("list", node_class=ListNode, section="Content",
|
||||
detail="list bullet", snippet='list ${style:bullet}\n item "${entry}"',
|
||||
detail="list bullet", snippet='list ${1:bullet}\n item "${2:Entry}"',
|
||||
highlight_values=["bullet", "dash", "number", "arrow"],
|
||||
is_container=True)
|
||||
|
||||
@@ -94,17 +94,17 @@ register_keyword("item", node_class=ListItem, section="",
|
||||
|
||||
register_keyword("gauge", node_class=Gauge, section="Data",
|
||||
detail="gauge label val max width",
|
||||
snippet='gauge "${label}" ${value} ${max:100} ${width:28} warn=${warn:75} crit=${crit:90}',
|
||||
snippet='gauge "${1:Label}" ${2:0} ${3:100} ${4:28} warn=${5:75} crit=${6:90}',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("sparkline", node_class=Sparkline, section="Data",
|
||||
detail="sparkline label values width",
|
||||
snippet='sparkline "${label}" "${values}" ${width:20}',
|
||||
snippet='sparkline "${1:Label}" "${2:1,2,3,4}" ${3:20}',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("status", node_class=Status, section="Data",
|
||||
detail="status label state",
|
||||
snippet='status "${label}" ${state:online}',
|
||||
snippet='status "${1:Label}" ${2:online}',
|
||||
highlight_values=["online", "offline", "degraded", "unknown", "alert"],
|
||||
is_leaf=True)
|
||||
|
||||
@@ -121,18 +121,18 @@ register_keyword("columns", section="",
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("hnav", node_class=HNav, section="Layout",
|
||||
detail='hnav bar', snippet='hnav ${style:bar}\n item "${label}" "${dest}" active',
|
||||
detail='hnav bar', snippet='hnav ${1:bar}\n item "${2:Label}" "${3:/page/dest.mu}" active',
|
||||
highlight_values=["bar", "tabs", "pills", "breadcrumb", "underline"],
|
||||
is_container=True)
|
||||
|
||||
register_keyword("vnav", node_class=VNav, section="Layout",
|
||||
detail='vnav list', snippet='vnav ${style:list}\n item "${label}" "${dest}"',
|
||||
detail='vnav list', snippet='vnav ${1:list}\n item "${2:Label}" "${3:/page/dest.mu}"',
|
||||
highlight_values=["list", "boxed", "tree", "sidebar", "minimal"],
|
||||
is_container=True)
|
||||
|
||||
register_keyword("image", node_class=ImageNode, section="Content",
|
||||
detail='image "path.png" braille 30',
|
||||
snippet='image "${path}" ${mode:braille} ${width:30}',
|
||||
snippet='image "${1:path.png}" ${2:braille} ${3:30}',
|
||||
highlight_values=["braille", "block", "ascii", "halfblock"],
|
||||
is_leaf=True)
|
||||
|
||||
@@ -151,7 +151,7 @@ register_keyword("caption", section="",
|
||||
|
||||
register_keyword("bigtitle", node_class=BigTitle, section="Content",
|
||||
detail='bigtitle "TEXT" block',
|
||||
snippet='bigtitle "${text}" ${font:block}',
|
||||
snippet='bigtitle "${1:TEXT}" ${2:block}',
|
||||
highlight_values=["block", "thin", "pixel"],
|
||||
is_leaf=True)
|
||||
|
||||
@@ -160,12 +160,12 @@ register_keyword("bigtitle", node_class=BigTitle, section="Content",
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("align", section="Style",
|
||||
detail="align center", snippet="align ${align:center}",
|
||||
detail="align center", snippet="align ${1:center}",
|
||||
highlight_values=["left", "center", "right"],
|
||||
is_style_directive=True)
|
||||
|
||||
register_keyword("color", section="Style",
|
||||
detail="color hex", snippet="color ${hex}",
|
||||
detail="color hex", snippet="color ${1:0cf}",
|
||||
is_style_directive=True)
|
||||
|
||||
register_keyword("bg", section="Style",
|
||||
@@ -189,32 +189,32 @@ register_keyword("underline", section="Style",
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("form", node_class=Form, section="Form",
|
||||
detail='form "name"', snippet='form "${name}"',
|
||||
detail='form "name"', snippet='form "${1:name}"',
|
||||
is_container=True)
|
||||
|
||||
register_keyword("field", node_class=Field, section="Form",
|
||||
detail='field "name" 24 "placeholder"',
|
||||
snippet='field "${name}" ${width:24} "${placeholder}"',
|
||||
snippet='field "${1:name}" ${2:24} "${3:placeholder}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("password", node_class=Password, section="Form",
|
||||
detail='password "name" 24 "placeholder"',
|
||||
snippet='password "${name}" ${width:24} "${placeholder}"',
|
||||
snippet='password "${1:name}" ${2:24} "${3:placeholder}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("radio", node_class=Radio, section="Form",
|
||||
detail='radio "group" "A" | "B" | "C"',
|
||||
snippet='radio "${group}" "${opt1}" | "${opt2}"',
|
||||
snippet='radio "${1:group}" "${2:Option A}" | "${3:Option B}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("checkbox", node_class=Checkbox, section="Form",
|
||||
detail='checkbox "name" "Label"',
|
||||
snippet='checkbox "${name}" "${label}"',
|
||||
snippet='checkbox "${1:name}" "${2:Label}"',
|
||||
is_leaf=True)
|
||||
|
||||
register_keyword("button", node_class=FormButton, section="Form",
|
||||
detail='button "Label" "/action"',
|
||||
snippet='button "${label}" "${dest}"',
|
||||
snippet='button "${1:Label}" "${2:/page/action.mu}"',
|
||||
is_leaf=True)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -222,17 +222,17 @@ register_keyword("button", node_class=FormButton, section="Form",
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
register_keyword("let", node_class=Let, section="Dynamic",
|
||||
detail='let name = "value"', snippet='let ${name} = "${value}"',
|
||||
detail='let name = "value"', snippet='let ${1:name} = "${2:value}"',
|
||||
is_metadata=True)
|
||||
|
||||
register_keyword("source", node_class=Source, section="Dynamic",
|
||||
detail='source name : shell "cmd"',
|
||||
snippet='source ${name} : shell "${command}"',
|
||||
snippet='source ${1:name} : shell "${2:command}"',
|
||||
highlight_values=["shell", "file", "json", "python", "rns", "param"],
|
||||
is_metadata=True)
|
||||
|
||||
register_keyword("if", node_class=IfBlock, section="Dynamic",
|
||||
detail="if $var > threshold", snippet="if ${condition}",
|
||||
detail="if $var > threshold", snippet="if ${1:condition}",
|
||||
is_container=True)
|
||||
|
||||
register_keyword("elif", section="Dynamic",
|
||||
@@ -242,20 +242,20 @@ register_keyword("else", section="Dynamic",
|
||||
detail="", snippet="")
|
||||
|
||||
register_keyword("for", node_class=ForLoop, section="Dynamic",
|
||||
detail="for item in $list", snippet='for ${var} in ${iterable}',
|
||||
detail="for item in $list", snippet='for ${1:item} in ${2:\\$list}',
|
||||
is_container=True)
|
||||
|
||||
register_keyword("cache", node_class=CacheControl, section="Dynamic",
|
||||
detail="cache 0", snippet="cache ${seconds:0}",
|
||||
detail="cache 0", snippet="cache ${1:0}",
|
||||
is_metadata=True)
|
||||
|
||||
register_keyword("on_submit", node_class=OnSubmit, section="Dynamic",
|
||||
detail='on_submit "form"', snippet='on_submit "${form_name}"',
|
||||
detail='on_submit "form"', snippet='on_submit "${1:form_name}"',
|
||||
is_container=True)
|
||||
|
||||
register_keyword("state", node_class=StateDecl, section="Dynamic",
|
||||
detail='state "name" "/path.json"',
|
||||
snippet='state "${name}" "${path}"',
|
||||
snippet='state "${1:name}" "${2:/tmp/state.json}"',
|
||||
is_metadata=True)
|
||||
|
||||
register_keyword("set", section="Dynamic", detail="", snippet="")
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 30 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 58 KiB |
38
frontend/src/assets/frame.themed.svg
Normal file
38
frontend/src/assets/frame.themed.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 496 KiB |
1
frontend/src/assets/frames.min.svg
Normal file
1
frontend/src/assets/frames.min.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.5 MiB |
18
frontend/src/assets/frames.svg
Normal file
18
frontend/src/assets/frames.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 2.8 MiB |
@@ -315,40 +315,36 @@ export const EXAMPLES: Example[] = [
|
||||
},
|
||||
{
|
||||
name: "Image Art",
|
||||
description: "Convert images to braille/block/ascii character art",
|
||||
source: `page "Gallery" 60
|
||||
description: "Convert images to character art (upload an image first)",
|
||||
source: `page "Image Embedding" 60
|
||||
|
||||
heading 1 "Image Embedding"
|
||||
text "Upload an image with the Image button above,"
|
||||
text "then use the image keyword to embed it."
|
||||
|
||||
spacer
|
||||
|
||||
row 2
|
||||
col 26
|
||||
image "backend/test_logo.png" braille 24
|
||||
align center
|
||||
caption "Braille mode"
|
||||
col 30
|
||||
heading 2 "Modes"
|
||||
label "braille" "2x4 dot matrix"
|
||||
label "block" "shade chars"
|
||||
label "ascii" "classic ramp"
|
||||
label "halfblock" "color blocks"
|
||||
spacer
|
||||
heading 2 "Options"
|
||||
label "dither" "floyd | threshold"
|
||||
label "invert" "flip light/dark"
|
||||
box light "Syntax"
|
||||
text 'image "path.png" mode width'
|
||||
spacer
|
||||
label "mode" "braille | block | ascii | halfblock"
|
||||
label "width" "output width in characters"
|
||||
|
||||
spacer
|
||||
|
||||
image "backend/test_logo.png" block 24
|
||||
align center
|
||||
caption "Block mode"
|
||||
heading 2 "Options"
|
||||
label "dither" "floyd | threshold | none"
|
||||
label "invert" "flip light and dark"
|
||||
label "align" "left | center | right"
|
||||
label "caption" "text below the image"
|
||||
|
||||
spacer
|
||||
|
||||
image "backend/test_logo.png" ascii 24
|
||||
align center
|
||||
caption "ASCII mode"`,
|
||||
box rounded "Example"
|
||||
text 'image "logo.png" braille 30'
|
||||
text ' dither floyd'
|
||||
text ' align center'
|
||||
text ' caption "My logo"'`,
|
||||
},
|
||||
{
|
||||
name: "Big Title",
|
||||
|
||||
@@ -65,7 +65,6 @@ function getCommands(): CmdEntry[] {
|
||||
* complete registry-driven list.
|
||||
*/
|
||||
export async function loadCommandsFromApi(): Promise<void> {
|
||||
if (window.__uframeCommandsLoaded) return;
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/dsl-meta");
|
||||
|
||||
@@ -14,24 +14,52 @@ import { tags } from "@lezer/highlight";
|
||||
|
||||
// Mutable sets — updated from /api/dsl-meta
|
||||
let KEYWORDS = new Set([
|
||||
// Fallback defaults (used before API response arrives)
|
||||
// Layout
|
||||
"page", "box", "row", "col", "spacer", "pad",
|
||||
"heading", "text", "label", "divider", "link",
|
||||
"list", "item", "gauge", "sparkline", "status",
|
||||
"table", "columns", "form", "field", "radio",
|
||||
"checkbox", "button", "source", "let", "if",
|
||||
"elif", "else", "for", "align", "color", "bg",
|
||||
"bold", "italic", "underline", "cache", "state",
|
||||
"on_submit", "theme", "component", "use",
|
||||
// Content
|
||||
"heading", "text", "label", "divider", "link", "list", "item",
|
||||
"bigtitle", "image", "dither", "invert", "caption",
|
||||
// Data
|
||||
"gauge", "sparkline", "status", "table", "columns",
|
||||
// Navigation
|
||||
"hnav", "vnav", "separator",
|
||||
// Form
|
||||
"form", "field", "radio", "checkbox", "button", "password",
|
||||
// Style
|
||||
"align", "color", "bg", "bold", "italic", "underline", "theme",
|
||||
// Dynamic
|
||||
"source", "let", "if", "elif", "else", "for", "cache", "state", "on_submit",
|
||||
"set", "append", "prepend",
|
||||
// Components
|
||||
"component", "use",
|
||||
]);
|
||||
|
||||
let WEIGHT_VALS = new Set([
|
||||
// Border weights
|
||||
"light", "heavy", "double", "rounded",
|
||||
// List styles
|
||||
"bullet", "dash", "number", "arrow",
|
||||
// Alignment
|
||||
"left", "center", "right",
|
||||
"online", "offline", "degraded", "unknown",
|
||||
// Status states
|
||||
"online", "offline", "degraded", "unknown", "alert",
|
||||
// Source types
|
||||
"shell", "file", "json", "python", "rns", "param",
|
||||
// Themes
|
||||
"default", "nouveau", "gothic", "bamboo", "circuit", "brutalist",
|
||||
// Nav styles
|
||||
"bar", "tabs", "pills", "breadcrumb", "underline",
|
||||
"boxed", "tree", "sidebar", "minimal",
|
||||
// Fonts
|
||||
"block", "thin", "pixel",
|
||||
// Image modes
|
||||
"braille", "ascii", "halfblock",
|
||||
// Dither
|
||||
"floyd", "threshold", "none",
|
||||
// Divider styles
|
||||
"dot",
|
||||
// Misc
|
||||
"active", "compact",
|
||||
]);
|
||||
|
||||
/** Update keywords and values from /api/dsl-meta response. */
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import NavBar from "./NavBar";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import frameSvg from "@/assets/frame.themed.svg";
|
||||
|
||||
const TITLE = `
|
||||
|
||||
▄▄▄▄███▄▄▄▄ ▄█ ▄████████ ▄████████ ▄██████▄ ███▄▄▄▄ ▄██████▄ ▄▄▄▄███▄▄▄▄ ▄█ ▄████████ ▄██████▄ ███▄▄▄▄
|
||||
▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███▀▀▀██▄
|
||||
███ ███ ███ ███▌ ███ █▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ █▀ ███ ███ ███ ███
|
||||
███ ███ ███ ███▌ ███ ▄███▄▄▄▄██▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███▌ ███ ▀▀███▀▀▀▀▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ █▄ ▀███████████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█ ███ █▀ █▀ ████████▀ ███ ███ ▀██████▀ ▀█ █▀ ▀██████▀ ▀█ ███ █▀ █▀ ████████▀ ▀██████▀ ▀█ █▀
|
||||
███ ███
|
||||
|
||||
`;
|
||||
|
||||
function toRoman(n: number): string {
|
||||
const vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
|
||||
const syms = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"];
|
||||
const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
|
||||
const syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"];
|
||||
let r = "";
|
||||
for (let i = 0; i < vals.length; i++) {
|
||||
while (n >= vals[i]) { r += syms[i]; n -= vals[i]; }
|
||||
@@ -28,15 +43,48 @@ function RomanClock() {
|
||||
}
|
||||
|
||||
export default function AppShell({ children }: { children: ReactNode }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="flex flex-col h-screen bg-background text-foreground">
|
||||
<NavBar />
|
||||
<main className="flex-1 overflow-auto">{children}</main>
|
||||
<main className="flex-1 overflow-auto">
|
||||
<div className="relative max-w-5xl min-w-5xl mx-auto min-h-full">
|
||||
{/* Frame SVG — background */}
|
||||
<img
|
||||
src={frameSvg}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="absolute inset-0 w-full min-w-full pointer-events-none select-none"
|
||||
style={{ zIndex: 0, objectFit: "fill" }}
|
||||
/>
|
||||
|
||||
{/* Top hole — ASCII title */}
|
||||
<div
|
||||
className="relative z-10 flex flex-col cursor-pointer overflow-hidden"
|
||||
style={{ paddingTop: 65, height: 185, paddingLeft: 60, paddingRight: 500, paddingBottom: 25 }}
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
<pre
|
||||
className="select-none text-primary/70 hover:text-primary transition-colors whitespace-pre origin-center"
|
||||
style={{ height: 60, fontSize: 6, lineHeight: 1.05, transform: "scale(0.50)", transformOrigin: "left center", alignContent: "center" }}
|
||||
>
|
||||
{TITLE}
|
||||
</pre>
|
||||
<span className="font-mono text-[10px] text-muted-foreground"><RomanClock /></span>
|
||||
</div>
|
||||
|
||||
{/* Bottom hole — main content */}
|
||||
<div
|
||||
className="relative z-10 overflow-auto"
|
||||
style={{ marginLeft: 12, marginRight: 68, width: 843, height: 1030, paddingTop: 8, paddingBottom: 20 }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="flex items-center justify-center gap-3 text-[10px] text-muted-foreground py-4 shrink-0 tracking-widest uppercase">
|
||||
<span>Built with hubris by the demiurge · MMXXVI</span>
|
||||
<span className="text-muted-foreground/50">│</span>
|
||||
<span className="font-mono"><RomanClock /></span>
|
||||
<span>Becoming with hubris · MMXXVI</span>
|
||||
</footer>
|
||||
</div>
|
||||
<Toaster />
|
||||
|
||||
@@ -1,46 +1,4 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import baldothSvg from "@/assets/baldoth.min.svg";
|
||||
|
||||
const TITLE = ` █████ ██ ██
|
||||
██████ █████ █████ █
|
||||
██ █ █ █████ █████ ███
|
||||
█ █ █ █ ██ █ ██ █
|
||||
█ █ █ █ ███ ████ ████ ████
|
||||
██ ██ █ █ ███ ████ ████ ████ █ █ ███ ████ ████ █ ███ ████ ████ ████ ███ ████ █ ███ ████ ████
|
||||
██ ██ █ █ ███ █ ███ █ ██ ████ █ ████ ████ ████ █ █ ████ ███ ████ ███ █ ███ █ ███ █ █ ████ ████ ████ █
|
||||
██ ██ █ █ ██ █ ████ ██ ██ ██ ██ ████ ██ ██ ██ ████ ████ ██ █ ████ ██ ██ ██ ████
|
||||
██ ██ █ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
██ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
█ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
█ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
████ █ ██ ██ ███ █ ███ ██████ ██ ██ ██████ ██ ██ ██ ██ ███ █ ██████ ██ ██
|
||||
█ █████ ██ ███ █ ███████ ███ ████ ███ ███ ████ ███ ███ ███ ███ █ ███████ ████ ███ ███
|
||||
█ ██ ███ █████ ███ ███ ███ ███ ███ ███ █████ ███ ███
|
||||
█
|
||||
█
|
||||
██`;
|
||||
|
||||
// NavBar removed — frame.svg is the visual wrapper now
|
||||
export default function NavBar() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center pt-6 pb-3 shrink-0 gap-2">
|
||||
<button
|
||||
onClick={() => navigate("/")}
|
||||
className="hover:opacity-80 transition-opacity cursor-pointer flex flex-col items-center gap-2"
|
||||
title="Go to Dashboard"
|
||||
>
|
||||
<div
|
||||
className="h-28 w-28"
|
||||
style={{
|
||||
backgroundColor: "var(--primary)",
|
||||
opacity: 0.5,
|
||||
mask: `url(${baldothSvg}) center/contain no-repeat`,
|
||||
WebkitMask: `url(${baldothSvg}) center/contain no-repeat`,
|
||||
}}
|
||||
/>
|
||||
<pre className="text-[4px] leading-[1] tracking-tighter select-none text-primary/40">{TITLE}</pre>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,131 +6,133 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--font-heading: var(--font-mono);
|
||||
--font-sans: 'JetBrains Mono Variable', 'Courier New', monospace;
|
||||
--font-mono: 'JetBrains Mono Variable', 'Courier New', monospace;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--radius-sm: 0px;
|
||||
--radius-md: 0px;
|
||||
--radius-lg: 0px;
|
||||
--radius-xl: 0px;
|
||||
--radius-2xl: 0px;
|
||||
--radius-3xl: 0px;
|
||||
--radius-4xl: 0px;
|
||||
--font-heading: var(--font-mono);
|
||||
--font-sans: 'JetBrains Mono Variable', 'Courier New', monospace;
|
||||
--font-mono: 'JetBrains Mono Variable', 'Courier New', monospace;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--radius-sm: 0px;
|
||||
--radius-md: 0px;
|
||||
--radius-lg: 0px;
|
||||
--radius-xl: 0px;
|
||||
--radius-2xl: 0px;
|
||||
--radius-3xl: 0px;
|
||||
--radius-4xl: 0px;
|
||||
}
|
||||
|
||||
/* ── Terracotta Light Theme ── */
|
||||
:root {
|
||||
--background: oklch(0.94 0.02 55);
|
||||
--foreground: oklch(0.18 0.03 45);
|
||||
--card: oklch(0.91 0.025 55);
|
||||
--card-foreground: oklch(0.18 0.03 45);
|
||||
--popover: oklch(0.91 0.025 55);
|
||||
--popover-foreground: oklch(0.18 0.03 45);
|
||||
--primary: oklch(0.55 0.14 45);
|
||||
--primary-foreground: oklch(0.95 0.02 55);
|
||||
--secondary: oklch(0.86 0.03 55);
|
||||
--secondary-foreground: oklch(0.18 0.03 45);
|
||||
--muted: oklch(0.86 0.025 55);
|
||||
--muted-foreground: oklch(0.45 0.04 45);
|
||||
--accent: oklch(0.84 0.035 55);
|
||||
--accent-foreground: oklch(0.18 0.03 45);
|
||||
--destructive: oklch(0.5 0.2 25);
|
||||
--border: oklch(0.6 0.08 45);
|
||||
--input: oklch(0.78 0.04 55);
|
||||
--ring: oklch(0.55 0.14 45);
|
||||
--chart-1: oklch(0.55 0.14 45);
|
||||
--chart-2: oklch(0.65 0.1 70);
|
||||
--chart-3: oklch(0.5 0.08 30);
|
||||
--chart-4: oklch(0.6 0.06 90);
|
||||
--chart-5: oklch(0.4 0.04 45);
|
||||
--radius: 0px;
|
||||
--sidebar: oklch(0.90 0.025 55);
|
||||
--sidebar-foreground: oklch(0.18 0.03 45);
|
||||
--sidebar-primary: oklch(0.55 0.14 45);
|
||||
--sidebar-primary-foreground: oklch(0.95 0.02 55);
|
||||
--sidebar-accent: oklch(0.84 0.035 55);
|
||||
--sidebar-accent-foreground: oklch(0.18 0.03 45);
|
||||
--sidebar-border: oklch(0.6 0.08 45);
|
||||
--sidebar-ring: oklch(0.55 0.14 45);
|
||||
--background: oklch(0.94 0.02 55);
|
||||
--foreground: oklch(0.18 0.03 45);
|
||||
--card: oklch(0.91 0.025 55);
|
||||
--card-foreground: oklch(0.18 0.03 45);
|
||||
--popover: oklch(0.91 0.025 55);
|
||||
--popover-foreground: oklch(0.18 0.03 45);
|
||||
--primary: oklch(0.55 0.14 45);
|
||||
--primary-foreground: oklch(0.95 0.02 55);
|
||||
--secondary: oklch(0.86 0.03 55);
|
||||
--secondary-foreground: oklch(0.18 0.03 45);
|
||||
--muted: oklch(0.86 0.025 55);
|
||||
--muted-foreground: oklch(0.45 0.04 45);
|
||||
--accent: oklch(0.84 0.035 55);
|
||||
--accent-foreground: oklch(0.18 0.03 45);
|
||||
--destructive: oklch(0.5 0.2 25);
|
||||
--border: oklch(0.6 0.08 45);
|
||||
--input: oklch(0.78 0.04 55);
|
||||
--ring: oklch(0.55 0.14 45);
|
||||
--chart-1: oklch(0.55 0.14 45);
|
||||
--chart-2: oklch(0.65 0.1 70);
|
||||
--chart-3: oklch(0.5 0.08 30);
|
||||
--chart-4: oklch(0.6 0.06 90);
|
||||
--chart-5: oklch(0.4 0.04 45);
|
||||
--radius: 0px;
|
||||
--sidebar: oklch(0.90 0.025 55);
|
||||
--sidebar-foreground: oklch(0.18 0.03 45);
|
||||
--sidebar-primary: oklch(0.55 0.14 45);
|
||||
--sidebar-primary-foreground: oklch(0.95 0.02 55);
|
||||
--sidebar-accent: oklch(0.84 0.035 55);
|
||||
--sidebar-accent-foreground: oklch(0.18 0.03 45);
|
||||
--sidebar-border: oklch(0.6 0.08 45);
|
||||
--sidebar-ring: oklch(0.55 0.14 45);
|
||||
}
|
||||
|
||||
/* ── Black-Figure Dark Theme (primary) ──
|
||||
Inspired by Greek pottery: black ground, terracotta figures, gold accents
|
||||
*/
|
||||
.dark {
|
||||
--background: oklch(0.12 0.015 45);
|
||||
--foreground: oklch(0.72 0.08 55);
|
||||
--card: oklch(0.16 0.02 45);
|
||||
--card-foreground: oklch(0.72 0.08 55);
|
||||
--popover: oklch(0.16 0.02 45);
|
||||
--popover-foreground: oklch(0.72 0.08 55);
|
||||
--primary: oklch(0.7 0.13 55);
|
||||
--primary-foreground: oklch(0.12 0.015 45);
|
||||
--secondary: oklch(0.22 0.025 45);
|
||||
--secondary-foreground: oklch(0.7 0.08 55);
|
||||
--muted: oklch(0.22 0.02 45);
|
||||
--muted-foreground: oklch(0.5 0.05 55);
|
||||
--accent: oklch(0.24 0.03 45);
|
||||
--accent-foreground: oklch(0.72 0.08 55);
|
||||
--destructive: oklch(0.55 0.18 25);
|
||||
--border: oklch(0.38 0.06 45);
|
||||
--input: oklch(0.26 0.03 45);
|
||||
--ring: oklch(0.7 0.13 55);
|
||||
--chart-1: oklch(0.7 0.13 55);
|
||||
--chart-2: oklch(0.65 0.1 70);
|
||||
--chart-3: oklch(0.55 0.08 30);
|
||||
--chart-4: oklch(0.6 0.06 90);
|
||||
--chart-5: oklch(0.45 0.04 45);
|
||||
--sidebar: oklch(0.14 0.018 45);
|
||||
--sidebar-foreground: oklch(0.72 0.08 55);
|
||||
--sidebar-primary: oklch(0.7 0.13 55);
|
||||
--sidebar-primary-foreground: oklch(0.12 0.015 45);
|
||||
--sidebar-accent: oklch(0.24 0.03 45);
|
||||
--sidebar-accent-foreground: oklch(0.72 0.08 55);
|
||||
--sidebar-border: oklch(0.38 0.06 45);
|
||||
--sidebar-ring: oklch(0.7 0.13 55);
|
||||
--background: oklch(0.12 0.015 45);
|
||||
--foreground: oklch(0.72 0.08 55);
|
||||
--card: oklch(0.16 0.02 45);
|
||||
--card-foreground: oklch(0.72 0.08 55);
|
||||
--popover: oklch(0.16 0.02 45);
|
||||
--popover-foreground: oklch(0.72 0.08 55);
|
||||
--primary: oklch(0.7 0.13 55);
|
||||
--primary-foreground: oklch(0.12 0.015 45);
|
||||
--secondary: oklch(0.22 0.025 45);
|
||||
--secondary-foreground: oklch(0.7 0.08 55);
|
||||
--muted: oklch(0.22 0.02 45);
|
||||
--muted-foreground: oklch(0.5 0.05 55);
|
||||
--accent: oklch(0.24 0.03 45);
|
||||
--accent-foreground: oklch(0.72 0.08 55);
|
||||
--destructive: oklch(0.55 0.18 25);
|
||||
--border: oklch(0.38 0.06 45);
|
||||
--input: oklch(0.26 0.03 45);
|
||||
--ring: oklch(0.7 0.13 55);
|
||||
--chart-1: oklch(0.7 0.13 55);
|
||||
--chart-2: oklch(0.65 0.1 70);
|
||||
--chart-3: oklch(0.55 0.08 30);
|
||||
--chart-4: oklch(0.6 0.06 90);
|
||||
--chart-5: oklch(0.45 0.04 45);
|
||||
--sidebar: oklch(0.14 0.018 45);
|
||||
--sidebar-foreground: oklch(0.72 0.08 55);
|
||||
--sidebar-primary: oklch(0.7 0.13 55);
|
||||
--sidebar-primary-foreground: oklch(0.12 0.015 45);
|
||||
--sidebar-accent: oklch(0.24 0.03 45);
|
||||
--sidebar-accent-foreground: oklch(0.72 0.08 55);
|
||||
--sidebar-border: oklch(0.38 0.06 45);
|
||||
--sidebar-ring: oklch(0.7 0.13 55);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply font-mono;
|
||||
}
|
||||
@@ -140,20 +142,25 @@
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: oklch(0.35 0.05 45) transparent;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: oklch(0.35 0.05 45);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: oklch(0.5 0.08 55);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
@@ -164,13 +171,19 @@
|
||||
}
|
||||
|
||||
/* Bold console aesthetic */
|
||||
button, [role="button"], [data-slot="button"] {
|
||||
button,
|
||||
[role="button"],
|
||||
[data-slot="button"] {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
/* Keyword hover tooltip */
|
||||
.cm-tooltip.cm-tooltip-hover {
|
||||
background: var(--card) !important;
|
||||
@@ -185,9 +198,14 @@
|
||||
}
|
||||
|
||||
/* Ensure all interactive elements have pointer cursor */
|
||||
a, [role="link"], [role="tab"], [role="option"],
|
||||
[data-slot="popover-trigger"], [data-slot="toggle-group-item"],
|
||||
[data-slot="alert-dialog-action"], [data-slot="alert-dialog-cancel"],
|
||||
a,
|
||||
[role="link"],
|
||||
[role="tab"],
|
||||
[role="option"],
|
||||
[data-slot="popover-trigger"],
|
||||
[data-slot="toggle-group-item"],
|
||||
[data-slot="alert-dialog-action"],
|
||||
[data-slot="alert-dialog-cancel"],
|
||||
.cm-tooltip-autocomplete [role="option"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -238,6 +256,7 @@
|
||||
6px 4px 0 0 var(--border);
|
||||
transition: box-shadow 0.1s, transform 0.1s;
|
||||
}
|
||||
|
||||
button[data-slot="button"][data-variant="default"]:active {
|
||||
box-shadow: none;
|
||||
transform: translate(2px, 2px);
|
||||
@@ -256,4 +275,4 @@
|
||||
input[data-slot="input"] {
|
||||
border: 2px solid var(--border);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,8 +103,8 @@ export default function DashboardView() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-4">
|
||||
<div className="border-2 border-border" style={{boxShadow:'3px 3px 0 0 var(--border),5px 3px 0 0 transparent,7px 3px 0 0 var(--border),4px 4px 0 0 transparent,6px 4px 0 0 var(--border),3px 5px 0 0 var(--border),5px 5px 0 0 transparent,7px 5px 0 0 var(--border),4px 6px 0 0 var(--border),6px 6px 0 0 transparent'}}>
|
||||
<div>
|
||||
<div>
|
||||
{/* Header row */}
|
||||
<div className="flex items-center px-4 py-2 border-b-2 border-border">
|
||||
<h1 className="text-sm font-semibold flex-1">Pages</h1>
|
||||
|
||||
@@ -142,8 +142,8 @@ export default function EditorView() {
|
||||
}, [handleSave]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full max-w-5xl mx-auto w-full px-4">
|
||||
<div className="flex flex-col flex-1 border-2 border-border min-h-0" style={{boxShadow:'3px 3px 0 0 var(--border),5px 3px 0 0 transparent,7px 3px 0 0 var(--border),4px 4px 0 0 transparent,6px 4px 0 0 var(--border),3px 5px 0 0 var(--border),5px 5px 0 0 transparent,7px 5px 0 0 var(--border),4px 6px 0 0 var(--border),6px 6px 0 0 transparent'}}>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<ToolBar
|
||||
pageName={pageName}
|
||||
onNameChange={isNew ? setPageName : undefined}
|
||||
|
||||
Reference in New Issue
Block a user