summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-09-17 04:43:44 +0000
committersanine <sanine.not@pm.me>2023-09-17 04:43:44 +0000
commitbfcc2e759807a692d8e0353e3bbecd109175032b (patch)
tree67949de6992714c703b390ef5f99ac5c06bfb28a
parent4f124ec785486c8505c6303e0d0aa3e7090612c7 (diff)
add yarrow-ui.cgi and theming
-rw-r--r--.gitignore1
-rwxr-xr-xdemo.lua2
-rw-r--r--draw.lua146
-rw-r--r--style.css86
-rwxr-xr-xyarrow-ui.cgi25
5 files changed, 185 insertions, 75 deletions
diff --git a/.gitignore b/.gitignore
index 8516afb..86e3d1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
azer.svg
+*.swp
diff --git a/demo.lua b/demo.lua
index 17ec5dd..7d32b54 100755
--- a/demo.lua
+++ b/demo.lua
@@ -38,7 +38,7 @@ local stats = {
legendary = {
description = "The death tyrant can take 3 legendary actions, using the Eye Ray option below. It can take only one legendary action at a time and only at the end of another creature's turn. The tyrant regains spent legendary actions at the start of its turn.",
- { name='Eye Ray', value='The death tyrant uses one random eye ray.' },
+ actions = {{ name='Eye Ray', value='The death tyrant uses one random eye ray.' }},
},
}
diff --git a/draw.lua b/draw.lua
index 586f804..5d39cc5 100644
--- a/draw.lua
+++ b/draw.lua
@@ -37,14 +37,14 @@ local function pad(top, bottom, f, h)
end
-local function _divider()
+local function _divider(theme)
local size = 3
return function(y) return m.rect{
x=0, y=y, width=500, height=size,
- style="fill:darkred;",
+ style="fill:"..theme.flair,
} end, size
end
-local divider = function() return pad(0, 10, _divider()) end
+local divider = function(theme) return pad(0, 10, _divider(theme)) end
local function empty()
@@ -115,41 +115,33 @@ end
--===== basic stats =====--
-local function header(stats)
+local function header(stats, theme)
local f, h = stack(
--{pad(10, 10, name(stats))},
{pad(10, 10, text(
stats.name, 40,
- 'font-variant-caps:small-caps;stroke:black;fill:darkred;font-family:serif'
+ 'font-variant-caps:small-caps;stroke:'..theme.text..';fill:'..theme.flair..';font-family:serif'
))},
{pad(0, 10, text(
string.format("%s %s, %s", capitalize(stats.size), stats.type, stats.alignment),
- 14, 'font-style:italic'
+ 14, 'font-style:italic;fill:'..theme.text
))}
)
return f, h
end
-local function _statline(name, value)
- local size = 14
- return function(y) return m.text(
- '<tspan style="font-weight:bold">'..name..'</tspan> '..value,
- { x=10, y=y+size, style="font-size:"..size.."px;fill:darkred;" }
- ) end, 14
-end
---local statline = function(name, value) return pad(0, 2, _statline(name, value)) end
-local function statline(name, value)
+local function statline(name, value, theme)
return pad(0, 2, wrapped_text(
string.format('<tspan style="font-weight:bold">%s</tspan> %s', name, value),
- 80, 10, 14, 2, 'fill:darkred'
+ 80, 10, 14, 2, 'fill:'..theme.flair
))
end
-local function armor_hp(stats)
+local function armor_hp(stats, theme)
local f, h = pad(0, 10, stack(
- {statline('Armor Class', stats.ac)},
- {statline('Hit Points', stats.hp)},
- {statline('Speed', stats.speed)}
+ {statline('Armor Class', stats.ac, theme)},
+ {statline('Hit Points', stats.hp, theme)},
+ {statline('Speed', stats.speed, theme)}
))
return f, h
end
@@ -157,63 +149,63 @@ end
local function bonus(ability_score)
return math.floor( (ability_score-10)/2 )
end
-local function scorebox(name, ability_score, x, y)
+local function scorebox(name, ability_score, x, y, theme)
local score = tonumber(ability_score)
local value = string.format("%d (%s%d)", score, (score<0 and '-') or '+', bonus(score))
return m.g{
- m.text(name, { x=x+10, y=y+15, style="font-weight:bold;font-size:15px;fill:darkred" }),
- m.text(value, {x=x, y=y+30, style="font-size:15px;fill:darkred;" }),
+ m.text(name, { x=x+10, y=y+15, style="font-weight:bold;font-size:15px;fill:"..theme.flair }),
+ m.text(value, {x=x, y=y+30, style="font-size:15px;fill:"..theme.flair }),
}
end
-local function ability_scores(stats)
+local function ability_scores(stats, theme)
return function(y) return m.g{
- scorebox('STR', stats.str, 10, y),
- scorebox('DEX', stats.dex, 90, y),
- scorebox('CON', stats.con, 170, y),
- scorebox('INT', stats.int, 250, y),
- scorebox('WIS', stats.wis, 330, y),
- scorebox('CHA', stats.cha, 410, y),
+ scorebox('STR', stats.str, 10, y, theme),
+ scorebox('DEX', stats.dex, 90, y, theme),
+ scorebox('CON', stats.con, 170, y, theme),
+ scorebox('INT', stats.int, 250, y, theme),
+ scorebox('WIS', stats.wis, 330, y, theme),
+ scorebox('CHA', stats.cha, 410, y, theme),
} end, 40
end
-local function optional_attribute(stats, name, key)
+local function optional_attribute(stats, name, key, theme)
local value = stats[key]
if value then
- local f, h = statline(name, value)
+ local f, h = statline(name, value, theme)
return f, h
else
local f, h = empty()
return f, h
end
end
-local function misc_attributes(stats)
+local function misc_attributes(stats, theme)
local f, h = stack(
- {optional_attribute(stats, 'Saving Throws', 'saving_throws')},
- {optional_attribute(stats, 'Skills', 'skills')},
- {optional_attribute(stats, 'Damage Vulnerabilities', 'vulnerabilities')},
- {optional_attribute(stats, 'Damage Resistances', 'resistances')},
- {optional_attribute(stats, 'Damage Immunities', 'immunities')},
- {optional_attribute(stats, 'Condition Immunities', 'condition_immunities')},
- {optional_attribute(stats, 'Senses', 'senses')},
- {optional_attribute(stats, 'Languages', 'languages')},
- {optional_attribute(stats, 'Challenge', 'cr')}
+ {optional_attribute(stats, 'Saving Throws', 'saving_throws', theme)},
+ {optional_attribute(stats, 'Skills', 'skills', theme)},
+ {optional_attribute(stats, 'Damage Vulnerabilities', 'vulnerabilities', theme)},
+ {optional_attribute(stats, 'Damage Resistances', 'resistances', theme)},
+ {optional_attribute(stats, 'Damage Immunities', 'immunities', theme)},
+ {optional_attribute(stats, 'Condition Immunities', 'condition_immunities', theme)},
+ {optional_attribute(stats, 'Senses', 'senses', theme)},
+ {optional_attribute(stats, 'Languages', 'languages', theme)},
+ {optional_attribute(stats, 'Challenge', 'cr', theme)}
)
- if h > 0 then return stack({pad(0, 10, f, h)}, {divider()})
+ if h > 0 then return stack({pad(0, 10, f, h)}, {divider(theme)})
else return empty(), 0 end
end
-local function base(stats)
+local function base(stats, theme)
local f, h = stack(
- {header(stats)},
- {divider()},
- {armor_hp(stats)},
- {divider()},
- {ability_scores(stats)},
- {divider()},
- {misc_attributes(stats)}
+ {header(stats, theme)},
+ {divider(theme)},
+ {armor_hp(stats, theme)},
+ {divider(theme)},
+ {ability_scores(stats, theme)},
+ {divider(theme)},
+ {misc_attributes(stats, theme)}
)
return f, h
end
@@ -221,18 +213,18 @@ end
--===== traits =====--
-local function trait(t)
+local function trait(t, theme)
return pad(0, 10, wrapped_text(
string.format('<tspan style="font-weight:bold;font-style:italic">%s.</tspan> %s', t.name, format(t.value)),
- 80, 10, 14, 5
+ 80, 10, 14, 5, 'fill:'..theme.text
))
end
-local function traits(stats)
+local function traits(stats, theme)
if not stats.traits then return empty() end
local tbl = {}
for _, t in ipairs(stats.traits) do
- table.insert(tbl, {trait(t)})
+ table.insert(tbl, {trait(t, theme)})
end
return stack(unpack(tbl))
end
@@ -240,51 +232,51 @@ end
--===== actions =====--
-local function subheader(title)
+local function subheader(title, theme)
return pad(0, 10, stack(
- {pad(0, 5, text(title, 25, 'font-variant-caps:small-caps;stroke:black;fill:darkred'))},
- {divider()}
+ {pad(0, 5, text(title, 25, 'font-variant-caps:small-caps;stroke:'..theme.text..';fill:'..theme.flair))},
+ {divider(theme)}
))
end
-local function actions(stats)
+local function actions(stats, theme)
if not stats.actions then return empty() end
local tbl = {}
for _, action in ipairs(stats.actions) do
- table.insert(tbl, {trait(action)})
+ table.insert(tbl, {trait(action, theme)})
end
return stack(
- {subheader('Actions')},
+ {subheader('Actions', theme)},
unpack(tbl)
)
end
-local function reactions(stats)
+local function reactions(stats, theme)
if not stats.reactions then return empty() end
local tbl = {}
for _, reaction in ipairs(stats.reactions) do
- table.insert(tbl, {trait(reaction)})
+ table.insert(tbl, {trait(reaction, theme)})
end
return stack(
- {subheader('Reactions')},
+ {subheader('Reactions', theme)},
unpack(tbl)
)
end
-local function legendary(stats)
+local function legendary(stats, theme)
if not stats.legendary then return empty() end
local tbl = {}
for _, action in ipairs(stats.legendary.actions) do
- table.insert(tbl, {trait(action)})
+ table.insert(tbl, {trait(action, theme)})
end
return stack(
- {subheader('Legendary Actions')},
- {pad(0, 10, wrapped_text(stats.legendary.description, 80, 10, 14, 2, ''))},
+ {subheader('Legendary Actions', theme)},
+ {pad(0, 10, wrapped_text(stats.legendary.description, 80, 10, 14, 2, 'fill:'..theme.text))},
unpack(tbl)
)
end
@@ -294,19 +286,25 @@ end
--===== draw =====--
-function draw(stats)
+function draw(stats, theme)
+ local theme = theme or {
+ bg = '#020202',
+ text = 'beige',
+ flair = 'tomato',
+ }
local f, h = stack(
- {base(stats)},
- {traits(stats)},
- {actions(stats)},
- {reactions(stats)},
- {legendary(stats)}
+ {base(stats, theme)},
+ {traits(stats, theme)},
+ {actions(stats, theme)},
+ {reactions(stats, theme)},
+ {legendary(stats, theme)}
)
return m.render(m.svg{
viewBox = string.format("0 0 500 %d", h),
width = 500,
height = height,
style = "font-family:sans-serif;",
+ m.rect{x=0, y=0, width=500, height=h, style='fill:'..theme.bg},
f(0),
})
end
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..90f985a
--- /dev/null
+++ b/style.css
@@ -0,0 +1,86 @@
+:root {
+ --light: #eee;
+ --dark: #1c1c1c;
+ /*--highlight: #ff3a21*/
+ --highlight: #f5ae2e;
+}
+
+body {
+ color: var(--light);
+ background: var(--dark);
+ font: 1.3em monospace;
+ text-size-adjust: auto;
+}
+
+h1 {
+ font: 1.0em monospace;
+ font-weight: bold;
+ text-align: left;
+ text-size-adjust: auto;
+ margin-bottom: 0;
+}
+
+h2 {
+ font: 1.0em monospace;
+ font-weight: bold;
+ text-align: left;
+ text-size-adjust: auto;
+ margin-bottom: 0;
+ margin-top: 3em;
+}
+
+a {
+ color: var(--highlight);
+}
+
+a:hover {
+ color: var(--dark);
+ background: var(--highlight);
+ text-decoration: none;
+}
+
+ul {
+ list-style: none;
+}
+
+li:before {
+ content: '* ';
+}
+
+pre {
+ border-width: 0 0 0 2px;
+ border-style: solid;
+ border-color: var(--highlight);
+ background: black;
+ padding: 6px;
+ border-radius: 2px;
+}
+
+code {
+ background: black;
+ padding: 0 6px;
+ border-radius: 4px;
+}
+
+pre code {
+ padding: 0;
+}
+
+#content {
+ max-width: 40em;
+ margin: auto;
+}
+
+#navigation {
+ text-align: center;
+}
+
+#navigation pre {
+ border: none;
+ background: var(--dark);
+}
+
+.centered {
+ text-align: center;
+}
+
diff --git a/yarrow-ui.cgi b/yarrow-ui.cgi
new file mode 100755
index 0000000..37c2f87
--- /dev/null
+++ b/yarrow-ui.cgi
@@ -0,0 +1,25 @@
+#!/usr/bin/env lua5.1
+
+local marigold = require 'marigold-cgi.marigold'
+local h = marigold.h
+local b64 = require '3rdparty.base64'
+local json = require '3rdparty.json'
+
+
+local img = h('img', { style='color: white', src='https://sanine.net/utils/yarrow/yarrow.cgi?statistics=eyJzdHIiOjE3LCJocCI6IjM5ICg2ZDggKyAxMikiLCJ3aXMiOjEzLCJhYyI6IjE3IChuYXR1cmFsIGFybW9yLCBzaGllbGQpIiwiY29uIjoxNSwiaW50IjoxMiwic3BlZWQiOiIzMCBmdC4iLCJhY3Rpb25zIjpbeyJuYW1lIjoiV2FyaGFtbWVyIiwidmFsdWUiOiIqTWVsZWUgV2VhcG9uIEF0dGFjazoqICs1IHRvIGhpdCwgcmVhY2ggNSBmdC4sIG9uZSB0YXJnZXQuICpIaXQ6KiA3ICgxZDggKyAzKSBibHVkZ2VvbmluZyBkYW1hZ2Ugb3IgOCAoMWQxMCArIDMpIGJsdWRnZW9uaW5nIGRhbWFnZSBpZiB1c2VkIHdpdGggdHdvIGhhbmRzIHRvIG1ha2UgYSBtZWxlZSBhdHRhY2ssIHBsdXMgMyAoMWQ2KSBmaXJlIGRhbWFnZS4ifV0sInNpemUiOiJtZWRpdW0iLCJ0eXBlIjoiZWxlbWVudGFsIiwiY2hhIjoxMCwibGVnZW5kYXJ5Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGRlYXRoIHR5cmFudCBjYW4gdGFrZSAzIGxlZ2VuZGFyeSBhY3Rpb25zLCB1c2luZyB0aGUgRXllIFJheSBvcHRpb24gYmVsb3cuIEl0IGNhbiB0YWtlIG9ubHkgb25lIGxlZ2VuZGFyeSBhY3Rpb24gYXQgYSB0aW1lIGFuZCBvbmx5IGF0IHRoZSBlbmQgb2YgYW5vdGhlciBjcmVhdHVyZSdzIHR1cm4uIFRoZSB0eXJhbnQgcmVnYWlucyBzcGVudCBsZWdlbmRhcnkgYWN0aW9ucyBhdCB0aGUgc3RhcnQgb2YgaXRzIHR1cm4uIiwiYWN0aW9ucyI6W3sibmFtZSI6IkV5ZSBSYXkiLCJ2YWx1ZSI6IlRoZSBkZWF0aCB0eXJhbnQgdXNlcyBvbmUgcmFuZG9tIGV5ZSByYXkuIn1dfSwiaW1tdW5pdGllcyI6ImZpcmUsIHBvaXNvbiIsInRyYWl0cyI6W3sibmFtZSI6IkhlYXRlZCBCb2R5IiwidmFsdWUiOiJBIGNyZWF0dXJlIHRoYXQgdG91Y2hlcyB0aGUgYXplciBvciBoaXRzIGl0IHdpdGggYSBtZWxlZSBhdHRhY2sgd2hpbGUgd2l0aGluIDUgZmVldCBvZiBpdCB0YWtlcyA1ICgxZDEwKSBmaXJlIGRhbWFnZS4ifSx7Im5hbWUiOiJIZWF0ZWQgV2VhcG9ucyIsInZhbHVlIjoiV2hlbiB0aGUgYXplciBoaXRzIHdpdGggYSBtZXRhbCBtZWxlZSB3ZWFwb24sIGl0IGRlYWxzIGFuZCBleHRyYSAzICgxZDYpIGZpcmUgZGFtYWdlIChpbmNsdWRlZCBpbiB0aGUgYXR0YWNrKS4ifSx7Im5hbWUiOiJJbGx1bWluYXRpb24iLCJ2YWx1ZSI6IlRoZSBhemVyIHNoZWRzIGJyaWdodCBsaWdodCBpbiBhIDEwPWZvb3QgcmFkaXVzIGFuZCBkaW0gbGlnaHQgZm9yIGFuIGFkZGl0aW9uYWwgMTAgZmVldC4ifV0sImFsaWdubWVudCI6Imxhd2Z1bCBuZXV0cmFsIiwic2F2aW5nX3Rocm93cyI6IkNvbiArNCIsIm5hbWUiOiJBemVyIiwiY29uZGl0aW9uX2ltbXVuaXRpZXMiOiJwb2lzb25lZCIsImxhbmd1YWdlcyI6IklnbmFuIiwic2Vuc2VzIjoicGFzc2l2ZSBQZXJjZXB0aW9uIDExIiwiZGV4IjoxMiwiY3IiOiIyICg0NTAgWFApIn0=' })
+
+
+local head = h('head', {
+ h('meta', { charset='utf-8' }),
+ h('meta', { name='viewport', content='width=device-width, initial-scale=1' }),
+ h('title', 'yarrow config'),
+ h('link', { rel='stylesheet', href='style.css' }),
+})
+
+local body = h('body', {
+ img,
+})
+
+
+print('Content-type: text/html\n')
+print(marigold.html(h('html', { head, body })))