first version

This commit is contained in:
Leon van Kammen 2026-07-21 19:19:52 +02:00
parent ee2500e427
commit 2671f43b8b
38 changed files with 788 additions and 257 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
experiences
result

View file

@ -1,7 +1,7 @@
```
$ source .env # not needed if your system has autoenv
$ build # fast: repeatable build
$ util/xrforge.sh build # fast: repeatable build
$ nix-build # slow: reproducable nix build (for experts)
```
@ -25,3 +25,12 @@ scan files
]]
```
## Scan manyfold
-------------
```
manual scan (via cli?)
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json'
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json'
```

View file

@ -1,110 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>XRForge - selfsovereign interoperable XR experiences</title>
<link rel="stylesheet" href="xrforge.css"></link>
</head>
<body>
<table width="100%" class="header">
<tr>
<td style="width:120px">
<div class="margin1">
<div class="logo col1">XR<div class="logo col2">f̅o̅r̅g̅e̅</div>
</div>
</div>
</td>
<td class="menu">
<div class="margin1">
<a href="#">Experiences</a>
<a href="#">Collections</a>
<a href="#">About</a>
<a href="#">Howto</a>
</div>
</td>
</tr>
</table>
<table width="100%">
<tr>
<td>
<center>
<table width="80%" class="page">
<tr>
<td>
<div style="background:linear-gradient(45deg,#0000,#0000,#F7F2,#0000,#07F2,#0000,#0000); height:120px;" class="jumbotron">
<h1>Hi there</h1>
This is a hosting tool for <a href="#">portable XR experiences</a>.
</div>
<hr style="margin-top:0">
<div class="btn">
<a href="#foo">button</a>
</div>
<div class="btn">
<a href="#foo" class="btn primary">button primary ⮮</a>
</div>
<div class="btn">
<a href="https://foo.com" target="_blank" class="btn secondary">button secondary ⮩</a>
</div>
<br>
<hr>
<br>
<div class="grid">
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 1
</div>
</div>
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 2
</div>
</div>
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 3
</div>
</div>
</div>
<div class="clear"></div>
<br>
<hr>
<table width="100%" class="footer">
<tr>
<td class="col3">footer col1</td>
<td class="col3">footer col1</td>
<td class="col3">footer col1</td>
</tr>
</table>
</td>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>
<!-- JanusXR code
-->
<!-- C-code
-->

View file

@ -1,52 +0,0 @@
package.path = package.path .. ";.lua/?.lua"
package.path = package.path .. ";src/.lua/?.lua"
package.path = package.path .. ";middleware/?.lua"
json = require "json"
-- special script called by main redbean process at startup
HidePath('/usr/share/zoneinfo/')
HidePath('/usr/share/ssl/')
dir = require "opendirectory"
function OnHttpRequest()
if( not dir.serve() ) then
print("default")
Route()
end
end
---- create
--app = require("soakbean") {
-- bin = "./redbean.com",
-- opts = {
-- my_cli_arg=1
-- },
-- cmd={
-- -- runtask = {file="sometask.lua", info="description of cli cmd"}
-- },
-- title = 'SOAKBEAN - a buddy of redbean',
-- subtitle = 'SOAKBEAN makes <a href=https://redbean.dev>redbean</a> programming easy',
-- notes = {'🤩 express-style programming', '🖧 easy routings', '♻ re-use middleware functions'}
--}
--
--app.url['^/data'] = '/data.lua' -- setup custom file endpoint
--
--app.get('^/', app.template('index.html') ) -- alias for app.tpl( LoadAsset('index.html'), app )
--
--app.post('^/save', function(req,res,next) -- setup inline POST endpoint
-- -- also .get(), .put(), .delete(), .options()
-- app.cache = req.body -- middleware auto-decodes json
-- res.status(200)
-- res.body({cache=app.cache}) -- middleware auto-encodes json
-- next()
--end)
--
--app --
--.use( require("json").middleware() ) -- try plug'n'play json API middleware
--.use( app.router( app.url ) ) -- try url router
--.use( app.response() ) -- try serve app response (if any)
--.use( function(req,next) Route() end) -- fallback default redbean fileserver
--
--function OnHttpRequest() app.run() end

2
src/.args Normal file
View file

@ -0,0 +1,2 @@
-D
.

65
src/.init.lua Normal file
View file

@ -0,0 +1,65 @@
package.path = package.path .. ";.lua/?.lua"
package.path = package.path .. ";src/.lua/?.lua"
package.path = package.path .. ";middleware/?.lua"
package.path = package.path .. ";cmd/?.lua"
util = require "util"
json = require "json"
-- special script called by main redbean process at startup
HidePath('/usr/share/zoneinfo/')
HidePath('/usr/share/ssl/')
dir = require "opendirectory"
-- create
app = require("soakbean") {
bin = "./xrforge.com",
cache = {},
opts = {
title="My XRForge experiences",
subtitle="Portable XR experiences for PhoneVR, VRheadset, desktop & mobile."
},
cmd={
help = {file="help.lua", info="displays help cmds"},
server = {file="server.lua", info="starts the webserver"}
},
title = 'XRForge - local-first XR experiences',
subtitle = 'XRForge makes local-first XR easy',
notes = {'🤩 portable selfsovereign', '🖧 JanusXR / XR Fragments', '♻ decentralized'}
}
app.url['^/data'] = '/data.lua' -- setup custom file endpoint
app.url['^/$'] = '/index.lua' -- alias for app.tpl( LoadAsset('index.html'), app )
app.get('^/admin/$', app.template('admin/index.html') ) -- alias for app.tpl( LoadAsset('index.html'), app )
app.post('^/save', function(req,res,next) -- setup inline POST endpoint
-- also .get(), .put(), .delete(), .options()
app.cache = req.body -- middleware auto-decodes json
res.status(200)
res.body({cache=app.cache}) -- middleware auto-encodes json
next()
end)
app --
.use( require("httpauth")() )
.use( require("json").middleware() ) -- try plug'n'play json API middleware
.use( app.router( app.url ) ) -- try url router
.use( app.response() ) -- try serve app response (if any)
.use( function(req,next) Route() end) -- fallback default redbean fileserver
app.on('template', function(a,b,c,d,e)
print("TEMPLATE!")
end)
function OnHttpRequest() app.run() end
print("ja")
--function OnHttpRequest()
-- if( not dir.serve("./experiences") ) then
-- print("default")
-- Route()
-- end
--end
--

View file

@ -29,16 +29,33 @@
local dir = {}
dir.serve = function()
local path = '.' .. GetPath()
if not path:find("..") == nil then return ServeError(403,"=]") end
dir.scan = function(path)
local stat = unix.stat(path)
if stat == nil then
return ServeError(404,"does not exist")
else
if not unix.S_ISDIR( stat:mode() ) then return false end -- let redbean take care of serving static file
end
state = assert(unix.opendir(path))
return stat, state
end
dir.foreach = function(path,cb)
local stat, state = dir.scan(path)
for name, kind, ino, off in state do
if name == "." then goto continue end
attr,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW)
if err then print("error: ".. name .. ": " ..err) end
cb(path,stat,state,name,kind,ino,off,attr)
::continue::
end
end
dir.serve = function(folder) -- raw redbean function (use dir.foreach in soakbean)
local path = '.' .. GetPath()
if not path:match('^' .. folder) then return false end -- should start with folder
if not path:find("..") == nil then return ServeError(403,"=]") end
SetStatus(200)
SetHeader('Content-Type', 'text/html; charset=utf-8')
@ -51,38 +68,36 @@ dir.serve = function()
Write('td { white-space: nowrap; }\r\n')
Write('.l { text-align: left; }\r\n')
Write('.r { text-align: right; }\r\n')
Write('.hide { display:none; }\r\n')
Write('</style>\r\n')
Write('<h3>Open Directory Demo</h3>\r\n')
Write('<h3>Open Directory Demo '.. path .. '</h3>\r\n')
Write('<table>\r\n')
Write('<thead>\r\n')
Write('<tr>\r\n')
Write('<th class=l>name\r\n')
Write('<th>type\r\n')
Write('<th class=r>ino\r\n')
Write('<th class=r>off\r\n')
Write('<th class="hide">type\r\n')
Write('<th class=hide r>ino\r\n')
Write('<th class=hide r>off\r\n')
Write('<th class=r>size\r\n')
Write('<th class=r>blocks\r\n')
Write('<th class=r>mode\r\n')
Write('<th class=r>uid\r\n')
Write('<th class=r>gid\r\n')
Write('<th class=r>dev\r\n')
Write('<th class=r>rdev\r\n')
Write('<th class=r>nlink\r\n')
Write('<th class=r>blksize\r\n')
Write('<th class=r>gen\r\n')
Write('<th class=r>flags\r\n')
Write('<th class=r>birthtim\r\n')
Write('<th class=r>mtim\r\n')
Write('<th class=r>atim\r\n')
Write('<th class=r>ctim\r\n')
Write('<th class=hide r>blocks\r\n')
Write('<th class=hide r>mode\r\n')
Write('<th class=hide r>uid\r\n')
Write('<th class=hide r>gid\r\n')
Write('<th class=hide r>dev\r\n')
Write('<th class=hide r>rdev\r\n')
Write('<th class=hide r>nlink\r\n')
Write('<th class=hide r>blksize\r\n')
Write('<th class=hide r>gen\r\n')
Write('<th class=hide r>flags\r\n')
Write('<th class=hide r>birthtim\r\n')
Write('<th class=hide r>mtim\r\n')
Write('<th class=hide r>atim\r\n')
Write('<th class=hide r>ctim\r\n')
Write('<tbody>\r\n')
print("opendir: "..path)
state = assert(unix.opendir(path))
for name, kind, ino, off in state do
if name == "." then goto continue end
local stat, state = dir.scan(path)
dir.foreach(path, function(path,stat,state,name,kind,ino,off,attr)
Write('<tr>\r\n')
Write('<td>')
local link = EscapeHtml(name)
@ -90,7 +105,7 @@ dir.serve = function()
Write('<a href="' .. GetPath() .. link .. '">' .. link .. '</a>')
Write('\r\n')
Write('<td>')
Write('<td class="hide">')
if kind == unix.DT_REG then Write('DT_REG')
elseif kind == unix.DT_DIR then Write('DT_DIR')
elseif kind == unix.DT_FIFO then Write('DT_FIFO')
@ -102,41 +117,38 @@ dir.serve = function()
end
Write('\r\n')
Write('<td class=r>%d\r\n' % {ino})
Write('<td class=r>%d\r\n' % {off})
Write('<td class="hide r">%d\r\n' % {ino})
Write('<td class="hide r">%d\r\n' % {off})
st,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW)
if st then
Write('<td class=r>%d\r\n' % {st:size()})
Write('<td class=r>%d\r\n' % {st:blocks()})
Write('<td class=r>%.7o\r\n' % {st:mode()})
Write('<td class=r>%d\r\n' % {st:uid()})
Write('<td class=r>%d\r\n' % {st:gid()})
Write('<td class=r>%d\r\n' % {st:dev()})
Write('<td class=r>%d,%d\r\n' % {unix.major(st:rdev()), unix.minor(st:rdev())})
Write('<td class=r>%d\r\n' % {st:nlink()})
Write('<td class=r>%d\r\n' % {st:blksize()})
Write('<td class=r>%d\r\n' % {st:gen()})
Write('<td class=r>%#x\r\n' % {st:flags()})
if attr then
Write('<td class="r">%d\r\n' % {attr:size()})
Write('<td class="hide r">%d\r\n' % {attr:blocks()})
Write('<td class="hide r">%.7o\r\n' % {attr:mode()})
Write('<td class="hide r">%d\r\n' % {attr:uid()})
Write('<td class="hide r">%d\r\n' % {attr:gid()})
Write('<td class="hide r">%d\r\n' % {attr:dev()})
Write('<td class="hide r">%d,%d\r\n' % {unix.major(attr:rdev()), unix.minor(attr:rdev())})
Write('<td class="hide r">%d\r\n' % {attr:nlink()})
Write('<td class="hide r">%d\r\n' % {attr:blksize()})
Write('<td class="hide r">%d\r\n' % {attr:gen()})
Write('<td class="hide r">%#x\r\n' % {attr:flags()})
function WriteTime(unixsec,nanos)
year,mon,mday,hour,min,sec,gmtoffsec = unix.localtime(unixsec)
Write('<td class=r>%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.9d%+.2d%.2d\r\n' % {
Write('<td class="hide r">%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.9d%+.2d%.2d\r\n' % {
year, mon, mday, hour, min, sec, nanos,
gmtoffsec / (60 * 60), math.abs(gmtoffsec) % 60})
end
WriteTime(st:birthtim())
WriteTime(st:mtim())
WriteTime(st:atim())
WriteTime(st:ctim())
WriteTime(attr:birthtim())
WriteTime(attr:mtim())
WriteTime(attr:atim())
WriteTime(attr:ctim())
else
Write('<td class=l colspan=15>%s\r\n' % {err})
end
::continue::
end
end)
Write('</table>\r\n')
state:close()
return true

View file

@ -49,6 +49,7 @@ sb = {
-- print( util.tpl("${name} is ${value}", {name = "foo", value = "bar"}) )
-- "foo is bar"
tpl = function(s,tab)
if not s then return "" end
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
end,

158
src/.lua/util.lua Normal file
View file

@ -0,0 +1,158 @@
local util = { }
util.clone = function(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[util.clone(orig_key)] = util.clone(orig_value)
end
setmetatable(copy, util.clone(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
util.flatten = function(tbl, prefix, result)
result = result or {}
for k, v in pairs(tbl) do
local new_key = (prefix or "") .. "." .. k
if type(v) == "table" then
util.flatten(v, new_key, result)
else
result[new_key] = v
end
end
return result
end
util.template = function(content,data)
local finaldata = util.merge(app,{})
finaldata = util.merge(finaldata, util.flatten(app.opts,"opts") )
finaldata.header = app.tpl( LoadAsset('template/header.html'), finaldata )
finaldata.footer = app.tpl( LoadAsset('template/footer.html'), finaldata )
return app.tpl( content, finaldata )
end
util.memoize = function(fn, ttl_seconds)
local cached_results = nil
local last_executed = 0
return function(...)
local now = os.time()
-- Check if cache is missing or if the TTL has passed
print("now = "..now .. " last_executed = " ..last_executed)
if not cached_results or (now - last_executed) >= ttl_seconds then
-- Pass all input arguments and pack all return values into a table
cached_results = { fn(...) }
last_executed = now
end
-- Unpack and return the cached results
return table.unpack(cached_results)
end
end
util.cmdExist = function(cmd)
if api.runtime.os == 'unixy' then cmd = cmd .. " 1>/dev/null 2>/dev/null" end
local stdout, retstr, retcode = os.execute(cmd)
return retcode ~= 127
end
function util.merge(t1,t2)
local t3 = {}
foreach( t1, function(k,v) t3[k] = v end)
foreach( t2, function(k,v) t3[k] = v end)
return t3
end
function util.dump(value, indent, seen)
indent = indent or ""
seen = seen or {}
local t = type(value)
if t == "string" then
return string.format("%q", value)
elseif t == "boolean" or t == "number" or t == "nil" then
return tostring(value)
elseif t == "function" or t == "userdata" or t == "thread" then
return string.format("<%s: %s>", t, tostring(value))
elseif t == "table" then
if seen[value] then
return string.format("<Circular Reference: %s>", tostring(value))
end
seen[value] = true
local result = "{\n"
local next_indent = indent .. " "
local seen_keys = {}
for i, v in ipairs(value) do
seen_keys[i] = true
result = result .. next_indent .. util.dump(v, next_indent, seen) .. ",\n"
end
for k, v in pairs(value) do
if not seen_keys[k] then
local key_str
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
key_str = k
else
key_str = "[" .. util.dump(k, next_indent, seen) .. "]"
end
result = result .. next_indent .. key_str .. " = " .. util.dump(v, next_indent, seen) .. ",\n"
end
end
if result:sub(-2) == ",\n" then
result = result:sub(1, -3) .. "\n"
end
return result .. indent .. "}"
end
end
function util.count(self)
local i = 0
if self == nil then return i end
if type(self) == 'table' then
for _, _ in pairs(self) do
i = i + 1
end
return i
else
return #self
end
end
util.call = function(fn,a,b,c,d,e,f)
return function(k,v)
if v ~= nil and v[fn] then
v[fn](a,b,c,d,e,f)
end
end
end
function foreach(t, f, sort)
if t ~= nil then
for k, v in pairs(t) do f(k, v) end
end
end
function trace(o)
if os.getenv('DEBUG') then
if type(o) == 'table' then
print_r(o)
else
print(o)
end
end
end
function print_r(t)
print( util.dump(t) )
end
return util

158
src/admin/index.html Normal file

File diff suppressed because one or more lines are too long

13
src/cmd/help.lua Normal file
View file

@ -0,0 +1,13 @@
return function(app,argv)
print("help!")
foreach( app.opts, function(k,v)
if not k:match("/") and not k:match("^help") then
print( app.bin .. " -- " .. k .. "=...\t # '" .. v .. "' e.g." )
end
end)
foreach( app.cmd, function(k,v)
print( app.bin .. " -- " .. k .. "\t\t # " .. v.info )
end)
os.exit()
end

3
src/cmd/server.lua Normal file
View file

@ -0,0 +1,3 @@
return function(app,argv)
print("starting server")
end

1
src/experiences Symbolic link
View file

@ -0,0 +1 @@
../experiences

12
src/img/arrow-down.svg Normal file
View file

@ -0,0 +1,12 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.0001 3.67157L13.0001 3.67157L13.0001 16.4999L16.2426 13.2574L17.6568 14.6716L12 20.3284L6.34314 14.6716L7.75735 13.2574L11.0001 16.5001L11.0001 3.67157Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 315 B

12
src/img/arrow-left.svg Normal file
View file

@ -0,0 +1,12 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20.3284 11.0001V13.0001L7.50011 13.0001L10.7426 16.2426L9.32842 17.6568L3.67157 12L9.32842 6.34314L10.7426 7.75735L7.49988 11.0001L20.3284 11.0001Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 307 B

12
src/img/arrow-right.svg Normal file
View file

@ -0,0 +1,12 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.0378 6.34317L13.6269 7.76069L16.8972 11.0157L3.29211 11.0293L3.29413 13.0293L16.8619 13.0157L13.6467 16.2459L15.0643 17.6568L20.7079 11.9868L15.0378 6.34317Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 320 B

12
src/img/arrow-up.svg Normal file
View file

@ -0,0 +1,12 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.6568 8.96219L16.2393 10.3731L12.9843 7.10285L12.9706 20.7079L10.9706 20.7059L10.9843 7.13806L7.75404 10.3532L6.34314 8.93572L12.0132 3.29211L17.6568 8.96219Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 320 B

View file

Before

Width:  |  Height:  |  Size: 662 B

After

Width:  |  Height:  |  Size: 662 B

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

16
src/index.lua Normal file
View file

@ -0,0 +1,16 @@
local util = require("util")
if not app.cache['index.html'] then
app.cache['index.html'] = util.memoize(
function()
print("processing!")
local html = util.template( LoadAsset("template/index.html"),app)
return html
end,
60*5 -- re-run every 5 mins (otherwise return cache)
)
end
SetStatus(200)
SetHeader('Content-Type', 'text/html; charset=utf-8')
Write( app.cache['index.html']() )

View file

@ -0,0 +1,50 @@
-- Middleware function to check credentials
function Authenticate(opts,realm)
realm = realm or "Restricted Area"
local auth_header = GetHeader("Authorization")
if auth_header and auth_header:find("^Basic ") then
-- Extract the Base64 portion
local encoded = auth_header:sub(7)
local decoded = DecodeBase64(encoded)
-- Basic Auth format is "username:password"
local user, pass = decoded:match("([^:]+):(.*)")
if user == opts.user and pass == opts.pass then
print("info: succesful login for user: "..user)
return true -- Access granted
end
end
-- Request credentials if missing or invalid
SetStatus(401, "Unauthorized")
SetHeader("WWW-Authenticate", 'Basic realm="' .. realm .. '"')
Write("401 Unauthorized - Access Denied\n")
return false
end
-- soakbean middleware functions
return function(opts)
opts = opts or {
user = "admin",
pass = "admin",
path = "^/admin"
}
return function(req,res,next)
-- Protect a specific path (e.g., /admin or everything under /private/)
if req.url:match( opts.path ) then
if not Authenticate(opts,"Admin Zone") then
print("ERROR")
else
req.authenticated = true
req.user = opts.user
next()
end
else
next()
end
end
end

2
src/run.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
../result/bin/xrforge.com -D . -p 8081 -- server

31
src/template/footer.html Normal file
View file

@ -0,0 +1,31 @@
<div class="clear"></div>
<br>
<hr>
<table width="100%" class="footer">
<tr>
<td class="col3">footer col1</td>
<td class="col3">footer col1</td>
<td class="col3">footer col1</td>
</tr>
</table>
</td>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>
<!-- JanusXR code
-->
<!-- C-code
-->

37
src/template/header.html Normal file
View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>XRForge - selfsovereign interoperable XR experiences</title>
<link rel="stylesheet" href="xrforge.css"></link>
</head>
<body background="#000000">
<table width="100%" class="header">
<tr>
<td style="width:120px">
<div class="margin1L">
<div class="logo col1">XR<div class="logo col2">f̅o̅r̅g̅e̅</div>
</div>
</div>
</td>
<td class="menu">
<div class="margin1L">
<a href="#">Experiences</a>
<a href="#">Collections</a>
<a href="#">About</a>
<a href="#">Howto</a>
</div>
</td>
</tr>
</table>
<hr style="margin:0"/>
<table width="100%">
<tr>
<td>
<center>
<table width="80%" class="page">
<tr>
<td>

45
src/template/index.html Normal file
View file

@ -0,0 +1,45 @@
${header}
<div style="height:120px;" class="jumbotron">
<h1>${opts.title}</h1>
${opts.subtitle}
</div>
<hr style="margin-top:0">
<div class="btn">
<a href="#foo">button</a>
</div>
<div class="btn">
<a href="#foo" class="primary">button primary <img src="img/arrow-down.svg" class="icon"/></a>
</div>
<div class="btn">
<a href="https://foo.com" target="_blank" class="secondary">button secondary <img src="img/arrow-right.svg" class="icon"/></a>
</div>
<br>
<hr>
<br>
<div class="grid">
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 1
</div>
</div>
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 2
</div>
</div>
<div class="tile">
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
<div class="text">
<img src="img/folder.svg" class="icon"/>&nbsp;Item 3
</div>
</div>
</div>
${footer}

View file

@ -1,4 +1,4 @@
/* Progressive enhancement
/* Progressive enhancment
* NetSurf and Dillo will ignore this entirely because of the modern orientation/feature query
*/
@import url("xrforge.max.css") supports(display: grid);
@ -15,6 +15,7 @@
.page{ width:640px; }
/* spacers */
.margin1L{ margin: 0px 20px 10px 0px;}
.margin1{ margin: 10px 20px; }
.padding1{ padding: 10px 20px 10px 20px; }
@ -23,13 +24,17 @@
margin:0;
padding:0;
}
body,html {
background:#112;
font-family: monospace,sans-serif;
color:#f7f7f7;
font-size:15px;
}
body{
margin:20px;
}
td,div,p{
word-spacing: 0.2em;
line-height:24px;
@ -40,10 +45,13 @@ td,div,p{
margin: 0px 10px 10px 0px;
}
.btn a{
.btn a,
.btn a:active,
.btn a:visited,
.btn a:link {
display: inline-block;
padding:8px 16px;
color: #AAD;
color: #f3f;
text-decoration:none;
}
@ -52,6 +60,8 @@ td,div,p{
background:#000;
}
.btn a, .border{ border:1px dotted #AAD; }
a, a:active, a:visited, a:link {
text-decoration: none;
color:#FFF;
@ -64,11 +74,6 @@ a:hover{
border-bottom:1px dotted #f3f;
}
.primary{ color: #f3f; }
.secondary{ color: #7070ff; }
.btn.primary{ background: #f3F; color:#000; }
.btn.secondary{ background: #7070ff; color:#000; }
.btn a, .border{ border:1px dotted #AAD; }
.border.primary{ border:1px dotted #87a; }
.border.secondary{ border:1px dotted #7070ff; }
@ -129,6 +134,8 @@ ul{
visibility: hidden;
}
img.icon{ display:none; }
/* progressive enhancement quirks */
@media (min-width:1px) {
.grid .tile{
@ -137,5 +144,8 @@ ul{
.grid .tile .img{
visibility: visible;
}
.page{ width:80%; }
.page{
width:100%;
max-width:1280px;
}
}

60
src/xrforge.max.css Normal file
View file

@ -0,0 +1,60 @@
body{
background:#000000;
}
.grid .tile .img{
visibility: visible;
border:1px dotted #223;
background-size:cover;
background-position:center;
background-repeat: no-repeat;
border-radius:5px 5px 0px 0px;
height:33vh;
}
.grid .tile{
margin: 0px 15px 15px 0px;
width:31%;
float:left;
background-color:#333;
}
@media (max-width: 890px){
.grid .tile {
width:47%;
}
}
@media (max-width: 713px){
.grid .tile {
float:none;
width:100%;
margin: 0px 0px 15px 0px;
}
}
.grid .tile .text{
padding: 10px 20px 10px 20px !important;
}
.grid .tile img.icon{
transform: translate(0,5px);
}
.clear{
clear:both;
}
.jumbotron{
padding:20px 0px;
background:linear-gradient(45deg,#0000,#0000,#F7F2,#0000,#07F2,#0000,#0000);
}
img.icon{
display: inline-block !important;
margin-top: 0px;
margin-bottom: -5px;
width: 17px;
}
.btn a.primary{ background: #ff33Ff; color:#000 !important;}
.btn a.secondary{ background: #7070ff; color:#000 !important;}

View file

@ -14,7 +14,7 @@ download(){
sha256sum -c /tmp/checksum || { echo "psuedosecurity checksum failed"; exit 1; }
chmod +x $OUTDIR/xrforge.com
}
test -f /tmp/xrforge.com || wget "$REDBEAN_URL" -O /tmp/xrforge.com
test -f /tmp/xrforge.com || wget --no-check-certificate "$REDBEAN_URL" -O /tmp/xrforge.com
cp /tmp/xrforge.com $OUTDIR/xrforge.com
verify
}
@ -24,6 +24,8 @@ download(){
build(){
download redbean
cd src
zip -r ../result/bin/xrforge.com * .lua .init.lua
}
"$@"

View file

@ -1,32 +0,0 @@
.grid .tile .img{
visibility: visible;
border:1px dotted #223;
background-size:cover;
background-position:center;
background-repeat: no-repeat;
border-radius:5px 5px 0px 0px;
height:33vh;
}
.grid .tile{
margin: 0px 15px 15px 0px;
width:31%;
float:left;
background-color:#333;
}
.grid .tile .text{
padding: 10px 20px 10px 20px !important;
}
.grid .tile img.icon{
transform: translate(0,5px);
}
.clear{
clear:both;
}
.jumbotron{
padding: 15px 20px 15px 20px;
}