feat: LCARS theme plugin — Star Trek computer interface
First-class pongo2 theme with 4 color presets (Federation, Red Alert, Sickbay, Engineering), 3 custom blocks (header, sidebar, panel), 2 page templates, heading/text overrides, email wrapper, bundled Antonio font, and full LCARS CSS with elbow brackets, pill buttons, and rounded bars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
e992d8247d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.so
|
||||||
BIN
assets/fonts/antonio-latin-ext.woff2
Normal file
BIN
assets/fonts/antonio-latin-ext.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/antonio-latin.woff2
Normal file
BIN
assets/fonts/antonio-latin.woff2
Normal file
Binary file not shown.
423
assets/style.css
Normal file
423
assets/style.css
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
/* ============================================================
|
||||||
|
LCARS Theme — Star Trek Computer Interface
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* --- Typography --- */
|
||||||
|
|
||||||
|
.lcars-page {
|
||||||
|
font-family: 'Antonio', sans-serif;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Color Utilities --- */
|
||||||
|
|
||||||
|
.lcars-bg-primary { background-color: hsl(var(--primary)); }
|
||||||
|
.lcars-bg-secondary { background-color: hsl(var(--secondary)); }
|
||||||
|
.lcars-bg-accent { background-color: hsl(var(--accent)); }
|
||||||
|
.lcars-bg-muted { background-color: hsl(var(--muted)); }
|
||||||
|
.lcars-text-primary { color: hsl(var(--primary)); }
|
||||||
|
.lcars-text-secondary { color: hsl(var(--secondary)); }
|
||||||
|
.lcars-text-accent { color: hsl(var(--accent)); }
|
||||||
|
|
||||||
|
/* --- Frame Layout --- */
|
||||||
|
|
||||||
|
.lcars-frame {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-content-area {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 12rem 0.25rem 1fr;
|
||||||
|
gap: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-frame-full .lcars-body-full {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Elbow Brackets --- */
|
||||||
|
|
||||||
|
.lcars-elbow-top,
|
||||||
|
.lcars-elbow-bottom {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-elbow-corner {
|
||||||
|
width: 12rem;
|
||||||
|
min-width: 12rem;
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-elbow-tl {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 2rem;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-elbow-bl {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 2rem;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-bar {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-bar-top {
|
||||||
|
background-color: hsl(var(--secondary));
|
||||||
|
border-radius: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-bar-bottom {
|
||||||
|
background-color: hsl(var(--secondary));
|
||||||
|
border-radius: 0 1rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-bar-accent {
|
||||||
|
height: 0.5rem;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
hsl(var(--primary)) 0%,
|
||||||
|
hsl(var(--primary)) 30%,
|
||||||
|
hsl(var(--secondary)) 30%,
|
||||||
|
hsl(var(--secondary)) 60%,
|
||||||
|
hsl(var(--accent)) 60%,
|
||||||
|
hsl(var(--accent)) 100%
|
||||||
|
);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Divider (between sidebar and main) --- */
|
||||||
|
|
||||||
|
.lcars-divider {
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
width: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sidebar --- */
|
||||||
|
|
||||||
|
.lcars-sidebar-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0 1.5rem 1.5rem 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsl(var(--background));
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
transition: filter 0.15s ease;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar-btn:hover {
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar-btn-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Main Content --- */
|
||||||
|
|
||||||
|
.lcars-main-area {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-main-full {
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Header Block --- */
|
||||||
|
|
||||||
|
.lcars-header-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-header-pill {
|
||||||
|
width: 6rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-header-title-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-subtitle {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: hsl(var(--secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-header-indicators {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-stardate {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-status {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-status-online {
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
color: hsl(var(--background));
|
||||||
|
animation: lcars-pulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-status-standby {
|
||||||
|
background-color: hsl(var(--secondary));
|
||||||
|
color: hsl(var(--background));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-status-alert {
|
||||||
|
background-color: hsl(var(--destructive));
|
||||||
|
color: hsl(var(--destructive-foreground));
|
||||||
|
animation: lcars-blink 1s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-status-offline {
|
||||||
|
background-color: hsl(var(--muted));
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-editor-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: hsl(var(--accent));
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Panel Block --- */
|
||||||
|
|
||||||
|
.lcars-panel {
|
||||||
|
border-left: 0.25rem solid hsl(var(--primary));
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-panel-secondary { border-left-color: hsl(var(--secondary)); }
|
||||||
|
.lcars-panel-accent { border-left-color: hsl(var(--accent)); }
|
||||||
|
|
||||||
|
.lcars-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-panel-header-pill {
|
||||||
|
width: 3rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background-color: inherit;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-panel-secondary .lcars-panel-header-pill { background-color: hsl(var(--secondary)); }
|
||||||
|
.lcars-panel-accent .lcars-panel-header-pill { background-color: hsl(var(--accent)); }
|
||||||
|
|
||||||
|
.lcars-panel-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-panel-header-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 0.125rem;
|
||||||
|
background-color: hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-panel-body {
|
||||||
|
padding: 0.75rem 1rem 0.75rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Heading Override --- */
|
||||||
|
|
||||||
|
.lcars-heading {
|
||||||
|
font-family: 'Antonio', sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
border-bottom: 0.125rem solid hsl(var(--primary));
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-heading-1 { font-size: 2.25rem; }
|
||||||
|
.lcars-heading-2 { font-size: 1.75rem; }
|
||||||
|
.lcars-heading-3 { font-size: 1.375rem; }
|
||||||
|
.lcars-heading-4 { font-size: 1.125rem; }
|
||||||
|
|
||||||
|
/* --- Text Override --- */
|
||||||
|
|
||||||
|
.lcars-prose {
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 1.7;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-prose a {
|
||||||
|
color: hsl(var(--secondary));
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-prose a:hover {
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-prose strong {
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-prose code {
|
||||||
|
background-color: hsl(var(--muted));
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Footer --- */
|
||||||
|
|
||||||
|
.lcars-footer-area {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Animations --- */
|
||||||
|
|
||||||
|
@keyframes lcars-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lcars-blink {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsive --- */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.lcars-content-area {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar-area {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-divider {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-elbow-corner {
|
||||||
|
width: 4rem;
|
||||||
|
min-width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-sidebar-btn {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-header-pill {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lcars-main-area {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
blocks.go
Normal file
53
blocks.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- LCARS Header Block ---
|
||||||
|
|
||||||
|
var LCARSHeaderMeta = blocks.BlockMeta{
|
||||||
|
Key: "lcars_header",
|
||||||
|
Title: "LCARS Header",
|
||||||
|
Description: "Top bar with elbow bracket, status indicators, and stardate",
|
||||||
|
Category: blocks.CategoryNavigation,
|
||||||
|
Source: "lcars",
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerDefaults = map[string]any{
|
||||||
|
"title": "LCARS",
|
||||||
|
"subtitle": "Library Computer Access/Retrieval System",
|
||||||
|
"stardate": "",
|
||||||
|
"status": "online",
|
||||||
|
"menu_name": "main",
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- LCARS Sidebar Block ---
|
||||||
|
|
||||||
|
var LCARSSidebarMeta = blocks.BlockMeta{
|
||||||
|
Key: "lcars_sidebar",
|
||||||
|
Title: "LCARS Sidebar",
|
||||||
|
Description: "Left panel with rounded-rectangle navigation buttons",
|
||||||
|
Category: blocks.CategoryNavigation,
|
||||||
|
Source: "lcars",
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidebarDefaults = map[string]any{
|
||||||
|
"items": []any{
|
||||||
|
map[string]any{"label": "Personnel", "url": "#", "color": "primary"},
|
||||||
|
map[string]any{"label": "Operations", "url": "#", "color": "secondary"},
|
||||||
|
map[string]any{"label": "Sciences", "url": "#", "color": "accent"},
|
||||||
|
map[string]any{"label": "Engineering", "url": "#", "color": "primary"},
|
||||||
|
map[string]any{"label": "Medical", "url": "#", "color": "secondary"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- LCARS Panel Block ---
|
||||||
|
|
||||||
|
var LCARSPanelMeta = blocks.BlockMeta{
|
||||||
|
Key: "lcars_panel",
|
||||||
|
Title: "LCARS Panel",
|
||||||
|
Description: "Framed content area with LCARS border treatment",
|
||||||
|
Category: blocks.CategoryLayout,
|
||||||
|
Source: "lcars",
|
||||||
|
}
|
||||||
60
embed.go
Normal file
60
embed.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed assets/*
|
||||||
|
var assetsFS embed.FS
|
||||||
|
|
||||||
|
//go:embed schemas/*
|
||||||
|
var schemasFS embed.FS
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var templateFS embed.FS
|
||||||
|
|
||||||
|
//go:embed presets.json
|
||||||
|
var presetsData []byte
|
||||||
|
|
||||||
|
//go:embed fonts.json
|
||||||
|
var fontsData []byte
|
||||||
|
|
||||||
|
//go:embed plugin.mod
|
||||||
|
var pluginModBytes []byte
|
||||||
|
|
||||||
|
func Assets() fs.FS {
|
||||||
|
sub, _ := fs.Sub(assetsFS, "assets")
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func SchemasFS() fs.FS {
|
||||||
|
sub, _ := fs.Sub(schemasFS, "schemas")
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func TemplatesFS() fs.FS {
|
||||||
|
sub, _ := fs.Sub(templateFS, "templates")
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssetsHandler() http.Handler {
|
||||||
|
return http.FileServer(http.FS(Assets()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThemePresets() []byte { return presetsData }
|
||||||
|
|
||||||
|
func BundledFonts() []byte { return fontsData }
|
||||||
|
|
||||||
|
func ThemeCSSManifest() *plugin.CSSManifest {
|
||||||
|
css, err := assetsFS.ReadFile("assets/style.css")
|
||||||
|
if err != nil {
|
||||||
|
return &plugin.CSSManifest{}
|
||||||
|
}
|
||||||
|
return &plugin.CSSManifest{
|
||||||
|
InputCSSAppend: string(css),
|
||||||
|
}
|
||||||
|
}
|
||||||
28
fonts.json
Normal file
28
fonts.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Antonio",
|
||||||
|
"family": "Antonio",
|
||||||
|
"variants": [
|
||||||
|
{
|
||||||
|
"weight": "300",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/antonio-latin.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "400",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/antonio-latin.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "600",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/antonio-latin.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "700",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/antonio-latin.woff2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module git.dev.alexdunmow.com/block/lcars
|
||||||
|
|
||||||
|
go 1.26.2
|
||||||
|
|
||||||
|
require git.dev.alexdunmow.com/block/core v0.10.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
connectrpc.com/connect v1.20.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||||
|
github.com/a-h/templ v0.3.1020 // indirect
|
||||||
|
github.com/flosch/pongo2/v6 v6.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||||
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
)
|
||||||
52
go.sum
Normal file
52
go.sum
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
|
||||||
|
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
||||||
|
git.dev.alexdunmow.com/block/core v0.10.0 h1:dWfYVbGuJOnvE58GcGGd5c71dAAKcjxrSEfjNepu4ro=
|
||||||
|
git.dev.alexdunmow.com/block/core v0.10.0/go.mod h1:y1/Q9UMG29AplbExecnq9M7y16PZ7cYd24bjZO1SCBQ=
|
||||||
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
||||||
|
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/flosch/pongo2/v6 v6.1.0 h1:A/NJbrQJJD2B2mbpw3DRFwBYG0xpCr3vwFlEr46y1HQ=
|
||||||
|
github.com/flosch/pongo2/v6 v6.1.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||||
|
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
57
master_pages.go
Normal file
57
master_pages.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "git.dev.alexdunmow.com/block/core/plugin"
|
||||||
|
|
||||||
|
func DefaultMasterPages() []plugin.MasterPageDefinition {
|
||||||
|
return []plugin.MasterPageDefinition{
|
||||||
|
{
|
||||||
|
Key: "lcars:standard-display",
|
||||||
|
Title: "LCARS Standard Display",
|
||||||
|
PageTemplates: []string{"default"},
|
||||||
|
Blocks: []plugin.MasterPageBlock{
|
||||||
|
{
|
||||||
|
BlockKey: "lcars:lcars_header",
|
||||||
|
Title: "LCARS Header",
|
||||||
|
Content: map[string]any{"title": "LCARS", "subtitle": "Library Computer Access/Retrieval System"},
|
||||||
|
Slot: "header",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BlockKey: "lcars:lcars_sidebar",
|
||||||
|
Title: "LCARS Sidebar",
|
||||||
|
Content: map[string]any{},
|
||||||
|
Slot: "sidebar",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BlockKey: "slot",
|
||||||
|
Title: "Main Content Slot",
|
||||||
|
Content: map[string]any{"slotName": "main", "placeholder": "Enter display data…"},
|
||||||
|
Slot: "main",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "lcars:full-display",
|
||||||
|
Title: "LCARS Full Display",
|
||||||
|
PageTemplates: []string{"full-display"},
|
||||||
|
Blocks: []plugin.MasterPageBlock{
|
||||||
|
{
|
||||||
|
BlockKey: "lcars:lcars_header",
|
||||||
|
Title: "LCARS Header",
|
||||||
|
Content: map[string]any{"title": "LCARS", "subtitle": "Main Viewer"},
|
||||||
|
Slot: "header",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BlockKey: "slot",
|
||||||
|
Title: "Main Content Slot",
|
||||||
|
Content: map[string]any{"slotName": "main", "placeholder": "Enter display data…"},
|
||||||
|
Slot: "main",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
3
plugin.mod
Normal file
3
plugin.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[plugin]
|
||||||
|
name = "lcars"
|
||||||
|
version = "0.1.0"
|
||||||
202
presets.json
Normal file
202
presets.json
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "federation",
|
||||||
|
"name": "Federation",
|
||||||
|
"description": "Classic LCARS — gold, periwinkle, and coral on black",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "40 33% 92%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "40 33% 92%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "40 33% 92%",
|
||||||
|
"primary": "39 90% 65%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "230 55% 70%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "40 20% 55%",
|
||||||
|
"accent": "10 70% 65%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 15%",
|
||||||
|
"input": "0 0% 15%",
|
||||||
|
"ring": "39 90% 65%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "40 33% 92%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "40 33% 92%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "40 33% 92%",
|
||||||
|
"primary": "39 90% 65%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "230 55% 70%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "40 20% 55%",
|
||||||
|
"accent": "10 70% 65%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 15%",
|
||||||
|
"input": "0 0% 15%",
|
||||||
|
"ring": "39 90% 65%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "red-alert",
|
||||||
|
"name": "Red Alert",
|
||||||
|
"description": "Emergency operations — red and amber on black",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "0 0% 92%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "0 0% 92%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "0 0% 92%",
|
||||||
|
"primary": "0 80% 50%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "35 95% 55%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "0 15% 55%",
|
||||||
|
"accent": "35 95% 55%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 30% 18%",
|
||||||
|
"input": "0 30% 18%",
|
||||||
|
"ring": "0 80% 50%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "0 0% 92%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "0 0% 92%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "0 0% 92%",
|
||||||
|
"primary": "0 80% 50%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "35 95% 55%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "0 15% 55%",
|
||||||
|
"accent": "35 95% 55%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 30% 18%",
|
||||||
|
"input": "0 30% 18%",
|
||||||
|
"ring": "0 80% 50%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sickbay",
|
||||||
|
"name": "Sickbay",
|
||||||
|
"description": "Medical operations — teal, cyan, and blue on black",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "180 20% 90%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "180 20% 90%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "180 20% 90%",
|
||||||
|
"primary": "174 72% 50%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "199 80% 55%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "180 15% 50%",
|
||||||
|
"accent": "199 80% 55%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "180 15% 15%",
|
||||||
|
"input": "180 15% 15%",
|
||||||
|
"ring": "174 72% 50%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "180 20% 90%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "180 20% 90%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "180 20% 90%",
|
||||||
|
"primary": "174 72% 50%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "199 80% 55%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "180 15% 50%",
|
||||||
|
"accent": "199 80% 55%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "180 15% 15%",
|
||||||
|
"input": "180 15% 15%",
|
||||||
|
"ring": "174 72% 50%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "engineering",
|
||||||
|
"name": "Engineering",
|
||||||
|
"description": "Engineering deck — orange, amber, and tan on black",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "35 30% 88%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "35 30% 88%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "35 30% 88%",
|
||||||
|
"primary": "25 95% 55%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "40 80% 60%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "30 20% 50%",
|
||||||
|
"accent": "33 65% 70%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "25 20% 16%",
|
||||||
|
"input": "25 20% 16%",
|
||||||
|
"ring": "25 95% 55%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 2%",
|
||||||
|
"foreground": "35 30% 88%",
|
||||||
|
"card": "0 0% 5%",
|
||||||
|
"cardForeground": "35 30% 88%",
|
||||||
|
"popover": "0 0% 5%",
|
||||||
|
"popoverForeground": "35 30% 88%",
|
||||||
|
"primary": "25 95% 55%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "40 80% 60%",
|
||||||
|
"secondaryForeground": "0 0% 0%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "30 20% 50%",
|
||||||
|
"accent": "33 65% 70%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "25 20% 16%",
|
||||||
|
"input": "25 20% 16%",
|
||||||
|
"ring": "25 95% 55%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
50
register.go
Normal file
50
register.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
"git.dev.alexdunmow.com/block/core/templates"
|
||||||
|
"git.dev.alexdunmow.com/block/core/templates/pongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var engine = pongo.NewEngine(TemplatesFS(), "/templates/lcars/style.css")
|
||||||
|
|
||||||
|
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
||||||
|
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
|
||||||
|
Key: "lcars",
|
||||||
|
Title: "LCARS",
|
||||||
|
Description: "Star Trek LCARS computer interface theme",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tr.RegisterPageTemplate("lcars", templates.PageTemplateMeta{
|
||||||
|
Key: "default",
|
||||||
|
Title: "Standard Display",
|
||||||
|
Description: "Classic LCARS layout with sidebar and header frame",
|
||||||
|
Slots: []string{"header", "sidebar", "main", "footer"},
|
||||||
|
}, engine.MustPageTemplate("default.html")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tr.RegisterPageTemplate("lcars", templates.PageTemplateMeta{
|
||||||
|
Key: "full-display",
|
||||||
|
Title: "Full Display",
|
||||||
|
Description: "Full-width LCARS display without sidebar",
|
||||||
|
Slots: []string{"header", "main", "footer"},
|
||||||
|
}, engine.MustPageTemplate("full_display.html")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.LoadSchemasFromFS(SchemasFS()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
br.Register(LCARSHeaderMeta, engine.MustBlockTemplateWithDefaults("blocks/header.html", headerDefaults))
|
||||||
|
br.Register(LCARSSidebarMeta, engine.MustBlockTemplateWithDefaults("blocks/sidebar.html", sidebarDefaults))
|
||||||
|
br.Register(LCARSPanelMeta, engine.MustBlockTemplate("blocks/panel.html"))
|
||||||
|
|
||||||
|
br.RegisterTemplateOverride("lcars", "heading", engine.MustTemplateOverride("blocks/heading_override.html"))
|
||||||
|
br.RegisterTemplateOverride("lcars", "text", engine.MustTemplateOverride("blocks/text_override.html"))
|
||||||
|
|
||||||
|
tr.RegisterEmailWrapper("lcars", engine.MustEmailWrapper("email_wrapper.html"))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
24
registration.go
Normal file
24
registration.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
"git.dev.alexdunmow.com/block/core/plugin"
|
||||||
|
"git.dev.alexdunmow.com/block/core/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Registration = plugin.PluginRegistration{
|
||||||
|
Name: "lcars",
|
||||||
|
Version: plugin.ParseModVersion(pluginModBytes),
|
||||||
|
Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
||||||
|
return Register(tr, br)
|
||||||
|
},
|
||||||
|
Assets: func() http.Handler { return AssetsHandler() },
|
||||||
|
Schemas: func() fs.FS { return SchemasFS() },
|
||||||
|
ThemePresets: func() []byte { return ThemePresets() },
|
||||||
|
BundledFonts: func() []byte { return BundledFonts() },
|
||||||
|
MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() },
|
||||||
|
CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() },
|
||||||
|
}
|
||||||
41
schemas/lcars_header.schema.json
Normal file
41
schemas/lcars_header.schema.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "LCARS Header",
|
||||||
|
"description": "Top bar with elbow bracket, status indicators, and stardate",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Title",
|
||||||
|
"description": "Main display title",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Subtitle",
|
||||||
|
"description": "Secondary text below the title",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"stardate": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Stardate",
|
||||||
|
"description": "Stardate display (leave empty for auto)",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Status",
|
||||||
|
"description": "System status indicator",
|
||||||
|
"enum": ["online", "standby", "alert", "offline"],
|
||||||
|
"default": "online",
|
||||||
|
"x-editor": "select"
|
||||||
|
},
|
||||||
|
"menu_name": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Menu Name",
|
||||||
|
"description": "Navigation menu to display",
|
||||||
|
"x-editor": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
}
|
||||||
29
schemas/lcars_panel.schema.json
Normal file
29
schemas/lcars_panel.schema.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "LCARS Panel",
|
||||||
|
"description": "Framed content area with LCARS border treatment",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Panel Title",
|
||||||
|
"description": "Title shown in the panel header bar",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Content",
|
||||||
|
"description": "Panel body content (HTML)",
|
||||||
|
"x-editor": "richtext"
|
||||||
|
},
|
||||||
|
"border_color": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Border Color",
|
||||||
|
"description": "Which theme color for the panel border",
|
||||||
|
"enum": ["primary", "secondary", "accent"],
|
||||||
|
"default": "primary",
|
||||||
|
"x-editor": "select"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
}
|
||||||
33
schemas/lcars_sidebar.schema.json
Normal file
33
schemas/lcars_sidebar.schema.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "LCARS Sidebar",
|
||||||
|
"description": "Left panel with rounded-rectangle navigation buttons",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Navigation Items",
|
||||||
|
"description": "List of sidebar navigation buttons",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"label": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Label"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "URL"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Color",
|
||||||
|
"enum": ["primary", "secondary", "accent"],
|
||||||
|
"default": "primary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["label", "url"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
templates/blocks/header.html
Normal file
23
templates/blocks/header.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<header class="lcars-header">
|
||||||
|
<div class="lcars-header-bar">
|
||||||
|
<div class="lcars-header-pill lcars-bg-primary"></div>
|
||||||
|
<div class="lcars-header-title-area">
|
||||||
|
<h1 class="lcars-title">{{ title }}</h1>
|
||||||
|
{% if subtitle %}
|
||||||
|
<span class="lcars-subtitle">{{ subtitle }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="lcars-header-indicators">
|
||||||
|
{% if stardate %}
|
||||||
|
<span class="lcars-stardate">SD {{ stardate }}</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="lcars-status lcars-status-{{ status|default:"online" }}">
|
||||||
|
{{ status|default:"online"|upper }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="lcars-header-pill lcars-bg-secondary"></div>
|
||||||
|
</div>
|
||||||
|
{% if ctx.isEditor %}
|
||||||
|
<div class="lcars-editor-badge">EDIT MODE</div>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
1
templates/blocks/heading_override.html
Normal file
1
templates/blocks/heading_override.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<{{ tag|default:"h2" }} class="lcars-heading lcars-heading-{{ level|default:2 }}">{{ text }}</{{ tag|default:"h2" }}>
|
||||||
14
templates/blocks/panel.html
Normal file
14
templates/blocks/panel.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<div class="lcars-panel lcars-panel-{{ border_color|default:"primary" }}">
|
||||||
|
{% if title %}
|
||||||
|
<div class="lcars-panel-header">
|
||||||
|
<div class="lcars-panel-header-pill"></div>
|
||||||
|
<span class="lcars-panel-title">{{ title }}</span>
|
||||||
|
<div class="lcars-panel-header-bar"></div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="lcars-panel-body">
|
||||||
|
{% if content %}
|
||||||
|
{{ content|safe }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
11
templates/blocks/sidebar.html
Normal file
11
templates/blocks/sidebar.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<nav class="lcars-sidebar">
|
||||||
|
{% for item in items %}
|
||||||
|
<a href="{{ item.url }}" class="lcars-sidebar-btn lcars-bg-{{ item.color|default:"primary" }}">
|
||||||
|
<span class="lcars-sidebar-btn-label">{{ item.label }}</span>
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
<div class="lcars-sidebar-btn lcars-bg-muted">
|
||||||
|
<span class="lcars-sidebar-btn-label">No items</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
1
templates/blocks/text_override.html
Normal file
1
templates/blocks/text_override.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="lcars-prose">{{ html|safe }}</div>
|
||||||
45
templates/default.html
Normal file
45
templates/default.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block body_class %}lcars-page lcars-default bg-background text-foreground antialiased min-h-screen{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="lcars-frame">
|
||||||
|
<div class="lcars-header-area">
|
||||||
|
{{ slots.header|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-body">
|
||||||
|
<div class="lcars-elbow-top">
|
||||||
|
<div class="lcars-elbow-corner lcars-elbow-tl"></div>
|
||||||
|
<div class="lcars-bar lcars-bar-top"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-content-area">
|
||||||
|
<aside class="lcars-sidebar-area">
|
||||||
|
{{ slots.sidebar|safe }}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="lcars-divider"></div>
|
||||||
|
|
||||||
|
<main class="lcars-main-area">
|
||||||
|
{% if slots.main %}
|
||||||
|
{{ slots.main|safe }}
|
||||||
|
{% else %}
|
||||||
|
<div class="lcars-empty-state">
|
||||||
|
<p class="text-muted-foreground uppercase tracking-widest">No data on file</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-elbow-bottom">
|
||||||
|
<div class="lcars-elbow-corner lcars-elbow-bl"></div>
|
||||||
|
<div class="lcars-bar lcars-bar-bottom"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="lcars-footer-area">
|
||||||
|
{{ slots.footer|safe }}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
43
templates/email_wrapper.html
Normal file
43
templates/email_wrapper.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
{% if preview_text %}
|
||||||
|
<span style="display:none;max-height:0;overflow:hidden;">{{ preview_text }}</span>
|
||||||
|
{% endif %}
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background-color: #050505; color: #e0d4b0; font-family: Arial, Helvetica, sans-serif; }
|
||||||
|
.lcars-email { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||||
|
.lcars-email-header { padding: 16px 20px; }
|
||||||
|
.lcars-email-header table { width: 100%; }
|
||||||
|
.lcars-email-pill { width: 80px; height: 32px; border-radius: 16px; background-color: {{ colors.primary }}; }
|
||||||
|
.lcars-email-title { font-size: 20px; font-weight: bold; text-transform: uppercase; letter-spacing: 3px; color: {{ colors.primary }}; padding-left: 12px; }
|
||||||
|
.lcars-email-bar { height: 4px; border-radius: 2px; margin-bottom: 24px; }
|
||||||
|
.lcars-email-body { padding: 0 20px; line-height: 1.6; font-size: 16px; }
|
||||||
|
.lcars-email-body a { color: {{ colors.secondary }}; text-decoration: underline; }
|
||||||
|
.lcars-email-footer { margin-top: 32px; padding-top: 16px; border-top: 2px solid {{ colors.border }}; font-size: 12px; text-transform: uppercase; letter-spacing: 2px; opacity: 0.6; text-align: center; }
|
||||||
|
.lcars-email-footer a { color: {{ colors.secondary }}; text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="lcars-email">
|
||||||
|
<div class="lcars-email-header">
|
||||||
|
<table><tr>
|
||||||
|
<td style="width:80px;"><div class="lcars-email-pill"></div></td>
|
||||||
|
<td class="lcars-email-title">{{ site_name }}</td>
|
||||||
|
</tr></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-email-bar" style="background: linear-gradient(to right, {{ colors.primary }} 0%, {{ colors.primary }} 30%, {{ colors.secondary }} 30%, {{ colors.secondary }} 60%, {{ colors.muted }} 60%, {{ colors.muted }} 100%);"></div>
|
||||||
|
|
||||||
|
<div class="lcars-email-body">
|
||||||
|
{{ body|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-email-footer">
|
||||||
|
{{ site_name }}{% if unsubscribe_url %} · <a href="{{ unsubscribe_url }}">Unsubscribe</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
templates/full_display.html
Normal file
31
templates/full_display.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block body_class %}lcars-page lcars-full-display bg-background text-foreground antialiased min-h-screen{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="lcars-frame lcars-frame-full">
|
||||||
|
<div class="lcars-header-area">
|
||||||
|
{{ slots.header|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lcars-body lcars-body-full">
|
||||||
|
<div class="lcars-bar-accent"></div>
|
||||||
|
|
||||||
|
<main class="lcars-main-area lcars-main-full">
|
||||||
|
{% if slots.main %}
|
||||||
|
{{ slots.main|safe }}
|
||||||
|
{% else %}
|
||||||
|
<div class="lcars-empty-state">
|
||||||
|
<p class="text-muted-foreground uppercase tracking-widest">No data on file</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="lcars-bar-accent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="lcars-footer-area">
|
||||||
|
{{ slots.footer|safe }}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user