- site/colorlock.py: both XWikis force black and white with !important (a HIGH CONTRAST DARK THEME block in the skin's cssVariablesInit.css). On our pages only, the design's colours win: each inline colour made !important, and a stylesheet scoped to the page root (.ssos) for the rest. Used by site/com/build.py and site/org/build.py; the .com reinstalled with it. - site/org/inquiries (Captain Bob, 2026-11-07): the .com's inquiries go to an admin-only master/detail on the .org -- Inquiries.WebHome (Live Data table), one page per inquiry under Inquiries.Entries with a sheet and a status, PublicInquiry (public) filing them with its author's rights and echoing nothing a visitor sends; a hidden trap field for robots. The .com form posts there. Installed and tested: a guest's inquiry filed and deleted; one whose name and message were script macros was stored as text, nothing ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
104 lines
6.6 KiB
Python
Executable File
104 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""build.py -- the starshipos-f18.com XWiki pages, made from the "StarshipOS.com" design canvas.
|
|
|
|
site/com/build.py canvas/{Main,Inquiries,TryIt}.dc.html -> {Main,Inquiries,TryIt}.WebHome.xwiki
|
|
|
|
The canvas is the source: its artboards are copied into canvas/ as they are, and this turns each into
|
|
one {{html}} block (which needs a page author with script rights). What changes on the way:
|
|
|
|
- the accent hole becomes its default colour, and the canvas's links become the wiki's pages
|
|
(Main.dc.html -> /bin/view/Main/, ...) and the real domains (starshipos.org -> starshipos-f18.org);
|
|
- the two images become attachments of Main.WebHome (badge.jpg, captain-bob.jpg -- web-size copies
|
|
of the canvas's uploads);
|
|
- Try it: the drawn console becomes the real one, an iframe of try.starshipos-f18.com;
|
|
- the inquiry form posts to the .org's PublicInquiry, which files it under Inquiries there
|
|
(admin-only, site/org/inquiries); the announcements form, which has no server, sends by e-mail;
|
|
- a link the canvas left as a placeholder ([SSRN PAPER URL]) points at the .org until it is given.
|
|
|
|
install.sh puts the pages and the images on the wiki.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
import colorlock # noqa: E402 the design's colours over the wiki's high-contrast theme
|
|
|
|
ACCENT = "#F5891F"
|
|
FONTS = ('<link rel="preconnect" href="https://fonts.googleapis.com">\n'
|
|
'<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700'
|
|
'&family=Exo+2:wght@600;700&display=swap" rel="stylesheet">')
|
|
ORG = "https://starshipos-f18.org/"
|
|
PAGES = ["Main", "Inquiries", "TryIt"]
|
|
IMAGES = {"/_blob/5b564d54d98e02ad600c913a9a1888f2": "badge.jpg",
|
|
"/_blob/5483451f292782a8b366e437181c4bcf": "captain-bob.jpg"}
|
|
|
|
CONSOLE = '''<div style="background: #050A14; border: 1px solid #34486A; border-radius: 8px; overflow: hidden">
|
|
<div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 20px; border-bottom: 1px solid #22324D; background: #0E182B; font-size: 13px; color: #A3AEC2">
|
|
<span style="display: flex; align-items: center; gap: 10px"><span aria-hidden="true" style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #4FD1B0"></span>StarshipOS · amd64 · your session</span>
|
|
<span>try.starshipos-f18.com</span>
|
|
</div>
|
|
<iframe src="https://try.starshipos-f18.com/" title="StarshipOS console" style="display: block; width: 100%; height: 30em; border: 0; background: #050A14"></iframe>
|
|
</div>
|
|
<p style="margin: 12px 0 0; font-size: 13px; color: #A3AEC2">If the console does not connect, every machine is in use: try again in a few minutes.</p>'''
|
|
|
|
|
|
def body(src):
|
|
m = re.search(r"<x-dc>(.*)</x-dc>", src, re.S)
|
|
html = re.sub(r"<helmet>.*?</helmet>", "", m.group(1), flags=re.S).strip()
|
|
html = html.replace("{{accent}}", ACCENT)
|
|
for p in PAGES:
|
|
html = html.replace('href="%s.dc.html"' % p, 'href="/bin/view/%s/"' % p)
|
|
html = html.replace('href="https://starshipos.org"', 'href="%s"' % ORG)
|
|
html = html.replace(">starshipos.org</a>", ">starshipos-f18.org</a>")
|
|
html = html.replace("on starshipos.org.", "on starshipos-f18.org.")
|
|
html = html.replace('href="[SSRN PAPER URL]"', 'href="%s"' % ORG)
|
|
for blob, name in IMAGES.items():
|
|
html = html.replace('src="%s"' % blob, 'src="/bin/download/Main/WebHome/%s"' % name)
|
|
html = html.replace("min-height: 100vh", "min-height: 0") # inside the wiki's page, not a screen
|
|
return html
|
|
|
|
|
|
def try_it(html):
|
|
start = html.index('<div style="background: #050A14')
|
|
end = html.index("</p>", html.index("[LIVE CONSOLE]")) + len("</p>")
|
|
return html[:start] + CONSOLE + html[end:]
|
|
|
|
|
|
def forms(html, page):
|
|
if page == "Main":
|
|
html = html.replace('<form style="flex: 1 1 420px; display: flex; flex-wrap: wrap; gap: 12px" aria-label="Subscribe to announcements">',
|
|
'<form action="mailto:info@starshipos.com?subject=Subscribe%20to%20launch%20announcements" method="post" enctype="text/plain" style="flex: 1 1 420px; display: flex; flex-wrap: wrap; gap: 12px" aria-label="Subscribe to announcements">')
|
|
html = html.replace('<input id="sub-email" type="email"', '<input id="sub-email" name="email" type="email" required')
|
|
html = html.replace('<button type="button" style="min-height: 48px; padding: 0 24px',
|
|
'<button type="submit" style="min-height: 48px; padding: 0 24px')
|
|
if page == "Inquiries":
|
|
html = html.replace('<form aria-label="Inquiry form"',
|
|
'<form action="https://starshipos-f18.org/bin/view/PublicInquiry/" method="post" aria-label="Inquiry form"')
|
|
# a field people cannot see; a robot fills it, and PublicInquiry keeps nothing it sends
|
|
html = html.replace('<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px">',
|
|
'<div aria-hidden="true" style="position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden">'
|
|
'<label for="q-website">Leave this empty</label><input id="q-website" name="website" type="text" tabindex="-1" autocomplete="off"></div>\n'
|
|
'<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px">', 1)
|
|
for i, n in (("q-name", "name"), ("q-email", "email"), ("q-org", "organization"), ("q-topic", "topic"),
|
|
("q-msg", "message"), ("q-list", "announcements")):
|
|
html = html.replace('id="%s"' % i, 'id="%s" name="%s"' % (i, n), 1)
|
|
html = html.replace('<button type="button" style="min-height: 48px; padding: 0 28px',
|
|
'<button type="submit" style="min-height: 48px; padding: 0 28px')
|
|
html = html.replace("Replies usually within a few days.",
|
|
"Replies usually within a few days.")
|
|
return html
|
|
|
|
|
|
if __name__ == "__main__":
|
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
for p in PAGES:
|
|
html = forms(body(open("canvas/%s.dc.html" % p).read()), p)
|
|
if p == "TryIt":
|
|
html = try_it(html)
|
|
html = colorlock.lock(html, '<div style="background: #0A1222')
|
|
assert "{{" not in html.replace(colorlock.STYLE, "") and "[SSRN" not in html and ".dc.html" not in html and "_blob" not in html, p
|
|
with open("%s.WebHome.xwiki" % p, "w") as f:
|
|
f.write('{{html clean="false"}}\n%s\n%s\n{{/html}}\n' % (FONTS, html))
|
|
print("wrote", ", ".join("%s.WebHome.xwiki" % p for p in PAGES))
|