mosaic-headless 1.2.1 → 1.4.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 -1
- package/README.ko.md +19 -1
- package/README.md +20 -1
- package/README.zh-TW.md +17 -1
- package/SKILL.md +110 -8
- 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 +22 -0
- package/data/browser-verification.csv +2283 -0
- package/data/data-class-hierarchy.csv +122 -0
- package/data/design-audit.csv +1 -0
- package/data/interaction-verification.csv +8 -0
- package/data/node-property-verification.csv +2 -2
- package/data/node-types.csv +123 -123
- package/data/style-state-verification.csv +53 -0
- package/package.json +1 -1
- package/references/failure-modes.md +52 -0
- package/references/interactions.md +53 -8
- package/sites/_moksa.py +63 -23
- package/sites/moksa.json +130 -56
- package/tools/build_page.py +34 -1
- package/tools/extract_node_types.py +26 -1
- package/tools/mo.py +715 -0
- package/tools/sweep_interactions.py +352 -0
- package/tools/sweep_node_properties.py +41 -2
- package/tools/sweep_style_states.py +225 -0
- package/tools/verify_browser.py +589 -0
|
@@ -193,6 +193,58 @@ disagree, you are not looking at what you wrote.
|
|
|
193
193
|
document that was actually committed. Do the same in anything else that checks a page
|
|
194
194
|
- including a browser: hard-reload is not always enough, a query string always is.
|
|
195
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
|
+
|
|
196
248
|
## Reproducing all of this
|
|
197
249
|
|
|
198
250
|
```bash
|
|
@@ -68,16 +68,61 @@ Confirmed from it:
|
|
|
68
68
|
it is a percentage of the scroll range, not milliseconds.
|
|
69
69
|
- **`easing` is defaulted to `ease`** by the server.
|
|
70
70
|
|
|
71
|
-
## What is NOT solved
|
|
71
|
+
## What is NOT solved - and exactly how far it now goes
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
`tools/sweep_interactions.py` commits candidate shapes to a live page and judges each
|
|
74
|
+
against `var mosaicInteractions` in the delivered HTML **and** against the row read
|
|
75
|
+
back out of the database, with negative controls. Results in
|
|
76
|
+
`data/interaction-verification.csv`.
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
**`propertyMetas` is NOT the problem, and the previous claim here was wrong.** It is
|
|
79
|
+
accepted, stored, and keeps its order:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
"propertyMetas": [{"uuid": "<uuid>", "type": "predefined", "timelineKey": "_",
|
|
83
|
+
"predefinedOptions": {"name": "opacity"}}]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`type` is one of `predefined | custom | static | collectionMode | collectionVariable
|
|
87
|
+
| display`; the options key is that type plus `Options`
|
|
88
|
+
(`KeyframePropertyMetaTypeFactoryAbstract::getPropertyMetaOptionsDataName()`), and
|
|
89
|
+
for `predefined` the `name` is the property itself - the full list is
|
|
90
|
+
`data/animatable-properties.csv`.
|
|
91
|
+
|
|
92
|
+
**`uuid` is optional on create and REQUIRED on update.** `DataArray::initValues`
|
|
93
|
+
mints one when it is missing, but `DataArray::applyCommitData` matches existing items
|
|
94
|
+
by uuid and rebuilds `order` from `array_column($new, 'uuid')` - so on any later
|
|
95
|
+
commit an item without a uuid is not matched, not carried into the new order, and
|
|
96
|
+
disappears. Measured both ways: the same shape stored 1 meta on create and 0 after an
|
|
97
|
+
update.
|
|
98
|
+
|
|
99
|
+
**What still does not bind is `initial` and the keyframes' `properties`.** Four
|
|
100
|
+
attempts, each with the row read back:
|
|
101
|
+
|
|
102
|
+
| attempt | result |
|
|
103
|
+
|---|---|
|
|
104
|
+
| one commit carrying metas + values | metas stored, 0 property values |
|
|
105
|
+
| second commit adding the values | metas stored, 0 property values |
|
|
106
|
+
| second commit that also REORDERS the metas, so `propertyMetas/order` genuinely changes | reorder took effect in storage, still 0 property values |
|
|
107
|
+
| the values written **straight into `wp_mosaic_nodes`**, all caches flushed | still absent from the payload |
|
|
108
|
+
|
|
109
|
+
That last row is the informative one: it rules out the trust model, the commit
|
|
110
|
+
validators and the cache. The gate is in the export itself -
|
|
111
|
+
`KeyframePropertiesDataSub::exportForInteraction()` will only emit a property for
|
|
112
|
+
which `$this->hasSubData($name)` is true, and those descriptors exist only where
|
|
113
|
+
`AnimationActionOptionsDataSubAbstract::syncAttachedPropertyMetas()` has created
|
|
114
|
+
them, on a shared `DataMeta` that starts empty every time the object is constructed.
|
|
115
|
+
|
|
116
|
+
So the honest position is: **the envelope, the trigger, the timeline and the property
|
|
117
|
+
metas can all be written headlessly; the property VALUES have not been.** Use the CSS
|
|
118
|
+
path, which is fully verified - see `data/style-state-verification.csv`, where 36 of
|
|
119
|
+
37 probed states compiled to exactly the selector the table promises.
|
|
120
|
+
|
|
121
|
+
The one experiment left, and it is cheap: author a single interaction in the editor,
|
|
122
|
+
then `SELECT data FROM wp_mosaic_nodes` for that node. One look settles it.
|
|
123
|
+
|
|
124
|
+
**`pointerEnter` produced no `mosaicInteractions` entry at all**, so the timed family
|
|
125
|
+
needs something the progress family does not - also unfinished.
|
|
81
126
|
|
|
82
127
|
## The failure mode that makes this hard
|
|
83
128
|
|
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)"
|
|
@@ -71,10 +72,22 @@ def T(tag, text, _t=None, _m=None, **st):
|
|
|
71
72
|
"style": bp(st, _t, _m) if (st or _t or _m) else None}
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
# The keyboard is a state too.
|
|
76
|
+
#
|
|
77
|
+
# `focus-visible` was verified by sweep_style_states.py to compile to
|
|
78
|
+
# `.M_EL<n>:FOCUS-VISIBLE` - note the UPPERCASE pseudo-class Mosaic emits, which is
|
|
79
|
+
# why grepping a delivered stylesheet for ":focus-visible" finds nothing at all. Of
|
|
80
|
+
# the seven globally usable states this page used exactly one, `hover`, which means
|
|
81
|
+
# every route through it was invisible to anyone not using a mouse.
|
|
82
|
+
FOCUS_RING = {"customStyles": "outline:2px solid rgb(255,90,54);outline-offset:3px;"}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def box(attr, style, children, hover=None, focus=None, _t=None, _m=None):
|
|
75
86
|
s = bp(style, _t, _m) or {"&": {"_": {}}}
|
|
76
87
|
if hover:
|
|
77
88
|
s["hover"] = {"_": hover}
|
|
89
|
+
if focus:
|
|
90
|
+
s["focus-visible"] = {"_": focus}
|
|
78
91
|
return {"type": "div", "data": {"attrID": attr}, "style": s, "children": children}
|
|
79
92
|
|
|
80
93
|
|
|
@@ -145,8 +158,13 @@ def figure(attr, num, size="56px", t="44px", m="34px"):
|
|
|
145
158
|
"font-variant-numeric:tabular-nums;"}}},
|
|
146
159
|
"children": [
|
|
147
160
|
{"type": "div", "data": {"attrID": attr + "-d%d" % j},
|
|
161
|
+
# No `display:inline-block` here: these are flex items, and the
|
|
162
|
+
# spec blockifies a flex item's display value. The declaration was
|
|
163
|
+
# in the stylesheet, correct, and computed to `block` anyway -
|
|
164
|
+
# which is exactly the class of dead code verify_browser.py exists
|
|
165
|
+
# to find, and it found this one.
|
|
148
166
|
"style": {"&": {"_": {"customStyles":
|
|
149
|
-
"overflow:hidden;
|
|
167
|
+
"overflow:hidden;"
|
|
150
168
|
"--d:%dms;" % (j * 70)}}},
|
|
151
169
|
"children": [T("h3", ch, color={"token": "--mk-ink"}, fontSize=size,
|
|
152
170
|
fontWeight="600", letterSpacing="-0.03em",
|
|
@@ -168,7 +186,8 @@ def clause(num, en, zh, attr):
|
|
|
168
186
|
"customStyles": "border-top:1px solid " + RULE + ";"},
|
|
169
187
|
_m={"gridCols": "48px 1fr"},
|
|
170
188
|
children=[
|
|
171
|
-
mono("§" + num, size="12px", color="--mk-accent",
|
|
189
|
+
mono("§" + num, size="12px", color="--mk-accent-ink",
|
|
190
|
+
track="0.06em"),
|
|
172
191
|
box(attr + "-t", {}, [
|
|
173
192
|
mono(en, size="11px", color="--mk-muted"),
|
|
174
193
|
T("h2", zh, color={"token": "--mk-ink"}, fontSize="34px",
|
|
@@ -625,7 +644,8 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
625
644
|
"transitionAll": "160ms ease",
|
|
626
645
|
"cursor": "pointer"},
|
|
627
646
|
"_m": {"fontSize": "12px"}},
|
|
628
|
-
"hover": {"_": {"color": {"token": "--mk-accent"}}}
|
|
647
|
+
"hover": {"_": {"color": {"token": "--mk-accent"}}},
|
|
648
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
629
649
|
"text": text}
|
|
630
650
|
for i, (text, href) in enumerate(NAV)
|
|
631
651
|
]},
|
|
@@ -634,8 +654,12 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
634
654
|
"url": "#contact"},
|
|
635
655
|
"style": {"&": {"_m": {"display": "none"},
|
|
636
656
|
"_t": {"fontSize": "12px"},
|
|
657
|
+
# 13px of the bright orange on the glass header is
|
|
658
|
+
# 3.10:1; the darkened cut is 4.9:1. The BORDER
|
|
659
|
+
# stays bright, because a rule is not text and
|
|
660
|
+
# nobody has to read it.
|
|
637
661
|
"_": {"backgroundColor": "rgba(0,0,0,0)",
|
|
638
|
-
"color": {"token": "--mk-accent"},
|
|
662
|
+
"color": {"token": "--mk-accent-ink"},
|
|
639
663
|
"fontSize": "13px", "fontFamily": MONO,
|
|
640
664
|
"letterSpacing": "0.06em",
|
|
641
665
|
"paddingTop": "6px", "paddingBottom": "6px",
|
|
@@ -645,7 +669,8 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
645
669
|
"color": "rgb(255,90,54)"},
|
|
646
670
|
"transitionAll": "180ms ease"}},
|
|
647
671
|
"hover": {"_": {"backgroundColor": {"token": "--mk-accent"},
|
|
648
|
-
"color": {"token": "--mk-
|
|
672
|
+
"color": {"token": "--mk-ink"}}},
|
|
673
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
649
674
|
"text": "START A PROJECT"},
|
|
650
675
|
]}
|
|
651
676
|
])]),
|
|
@@ -684,7 +709,7 @@ FOOTER = box("mk-footer",
|
|
|
684
709
|
]),
|
|
685
710
|
grid("mk-f-links", 2, "28px", tcols=2, mcols=2, children=[
|
|
686
711
|
box("mk-f-col-0", {}, [
|
|
687
|
-
mono("PAGES", size="10px", color="rgb(
|
|
712
|
+
mono("PAGES", size="10px", color="rgb(128,130,138)",
|
|
688
713
|
track="0.2em"),
|
|
689
714
|
box("mk-f-col-0-list",
|
|
690
715
|
{"marginTop": "16px", "display": "grid", "rowGap": "9px"},
|
|
@@ -693,7 +718,7 @@ FOOTER = box("mk-footer",
|
|
|
693
718
|
for t in ["作品集", "團隊成員", "關於我們", "服務報價"]]),
|
|
694
719
|
]),
|
|
695
720
|
box("mk-f-col-1", {}, [
|
|
696
|
-
mono("LEARN", size="10px", color="rgb(
|
|
721
|
+
mono("LEARN", size="10px", color="rgb(128,130,138)",
|
|
697
722
|
track="0.2em"),
|
|
698
723
|
box("mk-f-col-1-list",
|
|
699
724
|
{"marginTop": "16px", "display": "grid", "rowGap": "9px"},
|
|
@@ -713,9 +738,9 @@ FOOTER = box("mk-footer",
|
|
|
713
738
|
"customStyles": "flex-wrap:wrap;"}, None, None),
|
|
714
739
|
"children": [
|
|
715
740
|
mono("© 2026 MOKSA WEB — ALL RIGHTS RESERVED", size="10px",
|
|
716
|
-
color="rgb(
|
|
741
|
+
color="rgb(128,130,138)", track="0.1em"),
|
|
717
742
|
mono("BUILT HEADLESS ON MOSAIC", size="10px",
|
|
718
|
-
color="rgb(
|
|
743
|
+
color="rgb(128,130,138)", track="0.1em"),
|
|
719
744
|
]}]),
|
|
720
745
|
])])
|
|
721
746
|
|
|
@@ -767,7 +792,7 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
767
792
|
{"type": "button", "data": {"attrID": "mk-cta-%d" % n, "url": href},
|
|
768
793
|
"style": {"&": {"_": dict(
|
|
769
794
|
{"backgroundColor": {"token": "--mk-accent"},
|
|
770
|
-
"color": {"token": "--mk-
|
|
795
|
+
"color": {"token": "--mk-ink"},
|
|
771
796
|
"paddingLeft": "22px", "paddingRight": "22px",
|
|
772
797
|
"paddingTop": "13px", "paddingBottom": "13px",
|
|
773
798
|
"customStyles": "border:1px solid rgb(255,90,54);"}
|
|
@@ -779,6 +804,7 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
779
804
|
"border-bottom-color:rgb(22,24,28);"},
|
|
780
805
|
fontSize="13px", fontFamily=MONO, letterSpacing="0.1em",
|
|
781
806
|
radius="0px", cursor="pointer", transitionAll="180ms ease")},
|
|
807
|
+
"focus-visible": {"_": FOCUS_RING},
|
|
782
808
|
"hover": {"_": ({"backgroundColor": {"token": "--mk-ink"},
|
|
783
809
|
"customStyles": "border:1px solid rgb(22,24,28);"}
|
|
784
810
|
if n == 1
|
|
@@ -831,7 +857,7 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
831
857
|
{"display": "flex", "alignItems": "baseline",
|
|
832
858
|
"columnGap": "3px", "marginTop": "12px"},
|
|
833
859
|
[figure("mk-fig-%d" % i, num),
|
|
834
|
-
T("p", "+", color={"token": "--mk-accent"},
|
|
860
|
+
T("p", "+", color={"token": "--mk-accent-ink"},
|
|
835
861
|
fontSize="22px", fontFamily=DISPLAY, fontWeight="600",
|
|
836
862
|
_m={"fontSize": "16px"})]),
|
|
837
863
|
T("p", zh, color={"token": "--mk-muted"}, fontSize="12px",
|
|
@@ -868,7 +894,8 @@ SERVICE_SEC = section("services", [wrap("mk-svc-in", [
|
|
|
868
894
|
children=[
|
|
869
895
|
mono(num, size="12px", color="--mk-faint", track="0.06em"),
|
|
870
896
|
box("mk-svc-t-%d" % i, {}, [
|
|
871
|
-
mono(en, size="10px", color="--mk-accent",
|
|
897
|
+
mono(en, size="10px", color="--mk-accent-ink",
|
|
898
|
+
track="0.16em"),
|
|
872
899
|
T("h3", zh, color={"token": "--mk-ink"}, fontSize="19px",
|
|
873
900
|
fontWeight="500", marginTop="9px", fontFamily=CJK,
|
|
874
901
|
letterSpacing="0.01em"),
|
|
@@ -903,7 +930,8 @@ PROCESS_SEC = section("process", [wrap("mk-proc-in", [
|
|
|
903
930
|
T("h3", zh, color={"token": "--mk-ink"}, fontSize="19px",
|
|
904
931
|
fontWeight="500", fontFamily=CJK, marginTop="10px",
|
|
905
932
|
letterSpacing="0.01em"),
|
|
906
|
-
mono(en, size="10px", color="--mk-accent",
|
|
933
|
+
mono(en, size="10px", color="--mk-accent-ink",
|
|
934
|
+
track="0.16em",
|
|
907
935
|
marginTop="7px"),
|
|
908
936
|
T("p", body, color={"token": "--mk-muted"},
|
|
909
937
|
fontSize="13px", lineHeight="2", marginTop="12px"),
|
|
@@ -924,7 +952,15 @@ STACK_SEC = section("stack", [wrap("mk-stack-in", [
|
|
|
924
952
|
{"display": "grid", "gridCols": "160px 1fr", "columnGap": "24px",
|
|
925
953
|
"alignItems": "baseline",
|
|
926
954
|
"paddingTop": "18px", "paddingBottom": "18px",
|
|
927
|
-
|
|
955
|
+
# Each row arrives on its own, staggered by pushing its
|
|
956
|
+
# animation-range further down the scroll rather than by a
|
|
957
|
+
# delay: a scroll-driven animation has no clock to delay
|
|
958
|
+
# against, so the offset has to live in the range.
|
|
959
|
+
"customStyles":
|
|
960
|
+
"border-top:1px solid " + RULE + ";"
|
|
961
|
+
"animation:mk-rise both;animation-timeline:view();"
|
|
962
|
+
"animation-range:entry %d%% cover %d%%;"
|
|
963
|
+
% (2 + i * 4, 18 + i * 4)},
|
|
928
964
|
_m={"gridCols": "1fr", "rowGap": "10px",
|
|
929
965
|
"paddingTop": "16px", "paddingBottom": "16px"},
|
|
930
966
|
children=[
|
|
@@ -992,7 +1028,8 @@ WORK_SEC = section("works", [wrap("mk-works-in", [
|
|
|
992
1028
|
fontWeight="500", fontFamily=CJK, letterSpacing="0.01em",
|
|
993
1029
|
_m={"fontSize": "16px"}),
|
|
994
1030
|
mono(cat, size="11px", color="--mk-muted", track="0.1em"),
|
|
995
|
-
mono(domain, size="12px", color="--mk-accent",
|
|
1031
|
+
mono(domain, size="12px", color="--mk-accent-ink",
|
|
1032
|
+
track="0.02em"),
|
|
996
1033
|
])
|
|
997
1034
|
for i, (name, cat, domain) in enumerate(WORKS)
|
|
998
1035
|
]),
|
|
@@ -1034,7 +1071,7 @@ PRODUCT_SEC = section("products", [wrap("mk-prod-in", [
|
|
|
1034
1071
|
"paddingTop": "20px", "paddingBottom": "20px"},
|
|
1035
1072
|
children=[
|
|
1036
1073
|
mono("%02d" % (i + 1), size="12px",
|
|
1037
|
-
color="rgb(
|
|
1074
|
+
color="rgb(128,130,138)", track="0.06em"),
|
|
1038
1075
|
box("mk-prod-t-%d" % i, {}, [
|
|
1039
1076
|
mono(en, size="10px", color="--mk-accent",
|
|
1040
1077
|
track="0.16em"),
|
|
@@ -1081,7 +1118,7 @@ VOICE_SEC = section("mk-voices", [wrap("mk-voice-in", [
|
|
|
1081
1118
|
"customStyles": "border-top:1px solid " + RULE + ";"
|
|
1082
1119
|
"border-left:0;"},
|
|
1083
1120
|
children=[
|
|
1084
|
-
mono("“", size="24px", color="--mk-accent", track="0"),
|
|
1121
|
+
mono("“", size="24px", color="--mk-accent-ink", track="0"),
|
|
1085
1122
|
T("p", quote, color={"token": "--mk-ink"}, fontSize="14px",
|
|
1086
1123
|
lineHeight="2.05", marginTop="4px"),
|
|
1087
1124
|
box("mk-voice-a-%d" % i, {"marginTop": "22px"}, [
|
|
@@ -1104,7 +1141,8 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1104
1141
|
"customStyles": "border-top:1px solid " + RULE + ";"},
|
|
1105
1142
|
_m={"gridCols": "48px 1fr"},
|
|
1106
1143
|
children=[
|
|
1107
|
-
mono("§07", size="12px", color="--mk-accent",
|
|
1144
|
+
mono("§07", size="12px", color="--mk-accent-ink",
|
|
1145
|
+
track="0.06em"),
|
|
1108
1146
|
box("mk-contact-head-t", {}, [
|
|
1109
1147
|
mono("START A PROJECT", size="11px", color="--mk-muted"),
|
|
1110
1148
|
ml("h2", "準備好升級\n你的數位競爭力了嗎?",
|
|
@@ -1135,7 +1173,8 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1135
1173
|
_m={"fontSize": "13px"}),
|
|
1136
1174
|
])
|
|
1137
1175
|
for i, (k, v, vc) in enumerate([
|
|
1138
|
-
("EMAIL", "services@moksaweb.com",
|
|
1176
|
+
("EMAIL", "services@moksaweb.com",
|
|
1177
|
+
"--mk-accent-ink"),
|
|
1139
1178
|
("PHONE", "+886-958-839-939", "--mk-ink"),
|
|
1140
1179
|
("LOCATION", "TAICHUNG, TAIWAN", "--mk-ink"),
|
|
1141
1180
|
("RESPONSE", "WITHIN 1 BUSINESS DAY", "--mk-ink"),
|
|
@@ -1160,7 +1199,8 @@ INDEX = box("mk-index", {}, [
|
|
|
1160
1199
|
"fontFamily": MONO, "fontSize": "10px",
|
|
1161
1200
|
"letterSpacing": "0.14em",
|
|
1162
1201
|
"transitionAll": "220ms ease"}},
|
|
1163
|
-
"hover": {"_": {"color": {"token": "--mk-ink"}}}
|
|
1202
|
+
"hover": {"_": {"color": {"token": "--mk-ink"}}},
|
|
1203
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
1164
1204
|
"text": "§" + num + " " + name}
|
|
1165
1205
|
for i, (num, name, href, _tl) in enumerate(CLAUSES)
|
|
1166
1206
|
])
|