-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathshell.mjs
More file actions
169 lines (165 loc) · 9.04 KB
/
Copy pathshell.mjs
File metadata and controls
169 lines (165 loc) · 9.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* Shared HTML shell for statically-generated content pages (movement pages,
* the library index, doc pages). These live under playground/public/ and are
* copied through untouched by Vite, so they can't rely on hashed build
* assets: the theme is a small inlined stylesheet instead, kept close to
* the "Kinetic Lab" tokens in playground/src/style.css.
*/
const SITE = "https://www.posecode.org";
export function esc(s) {
return String(s)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
const BASE_CSS = `
:root{--bg:#0b0c0d;--panel:#111315;--panel-2:#171a1d;--border:#292d30;--border-2:#3a3f43;
--text:#f2f1eb;--text-2:#aaa9a2;--muted:#74746f;--accent:#d4ff3f;--accent-2:#d4ff3f;
--accent-ink:#0b0c0d;--accent-glow:rgba(212,255,63,.28);--accent-wash:rgba(212,255,63,.1);
--accent-line:rgba(212,255,63,.42);--mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,monospace;
--sans:"Hanken Grotesk",system-ui,-apple-system,"Segoe UI",sans-serif;--radius:3px;}
*{box-sizing:border-box}
html,body{margin:0}
body{background:var(--bg);color:var(--text);font-family:var(--sans);line-height:1.6;
-webkit-font-smoothing:antialiased;}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:840px;margin:0 auto;padding:0 24px}
header.site-nav{border-bottom:1px solid var(--border);padding:16px 0;margin-bottom:8px}
header.site-nav .wrap{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
.brand{display:flex;align-items:center;gap:9px;color:var(--text);font-weight:800;font-size:18px}
.brand .logo{color:var(--accent)}
nav.links{display:flex;gap:18px;font-size:13.5px;font-weight:600;flex-wrap:wrap}
nav.links a{color:var(--text-2)}
nav.links a:hover{color:var(--text)}
main{padding:36px 0 64px}
.eyebrow{font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:2px;
text-transform:uppercase;color:var(--accent);margin:0 0 12px}
h1{font-size:clamp(28px,4.5vw,42px);font-weight:900;letter-spacing:-1px;line-height:1.1;margin:0 0 14px}
h2{font-size:22px;font-weight:800;letter-spacing:-.3px;margin:36px 0 12px}
h3{font-size:17px;font-weight:700;margin:24px 0 8px}
h4{font-size:14px;font-weight:700;margin:18px 0 6px}
p{color:var(--text-2);margin:0 0 14px}
.meta-line{font-family:var(--mono);font-size:12.5px;color:var(--muted);margin:0 0 22px;
letter-spacing:.2px}
.meta-line b{color:var(--text-2);font-weight:600}
code{font-family:var(--mono);font-size:.92em;background:var(--panel-2);border:1px solid var(--border);
padding:1px 6px;border-radius:5px;color:var(--text)}
pre.code-block{font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--text-2);
background:var(--panel);border:1px solid var(--border-2);border-radius:var(--radius);
padding:16px 18px;overflow:auto;margin:0 0 18px}
pre.code-block code{background:none;border:none;padding:0;color:inherit}
.table-wrap{overflow-x:auto;margin:0 0 18px}
table{border-collapse:collapse;width:100%;font-size:13.5px}
th,td{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border)}
th{color:var(--text);font-weight:700;background:var(--panel-2)}
td{color:var(--text-2)}
ul,ol{color:var(--text-2);padding-left:22px;margin:0 0 16px}
li{margin-bottom:6px}
hr{border:none;border-top:1px solid var(--border);margin:28px 0}
.card{border:1px solid var(--border);border-radius:var(--radius);
background:linear-gradient(180deg,var(--panel),transparent);padding:20px 22px;margin:0 0 16px}
.btn{display:inline-flex;align-items:center;gap:8px;font-weight:700;font-size:14px;
color:var(--accent-ink);background:linear-gradient(180deg,var(--accent),var(--accent-2));
border-radius:8px;padding:11px 18px;text-decoration:none}
.btn:hover{filter:brightness(1.06);text-decoration:none}
.steps{list-style:none;padding:0;margin:0 0 22px;display:flex;flex-direction:column;gap:12px}
.steps li{border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;
background:var(--panel)}
.steps .step-name{font-weight:700;color:var(--text);display:block;margin-bottom:4px}
.steps .step-cue{color:var(--text-2);font-size:13.5px}
.steps .step-dur{font-family:var(--mono);font-size:11px;color:var(--muted);margin-left:8px}
.chip-list{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 8px}
.chip-list a{font-size:12.5px;font-weight:600;color:var(--text-2);background:var(--panel-2);
border:1px solid var(--border-2);border-radius:999px;padding:5px 12px}
.chip-list a:hover{color:var(--text);text-decoration:none;border-color:var(--accent-line)}
.domain-group{margin-bottom:26px}
.domain-group h2{margin-top:0}
.move-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
.move-card{display:block;border:1px solid var(--border);border-radius:var(--radius);
padding:13px 15px;background:var(--panel)}
.move-card:hover{border-color:var(--accent-line);text-decoration:none}
.move-card .mc-name{display:block;font-weight:700;color:var(--text);font-size:13.5px}
.move-card .mc-meta{display:block;font-size:11.5px;color:var(--muted);margin-top:2px}
footer.site-footer{border-top:1px solid var(--border);padding:26px 0 40px;margin-top:20px}
footer.site-footer p{font-size:12.5px;color:var(--muted);margin:0 0 6px;max-width:70ch}
@media(max-width:600px){.wrap{padding:0 18px}}
`;
export function pageShell({ title, description, canonicalPath, jsonLd, bodyHtml, ogImage = true }) {
const canonical = `${SITE}${canonicalPath}`;
// JSON.stringify already produces valid JSON; only guard against a literal
// "</script" inside string data (e.g. a cue) prematurely closing the tag.
const ld = jsonLd
? `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/<\/script/gi, "<\\/script")}</script>`
: "";
const safeTitle = esc(title);
const safeDesc = esc(description);
const og = ogImage
? `<meta property="og:image" content="${SITE}/og.png" />\n <meta name="twitter:image" content="${SITE}/og.png" />`
: "";
return `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${safeTitle}</title>
<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="description" content="${safeDesc}" />
<link rel="canonical" href="${canonical}" />
<meta name="theme-color" content="#0b0c0d" />
<meta property="og:title" content="${safeTitle}" />
<meta property="og:description" content="${safeDesc}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="${canonical}" />
<meta property="og:site_name" content="Posecode" />
${og}
<meta name="twitter:card" content="summary_large_image" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap"
rel="stylesheet"
/>
<style>${BASE_CSS}</style>
<link rel="stylesheet" href="/content-theme.css" />
${ld}
<script>
window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };
</script>
<script defer src="/_vercel/insights/script.js"></script>
</head>
<body>
<header class="site-nav">
<div class="wrap">
<a class="brand" href="/"><svg class="logo" aria-hidden="true" viewBox="0 0 100 100" width="18" height="18" fill="none"><g stroke="currentColor" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"><path d="M50 34V55"/><path d="M50 35L34 26L23 15"/><path d="M50 35L66 26L77 15"/><path d="M50 54L36 72L28 88"/><path d="M50 54L64 72L72 88"/></g><circle cx="50" cy="20" r="10" fill="currentColor"/></svg> Posecode</a>
<nav class="links" aria-label="Primary">
<a href="/play">Playground</a>
<a href="/moves/">Movement library</a>
<a href="/spec.html">Language spec</a>
<a href="/llm-guide.html">LLM authoring</a>
<a href="mailto:hello@posecode.org?subject=Posecode%20Feedback">Feedback</a>
<a href="https://github.com/posecode-dev/posecode" target="_blank" rel="noopener">GitHub</a>
</nav>
</div>
</header>
<main class="wrap">
${bodyHtml}
</main>
<footer class="site-footer">
<div class="wrap">
<p>Range-of-motion values are general literature data, not medical advice. Consult a
qualified professional for physiotherapy or exercise prescription.</p>
<p>
<a href="/">Posecode</a>, open source under Apache-2.0 and AGPL-3.0 ·
<a href="https://github.com/posecode-dev/posecode/blob/main/docs/legal/LICENSING.md" target="_blank" rel="noopener">Licensing</a> ·
<a href="mailto:hello@posecode.org?subject=Posecode%20Feedback">Feedback</a> ·
<a href="https://github.com/posecode-dev/posecode/issues" target="_blank" rel="noopener">GitHub Issues</a>
</p>
</div>
</footer>
</body>
</html>
`;
}