calculation-breakdown 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG-ARCHIVE.md +3 -0
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +179 -2
- package/dist/calculation-breakdown-builder.d.ts +56 -0
- package/dist/calculation-breakdown-builder.d.ts.map +1 -0
- package/dist/calculation-breakdown-builder.js +242 -0
- package/dist/calculation-breakdown-builder.js.map +1 -0
- package/dist/calculation-breakdown-human-string.d.ts +24 -0
- package/dist/calculation-breakdown-human-string.d.ts.map +1 -0
- package/dist/calculation-breakdown-human-string.js +86 -0
- package/dist/calculation-breakdown-human-string.js.map +1 -0
- package/dist/calculation-breakdown.error.d.ts +14 -0
- package/dist/calculation-breakdown.error.d.ts.map +1 -0
- package/dist/calculation-breakdown.error.js +16 -0
- package/dist/calculation-breakdown.error.js.map +1 -0
- package/dist/calculation-breakdown.interface.d.ts +134 -0
- package/dist/calculation-breakdown.interface.d.ts.map +1 -0
- package/dist/calculation-breakdown.interface.js +2 -0
- package/dist/calculation-breakdown.interface.js.map +1 -0
- package/dist/calculation-breakdown.type.d.ts +20 -0
- package/dist/calculation-breakdown.type.d.ts.map +1 -0
- package/dist/calculation-breakdown.type.js +2 -0
- package/dist/calculation-breakdown.type.js.map +1 -0
- package/dist/cjs/calculation-breakdown-builder.d.ts +55 -0
- package/dist/cjs/calculation-breakdown-builder.js +245 -0
- package/dist/cjs/calculation-breakdown-human-string.d.ts +23 -0
- package/dist/cjs/calculation-breakdown-human-string.js +88 -0
- package/dist/cjs/calculation-breakdown.error.d.ts +13 -0
- package/dist/cjs/calculation-breakdown.error.js +19 -0
- package/dist/cjs/calculation-breakdown.interface.d.ts +133 -0
- package/dist/cjs/calculation-breakdown.interface.js +2 -0
- package/dist/cjs/calculation-breakdown.type.d.ts +19 -0
- package/dist/cjs/calculation-breakdown.type.js +2 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/package.json +3 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/docs/_config.yml +19 -0
- package/docs/assets/calculation-breakdown/calculation-breakdown-builder.js +242 -0
- package/docs/assets/calculation-breakdown/calculation-breakdown-human-string.js +86 -0
- package/docs/assets/calculation-breakdown/calculation-breakdown.error.js +16 -0
- package/docs/assets/calculation-breakdown/calculation-breakdown.interface.js +2 -0
- package/docs/assets/calculation-breakdown/calculation-breakdown.type.js +2 -0
- package/docs/assets/calculation-breakdown/index.js +4 -0
- package/docs/changelog-archive.md +9 -0
- package/docs/changelog.md +25 -0
- package/docs/index.md +13 -0
- package/docs/playground/index.html +110 -0
- package/docs/playground/playground.css +407 -0
- package/docs/playground/playground.js +1303 -0
- package/package.json +66 -4
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const INFIX_OPERATORS = {
|
|
2
|
+
add: '+',
|
|
3
|
+
subtract: '−',
|
|
4
|
+
multiply: '×',
|
|
5
|
+
divide: '÷',
|
|
6
|
+
};
|
|
7
|
+
function localizeDecimal(value, locale) {
|
|
8
|
+
return locale === 'cs' ? value.replace('.', ',') : value;
|
|
9
|
+
}
|
|
10
|
+
function decimalToPercent(value) {
|
|
11
|
+
const negative = value.startsWith('-');
|
|
12
|
+
const unsigned = negative ? value.slice(1) : value;
|
|
13
|
+
const [whole = '0', fraction = ''] = unsigned.split('.');
|
|
14
|
+
const digits = `${whole}${fraction}`;
|
|
15
|
+
const decimalIndex = whole.length + 2;
|
|
16
|
+
const padded = digits.padEnd(decimalIndex, '0');
|
|
17
|
+
const integer = padded
|
|
18
|
+
.slice(0, decimalIndex)
|
|
19
|
+
.replace(/^0+(?=\d)/, '');
|
|
20
|
+
const decimals = padded.slice(decimalIndex).replace(/0+$/, '');
|
|
21
|
+
return `${negative ? '-' : ''}${integer}${decimals ? `.${decimals}` : ''}`;
|
|
22
|
+
}
|
|
23
|
+
function localizeDurationUnit(amount, unit, locale) {
|
|
24
|
+
const singularUnit = unit.slice(0, -1);
|
|
25
|
+
const unitPart = new Intl.NumberFormat(locale, {
|
|
26
|
+
style: 'unit',
|
|
27
|
+
unit: singularUnit,
|
|
28
|
+
unitDisplay: 'long',
|
|
29
|
+
useGrouping: false,
|
|
30
|
+
})
|
|
31
|
+
.formatToParts(Number(amount))
|
|
32
|
+
.find((part) => part.type === 'unit');
|
|
33
|
+
if (!unitPart) {
|
|
34
|
+
throw new Error(`Unable to localize duration unit: ${unit}`);
|
|
35
|
+
}
|
|
36
|
+
return unitPart.value;
|
|
37
|
+
}
|
|
38
|
+
function labelValue(value, semantic, options) {
|
|
39
|
+
if (options.includeNames === false)
|
|
40
|
+
return value;
|
|
41
|
+
const entry = options.dictionary[options.locale]?.[semantic];
|
|
42
|
+
const label = typeof entry === 'string' ? entry : (entry?.name ?? semantic);
|
|
43
|
+
return `${value} ${label}`;
|
|
44
|
+
}
|
|
45
|
+
function renderValue(value, options) {
|
|
46
|
+
const rawDecimal = value.type === 'number' || value.type === 'percent'
|
|
47
|
+
? value.value
|
|
48
|
+
: value.amount;
|
|
49
|
+
const decimal = localizeDecimal(rawDecimal, options.locale);
|
|
50
|
+
if (value.type === 'money') {
|
|
51
|
+
return labelValue(`${decimal} ${value.currency}`, value.semantic, options);
|
|
52
|
+
}
|
|
53
|
+
if (value.type === 'duration') {
|
|
54
|
+
return labelValue(`${decimal} ${localizeDurationUnit(value.amount, value.unit, options.locale)}`, value.semantic, options);
|
|
55
|
+
}
|
|
56
|
+
if (value.type === 'percent') {
|
|
57
|
+
return labelValue(`${localizeDecimal(decimalToPercent(value.value), options.locale)}%`, value.semantic, options);
|
|
58
|
+
}
|
|
59
|
+
return labelValue(decimal, value.semantic, options);
|
|
60
|
+
}
|
|
61
|
+
function renderNode(node, options, nested = false) {
|
|
62
|
+
if (node.type === 'constant') {
|
|
63
|
+
return localizeDecimal(node.value, options.locale);
|
|
64
|
+
}
|
|
65
|
+
if (node.type === 'group') {
|
|
66
|
+
const result = node.result
|
|
67
|
+
? ` = ${renderValue(node.result, options)}`
|
|
68
|
+
: '';
|
|
69
|
+
return `(${renderNode(node.expression, options)}${result})`;
|
|
70
|
+
}
|
|
71
|
+
if (node.type !== 'operation') {
|
|
72
|
+
return renderValue(node, options);
|
|
73
|
+
}
|
|
74
|
+
const [left, right] = node.operands;
|
|
75
|
+
if (node.operator === 'minimum' || node.operator === 'maximum') {
|
|
76
|
+
const functionName = node.operator === 'minimum' ? 'min' : 'max';
|
|
77
|
+
return `${functionName}(${renderNode(left, options)}, ${renderNode(right, options)})`;
|
|
78
|
+
}
|
|
79
|
+
const rendered = `${renderNode(left, options, true)} ${INFIX_OPERATORS[node.operator]} ${renderNode(right, options, true)}`;
|
|
80
|
+
return nested ? `(${rendered})` : rendered;
|
|
81
|
+
}
|
|
82
|
+
/** Renders a plain calculation breakdown without changing its serialized form. */
|
|
83
|
+
export function toHumanString(calculation, options) {
|
|
84
|
+
return `${renderNode(calculation.breakdown, options)} = ${renderValue(calculation.result, options)}`;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=calculation-breakdown-human-string.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Error thrown when a calculation breakdown contains invalid or incomplete input. */
|
|
2
|
+
export class CalculationBreakdownError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a calculation breakdown error.
|
|
6
|
+
*
|
|
7
|
+
* @param code - Stable code suitable for programmatic error handling.
|
|
8
|
+
* @param message - Human-readable explanation of the failure.
|
|
9
|
+
*/
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = 'CalculationBreakdownError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=calculation-breakdown.error.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { CalculationBreakdownBuilder } from './calculation-breakdown-builder.js';
|
|
2
|
+
export { toHumanString, } from './calculation-breakdown-human-string.js';
|
|
3
|
+
export { CalculationBreakdownError, } from './calculation-breakdown.error.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Changelog
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- Generated from CHANGELOG.md. Do not edit directly. -->
|
|
6
|
+
|
|
7
|
+
# Changelog
|
|
8
|
+
|
|
9
|
+
Notable changes, starting with 0.1.0. The latest five releases are detailed here; older releases are archived.
|
|
10
|
+
|
|
11
|
+
## 0.1.2 — 2026-10-26
|
|
12
|
+
|
|
13
|
+
- Added an opt-in locale mode to the playground that preserves calculation edits, keeps the default example limited to the calculation, and falls back to semantic keys without generated dictionary placeholders.
|
|
14
|
+
- Made the playground vertically scrollable and its three panels usable on mobile screens.
|
|
15
|
+
- Fixed the deployed playground by including every browser ESM module, and kept release artifact inspection JSON-safe by suppressing duplicate package lifecycle scripts.
|
|
16
|
+
|
|
17
|
+
## 0.1.1 — 2026-10-26
|
|
18
|
+
|
|
19
|
+
- Added the guarded npm, Git tag, release-PR, and GitHub Pages deployment workflow used by `payload-vars`.
|
|
20
|
+
|
|
21
|
+
## 0.1.0 — 2026-10-26
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Added the typed calculation breakdown builder, localized human-readable formatter, and interactive invoice playground.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: calculation-breakdown
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- Generated by scripts/lib/docs.mjs. Do not edit directly. -->
|
|
6
|
+
|
|
7
|
+
# calculation-breakdown
|
|
8
|
+
|
|
9
|
+
Package version: v0.1.2
|
|
10
|
+
|
|
11
|
+
Typed, serializable breakdowns of calculations performed elsewhere.
|
|
12
|
+
|
|
13
|
+
[Open the playground](playground/) · [Read the README](https://github.com/ThisMeansMore/calculation-breakdown#readme) · [Changelog](changelog.html)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="An interactive calculation-breakdown builder playground."
|
|
9
|
+
/>
|
|
10
|
+
<title>calculation-breakdown playground</title>
|
|
11
|
+
<link rel="stylesheet" href="./playground.css" />
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header class="topbar">
|
|
15
|
+
<div>
|
|
16
|
+
<a class="brand" href="../README.md">calculation-breakdown</a>
|
|
17
|
+
<span class="badge">playground</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="actions">
|
|
20
|
+
<label class="option-control">
|
|
21
|
+
<input id="use-locale" type="checkbox" />
|
|
22
|
+
<span>Use locale</span>
|
|
23
|
+
</label>
|
|
24
|
+
<button id="reset" class="button button-secondary" type="button">
|
|
25
|
+
Reset
|
|
26
|
+
</button>
|
|
27
|
+
<button id="run" class="button" type="button">
|
|
28
|
+
<span aria-hidden="true">▶</span> Run
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</header>
|
|
32
|
+
|
|
33
|
+
<main class="workspace">
|
|
34
|
+
<section class="panel editor-panel" aria-labelledby="builder-heading">
|
|
35
|
+
<div class="panel-heading">
|
|
36
|
+
<div>
|
|
37
|
+
<span class="step">01</span>
|
|
38
|
+
<h1 id="builder-heading">Builder</h1>
|
|
39
|
+
</div>
|
|
40
|
+
<span>TypeScript</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="code-editor">
|
|
43
|
+
<pre id="builder-highlight" class="code-highlight" aria-hidden="true"></pre>
|
|
44
|
+
<textarea id="builder" wrap="off" spellcheck="false" aria-label="Builder TypeScript"></textarea>
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<section class="panel editor-panel" aria-labelledby="css-heading">
|
|
49
|
+
<div class="panel-heading">
|
|
50
|
+
<div>
|
|
51
|
+
<span class="step">02</span>
|
|
52
|
+
<h2 id="css-heading">Formula CSS</h2>
|
|
53
|
+
</div>
|
|
54
|
+
<span>Package rendering only</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="code-editor">
|
|
57
|
+
<pre id="css-highlight" class="code-highlight" aria-hidden="true"></pre>
|
|
58
|
+
<textarea id="css" spellcheck="false" aria-label="Preview CSS"></textarea>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section class="panel preview-panel" aria-labelledby="preview-heading">
|
|
63
|
+
<div class="panel-heading">
|
|
64
|
+
<div>
|
|
65
|
+
<span class="step">03</span>
|
|
66
|
+
<h2 id="preview-heading">UI preview</h2>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="panel-heading-meta">
|
|
69
|
+
<span>Headless package · demo visualization only</span>
|
|
70
|
+
<span id="status" role="status">Ready</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div id="preview" class="preview-stage"></div>
|
|
74
|
+
</section>
|
|
75
|
+
</main>
|
|
76
|
+
|
|
77
|
+
<template id="preview-template">
|
|
78
|
+
<article class="calculation-card">
|
|
79
|
+
<div class="calculation-card__header">
|
|
80
|
+
<div class="calculation-card__eyebrow" data-title>Calculation</div>
|
|
81
|
+
<div class="language-switcher" role="group" aria-label="Preview language">
|
|
82
|
+
<button type="button" data-locale="en" aria-pressed="true">EN</button>
|
|
83
|
+
<button type="button" data-locale="cs" aria-pressed="false">CS</button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="card-controls">
|
|
87
|
+
<div class="view-switcher" role="group" aria-label="Preview format">
|
|
88
|
+
<button type="button" data-view="table" aria-pressed="true">Table</button>
|
|
89
|
+
<button type="button" data-view="formula" aria-pressed="false">Formula</button>
|
|
90
|
+
<button type="button" data-view="formula-names" aria-pressed="false">Formula with names</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<code class="calculation-card__command" data-command></code>
|
|
94
|
+
<div class="calculation-card__expression" data-expression></div>
|
|
95
|
+
<dl class="calculation-card__meta">
|
|
96
|
+
<div>
|
|
97
|
+
<dt>Result type</dt>
|
|
98
|
+
<dd data-result-type></dd>
|
|
99
|
+
</div>
|
|
100
|
+
</dl>
|
|
101
|
+
<details class="calculation-card__json">
|
|
102
|
+
<summary>Serialized breakdown</summary>
|
|
103
|
+
<pre data-json></pre>
|
|
104
|
+
</details>
|
|
105
|
+
</article>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<script type="module" src="./playground.js"></script>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: dark;
|
|
3
|
+
--canvas: #11110f;
|
|
4
|
+
--panel: #191916;
|
|
5
|
+
--panel-raised: #20201c;
|
|
6
|
+
--line: #35352e;
|
|
7
|
+
--muted: #96968a;
|
|
8
|
+
--text: #f3f1e8;
|
|
9
|
+
--accent: #d9ff57;
|
|
10
|
+
--danger: #ff806d;
|
|
11
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
|
12
|
+
"Segoe UI", sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
* { box-sizing: border-box; }
|
|
16
|
+
|
|
17
|
+
html,
|
|
18
|
+
body {
|
|
19
|
+
height: 100%;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
margin: 0;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
background: var(--canvas);
|
|
28
|
+
color: var(--text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.topbar {
|
|
32
|
+
flex: 0 0 auto;
|
|
33
|
+
min-height: 68px;
|
|
34
|
+
padding: 12px 18px;
|
|
35
|
+
border-bottom: 1px solid var(--line);
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
gap: 20px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.brand {
|
|
43
|
+
color: var(--text);
|
|
44
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
45
|
+
font-size: 15px;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.badge {
|
|
51
|
+
margin-left: 10px;
|
|
52
|
+
padding: 3px 7px;
|
|
53
|
+
border: 1px solid #55651f;
|
|
54
|
+
border-radius: 999px;
|
|
55
|
+
color: var(--accent);
|
|
56
|
+
font-size: 11px;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
letter-spacing: .08em;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.actions,
|
|
62
|
+
.locale-control {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 9px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.locale-control { color: var(--muted); font-size: 12px; }
|
|
69
|
+
|
|
70
|
+
.option-control {
|
|
71
|
+
display: inline-flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 7px;
|
|
74
|
+
color: var(--muted);
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.option-control input {
|
|
80
|
+
width: 15px;
|
|
81
|
+
height: 15px;
|
|
82
|
+
margin: 0;
|
|
83
|
+
accent-color: var(--accent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
select,
|
|
87
|
+
.button {
|
|
88
|
+
height: 36px;
|
|
89
|
+
border: 1px solid var(--line);
|
|
90
|
+
border-radius: 7px;
|
|
91
|
+
font: inherit;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
select {
|
|
95
|
+
padding: 0 30px 0 10px;
|
|
96
|
+
background: var(--panel);
|
|
97
|
+
color: var(--text);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.button {
|
|
101
|
+
padding: 0 14px;
|
|
102
|
+
background: var(--accent);
|
|
103
|
+
color: #181a0c;
|
|
104
|
+
font-size: 13px;
|
|
105
|
+
font-weight: 750;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.button:hover { filter: brightness(1.08); }
|
|
110
|
+
.button-secondary { background: transparent; color: var(--text); }
|
|
111
|
+
|
|
112
|
+
.workspace {
|
|
113
|
+
flex: 1;
|
|
114
|
+
min-height: 0;
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: minmax(280px, .92fr) minmax(260px, .72fr) minmax(360px, 1.25fr);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.panel {
|
|
120
|
+
min-width: 0;
|
|
121
|
+
min-height: 0;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
border-right: 1px solid var(--line);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.panel:last-child { border-right: 0; }
|
|
129
|
+
|
|
130
|
+
.panel-heading {
|
|
131
|
+
min-height: 50px;
|
|
132
|
+
padding: 0 14px;
|
|
133
|
+
border-bottom: 1px solid var(--line);
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
gap: 12px;
|
|
138
|
+
color: var(--muted);
|
|
139
|
+
font-size: 11px;
|
|
140
|
+
letter-spacing: .04em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.panel-heading > div { display: flex; align-items: center; gap: 9px; }
|
|
144
|
+
.panel-heading-meta { justify-content: flex-end; text-align: right; }
|
|
145
|
+
.panel-heading h1, .panel-heading h2 { margin: 0; color: var(--text); font-size: 13px; }
|
|
146
|
+
.step { color: var(--accent); font-family: ui-monospace, monospace; }
|
|
147
|
+
|
|
148
|
+
.code-editor {
|
|
149
|
+
position: relative;
|
|
150
|
+
flex: 1;
|
|
151
|
+
min-height: 0;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
background: var(--panel);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
textarea,
|
|
157
|
+
.code-highlight {
|
|
158
|
+
position: absolute;
|
|
159
|
+
inset: 0;
|
|
160
|
+
width: 100%;
|
|
161
|
+
height: 100%;
|
|
162
|
+
margin: 0;
|
|
163
|
+
resize: none;
|
|
164
|
+
border: 0;
|
|
165
|
+
outline: 0;
|
|
166
|
+
padding: 18px;
|
|
167
|
+
overflow: auto;
|
|
168
|
+
white-space: pre;
|
|
169
|
+
overflow-wrap: normal;
|
|
170
|
+
font: 13px/21px Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
171
|
+
font-synthesis: none;
|
|
172
|
+
font-variant-ligatures: none;
|
|
173
|
+
letter-spacing: 0;
|
|
174
|
+
word-spacing: 0;
|
|
175
|
+
text-indent: 0;
|
|
176
|
+
tab-size: 2;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.code-highlight {
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
color: #d7d7c8;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#builder {
|
|
186
|
+
overflow-x: scroll;
|
|
187
|
+
overflow-y: auto;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
textarea {
|
|
191
|
+
z-index: 1;
|
|
192
|
+
background: transparent;
|
|
193
|
+
color: transparent;
|
|
194
|
+
-webkit-text-fill-color: transparent;
|
|
195
|
+
caret-color: var(--accent);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
textarea::selection {
|
|
199
|
+
background: rgba(123, 148, 54, .42);
|
|
200
|
+
-webkit-text-fill-color: transparent;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
textarea:focus { box-shadow: inset 0 0 0 1px #596927; }
|
|
204
|
+
|
|
205
|
+
.code-editor:focus-within .code-highlight {
|
|
206
|
+
visibility: hidden;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.code-editor textarea:focus {
|
|
210
|
+
color: #d7d7c8;
|
|
211
|
+
-webkit-text-fill-color: #d7d7c8;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.token-comment { color: #707568; }
|
|
215
|
+
.token-keyword, .token-atrule { color: #d780f5; }
|
|
216
|
+
.token-string { color: #b7d77a; }
|
|
217
|
+
.token-number, .token-color { color: #f2a65a; }
|
|
218
|
+
.token-function { color: #78c7f2; }
|
|
219
|
+
.token-property { color: #7bd8c4; }
|
|
220
|
+
.token-selector { color: #f1d17b; }
|
|
221
|
+
.preview-panel { background: #151513; }
|
|
222
|
+
|
|
223
|
+
.preview-stage {
|
|
224
|
+
flex: 1;
|
|
225
|
+
min-height: 0;
|
|
226
|
+
padding: clamp(20px, 4vw, 54px);
|
|
227
|
+
display: grid;
|
|
228
|
+
place-items: start center;
|
|
229
|
+
overflow-x: hidden;
|
|
230
|
+
overflow-y: auto;
|
|
231
|
+
background-image: radial-gradient(#34342e 1px, transparent 1px);
|
|
232
|
+
background-size: 18px 18px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.preview-list {
|
|
236
|
+
width: min(100%, 620px);
|
|
237
|
+
min-width: 0;
|
|
238
|
+
display: grid;
|
|
239
|
+
gap: 24px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.calculation-card {
|
|
243
|
+
position: relative;
|
|
244
|
+
width: min(100%, 560px);
|
|
245
|
+
min-width: 0;
|
|
246
|
+
justify-self: center;
|
|
247
|
+
overflow-x: auto;
|
|
248
|
+
overflow-y: hidden;
|
|
249
|
+
padding: 30px;
|
|
250
|
+
border: 1px solid #d9dfc2;
|
|
251
|
+
border-radius: 18px;
|
|
252
|
+
background: #f7f8f1;
|
|
253
|
+
color: #1c2114;
|
|
254
|
+
box-shadow: 0 28px 70px rgba(0, 0, 0, .32);
|
|
255
|
+
font-family: Inter, system-ui, sans-serif;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.calculation-card__header {
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
justify-content: space-between;
|
|
262
|
+
gap: 16px;
|
|
263
|
+
margin-bottom: 14px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.calculation-card__eyebrow {
|
|
267
|
+
margin: 0;
|
|
268
|
+
color: #637236;
|
|
269
|
+
font-size: 11px;
|
|
270
|
+
font-weight: 800;
|
|
271
|
+
letter-spacing: .12em;
|
|
272
|
+
text-transform: uppercase;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.card-controls {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
justify-content: flex-start;
|
|
279
|
+
gap: 9px;
|
|
280
|
+
margin-bottom: 18px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.language-switcher,
|
|
284
|
+
.view-switcher {
|
|
285
|
+
display: grid;
|
|
286
|
+
grid-template-columns: 1fr 1fr;
|
|
287
|
+
padding: 3px;
|
|
288
|
+
border: 1px solid #d4d9c5;
|
|
289
|
+
border-radius: 999px;
|
|
290
|
+
background: #e9ecdf;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.language-switcher[hidden] { display: none; }
|
|
294
|
+
|
|
295
|
+
.view-switcher {
|
|
296
|
+
grid-template-columns: repeat(3, 1fr);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.language-switcher button,
|
|
300
|
+
.view-switcher button {
|
|
301
|
+
min-width: 34px;
|
|
302
|
+
padding: 5px 8px;
|
|
303
|
+
border: 0;
|
|
304
|
+
border-radius: 999px;
|
|
305
|
+
background: transparent;
|
|
306
|
+
color: #747966;
|
|
307
|
+
font: 800 10px/1 ui-monospace, monospace;
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.language-switcher button[aria-pressed="true"],
|
|
312
|
+
.view-switcher button[aria-pressed="true"] {
|
|
313
|
+
background: #1c2114;
|
|
314
|
+
color: #f7f8f1;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.calculation-card__meta {
|
|
318
|
+
display: grid;
|
|
319
|
+
grid-template-columns: 1fr;
|
|
320
|
+
gap: 12px;
|
|
321
|
+
margin: 26px 0 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.calculation-card__meta div {
|
|
325
|
+
padding-top: 12px;
|
|
326
|
+
border-top: 1px solid #d9dfc2;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.calculation-card__meta dt {
|
|
330
|
+
color: #6b705f;
|
|
331
|
+
font-size: 11px;
|
|
332
|
+
text-transform: uppercase;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.calculation-card__meta dd {
|
|
336
|
+
margin: 5px 0 0;
|
|
337
|
+
font: 600 13px ui-monospace, monospace;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.calculation-card__command {
|
|
341
|
+
display: block;
|
|
342
|
+
margin-bottom: 14px;
|
|
343
|
+
color: #6b705f;
|
|
344
|
+
font: 11px/1.5 ui-monospace, monospace;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.calculation-card__command[hidden] { display: none; }
|
|
349
|
+
|
|
350
|
+
.calculation-card__json { margin-top: 24px; }
|
|
351
|
+
.calculation-card__json summary { cursor: pointer; font-size: 13px; }
|
|
352
|
+
.calculation-card__json pre {
|
|
353
|
+
max-height: 240px;
|
|
354
|
+
overflow: auto;
|
|
355
|
+
padding: 14px;
|
|
356
|
+
border-radius: 9px;
|
|
357
|
+
background: #e9ecdf;
|
|
358
|
+
font: 11px/1.5 ui-monospace, monospace;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.preview-error {
|
|
362
|
+
width: min(100%, 560px);
|
|
363
|
+
padding: 18px;
|
|
364
|
+
border: 1px solid #693b33;
|
|
365
|
+
border-radius: 10px;
|
|
366
|
+
background: #291b18;
|
|
367
|
+
color: var(--danger);
|
|
368
|
+
font: 13px/1.55 ui-monospace, monospace;
|
|
369
|
+
white-space: pre-wrap;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#status[data-state="error"] { color: var(--danger); }
|
|
373
|
+
#status[data-state="success"] { color: var(--accent); }
|
|
374
|
+
|
|
375
|
+
@media (max-width: 980px) {
|
|
376
|
+
.workspace {
|
|
377
|
+
grid-template-columns: 1fr 1fr;
|
|
378
|
+
grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
379
|
+
}
|
|
380
|
+
.preview-panel { grid-column: 1 / -1; }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@media (max-width: 650px) {
|
|
384
|
+
html,
|
|
385
|
+
body {
|
|
386
|
+
height: auto;
|
|
387
|
+
min-height: 100%;
|
|
388
|
+
overflow-x: hidden;
|
|
389
|
+
overflow-y: auto;
|
|
390
|
+
-webkit-overflow-scrolling: touch;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.topbar { align-items: flex-start; flex-direction: column; }
|
|
394
|
+
.actions { width: 100%; flex-wrap: wrap; }
|
|
395
|
+
.workspace {
|
|
396
|
+
flex: none;
|
|
397
|
+
grid-template-columns: 1fr;
|
|
398
|
+
grid-template-rows: none;
|
|
399
|
+
grid-auto-rows: max(560px, 80dvh);
|
|
400
|
+
}
|
|
401
|
+
.preview-panel { grid-column: auto; }
|
|
402
|
+
.panel {
|
|
403
|
+
min-height: 560px;
|
|
404
|
+
border-right: 0;
|
|
405
|
+
border-bottom: 1px solid var(--line);
|
|
406
|
+
}
|
|
407
|
+
}
|