mosaic-headless 1.2.0 → 1.3.0
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/README.ja.md +19 -4
- package/README.ko.md +111 -0
- package/README.md +30 -5
- package/README.zh-TW.md +17 -4
- package/SKILL.md +79 -7
- package/assets/templates/platforms/claude-ai.json +1 -1
- package/assets/templates/platforms/claude-code.json +1 -1
- package/assets/templates/platforms/codex-cli.json +1 -1
- package/assets/templates/platforms/copilot.json +1 -1
- package/assets/templates/platforms/gemini-cli.json +1 -1
- package/bin/check-release.mjs +29 -2
- package/data/animatable-properties.csv +23 -23
- package/data/browser-verification.csv +2238 -0
- package/data/condition-comparators.csv +13 -13
- package/data/condition-subjects.csv +60 -60
- package/data/data-class-hierarchy.csv +122 -0
- package/data/db-columns.csv +207 -207
- package/data/default-children.csv +11 -11
- package/data/design-audit.csv +1 -0
- package/data/dynamic-variables.csv +75 -75
- package/data/element-classes.csv +152 -152
- package/data/evaluator-functions.csv +20 -20
- package/data/interaction-types.csv +13 -13
- package/data/node-properties.csv +182 -182
- package/data/node-property-verification.csv +182 -182
- package/data/node-types.csv +123 -123
- package/data/node-verification.csv +123 -123
- package/data/placement-rules.csv +123 -123
- package/data/pluggables.csv +208 -208
- package/data/property-verification.csv +171 -171
- package/data/rest-routes.csv +115 -115
- package/data/rwd-verification.csv +577 -570
- package/data/style-properties.csv +99 -99
- package/data/style-states.csv +54 -54
- package/data/style-value-shapes.csv +23 -23
- package/data/style-verification.csv +99 -99
- package/package.json +59 -59
- package/references/failure-modes.md +74 -0
- package/sites/_moksa.py +106 -44
- package/sites/moksa.json +9075 -8684
- package/tools/build_page.py +34 -1
- package/tools/extract_node_types.py +26 -1
- package/tools/mo.py +686 -0
- package/tools/sweep_node_properties.py +41 -2
- package/tools/verify_browser.py +589 -0
- package/tools/verify_rwd.py +14 -1
- package/README.zh-CN.md +0 -87
- package/tools/build_all.py +0 -55
- package/tools/build_report.py +0 -221
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mosaic-headless",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI-agent skill: build Mosaic Pro (Nextend) WordPress sites by writing the underlying data model directly - 23 custom tables, no visual editor, no DOM. Every node type, style property and node property swept against a live install and asserted on the delivered HTML and compiled CSS. Installs into Claude Code, Cursor, Codex CLI, Gemini CLI, Copilot, Continue, Windsurf and Claude.ai.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"wordpress",
|
|
7
|
-
"mosaic",
|
|
8
|
-
"mosaic-pro",
|
|
9
|
-
"nextend",
|
|
10
|
-
"page-builder",
|
|
11
|
-
"claude-code",
|
|
12
|
-
"agent-skill",
|
|
13
|
-
"ai-agent",
|
|
14
|
-
"headless",
|
|
15
|
-
"woocommerce",
|
|
16
|
-
"wp-cli"
|
|
17
|
-
],
|
|
18
|
-
"homepage": "https://github.com/Moksa1123/mosaic-headless#readme",
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/Moksa1123/mosaic-headless.git"
|
|
22
|
-
},
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/Moksa1123/mosaic-headless/issues"
|
|
25
|
-
},
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"author": "moksa (https://moksaweb.com)",
|
|
28
|
-
"type": "module",
|
|
29
|
-
"bin": {
|
|
30
|
-
"mosaic-headless": "bin/install.mjs"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"bin/",
|
|
34
|
-
"assets/",
|
|
35
|
-
"data/*.csv",
|
|
36
|
-
"references/",
|
|
37
|
-
"sites/_moksa.py",
|
|
38
|
-
"sites/moksa.json",
|
|
39
|
-
"tools/*.py",
|
|
40
|
-
"tools/*.php",
|
|
41
|
-
"SKILL.md",
|
|
42
|
-
"README.md",
|
|
43
|
-
"README.zh-TW.md",
|
|
44
|
-
"README.
|
|
45
|
-
"README.
|
|
46
|
-
"LICENSE"
|
|
47
|
-
],
|
|
48
|
-
"engines": {
|
|
49
|
-
"node": ">=18"
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"install-skill": "node bin/install.mjs",
|
|
53
|
-
"check": "node bin/check-release.mjs",
|
|
54
|
-
"preversion": "npm run check",
|
|
55
|
-
"version": "node bin/sync-version.mjs && git add SKILL.md assets/templates/platforms",
|
|
56
|
-
"postversion": "git push && git push --tags",
|
|
57
|
-
"prepublishOnly": "npm run check"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mosaic-headless",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "AI-agent skill: build Mosaic Pro (Nextend) WordPress sites by writing the underlying data model directly - 23 custom tables, no visual editor, no DOM. Every node type, style property and node property swept against a live install and asserted on the delivered HTML and compiled CSS. Installs into Claude Code, Cursor, Codex CLI, Gemini CLI, Copilot, Continue, Windsurf and Claude.ai.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wordpress",
|
|
7
|
+
"mosaic",
|
|
8
|
+
"mosaic-pro",
|
|
9
|
+
"nextend",
|
|
10
|
+
"page-builder",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"agent-skill",
|
|
13
|
+
"ai-agent",
|
|
14
|
+
"headless",
|
|
15
|
+
"woocommerce",
|
|
16
|
+
"wp-cli"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/Moksa1123/mosaic-headless#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Moksa1123/mosaic-headless.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Moksa1123/mosaic-headless/issues"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "moksa (https://moksaweb.com)",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"mosaic-headless": "bin/install.mjs"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin/",
|
|
34
|
+
"assets/",
|
|
35
|
+
"data/*.csv",
|
|
36
|
+
"references/",
|
|
37
|
+
"sites/_moksa.py",
|
|
38
|
+
"sites/moksa.json",
|
|
39
|
+
"tools/*.py",
|
|
40
|
+
"tools/*.php",
|
|
41
|
+
"SKILL.md",
|
|
42
|
+
"README.md",
|
|
43
|
+
"README.zh-TW.md",
|
|
44
|
+
"README.ja.md",
|
|
45
|
+
"README.ko.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"install-skill": "node bin/install.mjs",
|
|
53
|
+
"check": "node bin/check-release.mjs",
|
|
54
|
+
"preversion": "npm run check",
|
|
55
|
+
"version": "node bin/sync-version.mjs && git add SKILL.md assets/templates/platforms",
|
|
56
|
+
"postversion": "git push && git push --tags",
|
|
57
|
+
"prepublishOnly": "npm run check"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -171,6 +171,80 @@ required children on commit.
|
|
|
171
171
|
A record committed with `"modified_gmt": "2026-09-05 17:00:00"` came back stored as
|
|
172
172
|
`16:59:16`. The server overwrites it. Do not use a value you sent as a local cache key.
|
|
173
173
|
|
|
174
|
+
## A page cache turns "it worked" into "the node does not exist"
|
|
175
|
+
|
|
176
|
+
Not a Mosaic failure - a failure of *checking* Mosaic, and it produces the exact
|
|
177
|
+
result this skill exists to prevent: a confident negative about something that is
|
|
178
|
+
actually there.
|
|
179
|
+
|
|
180
|
+
Commit succeeds. `build_site.py` reports the byte size the renderer produced. Then
|
|
181
|
+
the verifier fetches the URL, a full-page cache in front of WordPress (Varnish on
|
|
182
|
+
Cloudways, or any CDN) answers with the pre-commit HTML, and every declaration on a
|
|
183
|
+
node you have just added comes back `no-element`. Measured on this install: the
|
|
184
|
+
builder saw 138878 bytes, `curl` on the same URL a second later got 136021, and
|
|
185
|
+
seven declarations on two brand-new grid wrappers were reported missing while the
|
|
186
|
+
wrappers were sitting correctly in the database and in the committed JSON.
|
|
187
|
+
|
|
188
|
+
The tell is the byte count: if the size the builder reports and the size you fetch
|
|
189
|
+
disagree, you are not looking at what you wrote.
|
|
190
|
+
|
|
191
|
+
`tools/verify_rwd.py` now appends a unique `_v=<ms>` query string and sends
|
|
192
|
+
`Cache-Control: no-cache` on every fetch, so the assertion is always made against the
|
|
193
|
+
document that was actually committed. Do the same in anything else that checks a page
|
|
194
|
+
- including a browser: hard-reload is not always enough, a query string always is.
|
|
195
|
+
|
|
196
|
+
## Changing a design token's VALUE leaves the old one in `:root`
|
|
197
|
+
|
|
198
|
+
A collection variable is a row, and a row is identified by its ID. If the writer
|
|
199
|
+
mints a fresh ID each run - which `build_page.py` did, because its `_varIDs` cache
|
|
200
|
+
starts empty every time - then changing a token's value does not change the token.
|
|
201
|
+
It adds a second one.
|
|
202
|
+
|
|
203
|
+
The rows hang off the theme's collection, and `build_site.py` makes a fresh master
|
|
204
|
+
per build while the collection persists, so nothing ever cleans them up. Both
|
|
205
|
+
declarations reach `:root` and the later one wins:
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
:root{ ... --mk-faint: rgb(107, 109, 113); ... --mk-faint: rgb(160, 162, 168); ... }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Measured: after changing `--mk-faint`, a rebuild reported OK, the new variable
|
|
212
|
+
existed with the correct value, `verify_rwd.py` passed, and the page kept rendering
|
|
213
|
+
the old grey. Ten stale rows had accumulated across earlier builds. **Every
|
|
214
|
+
server-side check agreed with the intent and the page still disagreed** - only
|
|
215
|
+
`verify_browser.py`, reading the computed colour off the element, could see it.
|
|
216
|
+
|
|
217
|
+
The fix is that a token's row ID must be a function of its name -
|
|
218
|
+
`uuid5(VAR_NAMESPACE, "--mk-faint")` - so a rebuild rebinds the row instead of
|
|
219
|
+
adding one, plus a reap of anything on the collection claiming a managed custom
|
|
220
|
+
property under an ID we did not derive. Both are in `theme_records()`.
|
|
221
|
+
|
|
222
|
+
The general shape is worth remembering beyond tokens: **anything keyed by a random
|
|
223
|
+
ID that you write repeatedly will accumulate**, and duplicates in a cascade fail
|
|
224
|
+
silently in favour of whichever happens to be last.
|
|
225
|
+
|
|
226
|
+
## A declaration can be present, correct, and still wrong
|
|
227
|
+
|
|
228
|
+
The fourth failure mode in SKILL.md - "wrong value SHAPE, HTTP 200, and the CSS rule
|
|
229
|
+
is simply absent" - has a quieter sibling: the rule is *present*, the value is what
|
|
230
|
+
you asked for, and what the browser does with it is not what you meant.
|
|
231
|
+
|
|
232
|
+
Three ways, all measured on the example page:
|
|
233
|
+
|
|
234
|
+
- **The unit resolves against something you did not think about.**
|
|
235
|
+
`letter-spacing: -0.035em` is a normal amount of tightening for a Latin display
|
|
236
|
+
face. On a 62px headline it is -2.17px, and the headline was Chinese.
|
|
237
|
+
- **The face cannot render the text.** `font-family: 'Space Grotesk', 'Noto Sans TC'`
|
|
238
|
+
is honoured exactly: Space Grotesk has no CJK coverage, so Han characters come from
|
|
239
|
+
Noto Sans TC - while still carrying the tracking that was chosen for the Latin
|
|
240
|
+
face. Nothing reports a fallback.
|
|
241
|
+
- **The property was never in play.** `display: inline-block` on a flex item is
|
|
242
|
+
blockified by the spec. The declaration was in the stylesheet, correct, and
|
|
243
|
+
computed to `block`.
|
|
244
|
+
|
|
245
|
+
None of these is visible to anything that reads the stylesheet, because the
|
|
246
|
+
stylesheet is right. `verify_browser.py` asks the element instead.
|
|
247
|
+
|
|
174
248
|
## Reproducing all of this
|
|
175
249
|
|
|
176
250
|
```bash
|
package/sites/_moksa.py
CHANGED
|
@@ -41,9 +41,10 @@ TOKENS = {
|
|
|
41
41
|
"--mk-paper": {"type": "color", "value": "rgb(250,250,247)"},
|
|
42
42
|
"--mk-panel": {"type": "color", "value": "rgb(243,243,239)"},
|
|
43
43
|
"--mk-accent": {"type": "color", "value": "rgb(255,90,54)"},
|
|
44
|
-
"--mk-
|
|
44
|
+
"--mk-accent-ink": {"type": "color", "value": "rgb(191,68,40)"},
|
|
45
|
+
"--mk-muted": {"type": "color", "value": "rgb(90,92,97)"},
|
|
45
46
|
"--mk-rule": {"type": "color", "value": "rgb(214,214,206)"},
|
|
46
|
-
"--mk-faint": {"type": "color", "value": "rgb(
|
|
47
|
+
"--mk-faint": {"type": "color", "value": "rgb(107,109,113)"},
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
RULE = "rgba(22,24,28,.16)"
|
|
@@ -131,7 +132,7 @@ def mono(text, size="11px", color="--mk-muted", track="0.14em", **st):
|
|
|
131
132
|
fontSize=size, letterSpacing=track, **st)
|
|
132
133
|
|
|
133
134
|
|
|
134
|
-
def figure(attr, num, size="
|
|
135
|
+
def figure(attr, num, size="56px", t="44px", m="34px"):
|
|
135
136
|
"""A number set as one masked box per digit.
|
|
136
137
|
|
|
137
138
|
A count-up needs JavaScript; a digit rolling out from behind its own edge does
|
|
@@ -145,12 +146,17 @@ def figure(attr, num, size="40px", t="34px", m="27px"):
|
|
|
145
146
|
"font-variant-numeric:tabular-nums;"}}},
|
|
146
147
|
"children": [
|
|
147
148
|
{"type": "div", "data": {"attrID": attr + "-d%d" % j},
|
|
149
|
+
# No `display:inline-block` here: these are flex items, and the
|
|
150
|
+
# spec blockifies a flex item's display value. The declaration was
|
|
151
|
+
# in the stylesheet, correct, and computed to `block` anyway -
|
|
152
|
+
# which is exactly the class of dead code verify_browser.py exists
|
|
153
|
+
# to find, and it found this one.
|
|
148
154
|
"style": {"&": {"_": {"customStyles":
|
|
149
|
-
"overflow:hidden;
|
|
155
|
+
"overflow:hidden;"
|
|
150
156
|
"--d:%dms;" % (j * 70)}}},
|
|
151
157
|
"children": [T("h3", ch, color={"token": "--mk-ink"}, fontSize=size,
|
|
152
|
-
fontWeight="
|
|
153
|
-
lineHeight="1.
|
|
158
|
+
fontWeight="600", letterSpacing="-0.03em",
|
|
159
|
+
lineHeight="1.02", fontFamily=DISPLAY,
|
|
154
160
|
_t={"fontSize": t}, _m={"fontSize": m})]}
|
|
155
161
|
for j, ch in enumerate(num)
|
|
156
162
|
]}
|
|
@@ -168,11 +174,12 @@ def clause(num, en, zh, attr):
|
|
|
168
174
|
"customStyles": "border-top:1px solid " + RULE + ";"},
|
|
169
175
|
_m={"gridCols": "48px 1fr"},
|
|
170
176
|
children=[
|
|
171
|
-
mono("§" + num, size="12px", color="--mk-accent",
|
|
177
|
+
mono("§" + num, size="12px", color="--mk-accent-ink",
|
|
178
|
+
track="0.06em"),
|
|
172
179
|
box(attr + "-t", {}, [
|
|
173
180
|
mono(en, size="11px", color="--mk-muted"),
|
|
174
181
|
T("h2", zh, color={"token": "--mk-ink"}, fontSize="34px",
|
|
175
|
-
fontWeight="600", letterSpacing="
|
|
182
|
+
fontWeight="600", letterSpacing="0.01em", lineHeight="1.35",
|
|
176
183
|
fontFamily=DISPLAY, marginTop="10px",
|
|
177
184
|
_t={"fontSize": "29px"}, _m={"fontSize": "23px"}),
|
|
178
185
|
]),
|
|
@@ -234,6 +241,16 @@ SPEC = [
|
|
|
234
241
|
("SUPPORT", "876", "技術支援與服務"),
|
|
235
242
|
]
|
|
236
243
|
|
|
244
|
+
# The masthead's right-hand column. A profile document states its terms in its own
|
|
245
|
+
# header, and it is the content that fills the frame the headline leaves empty.
|
|
246
|
+
SIDE = [
|
|
247
|
+
("DISCIPLINE", "Web / AI / Automation"),
|
|
248
|
+
("STACK", "WordPress · n8n · Claude"),
|
|
249
|
+
("ENGAGEMENT", "專案制 / 長期維運"),
|
|
250
|
+
("LEAD TIME", "3–8 週"),
|
|
251
|
+
("RESPONSE", "1 個工作天"),
|
|
252
|
+
]
|
|
253
|
+
|
|
237
254
|
SERVICES = [
|
|
238
255
|
("01", "WEB & E-COMMERCE", "網站與電商",
|
|
239
256
|
"品牌官網、電商平台與 WordPress 開發,乾淨的結構與好管理的後台。"),
|
|
@@ -624,8 +641,12 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
624
641
|
"url": "#contact"},
|
|
625
642
|
"style": {"&": {"_m": {"display": "none"},
|
|
626
643
|
"_t": {"fontSize": "12px"},
|
|
644
|
+
# 13px of the bright orange on the glass header is
|
|
645
|
+
# 3.10:1; the darkened cut is 4.9:1. The BORDER
|
|
646
|
+
# stays bright, because a rule is not text and
|
|
647
|
+
# nobody has to read it.
|
|
627
648
|
"_": {"backgroundColor": "rgba(0,0,0,0)",
|
|
628
|
-
"color": {"token": "--mk-accent"},
|
|
649
|
+
"color": {"token": "--mk-accent-ink"},
|
|
629
650
|
"fontSize": "13px", "fontFamily": MONO,
|
|
630
651
|
"letterSpacing": "0.06em",
|
|
631
652
|
"paddingTop": "6px", "paddingBottom": "6px",
|
|
@@ -635,7 +656,7 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
635
656
|
"color": "rgb(255,90,54)"},
|
|
636
657
|
"transitionAll": "180ms ease"}},
|
|
637
658
|
"hover": {"_": {"backgroundColor": {"token": "--mk-accent"},
|
|
638
|
-
"color": {"token": "--mk-
|
|
659
|
+
"color": {"token": "--mk-ink"}}}},
|
|
639
660
|
"text": "START A PROJECT"},
|
|
640
661
|
]}
|
|
641
662
|
])]),
|
|
@@ -659,7 +680,7 @@ FOOTER = box("mk-footer",
|
|
|
659
680
|
mono("MAKE WEB MEANINGFUL", size="11px", color="--mk-accent",
|
|
660
681
|
track="0.2em"),
|
|
661
682
|
T("h2", "打造有價值的網站體驗。", color={"token": "--mk-paper"},
|
|
662
|
-
fontSize="30px", fontWeight="500", letterSpacing="
|
|
683
|
+
fontSize="30px", fontWeight="500", letterSpacing="0.02em",
|
|
663
684
|
lineHeight="1.5", fontFamily=CJK, marginTop="16px",
|
|
664
685
|
_m={"fontSize": "22px"}),
|
|
665
686
|
box("mk-f-contact",
|
|
@@ -674,7 +695,7 @@ FOOTER = box("mk-footer",
|
|
|
674
695
|
]),
|
|
675
696
|
grid("mk-f-links", 2, "28px", tcols=2, mcols=2, children=[
|
|
676
697
|
box("mk-f-col-0", {}, [
|
|
677
|
-
mono("PAGES", size="10px", color="rgb(
|
|
698
|
+
mono("PAGES", size="10px", color="rgb(128,130,138)",
|
|
678
699
|
track="0.2em"),
|
|
679
700
|
box("mk-f-col-0-list",
|
|
680
701
|
{"marginTop": "16px", "display": "grid", "rowGap": "9px"},
|
|
@@ -683,7 +704,7 @@ FOOTER = box("mk-footer",
|
|
|
683
704
|
for t in ["作品集", "團隊成員", "關於我們", "服務報價"]]),
|
|
684
705
|
]),
|
|
685
706
|
box("mk-f-col-1", {}, [
|
|
686
|
-
mono("LEARN", size="10px", color="rgb(
|
|
707
|
+
mono("LEARN", size="10px", color="rgb(128,130,138)",
|
|
687
708
|
track="0.2em"),
|
|
688
709
|
box("mk-f-col-1-list",
|
|
689
710
|
{"marginTop": "16px", "display": "grid", "rowGap": "9px"},
|
|
@@ -703,17 +724,17 @@ FOOTER = box("mk-footer",
|
|
|
703
724
|
"customStyles": "flex-wrap:wrap;"}, None, None),
|
|
704
725
|
"children": [
|
|
705
726
|
mono("© 2026 MOKSA WEB — ALL RIGHTS RESERVED", size="10px",
|
|
706
|
-
color="rgb(
|
|
727
|
+
color="rgb(128,130,138)", track="0.1em"),
|
|
707
728
|
mono("BUILT HEADLESS ON MOSAIC", size="10px",
|
|
708
|
-
color="rgb(
|
|
729
|
+
color="rgb(128,130,138)", track="0.1em"),
|
|
709
730
|
]}]),
|
|
710
731
|
])])
|
|
711
732
|
|
|
712
733
|
|
|
713
734
|
# ── the page ──────────────────────────────────────────────────────────────────
|
|
714
735
|
HERO_LINE = dict(color={"token": "--mk-ink"}, fontSize="62px", fontWeight="600",
|
|
715
|
-
lineHeight="1.
|
|
716
|
-
_t={"fontSize": "
|
|
736
|
+
lineHeight="1.14", letterSpacing="0.005em", fontFamily=DISPLAY,
|
|
737
|
+
_t={"fontSize": "48px"}, _m={"fontSize": "32px"})
|
|
717
738
|
|
|
718
739
|
MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
719
740
|
# the document's own header block: what this is, where it is from, which revision
|
|
@@ -733,11 +754,19 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
733
754
|
{"display": "flex", "alignItems": "center", "marginTop": "10px"},
|
|
734
755
|
[box("mk-caret", {}, [])]),
|
|
735
756
|
]),
|
|
757
|
+
box("mk-mast-body",
|
|
758
|
+
{"display": "grid", "gridCols": "1.35fr .65fr", "columnGap": "56px",
|
|
759
|
+
"rowGap": "32px", "alignItems": "start", "marginTop": "26px"},
|
|
760
|
+
_t={"gridCols": "1.2fr .8fr", "columnGap": "36px"},
|
|
761
|
+
_m={"gridCols": "repeat(1, 1fr)", "columnGap": "0px", "rowGap": "26px",
|
|
762
|
+
"marginTop": "20px"},
|
|
763
|
+
children=[
|
|
764
|
+
box("mk-mast-left", {}, [
|
|
736
765
|
{"type": "text", "data": {"tagName": "p", "attrID": "mk-mast-lede"},
|
|
737
766
|
"style": bp({"color": {"token": "--mk-muted"}, "fontSize": "16px",
|
|
738
|
-
"lineHeight": "2", "marginTop": "
|
|
767
|
+
"lineHeight": "2", "marginTop": "0px", "maxWidth": "30em"},
|
|
739
768
|
None,
|
|
740
|
-
{"fontSize": "14px", "lineHeight": "1.95", "marginTop": "
|
|
769
|
+
{"fontSize": "14px", "lineHeight": "1.95", "marginTop": "0px"}),
|
|
741
770
|
"text": "我打造網站、開發軟體、導入 AI、串起自動化流程,"
|
|
742
771
|
"讓技術不只是工具,而是幫你省下時間、長出業績的數位夥伴。"},
|
|
743
772
|
{"type": "div", "data": {"attrID": "mk-mast-cta"},
|
|
@@ -747,19 +776,46 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
747
776
|
"children": [
|
|
748
777
|
# underlined links, not buttons: a document points, it does not sell
|
|
749
778
|
{"type": "button", "data": {"attrID": "mk-cta-%d" % n, "url": href},
|
|
750
|
-
"style": {"&": {"_":
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
779
|
+
"style": {"&": {"_": dict(
|
|
780
|
+
{"backgroundColor": {"token": "--mk-accent"},
|
|
781
|
+
"color": {"token": "--mk-ink"},
|
|
782
|
+
"paddingLeft": "22px", "paddingRight": "22px",
|
|
783
|
+
"paddingTop": "13px", "paddingBottom": "13px",
|
|
784
|
+
"customStyles": "border:1px solid rgb(255,90,54);"}
|
|
785
|
+
if n == 1 else
|
|
786
|
+
{"backgroundColor": "rgba(0,0,0,0)", "color": {"token": "--mk-ink"},
|
|
787
|
+
"paddingLeft": "0px", "paddingRight": "0px",
|
|
788
|
+
"paddingTop": "13px", "paddingBottom": "13px",
|
|
789
|
+
"customStyles": "border:1px solid rgba(0,0,0,0);"
|
|
790
|
+
"border-bottom-color:rgb(22,24,28);"},
|
|
791
|
+
fontSize="13px", fontFamily=MONO, letterSpacing="0.1em",
|
|
792
|
+
radius="0px", cursor="pointer", transitionAll="180ms ease")},
|
|
793
|
+
"hover": {"_": ({"backgroundColor": {"token": "--mk-ink"},
|
|
794
|
+
"customStyles": "border:1px solid rgb(22,24,28);"}
|
|
795
|
+
if n == 1
|
|
796
|
+
else {"color": {"token": "--mk-accent"}})}},
|
|
759
797
|
"text": text}
|
|
760
|
-
for n, (text, href) in enumerate([("→
|
|
761
|
-
("→
|
|
798
|
+
for n, (text, href) in enumerate([("服務項目 →", "#services"),
|
|
799
|
+
("精選作品 →", "#works")], start=1)
|
|
762
800
|
]},
|
|
801
|
+
]),
|
|
802
|
+
# the right-hand column: what a spec sheet puts in its header block
|
|
803
|
+
box("mk-mast-side",
|
|
804
|
+
{"display": "grid", "rowGap": "0px",
|
|
805
|
+
"customStyles": "border-top:1px solid " + RULE_INK + ";"},
|
|
806
|
+
_m={"customStyles": "border-top:1px solid " + RULE + ";"},
|
|
807
|
+
children=[
|
|
808
|
+
box("mk-mast-side-%d" % i,
|
|
809
|
+
{"display": "grid", "gridCols": "1fr auto",
|
|
810
|
+
"columnGap": "14px", "alignItems": "baseline",
|
|
811
|
+
"paddingTop": "11px", "paddingBottom": "11px",
|
|
812
|
+
"customStyles": ("" if i == 0
|
|
813
|
+
else "border-top:1px solid " + RULE + ";")},
|
|
814
|
+
[mono(k, size="10px", color="--mk-faint", track="0.18em"),
|
|
815
|
+
mono(v, size="12px", color="--mk-ink", track="0.02em")])
|
|
816
|
+
for i, (k, v) in enumerate(SIDE)
|
|
817
|
+
]),
|
|
818
|
+
]),
|
|
763
819
|
box("mk-spec-rule", {"marginTop": "56px"}, [], _m={"marginTop": "40px"}),
|
|
764
820
|
# the figures as a datasheet, not as four big numbers looking for attention
|
|
765
821
|
grid("mk-spec", 4, "0px", tcols=2, mcols=2,
|
|
@@ -786,8 +842,9 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
786
842
|
{"display": "flex", "alignItems": "baseline",
|
|
787
843
|
"columnGap": "3px", "marginTop": "12px"},
|
|
788
844
|
[figure("mk-fig-%d" % i, num),
|
|
789
|
-
T("p", "+", color={"token": "--mk-accent"},
|
|
790
|
-
fontSize="
|
|
845
|
+
T("p", "+", color={"token": "--mk-accent-ink"},
|
|
846
|
+
fontSize="22px", fontFamily=DISPLAY, fontWeight="600",
|
|
847
|
+
_m={"fontSize": "16px"})]),
|
|
791
848
|
T("p", zh, color={"token": "--mk-muted"}, fontSize="12px",
|
|
792
849
|
marginTop="10px", lineHeight="1.7", _m={"fontSize": "11px"}),
|
|
793
850
|
])
|
|
@@ -822,7 +879,8 @@ SERVICE_SEC = section("services", [wrap("mk-svc-in", [
|
|
|
822
879
|
children=[
|
|
823
880
|
mono(num, size="12px", color="--mk-faint", track="0.06em"),
|
|
824
881
|
box("mk-svc-t-%d" % i, {}, [
|
|
825
|
-
mono(en, size="10px", color="--mk-accent",
|
|
882
|
+
mono(en, size="10px", color="--mk-accent-ink",
|
|
883
|
+
track="0.16em"),
|
|
826
884
|
T("h3", zh, color={"token": "--mk-ink"}, fontSize="19px",
|
|
827
885
|
fontWeight="500", marginTop="9px", fontFamily=CJK,
|
|
828
886
|
letterSpacing="0.01em"),
|
|
@@ -857,7 +915,8 @@ PROCESS_SEC = section("process", [wrap("mk-proc-in", [
|
|
|
857
915
|
T("h3", zh, color={"token": "--mk-ink"}, fontSize="19px",
|
|
858
916
|
fontWeight="500", fontFamily=CJK, marginTop="10px",
|
|
859
917
|
letterSpacing="0.01em"),
|
|
860
|
-
mono(en, size="10px", color="--mk-accent",
|
|
918
|
+
mono(en, size="10px", color="--mk-accent-ink",
|
|
919
|
+
track="0.16em",
|
|
861
920
|
marginTop="7px"),
|
|
862
921
|
T("p", body, color={"token": "--mk-muted"},
|
|
863
922
|
fontSize="13px", lineHeight="2", marginTop="12px"),
|
|
@@ -946,7 +1005,8 @@ WORK_SEC = section("works", [wrap("mk-works-in", [
|
|
|
946
1005
|
fontWeight="500", fontFamily=CJK, letterSpacing="0.01em",
|
|
947
1006
|
_m={"fontSize": "16px"}),
|
|
948
1007
|
mono(cat, size="11px", color="--mk-muted", track="0.1em"),
|
|
949
|
-
mono(domain, size="12px", color="--mk-accent",
|
|
1008
|
+
mono(domain, size="12px", color="--mk-accent-ink",
|
|
1009
|
+
track="0.02em"),
|
|
950
1010
|
])
|
|
951
1011
|
for i, (name, cat, domain) in enumerate(WORKS)
|
|
952
1012
|
]),
|
|
@@ -970,8 +1030,8 @@ PRODUCT_SEC = section("products", [wrap("mk-prod-in", [
|
|
|
970
1030
|
mono("OUR PRODUCTS", size="11px", color="rgb(140,142,150)"),
|
|
971
1031
|
T("h2", "不只接案,也開發自己的產品",
|
|
972
1032
|
color={"token": "--mk-paper"}, fontSize="34px",
|
|
973
|
-
fontWeight="600", letterSpacing="
|
|
974
|
-
lineHeight="1.
|
|
1033
|
+
fontWeight="600", letterSpacing="0.01em",
|
|
1034
|
+
lineHeight="1.35", fontFamily=DISPLAY, marginTop="10px",
|
|
975
1035
|
_t={"fontSize": "29px"}, _m={"fontSize": "23px"}),
|
|
976
1036
|
]),
|
|
977
1037
|
]),
|
|
@@ -988,14 +1048,14 @@ PRODUCT_SEC = section("products", [wrap("mk-prod-in", [
|
|
|
988
1048
|
"paddingTop": "20px", "paddingBottom": "20px"},
|
|
989
1049
|
children=[
|
|
990
1050
|
mono("%02d" % (i + 1), size="12px",
|
|
991
|
-
color="rgb(
|
|
1051
|
+
color="rgb(128,130,138)", track="0.06em"),
|
|
992
1052
|
box("mk-prod-t-%d" % i, {}, [
|
|
993
1053
|
mono(en, size="10px", color="--mk-accent",
|
|
994
1054
|
track="0.16em"),
|
|
995
1055
|
T("h3", name, color={"token": "--mk-paper"},
|
|
996
1056
|
fontSize="24px", fontWeight="600",
|
|
997
1057
|
fontFamily=DISPLAY, marginTop="9px",
|
|
998
|
-
letterSpacing="
|
|
1058
|
+
letterSpacing="0.01em", _m={"fontSize": "21px"}),
|
|
999
1059
|
# a property of the product, not a column of the
|
|
1000
1060
|
# table: as a fourth grid child it landed alone in
|
|
1001
1061
|
# the 72px number gutter on row two at tablet
|
|
@@ -1035,7 +1095,7 @@ VOICE_SEC = section("mk-voices", [wrap("mk-voice-in", [
|
|
|
1035
1095
|
"customStyles": "border-top:1px solid " + RULE + ";"
|
|
1036
1096
|
"border-left:0;"},
|
|
1037
1097
|
children=[
|
|
1038
|
-
mono("“", size="24px", color="--mk-accent", track="0"),
|
|
1098
|
+
mono("“", size="24px", color="--mk-accent-ink", track="0"),
|
|
1039
1099
|
T("p", quote, color={"token": "--mk-ink"}, fontSize="14px",
|
|
1040
1100
|
lineHeight="2.05", marginTop="4px"),
|
|
1041
1101
|
box("mk-voice-a-%d" % i, {"marginTop": "22px"}, [
|
|
@@ -1058,13 +1118,14 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1058
1118
|
"customStyles": "border-top:1px solid " + RULE + ";"},
|
|
1059
1119
|
_m={"gridCols": "48px 1fr"},
|
|
1060
1120
|
children=[
|
|
1061
|
-
mono("§07", size="12px", color="--mk-accent",
|
|
1121
|
+
mono("§07", size="12px", color="--mk-accent-ink",
|
|
1122
|
+
track="0.06em"),
|
|
1062
1123
|
box("mk-contact-head-t", {}, [
|
|
1063
1124
|
mono("START A PROJECT", size="11px", color="--mk-muted"),
|
|
1064
1125
|
ml("h2", "準備好升級\n你的數位競爭力了嗎?",
|
|
1065
1126
|
color={"token": "--mk-ink"}, fontSize="44px",
|
|
1066
|
-
fontWeight="600", letterSpacing="
|
|
1067
|
-
lineHeight="1.
|
|
1127
|
+
fontWeight="600", letterSpacing="0.01em",
|
|
1128
|
+
lineHeight="1.3", fontFamily=DISPLAY, marginTop="12px",
|
|
1068
1129
|
_t={"fontSize": "36px"}, _m={"fontSize": "26px"}),
|
|
1069
1130
|
T("p", "先看方案抓預算,或直接告訴我們你想解決的問題,"
|
|
1070
1131
|
"一個工作天內回覆。",
|
|
@@ -1089,7 +1150,8 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1089
1150
|
_m={"fontSize": "13px"}),
|
|
1090
1151
|
])
|
|
1091
1152
|
for i, (k, v, vc) in enumerate([
|
|
1092
|
-
("EMAIL", "services@moksaweb.com",
|
|
1153
|
+
("EMAIL", "services@moksaweb.com",
|
|
1154
|
+
"--mk-accent-ink"),
|
|
1093
1155
|
("PHONE", "+886-958-839-939", "--mk-ink"),
|
|
1094
1156
|
("LOCATION", "TAICHUNG, TAIWAN", "--mk-ink"),
|
|
1095
1157
|
("RESPONSE", "WITHIN 1 BUSINESS DAY", "--mk-ink"),
|