diff --git a/backend/uframe/keywords.py b/backend/uframe/keywords.py index b7bbcec..3d4c018 100644 --- a/backend/uframe/keywords.py +++ b/backend/uframe/keywords.py @@ -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="") diff --git a/frontend/src/assets/baldoth.min.svg b/frontend/src/assets/baldoth.min.svg deleted file mode 100644 index 6bd18ee..0000000 --- a/frontend/src/assets/baldoth.min.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/baldoth.svg b/frontend/src/assets/baldoth.svg deleted file mode 100644 index 2586d57..0000000 --- a/frontend/src/assets/baldoth.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/frontend/src/assets/frame.themed.svg b/frontend/src/assets/frame.themed.svg new file mode 100644 index 0000000..4228900 --- /dev/null +++ b/frontend/src/assets/frame.themed.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/frontend/src/assets/frames.min.svg b/frontend/src/assets/frames.min.svg new file mode 100644 index 0000000..f19ee1d --- /dev/null +++ b/frontend/src/assets/frames.min.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/frames.svg b/frontend/src/assets/frames.svg new file mode 100644 index 0000000..b36b855 --- /dev/null +++ b/frontend/src/assets/frames.svg @@ -0,0 +1,18 @@ + + + + diff --git a/frontend/src/components/editor/examples.ts b/frontend/src/components/editor/examples.ts index 37d8f20..8dcb44d 100644 --- a/frontend/src/components/editor/examples.ts +++ b/frontend/src/components/editor/examples.ts @@ -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", diff --git a/frontend/src/components/editor/uframeCommands.ts b/frontend/src/components/editor/uframeCommands.ts index ed0a419..89e5ac1 100644 --- a/frontend/src/components/editor/uframeCommands.ts +++ b/frontend/src/components/editor/uframeCommands.ts @@ -65,7 +65,6 @@ function getCommands(): CmdEntry[] { * complete registry-driven list. */ export async function loadCommandsFromApi(): Promise { - if (window.__uframeCommandsLoaded) return; try { const res = await fetch("/api/dsl-meta"); diff --git a/frontend/src/components/editor/uframeHighlight.ts b/frontend/src/components/editor/uframeHighlight.ts index 08193d8..b5e0759 100644 --- a/frontend/src/components/editor/uframeHighlight.ts +++ b/frontend/src/components/editor/uframeHighlight.ts @@ -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. */ diff --git a/frontend/src/components/shared/AppShell.tsx b/frontend/src/components/shared/AppShell.tsx index 50f841b..10474e1 100644 --- a/frontend/src/components/shared/AppShell.tsx +++ b/frontend/src/components/shared/AppShell.tsx @@ -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 (
- -
{children}
+
+
+ {/* Frame SVG — background */} + + + {/* Top hole — ASCII title */} +
navigate("/")} + > +
+                {TITLE}
+              
+ +
+ + {/* Bottom hole — main content */} +
+ {children} +
+
+
- Built with hubris by the demiurge · MMXXVI - - + Becoming with hubris · MMXXVI
diff --git a/frontend/src/components/shared/NavBar.tsx b/frontend/src/components/shared/NavBar.tsx index 8e9070d..b051cb0 100644 --- a/frontend/src/components/shared/NavBar.tsx +++ b/frontend/src/components/shared/NavBar.tsx @@ -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 ( -
- -
- ); + return null; } diff --git a/frontend/src/index.css b/frontend/src/index.css index 2885524..7df959e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -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); } -} +} \ No newline at end of file diff --git a/frontend/src/routes/DashboardView.tsx b/frontend/src/routes/DashboardView.tsx index 4f8d02f..bbe6a49 100644 --- a/frontend/src/routes/DashboardView.tsx +++ b/frontend/src/routes/DashboardView.tsx @@ -103,8 +103,8 @@ export default function DashboardView() { ); return ( -
-
+
+
{/* Header row */}

Pages

diff --git a/frontend/src/routes/EditorView.tsx b/frontend/src/routes/EditorView.tsx index a4de113..a3c05b2 100644 --- a/frontend/src/routes/EditorView.tsx +++ b/frontend/src/routes/EditorView.tsx @@ -142,8 +142,8 @@ export default function EditorView() { }, [handleSave]); return ( -
-
+
+