mosaic-headless 1.7.0 → 1.9.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 +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh-TW.md +2 -2
- package/SKILL.md +13 -9
- 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 +3 -3
- package/data/browser-verification.csv +30 -0
- package/data/design-audit.csv +3 -0
- package/data/intro-verification.csv +13 -12
- package/data/rwd-verification.csv +3 -0
- package/package.json +1 -1
- package/sites/_moksa.py +180 -10
- package/sites/moksa.json +1572 -35
- package/tools/verify_browser.py +53 -7
- package/tools/verify_intro.py +49 -5
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"),
|
|
@@ -396,6 +405,13 @@ def motion_css():
|
|
|
396
405
|
for i, (_g, items) in enumerate(STACK)
|
|
397
406
|
for j in range(len(items)))
|
|
398
407
|
marks = ",".join("#mk-mark-%d" % i for i in range(4))
|
|
408
|
+
idx_words = ",".join("#mk-idx-w-%d" % i for i in range(len(CLAUSES)))
|
|
409
|
+
# the diagram's arrows, and the delay that makes the pulse travel down it
|
|
410
|
+
flow_arrows = ",".join("#mk-ar-%s-%d>*" % (c, i)
|
|
411
|
+
for i in range(len(FLOW)) for c in ("a", "b"))
|
|
412
|
+
flow_stagger = "\n".join(
|
|
413
|
+
" #mk-ar-%s-%d>*{animation-delay:%dms}" % (c, i, i * 260 + j * 130)
|
|
414
|
+
for i in range(len(FLOW)) for j, c in enumerate(("a", "b")))
|
|
399
415
|
# the boot sequence, phase by phase: the clauses report in, the log writes
|
|
400
416
|
# itself out a line at a time, and the ruler ticks up the left margin
|
|
401
417
|
boot_stagger = "\n".join(
|
|
@@ -461,6 +477,17 @@ def motion_css():
|
|
|
461
477
|
"@keyframes mk-idxon{from,to{color:rgb(255,90,54);"
|
|
462
478
|
"letter-spacing:.2em}}",
|
|
463
479
|
"@keyframes mk-dotin{from{transform:scale(0)}to{transform:scale(1)}}",
|
|
480
|
+
# the accent band pans sideways while it crosses the viewport
|
|
481
|
+
"@keyframes mk-pan{from{transform:translateX(2%)}"
|
|
482
|
+
"to{transform:translateX(-34%)}}",
|
|
483
|
+
"@keyframes mk-panloop{from{transform:translateX(0)}"
|
|
484
|
+
"to{transform:translateX(-50%)}}",
|
|
485
|
+
# a headline that fills with ink as it is read past
|
|
486
|
+
"@keyframes mk-fillscrub{from{background-position:100% 0}"
|
|
487
|
+
"to{background-position:0 0}}",
|
|
488
|
+
# a pulse travelling down the diagram's arrows
|
|
489
|
+
"@keyframes mk-flow{0%{transform:translateX(-7px);opacity:.2}"
|
|
490
|
+
"45%{opacity:1}100%{transform:translateX(7px);opacity:.2}}",
|
|
464
491
|
|
|
465
492
|
# ── the document frame ───────────────────────────────────────────────
|
|
466
493
|
# A spec sheet has margins. These two fixed rules are what say the content
|
|
@@ -500,6 +527,44 @@ def motion_css():
|
|
|
500
527
|
# belongs to a page set in a terminal face
|
|
501
528
|
"#mk-caret{width:13px;height:19px;background:rgb(255,90,54)}",
|
|
502
529
|
"#mk-plate-dash{width:34px;height:1px;background:rgb(191,68,40)}",
|
|
530
|
+
|
|
531
|
+
# ── the clock ────────────────────────────────────────────────────────
|
|
532
|
+
# Two registered integers, stepped rather than eased, so each one lands on a
|
|
533
|
+
# whole number and reads as a tick instead of a blur. `decimal-leading-zero`
|
|
534
|
+
# is what keeps it two digits without any padding logic.
|
|
535
|
+
"@property --mk-s{syntax:'<integer>';initial-value:0;inherits:true}",
|
|
536
|
+
"@property --mk-m{syntax:'<integer>';initial-value:0;inherits:true}",
|
|
537
|
+
"@keyframes mk-secs{to{--mk-s:60}}",
|
|
538
|
+
"@keyframes mk-mins{to{--mk-m:60}}",
|
|
539
|
+
"@keyframes mk-livedot{0%,55%{opacity:1}56%,100%{opacity:.15}}",
|
|
540
|
+
"#mk-clock{display:flex;align-items:baseline;column-gap:5px;"
|
|
541
|
+
"margin-left:18px;padding-left:18px;"
|
|
542
|
+
"border-left:1px solid " + RULE + "}",
|
|
543
|
+
"#mk-clock-dot{width:5px;height:5px;background:rgb(255,90,54);"
|
|
544
|
+
"align-self:center;margin-right:3px}",
|
|
545
|
+
"#mk-clock-m,#mk-clock-s{font-family:" + MONO + ";font-size:11px;"
|
|
546
|
+
"color:rgb(22,24,28);font-variant-numeric:tabular-nums;"
|
|
547
|
+
"letter-spacing:0.02em}",
|
|
548
|
+
"#mk-clock-s::after{counter-reset:s var(--mk-s);"
|
|
549
|
+
"content:counter(s,decimal-leading-zero)}",
|
|
550
|
+
"#mk-clock-m::after{counter-reset:m var(--mk-m);"
|
|
551
|
+
"content:counter(m,decimal-leading-zero)}",
|
|
552
|
+
"@media (max-width:1023px){#mk-clock{display:none}}",
|
|
553
|
+
|
|
554
|
+
# ── an ambient sweep ─────────────────────────────────────────────────
|
|
555
|
+
# One hairline crossing the viewport, forever, at an opacity that is almost
|
|
556
|
+
# an excuse. It is the difference between a page that is still and a page
|
|
557
|
+
# that is idling.
|
|
558
|
+
"@keyframes mk-sweepdown{from{transform:translateY(-10vh)}"
|
|
559
|
+
"to{transform:translateY(110vh)}}",
|
|
560
|
+
'#mk-sweep{content:"";position:fixed;left:0;right:0;top:0;height:1px;'
|
|
561
|
+
"z-index:2;pointer-events:none;background:linear-gradient(90deg,"
|
|
562
|
+
"rgba(255,90,54,0),rgba(255,90,54,.30) 22%,rgba(255,90,54,.30) 78%,"
|
|
563
|
+
"rgba(255,90,54,0))}",
|
|
564
|
+
|
|
565
|
+
# ── the margin rules carry a travelling segment ──────────────────────
|
|
566
|
+
"@keyframes mk-railrun{from{background-position:0 -40vh}"
|
|
567
|
+
"to{background-position:0 140vh}}",
|
|
503
568
|
"#mk-mast-rule,#mk-spec-rule{height:1px;background:" + RULE + ";"
|
|
504
569
|
"transform:scaleX(0);transform-origin:0 50%}",
|
|
505
570
|
digits + "{display:block;transform:translateY(105%)}",
|
|
@@ -528,8 +593,23 @@ def motion_css():
|
|
|
528
593
|
|
|
529
594
|
# the clause index. It needs room outside the document margin, so it only
|
|
530
595
|
# appears once the viewport is wide enough to have that room.
|
|
531
|
-
|
|
532
|
-
|
|
596
|
+
# The index is FIXED, so it does not sit on the ground it was declared
|
|
597
|
+
# against - it sits on whatever has scrolled underneath. Over the accent
|
|
598
|
+
# band its faint grey became unreadable. Giving it its own paper ground
|
|
599
|
+
# means it carries its background with it instead of borrowing one.
|
|
600
|
+
# The index lives in the margin, so where it can show its words is a
|
|
601
|
+
# function of how much margin there is: the container is 1240px centred, so
|
|
602
|
+
# the left margin is (W-1240)/2, and the full panel measures 178px. First
|
|
603
|
+
# attempt put the threshold at 1560 and 1600px still overlapped by 6px -
|
|
604
|
+
# measured, not estimated. 1680 leaves 34px of air; below it the panel keeps
|
|
605
|
+
# the clause numbers only, which fit the gutter at any width.
|
|
606
|
+
"#mk-index{position:fixed;left:8px;top:50%;transform:translateY(-50%);"
|
|
607
|
+
"z-index:3;display:grid;row-gap:11px;pointer-events:auto;"
|
|
608
|
+
"background:rgb(250,250,247);padding:14px 10px;"
|
|
609
|
+
"border:1px solid " + RULE + "}",
|
|
610
|
+
"#mk-index>*{display:flex;column-gap:8px;align-items:baseline}",
|
|
611
|
+
"@media (max-width:1679px){" + idx_words + "{display:none}"
|
|
612
|
+
"#mk-index{padding:12px 7px;left:4px}}",
|
|
533
613
|
"@media (max-width:1439px){#mk-index{display:none}}",
|
|
534
614
|
|
|
535
615
|
# process: the sequence marker on each step, and the line it sits on
|
|
@@ -698,6 +778,19 @@ def motion_css():
|
|
|
698
778
|
" #mk-mast-meta{animation-delay:3.06s}",
|
|
699
779
|
" #mk-mast-lede{animation-delay:3.54s}",
|
|
700
780
|
" #mk-mast-cta{animation-delay:3.66s}",
|
|
781
|
+
" " + flow_arrows + "{animation:mk-flow 1.9s ease-in-out infinite}",
|
|
782
|
+
" #mk-clock-s{animation:mk-secs 60s steps(60,end) infinite}",
|
|
783
|
+
" #mk-clock-m{animation:mk-mins 3600s steps(60,end) infinite}",
|
|
784
|
+
" #mk-clock-dot{animation:mk-livedot 2s steps(1,end) infinite}",
|
|
785
|
+
" #mk-sweep{animation:mk-sweepdown 11s linear infinite}",
|
|
786
|
+
" #mk-pan-loop{animation:mk-panloop 26s linear infinite}",
|
|
787
|
+
# the two fixed document rules get a lit segment sliding down them forever
|
|
788
|
+
" #mk-doc::before,#mk-doc::after{"
|
|
789
|
+
"background-image:linear-gradient(180deg,rgba(255,90,54,0),"
|
|
790
|
+
"rgba(255,90,54,.55) 45%,rgba(255,90,54,.55) 55%,rgba(255,90,54,0));"
|
|
791
|
+
"background-size:1px 34vh;background-repeat:no-repeat;"
|
|
792
|
+
"animation:mk-railrun 9s linear infinite}",
|
|
793
|
+
flow_stagger,
|
|
701
794
|
" #mk-caret{animation:mk-caret 1.15s steps(1,end) infinite;"
|
|
702
795
|
"animation-delay:3.84s}",
|
|
703
796
|
" #mk-mast-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
|
|
@@ -731,6 +824,28 @@ def motion_css():
|
|
|
731
824
|
# the sequence markers pop as each step arrives
|
|
732
825
|
" " + proc_dots + "{animation:mk-dotin .01s linear both;"
|
|
733
826
|
"animation-timeline:view();animation-range:entry 14% cover 26%}",
|
|
827
|
+
# the band pans across the whole time it is on screen
|
|
828
|
+
" #mk-pan-track{animation:mk-pan linear both;"
|
|
829
|
+
"animation-timeline:view();animation-range:cover 0% cover 100%}",
|
|
830
|
+
# The statement fills with ink as it passes. The gradient is built so the
|
|
831
|
+
# UNANIMATED state shows the faint half - `color:transparent` plus
|
|
832
|
+
# `background-clip:text` is one of the few ways to make text that is
|
|
833
|
+
# genuinely invisible if its animation never runs, and the page must never
|
|
834
|
+
# depend on that. At `from` the visible half is the faint colour, so the
|
|
835
|
+
# worst case is a paler headline, not a missing one.
|
|
836
|
+
" #mk-plate-t h2{background-image:linear-gradient(90deg,"
|
|
837
|
+
# The unfilled half is `--mk-faint`, not a decorative pale grey. At
|
|
838
|
+
# rgb(168,170,176) the tail of the line measured 2.3:1 against the panel
|
|
839
|
+
# while the scrub had not reached it - readable only once you had scrolled
|
|
840
|
+
# far enough, which is not a thing to ask of a sentence. This value clears
|
|
841
|
+
# 4.66:1 on its own, so the effect is a change in weight rather than a
|
|
842
|
+
# change between legible and not.
|
|
843
|
+
"rgb(22,24,28) 0 50%,rgb(107,109,113) 50% 100%);"
|
|
844
|
+
"background-size:220% 100%;background-position:100% 0;"
|
|
845
|
+
"-webkit-background-clip:text;background-clip:text;"
|
|
846
|
+
"color:rgba(0,0,0,0);"
|
|
847
|
+
"animation:mk-fillscrub linear both;animation-timeline:view();"
|
|
848
|
+
"animation-range:entry 24% cover 58%}",
|
|
734
849
|
" }",
|
|
735
850
|
" @supports (timeline-scope:--x){",
|
|
736
851
|
# the index entry lights while its own section is on screen. No fill mode, so
|
|
@@ -789,6 +904,20 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
789
904
|
T("p", "/ STUDIO", color={"token": "--mk-faint"},
|
|
790
905
|
fontSize="11px", letterSpacing="0.06em", fontFamily=MONO,
|
|
791
906
|
_m={"display": "none"})]},
|
|
907
|
+
# A running session clock. It counts from the moment the document
|
|
908
|
+
# loaded, which is why it says T+ rather than pretending to be the
|
|
909
|
+
# time of day - a page cannot know that without JavaScript, and
|
|
910
|
+
# labelling a page-load counter "14:32" would be a small lie told
|
|
911
|
+
# in the most trustworthy typeface on the site.
|
|
912
|
+
box("mk-clock", {}, [
|
|
913
|
+
box("mk-clock-dot", {}, []),
|
|
914
|
+
T("p", "T+", color={"token": "--mk-faint"}, fontSize="10px",
|
|
915
|
+
fontFamily=MONO, letterSpacing="0.18em"),
|
|
916
|
+
box("mk-clock-m", {}, []),
|
|
917
|
+
T("p", ":", color={"token": "--mk-faint"}, fontSize="11px",
|
|
918
|
+
fontFamily=MONO),
|
|
919
|
+
box("mk-clock-s", {}, []),
|
|
920
|
+
]),
|
|
792
921
|
{"type": "menu", "data": {"attrID": "mk-nav"},
|
|
793
922
|
"style": bp({"display": "flex", "columnGap": "30px",
|
|
794
923
|
"alignItems": "center"},
|
|
@@ -1343,6 +1472,49 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1343
1472
|
]),
|
|
1344
1473
|
])])
|
|
1345
1474
|
|
|
1475
|
+
# ── the pan strip ─────────────────────────────────────────────────────────────
|
|
1476
|
+
# The page has no field of colour anywhere - the accent has only ever been a rule, a
|
|
1477
|
+
# mark or a small block. A band of it, with the whole line panning sideways as you
|
|
1478
|
+
# scroll down, is the one moment here that is purely about being looked at. Ink on
|
|
1479
|
+
# the accent measures 5.73:1, which is why the type is dark rather than light.
|
|
1480
|
+
PAN_WORDS = ["WEB", "COMMERCE", "AUTOMATION", "AI", "ERP", "SEO", "SOFTWARE"]
|
|
1481
|
+
|
|
1482
|
+
PAN = box("mk-pan",
|
|
1483
|
+
{"backgroundColor": {"token": "--mk-accent"},
|
|
1484
|
+
"paddingTop": "34px", "paddingBottom": "34px",
|
|
1485
|
+
"customStyles": "overflow:hidden;"},
|
|
1486
|
+
_m={"paddingTop": "22px", "paddingBottom": "22px"},
|
|
1487
|
+
children=[
|
|
1488
|
+
# Two nested motions, because one transform cannot do both jobs.
|
|
1489
|
+
# The outer track is scrubbed by the scroll; the inner loop runs on
|
|
1490
|
+
# its own clock so the band is still moving when nobody is scrolling.
|
|
1491
|
+
# The word list is emitted TWICE and the loop travels exactly half its
|
|
1492
|
+
# own width, which is what makes the wrap invisible.
|
|
1493
|
+
box("mk-pan-track", {"customStyles": "width:max-content;"}, [
|
|
1494
|
+
box("mk-pan-loop",
|
|
1495
|
+
{"display": "flex", "columnGap": "56px",
|
|
1496
|
+
"alignItems": "baseline",
|
|
1497
|
+
"customStyles": "white-space:nowrap;width:max-content;"},
|
|
1498
|
+
_m={"columnGap": "30px"},
|
|
1499
|
+
children=[
|
|
1500
|
+
node for c in range(2)
|
|
1501
|
+
for i, w in enumerate(PAN_WORDS) for node in (
|
|
1502
|
+
box("mk-pan-n-%d-%d" % (c, i), {},
|
|
1503
|
+
[mono("%02d" % (i + 1), size="11px",
|
|
1504
|
+
color="rgb(22,24,28)", track="0.18em")]),
|
|
1505
|
+
box("mk-pan-w-%d-%d" % (c, i), {},
|
|
1506
|
+
[T("p", w, color={"token": "--mk-ink"},
|
|
1507
|
+
fontSize="58px", fontWeight="600",
|
|
1508
|
+
fontFamily=DISPLAY, letterSpacing="-0.02em",
|
|
1509
|
+
lineHeight="1",
|
|
1510
|
+
_t={"fontSize": "44px"},
|
|
1511
|
+
_m={"fontSize": "31px"})]),
|
|
1512
|
+
)
|
|
1513
|
+
]),
|
|
1514
|
+
]),
|
|
1515
|
+
])
|
|
1516
|
+
|
|
1517
|
+
|
|
1346
1518
|
# ── the capability matrix ─────────────────────────────────────────────────────
|
|
1347
1519
|
# The page could tell you what the studio does; a specification SHOWS you, in a form
|
|
1348
1520
|
# you can read across. Filled, half and hollow marks carry the whole answer without
|
|
@@ -1426,11 +1598,6 @@ MATRIX_SEC = section("matrix", [wrap("mk-mx-in", [
|
|
|
1426
1598
|
# austere for eleven screens. A diagram is the one element that says something no
|
|
1427
1599
|
# sentence here can, and drawn in hairline boxes it belongs to the same document
|
|
1428
1600
|
# rather than arriving from a different one.
|
|
1429
|
-
FLOW = [
|
|
1430
|
-
("網站表單", "FORM", "n8n 節點", "WORKFLOW", "ERP 建單", "ERP"),
|
|
1431
|
-
("電商訂單", "ORDER", "Claude 判讀", "REASONING", "通知/報表", "NOTIFY"),
|
|
1432
|
-
("客服訊息", "INBOX", "分類與派工", "TRIAGE", "CRM 紀錄", "CRM"),
|
|
1433
|
-
]
|
|
1434
1601
|
|
|
1435
1602
|
|
|
1436
1603
|
def flow_cell(attr, zh, en):
|
|
@@ -1646,7 +1813,10 @@ INDEX = box("mk-index", {}, [
|
|
|
1646
1813
|
"transitionAll": "220ms ease"}},
|
|
1647
1814
|
"hover": {"_": {"color": {"token": "--mk-ink"}}},
|
|
1648
1815
|
"focus-visible": {"_": FOCUS_RING}},
|
|
1649
|
-
"
|
|
1816
|
+
"children": [
|
|
1817
|
+
box("mk-idx-n-%d" % i, {}, [T("p", "§" + num)]),
|
|
1818
|
+
box("mk-idx-w-%d" % i, {}, [T("p", name)]),
|
|
1819
|
+
]}
|
|
1650
1820
|
for i, (num, name, href, _tl) in enumerate(CLAUSES)
|
|
1651
1821
|
])
|
|
1652
1822
|
|
|
@@ -1657,11 +1827,11 @@ CUE = box("mk-cue", {}, [
|
|
|
1657
1827
|
|
|
1658
1828
|
HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
|
|
1659
1829
|
"children": [BOOT, box("mk-doc", {}, [
|
|
1660
|
-
MARKS, INDEX, CUE,
|
|
1830
|
+
MARKS, INDEX, CUE, box("mk-sweep", {}, []),
|
|
1661
1831
|
# paper, panel, paper, ink, paper, ink, paper - the page
|
|
1662
1832
|
# changes ground five times so it reads as chapters
|
|
1663
1833
|
MASTHEAD, TICKER_BAND, PLATE, SERVICE_SEC, MATRIX_SEC,
|
|
1664
|
-
PROCESS_SEC, FIGURE_SEC, WORK_SEC, SPECIMEN, STACK_SEC,
|
|
1834
|
+
PROCESS_SEC, FIGURE_SEC, WORK_SEC, PAN, SPECIMEN, STACK_SEC,
|
|
1665
1835
|
PRODUCT_SEC, VOICE_SEC, CONTACT,
|
|
1666
1836
|
])]}
|
|
1667
1837
|
|