mosaic-headless 1.6.0 → 1.8.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.
@@ -1 +1,4 @@
1
1
  url,breakpoints,check,level,node,detail,sample
2
+ https://mosaic.moksaweb.com/moksa/,"_,_t,_m",TEXT_CLIP,warn,p.M_EL206,"color is rgba(0, 0, 0, 0); painted by something else - contrast NOT checked here",01
3
+ https://mosaic.moksaweb.com/moksa/,"_,_t,_m",TEXT_CLIP,warn,h2.M_EL209,"color is rgba(0, 0, 0, 0); painted by background-clip:text - contrast NOT checked here","技術是拿來解決問題的,
4
+ 不是拿來炫耀的。"
@@ -2,7 +2,7 @@ check,result,detail
2
2
  PLAYS,PASS,43 of 43 intro elements changed state
3
3
  COUNTS,PASS,--mk-n reached 100
4
4
  ENDS,PASS,veil is out of hit-testing from 4200ms
5
- NO_TRAP,PASS,"0 content elements still at opacity 0 in the viewport (41 below the fold awaiting their own scroll timeline, 1 mid-blink)"
5
+ NO_TRAP,PASS,"0 content elements still at opacity 0 in the viewport (61 below the fold awaiting their own scroll timeline, 1 mid-blink)"
6
6
  CLEARS,PASS,a real click landed on 'mk-caret-row'
7
7
  DEGRADES,PASS,with reduced motion the veil is display:none throughout
8
8
  READABLE,PASS,0 elements invisible at 120ms with reduced motion
@@ -10,13 +10,13 @@ READABLE,PASS,0 elements invisible at 120ms with reduced motion
10
10
  t_ms,counter,veil_visibility,veil_clip,veil_covers_centre,hit_centre,invisible_content
11
11
  120,,,,False,html.no-js,0
12
12
  400,0,visible,none,True,mk-boot,7
13
- 800,15,visible,none,True,mk-boot,7
14
- 1300,42,visible,none,True,mk-boot,6
15
- 1800,69,visible,none,True,p.M_EL56,3
16
- 2300,97,visible,none,True,p.M_EL56,3
13
+ 800,11,visible,none,True,mk-boot,7
14
+ 1300,38,visible,none,True,mk-boot,7
15
+ 1800,66,visible,none,True,p.M_EL56,3
16
+ 2300,93,visible,none,True,p.M_EL56,3
17
17
  2700,100,visible,none,True,p.M_EL56,3
18
- 3000,100,visible,inset(0px 0px 3.1269%),True,p.M_EL56,2
19
- 3300,100,visible,inset(0px 0px 93.9568%),False,mk-caret-row,2
18
+ 3000,100,visible,inset(0px 0px 0.209424%),True,p.M_EL56,3
19
+ 3300,100,visible,inset(0px 0px 80.1684%),False,mk-caret-row,2
20
20
  3700,100,hidden,inset(0px 0px 100%),False,mk-caret-row,0
21
21
  4200,100,hidden,inset(0px 0px 100%),False,mk-caret-row,0
22
22
  5200,100,hidden,inset(0px 0px 100%),False,mk-caret-row,0
@@ -87,6 +87,46 @@ https://mosaic.moksaweb.com/moksa/,mk-svc-3,_m,grid-template-columns,"repeat(1,
87
87
  https://mosaic.moksaweb.com/moksa/,mk-svc-3,_m,row-gap,10px,10px,verified
88
88
  https://mosaic.moksaweb.com/moksa/,mk-svc-3,_m,padding-top,20px,20px,verified
89
89
  https://mosaic.moksaweb.com/moksa/,mk-svc-3,_m,padding-bottom,20px,20px,verified
90
+ https://mosaic.moksaweb.com/moksa/,matrix,_t,padding-left,28px,28px,verified
91
+ https://mosaic.moksaweb.com/moksa/,matrix,_t,padding-right,28px,28px,verified
92
+ https://mosaic.moksaweb.com/moksa/,matrix,_m,padding-left,18px,18px,verified
93
+ https://mosaic.moksaweb.com/moksa/,matrix,_m,padding-right,18px,18px,verified
94
+ https://mosaic.moksaweb.com/moksa/,mk-mx-pad,_m,padding-top,52px,52px,verified
95
+ https://mosaic.moksaweb.com/moksa/,mk-mx-pad,_m,padding-bottom,52px,52px,verified
96
+ https://mosaic.moksaweb.com/moksa/,mk-mx-cols,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
97
+ https://mosaic.moksaweb.com/moksa/,mk-mx-cols,_m,column-gap,6px,6px,verified
98
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-0,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
99
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-0,_m,column-gap,6px,6px,verified
100
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-0,_m,padding-top,11px,11px,verified
101
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-0,_m,padding-bottom,11px,11px,verified
102
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-1,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
103
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-1,_m,column-gap,6px,6px,verified
104
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-1,_m,padding-top,11px,11px,verified
105
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-1,_m,padding-bottom,11px,11px,verified
106
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-2,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
107
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-2,_m,column-gap,6px,6px,verified
108
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-2,_m,padding-top,11px,11px,verified
109
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-2,_m,padding-bottom,11px,11px,verified
110
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-3,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
111
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-3,_m,column-gap,6px,6px,verified
112
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-3,_m,padding-top,11px,11px,verified
113
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-3,_m,padding-bottom,11px,11px,verified
114
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-4,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
115
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-4,_m,column-gap,6px,6px,verified
116
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-4,_m,padding-top,11px,11px,verified
117
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-4,_m,padding-bottom,11px,11px,verified
118
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-5,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
119
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-5,_m,column-gap,6px,6px,verified
120
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-5,_m,padding-top,11px,11px,verified
121
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-5,_m,padding-bottom,11px,11px,verified
122
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-6,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
123
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-6,_m,column-gap,6px,6px,verified
124
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-6,_m,padding-top,11px,11px,verified
125
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-6,_m,padding-bottom,11px,11px,verified
126
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-7,_m,grid-template-columns,"1fr repeat(3, 46px)","1fr repeat(3, 46px)",verified
127
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-7,_m,column-gap,6px,6px,verified
128
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-7,_m,padding-top,11px,11px,verified
129
+ https://mosaic.moksaweb.com/moksa/,mk-mx-r-7,_m,padding-bottom,11px,11px,verified
90
130
  https://mosaic.moksaweb.com/moksa/,process,_t,padding-left,28px,28px,verified
91
131
  https://mosaic.moksaweb.com/moksa/,process,_t,padding-right,28px,28px,verified
92
132
  https://mosaic.moksaweb.com/moksa/,process,_m,padding-left,18px,18px,verified
@@ -104,6 +144,22 @@ https://mosaic.moksaweb.com/moksa/,mk-proc-2,_m,padding-top,20px,20px,verified
104
144
  https://mosaic.moksaweb.com/moksa/,mk-proc-2,_m,padding-right,0px,0px,verified
105
145
  https://mosaic.moksaweb.com/moksa/,mk-proc-3,_m,padding-top,20px,20px,verified
106
146
  https://mosaic.moksaweb.com/moksa/,mk-proc-3,_m,padding-right,0px,0px,verified
147
+ https://mosaic.moksaweb.com/moksa/,figure,_t,padding-left,28px,28px,verified
148
+ https://mosaic.moksaweb.com/moksa/,figure,_t,padding-right,28px,28px,verified
149
+ https://mosaic.moksaweb.com/moksa/,figure,_m,padding-left,18px,18px,verified
150
+ https://mosaic.moksaweb.com/moksa/,figure,_m,padding-right,18px,18px,verified
151
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-pad,_m,padding-top,54px,54px,verified
152
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-pad,_m,padding-bottom,54px,54px,verified
153
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-grid,_t,grid-template-columns,1fr 34px 1fr 34px 1fr,1fr 34px 1fr 34px 1fr,verified
154
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-grid,_m,grid-template-columns,"repeat(1, 1fr)","repeat(1, 1fr)",verified
155
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-grid,_m,row-gap,8px,8px,verified
156
+ https://mosaic.moksaweb.com/moksa/,mk-fig2-grid,_m,margin-top,24px,24px,verified
157
+ https://mosaic.moksaweb.com/moksa/,mk-ar-a-0,_m,transform,rotate(90deg),rotate(90deg),verified
158
+ https://mosaic.moksaweb.com/moksa/,mk-ar-b-0,_m,transform,rotate(90deg),rotate(90deg),verified
159
+ https://mosaic.moksaweb.com/moksa/,mk-ar-a-1,_m,transform,rotate(90deg),rotate(90deg),verified
160
+ https://mosaic.moksaweb.com/moksa/,mk-ar-b-1,_m,transform,rotate(90deg),rotate(90deg),verified
161
+ https://mosaic.moksaweb.com/moksa/,mk-ar-a-2,_m,transform,rotate(90deg),rotate(90deg),verified
162
+ https://mosaic.moksaweb.com/moksa/,mk-ar-b-2,_m,transform,rotate(90deg),rotate(90deg),verified
107
163
  https://mosaic.moksaweb.com/moksa/,works,_t,padding-left,28px,28px,verified
108
164
  https://mosaic.moksaweb.com/moksa/,works,_t,padding-right,28px,28px,verified
109
165
  https://mosaic.moksaweb.com/moksa/,works,_m,padding-left,18px,18px,verified
@@ -148,6 +204,9 @@ https://mosaic.moksaweb.com/moksa/,mk-work-8,_m,grid-template-columns,1fr,1fr,ve
148
204
  https://mosaic.moksaweb.com/moksa/,mk-work-8,_m,row-gap,5px,5px,verified
149
205
  https://mosaic.moksaweb.com/moksa/,mk-work-8,_m,padding-top,15px,15px,verified
150
206
  https://mosaic.moksaweb.com/moksa/,mk-work-8,_m,padding-bottom,15px,15px,verified
207
+ https://mosaic.moksaweb.com/moksa/,mk-pan,_m,padding-top,22px,22px,verified
208
+ https://mosaic.moksaweb.com/moksa/,mk-pan,_m,padding-bottom,22px,22px,verified
209
+ https://mosaic.moksaweb.com/moksa/,mk-pan-track,_m,column-gap,30px,30px,verified
151
210
  https://mosaic.moksaweb.com/moksa/,specimen,_t,padding-left,28px,28px,verified
152
211
  https://mosaic.moksaweb.com/moksa/,specimen,_t,padding-right,28px,28px,verified
153
212
  https://mosaic.moksaweb.com/moksa/,specimen,_m,padding-left,18px,18px,verified
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosaic-headless",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
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
5
  "keywords": [
6
6
  "wordpress",
package/sites/_moksa.py CHANGED
@@ -334,6 +334,15 @@ BOOT_LOG = [
334
334
  ]
335
335
  RULER_TICKS = 13
336
336
 
337
+ # FIGURE 01's rows. Up here with the other tables rather than beside the section
338
+ # that draws it, because motion_css() reads it to build the arrow stagger and is
339
+ # defined long before the figure is.
340
+ FLOW = [
341
+ ("網站表單", "FORM", "n8n 節點", "WORKFLOW", "ERP 建單", "ERP"),
342
+ ("電商訂單", "ORDER", "Claude 判讀", "REASONING", "通知/報表", "NOTIFY"),
343
+ ("客服訊息", "INBOX", "分類與派工", "TRIAGE", "CRM 紀錄", "CRM"),
344
+ ]
345
+
337
346
  CLAUSES = [
338
347
  ("01", "SERVICES", "#services", "--mk-s1"),
339
348
  ("02", "PROCESS", "#process", "--mk-s2"),
@@ -358,6 +367,12 @@ REVEALS = [
358
367
  ("mk-work-4", 3), ("mk-work-5", 3), ("mk-work-6", 4), ("mk-work-7", 4),
359
368
  ("mk-work-8", 5),
360
369
  ("mk-plate-num", 0), ("mk-plate-t", 1),
370
+ ("mk-mx-head", 0), ("mk-mx-cols", 1),
371
+ ("mk-mx-r-0", 1), ("mk-mx-r-1", 2), ("mk-mx-r-2", 2), ("mk-mx-r-3", 3),
372
+ ("mk-mx-r-4", 3), ("mk-mx-r-5", 4), ("mk-mx-r-6", 4), ("mk-mx-r-7", 5),
373
+ ("mk-fig2-head", 0), ("mk-fl-a-0", 1), ("mk-fl-b-0", 2), ("mk-fl-c-0", 3),
374
+ ("mk-fl-a-1", 2), ("mk-fl-b-1", 3), ("mk-fl-c-1", 4),
375
+ ("mk-fl-a-2", 3), ("mk-fl-b-2", 4), ("mk-fl-c-2", 5),
361
376
  ("mk-spec2-head", 0), ("mk-spec2-r-0", 1), ("mk-spec2-r-1", 2),
362
377
  ("mk-spec2-r-2", 3), ("mk-spec2-r-3", 4),
363
378
  ("mk-stack-head", 0), ("mk-stack-0", 1), ("mk-stack-1", 2), ("mk-stack-2", 3),
@@ -390,6 +405,12 @@ def motion_css():
390
405
  for i, (_g, items) in enumerate(STACK)
391
406
  for j in range(len(items)))
392
407
  marks = ",".join("#mk-mark-%d" % i for i in range(4))
408
+ # the diagram's arrows, and the delay that makes the pulse travel down it
409
+ flow_arrows = ",".join("#mk-ar-%s-%d>*" % (c, i)
410
+ for i in range(len(FLOW)) for c in ("a", "b"))
411
+ flow_stagger = "\n".join(
412
+ " #mk-ar-%s-%d>*{animation-delay:%dms}" % (c, i, i * 260 + j * 130)
413
+ for i in range(len(FLOW)) for j, c in enumerate(("a", "b")))
393
414
  # the boot sequence, phase by phase: the clauses report in, the log writes
394
415
  # itself out a line at a time, and the ruler ticks up the left margin
395
416
  boot_stagger = "\n".join(
@@ -455,6 +476,15 @@ def motion_css():
455
476
  "@keyframes mk-idxon{from,to{color:rgb(255,90,54);"
456
477
  "letter-spacing:.2em}}",
457
478
  "@keyframes mk-dotin{from{transform:scale(0)}to{transform:scale(1)}}",
479
+ # the accent band pans sideways while it crosses the viewport
480
+ "@keyframes mk-pan{from{transform:translateX(2%)}"
481
+ "to{transform:translateX(-34%)}}",
482
+ # a headline that fills with ink as it is read past
483
+ "@keyframes mk-fillscrub{from{background-position:100% 0}"
484
+ "to{background-position:0 0}}",
485
+ # a pulse travelling down the diagram's arrows
486
+ "@keyframes mk-flow{0%{transform:translateX(-7px);opacity:.2}"
487
+ "45%{opacity:1}100%{transform:translateX(7px);opacity:.2}}",
458
488
 
459
489
  # ── the document frame ───────────────────────────────────────────────
460
490
  # A spec sheet has margins. These two fixed rules are what say the content
@@ -522,8 +552,14 @@ def motion_css():
522
552
 
523
553
  # the clause index. It needs room outside the document margin, so it only
524
554
  # appears once the viewport is wide enough to have that room.
525
- "#mk-index{position:fixed;left:14px;top:50%;transform:translateY(-50%);"
526
- "z-index:3;display:grid;row-gap:11px;pointer-events:auto}",
555
+ # The index is FIXED, so it does not sit on the ground it was declared
556
+ # against - it sits on whatever has scrolled underneath. Over the accent
557
+ # band its faint grey became unreadable. Giving it its own paper ground
558
+ # means it carries its background with it instead of borrowing one.
559
+ "#mk-index{position:fixed;left:8px;top:50%;transform:translateY(-50%);"
560
+ "z-index:3;display:grid;row-gap:11px;pointer-events:auto;"
561
+ "background:rgb(250,250,247);padding:14px 10px;"
562
+ "border:1px solid " + RULE + "}",
527
563
  "@media (max-width:1439px){#mk-index{display:none}}",
528
564
 
529
565
  # process: the sequence marker on each step, and the line it sits on
@@ -692,6 +728,8 @@ def motion_css():
692
728
  " #mk-mast-meta{animation-delay:3.06s}",
693
729
  " #mk-mast-lede{animation-delay:3.54s}",
694
730
  " #mk-mast-cta{animation-delay:3.66s}",
731
+ " " + flow_arrows + "{animation:mk-flow 1.9s ease-in-out infinite}",
732
+ flow_stagger,
695
733
  " #mk-caret{animation:mk-caret 1.15s steps(1,end) infinite;"
696
734
  "animation-delay:3.84s}",
697
735
  " #mk-mast-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
@@ -725,6 +763,28 @@ def motion_css():
725
763
  # the sequence markers pop as each step arrives
726
764
  " " + proc_dots + "{animation:mk-dotin .01s linear both;"
727
765
  "animation-timeline:view();animation-range:entry 14% cover 26%}",
766
+ # the band pans across the whole time it is on screen
767
+ " #mk-pan-track{animation:mk-pan linear both;"
768
+ "animation-timeline:view();animation-range:cover 0% cover 100%}",
769
+ # The statement fills with ink as it passes. The gradient is built so the
770
+ # UNANIMATED state shows the faint half - `color:transparent` plus
771
+ # `background-clip:text` is one of the few ways to make text that is
772
+ # genuinely invisible if its animation never runs, and the page must never
773
+ # depend on that. At `from` the visible half is the faint colour, so the
774
+ # worst case is a paler headline, not a missing one.
775
+ " #mk-plate-t h2{background-image:linear-gradient(90deg,"
776
+ # The unfilled half is `--mk-faint`, not a decorative pale grey. At
777
+ # rgb(168,170,176) the tail of the line measured 2.3:1 against the panel
778
+ # while the scrub had not reached it - readable only once you had scrolled
779
+ # far enough, which is not a thing to ask of a sentence. This value clears
780
+ # 4.66:1 on its own, so the effect is a change in weight rather than a
781
+ # change between legible and not.
782
+ "rgb(22,24,28) 0 50%,rgb(107,109,113) 50% 100%);"
783
+ "background-size:220% 100%;background-position:100% 0;"
784
+ "-webkit-background-clip:text;background-clip:text;"
785
+ "color:rgba(0,0,0,0);"
786
+ "animation:mk-fillscrub linear both;animation-timeline:view();"
787
+ "animation-range:entry 24% cover 58%}",
728
788
  " }",
729
789
  " @supports (timeline-scope:--x){",
730
790
  # the index entry lights while its own section is on screen. No fill mode, so
@@ -1337,6 +1397,178 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
1337
1397
  ]),
1338
1398
  ])])
1339
1399
 
1400
+ # ── the pan strip ─────────────────────────────────────────────────────────────
1401
+ # The page has no field of colour anywhere - the accent has only ever been a rule, a
1402
+ # mark or a small block. A band of it, with the whole line panning sideways as you
1403
+ # scroll down, is the one moment here that is purely about being looked at. Ink on
1404
+ # the accent measures 5.73:1, which is why the type is dark rather than light.
1405
+ PAN_WORDS = ["WEB", "COMMERCE", "AUTOMATION", "AI", "ERP", "SEO", "SOFTWARE"]
1406
+
1407
+ PAN = box("mk-pan",
1408
+ {"backgroundColor": {"token": "--mk-accent"},
1409
+ "paddingTop": "34px", "paddingBottom": "34px",
1410
+ "customStyles": "overflow:hidden;"},
1411
+ _m={"paddingTop": "22px", "paddingBottom": "22px"},
1412
+ children=[
1413
+ box("mk-pan-track",
1414
+ {"display": "flex", "columnGap": "56px", "alignItems": "baseline",
1415
+ "customStyles": "white-space:nowrap;width:max-content;"},
1416
+ _m={"columnGap": "30px"},
1417
+ children=[
1418
+ node for i, w in enumerate(PAN_WORDS) for node in (
1419
+ box("mk-pan-n-%d" % i, {},
1420
+ [mono("%02d" % (i + 1), size="11px",
1421
+ color="rgb(22,24,28)", track="0.18em")]),
1422
+ box("mk-pan-w-%d" % i, {},
1423
+ [T("p", w, color={"token": "--mk-ink"},
1424
+ fontSize="58px", fontWeight="600",
1425
+ fontFamily=DISPLAY, letterSpacing="-0.02em",
1426
+ lineHeight="1",
1427
+ _t={"fontSize": "44px"},
1428
+ _m={"fontSize": "31px"})]),
1429
+ )
1430
+ ]),
1431
+ ])
1432
+
1433
+
1434
+ # ── the capability matrix ─────────────────────────────────────────────────────
1435
+ # The page could tell you what the studio does; a specification SHOWS you, in a form
1436
+ # you can read across. Filled, half and hollow marks carry the whole answer without
1437
+ # a sentence, and the density is the point - this is the one block on the page that
1438
+ # rewards being read as a grid rather than as a line.
1439
+ MATRIX_COLS = ["網站", "電商", "自動化"]
1440
+ MATRIX = [
1441
+ ("客製化設計", "BESPOKE DESIGN", "●", "●", "◐"),
1442
+ ("RWD 三段點", "RESPONSIVE", "●", "●", "○"),
1443
+ ("SEO 結構化資料", "STRUCTURED DATA", "●", "●", "○"),
1444
+ ("金流/物流串接", "PAYMENT / LOGISTICS", "○", "●", "◐"),
1445
+ ("會員與訂單系統", "ACCOUNTS / ORDERS", "○", "●", "◐"),
1446
+ ("n8n 流程自動化", "WORKFLOW AUTOMATION", "◐", "◐", "●"),
1447
+ ("AI 內容與判讀", "AI CONTENT / REASONING", "◐", "◐", "●"),
1448
+ ("上線後維運", "MAINTENANCE", "●", "●", "●"),
1449
+ ]
1450
+
1451
+
1452
+ def mark(sym, i, j):
1453
+ """One cell of the matrix. The symbol carries the meaning; the colour only
1454
+ reinforces it, so it still reads if the colour is gone."""
1455
+ colour = "--mk-ink" if sym == "●" else (
1456
+ "--mk-accent-ink" if sym == "◐" else "--mk-faint")
1457
+ return box("mk-mx-c-%d-%d" % (i, j),
1458
+ {"display": "flex", "justifyContent": "center"},
1459
+ [T("p", sym, color={"token": colour}, fontSize="13px",
1460
+ fontFamily=MONO, lineHeight="1")])
1461
+
1462
+
1463
+ MATRIX_SEC = section("matrix", [wrap("mk-mx-in", [
1464
+ box("mk-mx-pad", {"paddingTop": "78px", "paddingBottom": "78px"},
1465
+ _m={"paddingTop": "52px", "paddingBottom": "52px"},
1466
+ children=[
1467
+ box("mk-mx-head",
1468
+ {"display": "flex", "justifyContent": "space-between",
1469
+ "columnGap": "20px", "rowGap": "8px", "alignItems": "baseline",
1470
+ "customStyles": "flex-wrap:wrap;"},
1471
+ [mono("TABLE 01 — CAPABILITY", size="10px",
1472
+ color="--mk-accent-ink", track="0.22em"),
1473
+ mono("● 標準 ◐ 選配 ○ 不含", size="10px",
1474
+ color="--mk-faint", track="0.12em")]),
1475
+ # the column header, then one row per capability
1476
+ box("mk-mx-cols",
1477
+ {"display": "grid", "gridCols": "1fr repeat(3, 78px)",
1478
+ "columnGap": "10px", "alignItems": "end", "marginTop": "26px",
1479
+ "paddingBottom": "10px",
1480
+ "customStyles": "border-bottom:1px solid " + RULE_INK + ";"},
1481
+ _m={"gridCols": "1fr repeat(3, 46px)", "columnGap": "6px"},
1482
+ children=[mono("CAPABILITY", size="10px", color="--mk-faint",
1483
+ track="0.18em")]
1484
+ + [box("mk-mx-h-%d" % j,
1485
+ {"display": "flex", "justifyContent": "center"},
1486
+ [mono(c, size="11px", color="--mk-ink", track="0.06em")])
1487
+ for j, c in enumerate(MATRIX_COLS)]),
1488
+ box("mk-mx-rows", {}, [
1489
+ box("mk-mx-r-%d" % i,
1490
+ {"display": "grid", "gridCols": "1fr repeat(3, 78px)",
1491
+ "columnGap": "10px", "alignItems": "center",
1492
+ "paddingTop": "13px", "paddingBottom": "13px",
1493
+ "customStyles": "border-bottom:1px solid " + RULE + ";"},
1494
+ _m={"gridCols": "1fr repeat(3, 46px)", "columnGap": "6px",
1495
+ "paddingTop": "11px", "paddingBottom": "11px"},
1496
+ children=[
1497
+ box("mk-mx-n-%d" % i,
1498
+ {"display": "flex", "columnGap": "12px",
1499
+ "alignItems": "baseline", "rowGap": "2px",
1500
+ "customStyles": "flex-wrap:wrap;"},
1501
+ [T("p", zh, color={"token": "--mk-ink"},
1502
+ fontSize="14px", _m={"fontSize": "12.5px"}),
1503
+ mono(en, size="9px", color="--mk-faint",
1504
+ track="0.16em", _m={"display": "none"})]),
1505
+ ] + [mark(sym, i, j) for j, sym in enumerate(marks)])
1506
+ for i, (zh, en, *marks) in enumerate(MATRIX)
1507
+ ]),
1508
+ ]),
1509
+ ])], bg="--mk-panel")
1510
+
1511
+
1512
+ # ── FIGURE 01: what a workflow actually looks like ────────────────────────────
1513
+ # The page had no graphic on it at all - every section was type and rules, which is
1514
+ # austere for eleven screens. A diagram is the one element that says something no
1515
+ # sentence here can, and drawn in hairline boxes it belongs to the same document
1516
+ # rather than arriving from a different one.
1517
+
1518
+
1519
+ def flow_cell(attr, zh, en):
1520
+ return box(attr,
1521
+ {"paddingTop": "14px", "paddingBottom": "14px",
1522
+ "paddingLeft": "16px", "paddingRight": "16px",
1523
+ "customStyles": "border:1px solid " + RULE + ";"},
1524
+ [mono(en, size="9px", color="--mk-faint", track="0.18em"),
1525
+ T("p", zh, color={"token": "--mk-ink"}, fontSize="14px",
1526
+ marginTop="6px", _m={"fontSize": "13px"})])
1527
+
1528
+
1529
+ def arrow(attr):
1530
+ return box(attr, {"display": "flex", "justifyContent": "center",
1531
+ "alignItems": "center"},
1532
+ [T("p", "→", color={"token": "--mk-accent-ink"}, fontSize="15px",
1533
+ fontFamily=MONO)],
1534
+ _m={"customStyles": "transform:rotate(90deg);"})
1535
+
1536
+
1537
+ FIGURE_SEC = section("figure", [wrap("mk-fig2-in", [
1538
+ box("mk-fig2-pad", {"paddingTop": "82px", "paddingBottom": "82px"},
1539
+ _m={"paddingTop": "54px", "paddingBottom": "54px"},
1540
+ children=[
1541
+ box("mk-fig2-head",
1542
+ {"display": "flex", "justifyContent": "space-between",
1543
+ "columnGap": "20px", "rowGap": "8px",
1544
+ "customStyles": "flex-wrap:wrap;"},
1545
+ [mono("FIGURE 01 — A WORKFLOW", size="10px",
1546
+ color="--mk-accent-ink", track="0.22em"),
1547
+ mono("INPUT → PROCESS → OUTPUT", size="10px",
1548
+ color="--mk-faint", track="0.18em")]),
1549
+ box("mk-fig2-grid",
1550
+ {"display": "grid", "gridCols": "1fr 44px 1fr 44px 1fr",
1551
+ "columnGap": "0px", "rowGap": "16px", "marginTop": "34px"},
1552
+ _t={"gridCols": "1fr 34px 1fr 34px 1fr"},
1553
+ _m={"gridCols": "repeat(1, 1fr)", "rowGap": "8px",
1554
+ "marginTop": "24px"},
1555
+ children=[
1556
+ node for i, row in enumerate(FLOW) for node in (
1557
+ flow_cell("mk-fl-a-%d" % i, row[0], row[1]),
1558
+ arrow("mk-ar-a-%d" % i),
1559
+ flow_cell("mk-fl-b-%d" % i, row[2], row[3]),
1560
+ arrow("mk-ar-b-%d" % i),
1561
+ flow_cell("mk-fl-c-%d" % i, row[4], row[5]),
1562
+ )
1563
+ ]),
1564
+ T("p", "同一條線可以接任何一端:表單、訂單、訊息進來,"
1565
+ "判斷與轉換在中間,結果送到你本來就在用的系統裡。",
1566
+ color={"token": "--mk-muted"}, fontSize="13px", lineHeight="1.95",
1567
+ marginTop="26px", maxWidth="42em", _m={"fontSize": "12.5px"}),
1568
+ ]),
1569
+ ])])
1570
+
1571
+
1340
1572
  # ── PLATE 01: a statement ─────────────────────────────────────────────────────
1341
1573
  # Every clause on this page has the same rhythm - a numbered head, then hairline
1342
1574
  # rows. That is correct for a specification and monotonous for a page. A document
@@ -1511,8 +1743,9 @@ HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
1511
1743
  MARKS, INDEX, CUE,
1512
1744
  # paper, panel, paper, ink, paper, ink, paper - the page
1513
1745
  # changes ground five times so it reads as chapters
1514
- MASTHEAD, TICKER_BAND, PLATE, SERVICE_SEC, PROCESS_SEC,
1515
- WORK_SEC, SPECIMEN, STACK_SEC, PRODUCT_SEC, VOICE_SEC, CONTACT,
1746
+ MASTHEAD, TICKER_BAND, PLATE, SERVICE_SEC, MATRIX_SEC,
1747
+ PROCESS_SEC, FIGURE_SEC, WORK_SEC, PAN, SPECIMEN, STACK_SEC,
1748
+ PRODUCT_SEC, VOICE_SEC, CONTACT,
1516
1749
  ])]}
1517
1750
 
1518
1751
  SITE = {