diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | demo.lua | 2 | ||||
-rw-r--r-- | draw.lua | 146 | ||||
-rw-r--r-- | style.css | 86 | ||||
-rwxr-xr-x | yarrow-ui.cgi | 25 |
5 files changed, 185 insertions, 75 deletions
@@ -1 +1,2 @@ azer.svg +*.swp @@ -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.' }}, }, } @@ -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 }))) |