mosaic-headless 1.5.1 → 1.7.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 +12 -6
- 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 +2 -2
- package/data/browser-verification.csv +965 -0
- package/data/intro-verification.csv +15 -12
- package/data/rwd-verification.csv +88 -0
- package/package.json +1 -1
- package/sites/_moksa.py +355 -18
- package/sites/moksa.json +4524 -626
- package/tools/verify_intro.py +3 -2
package/sites/_moksa.py
CHANGED
|
@@ -323,6 +323,17 @@ STACK = [
|
|
|
323
323
|
|
|
324
324
|
# the clause index, fixed to the left margin. One entry per section, and the entry
|
|
325
325
|
# lights up while its section is on screen - see the named view timelines below.
|
|
326
|
+
# What the boot screen reports while it works. Real steps in this page's own
|
|
327
|
+
# construction rather than decoration: the faces it loads, the tokens it resolves,
|
|
328
|
+
# the breakpoints it compiles, the clauses it mounts.
|
|
329
|
+
BOOT_LOG = [
|
|
330
|
+
"LOADING TYPEFACES — IBM PLEX MONO / SPACE GROTESK / NOTO SANS TC",
|
|
331
|
+
"RESOLVING DESIGN TOKENS — 8 COLLECTION VARIABLES",
|
|
332
|
+
"COMPILING BREAKPOINTS — 1440 / 1079 / 767",
|
|
333
|
+
"MOUNTING CLAUSES §01–§07",
|
|
334
|
+
]
|
|
335
|
+
RULER_TICKS = 13
|
|
336
|
+
|
|
326
337
|
CLAUSES = [
|
|
327
338
|
("01", "SERVICES", "#services", "--mk-s1"),
|
|
328
339
|
("02", "PROCESS", "#process", "--mk-s2"),
|
|
@@ -346,6 +357,15 @@ REVEALS = [
|
|
|
346
357
|
("mk-work-0", 1), ("mk-work-1", 1), ("mk-work-2", 2), ("mk-work-3", 2),
|
|
347
358
|
("mk-work-4", 3), ("mk-work-5", 3), ("mk-work-6", 4), ("mk-work-7", 4),
|
|
348
359
|
("mk-work-8", 5),
|
|
360
|
+
("mk-plate-num", 0), ("mk-plate-t", 1),
|
|
361
|
+
("mk-mx-head", 0), ("mk-mx-cols", 1),
|
|
362
|
+
("mk-mx-r-0", 1), ("mk-mx-r-1", 2), ("mk-mx-r-2", 2), ("mk-mx-r-3", 3),
|
|
363
|
+
("mk-mx-r-4", 3), ("mk-mx-r-5", 4), ("mk-mx-r-6", 4), ("mk-mx-r-7", 5),
|
|
364
|
+
("mk-fig2-head", 0), ("mk-fl-a-0", 1), ("mk-fl-b-0", 2), ("mk-fl-c-0", 3),
|
|
365
|
+
("mk-fl-a-1", 2), ("mk-fl-b-1", 3), ("mk-fl-c-1", 4),
|
|
366
|
+
("mk-fl-a-2", 3), ("mk-fl-b-2", 4), ("mk-fl-c-2", 5),
|
|
367
|
+
("mk-spec2-head", 0), ("mk-spec2-r-0", 1), ("mk-spec2-r-1", 2),
|
|
368
|
+
("mk-spec2-r-2", 3), ("mk-spec2-r-3", 4),
|
|
349
369
|
("mk-stack-head", 0), ("mk-stack-0", 1), ("mk-stack-1", 2), ("mk-stack-2", 3),
|
|
350
370
|
("mk-stack-3", 4), ("mk-stack-4", 5),
|
|
351
371
|
("mk-prod-head", 0), ("mk-prod-0", 1), ("mk-prod-1", 2),
|
|
@@ -376,10 +396,17 @@ def motion_css():
|
|
|
376
396
|
for i, (_g, items) in enumerate(STACK)
|
|
377
397
|
for j in range(len(items)))
|
|
378
398
|
marks = ",".join("#mk-mark-%d" % i for i in range(4))
|
|
379
|
-
# the boot
|
|
399
|
+
# the boot sequence, phase by phase: the clauses report in, the log writes
|
|
400
|
+
# itself out a line at a time, and the ruler ticks up the left margin
|
|
380
401
|
boot_stagger = "\n".join(
|
|
381
|
-
" #mk-boot-c%d{animation-delay:%dms}" % (i,
|
|
402
|
+
" #mk-boot-c%d{animation-delay:%dms}" % (i, 700 + i * 200)
|
|
382
403
|
for i in range(len(CLAUSES)))
|
|
404
|
+
log_stagger = "\n".join(
|
|
405
|
+
" #mk-boot-l%d{animation-delay:%dms}" % (i, 550 + i * 380)
|
|
406
|
+
for i in range(len(BOOT_LOG)))
|
|
407
|
+
ruler_stagger = "\n".join(
|
|
408
|
+
" #mk-boot-t%d{animation-delay:%dms}" % (i, 120 + i * 26)
|
|
409
|
+
for i in range(RULER_TICKS))
|
|
383
410
|
# a named view timeline per section, declared on the section and consumed by its
|
|
384
411
|
# index entry - `timeline-scope` on #mk-doc is what lets the name cross between
|
|
385
412
|
# two elements that are not ancestor and descendant
|
|
@@ -472,6 +499,7 @@ def motion_css():
|
|
|
472
499
|
# the blinking caret under the headline: the one piece of ornament, and it
|
|
473
500
|
# belongs to a page set in a terminal face
|
|
474
501
|
"#mk-caret{width:13px;height:19px;background:rgb(255,90,54)}",
|
|
502
|
+
"#mk-plate-dash{width:34px;height:1px;background:rgb(191,68,40)}",
|
|
475
503
|
"#mk-mast-rule,#mk-spec-rule{height:1px;background:" + RULE + ";"
|
|
476
504
|
"transform:scaleX(0);transform-origin:0 50%}",
|
|
477
505
|
digits + "{display:block;transform:translateY(105%)}",
|
|
@@ -559,23 +587,35 @@ def motion_css():
|
|
|
559
587
|
"@keyframes mk-boot-scan{from{transform:translateY(0)}"
|
|
560
588
|
"to{transform:translateY(100vh)}}",
|
|
561
589
|
"@keyframes mk-boot-fade{from{opacity:0}to{opacity:1}}",
|
|
590
|
+
# a log line arriving: clipped from the left, like a terminal writing it
|
|
591
|
+
"@keyframes mk-boot-type{from{clip-path:inset(0 100% 0 0);opacity:.3}"
|
|
592
|
+
"to{clip-path:inset(0 0 0 0);opacity:1}}",
|
|
593
|
+
"@keyframes mk-boot-caret{0%,49%{opacity:1}50%,99%{opacity:0}}",
|
|
594
|
+
# the corner crosshairs draw themselves in
|
|
595
|
+
"@keyframes mk-boot-cross{from{opacity:0;transform:scale(.55)}"
|
|
596
|
+
"to{opacity:1;transform:scale(1)}}",
|
|
597
|
+
"@keyframes mk-boot-tick{from{opacity:0;transform:scaleX(.2)}"
|
|
598
|
+
"to{opacity:1;transform:scaleX(1)}}",
|
|
599
|
+
# the stamp lands hard, the way a stamp does
|
|
600
|
+
"@keyframes mk-boot-stamp{0%{opacity:0;transform:scale(1.35)}"
|
|
601
|
+
"55%{opacity:1;transform:scale(.97)}100%{opacity:1;transform:scale(1)}}",
|
|
562
602
|
|
|
563
603
|
# ── motion, all of it opt-out-able ───────────────────────────────────
|
|
564
604
|
"@media (prefers-reduced-motion:no-preference){",
|
|
565
605
|
" #mk-boot{display:grid;position:fixed;inset:0;z-index:999;"
|
|
566
606
|
"background:rgb(250,250,247);align-content:center;justify-items:center;"
|
|
567
|
-
"row-gap:
|
|
568
|
-
"animation:mk-boot-out .
|
|
607
|
+
"row-gap:20px;overflow:hidden;"
|
|
608
|
+
"animation:mk-boot-out .62s cubic-bezier(.7,0,.3,1) 2.95s forwards}",
|
|
569
609
|
" #mk-boot-head,#mk-boot-foot{position:absolute;left:40px;right:40px;"
|
|
570
610
|
"display:flex;justify-content:space-between;font-family:" + MONO + ";"
|
|
571
611
|
"font-size:10px;letter-spacing:.2em;color:rgb(107,109,113);"
|
|
572
612
|
"animation:mk-boot-fade .4s ease both}",
|
|
573
613
|
" #mk-boot-head{top:34px}",
|
|
574
614
|
" #mk-boot-foot{bottom:30px;animation-delay:.1s}",
|
|
575
|
-
" #mk-boot-num{font-family:" + MONO + ";font-size:
|
|
615
|
+
" #mk-boot-num{font-family:" + MONO + ";font-size:104px;font-weight:500;"
|
|
576
616
|
"line-height:1;letter-spacing:-.04em;color:rgb(22,24,28);"
|
|
577
617
|
"font-variant-numeric:tabular-nums;min-width:3ch;text-align:right;"
|
|
578
|
-
"animation:mk-count
|
|
618
|
+
"animation:mk-count 2.05s linear .3s both}",
|
|
579
619
|
' #mk-boot-num::after{counter-reset:n var(--mk-n);content:counter(n)}',
|
|
580
620
|
" #mk-boot-row{display:flex;align-items:flex-end;column-gap:8px;"
|
|
581
621
|
"line-height:1}",
|
|
@@ -588,12 +628,17 @@ def motion_css():
|
|
|
588
628
|
"background:rgba(22,24,28,.14);position:relative}",
|
|
589
629
|
" #mk-boot-fill{position:absolute;inset:0;background:rgb(255,90,54);"
|
|
590
630
|
"transform-origin:0 50%;"
|
|
591
|
-
"animation:mk-boot-fill
|
|
631
|
+
"animation:mk-boot-fill 2.05s linear .3s both}",
|
|
632
|
+
# ticks along the rule, so the bar reads as a measure rather than a bar
|
|
633
|
+
' #mk-boot-track::after{content:"";position:absolute;left:0;right:0;'
|
|
634
|
+
"top:-3px;height:3px;background:repeating-linear-gradient(90deg,"
|
|
635
|
+
"rgba(22,24,28,.22) 0 1px,transparent 1px 42px);"
|
|
636
|
+
"animation:mk-boot-fade .5s ease .15s both}",
|
|
592
637
|
" #mk-boot-list{display:flex;column-gap:18px;row-gap:8px;"
|
|
593
638
|
"flex-wrap:wrap;justify-content:center;font-family:" + MONO + ";"
|
|
594
639
|
"font-size:10px;letter-spacing:.18em}",
|
|
595
640
|
" #mk-boot-list>*{opacity:.14;"
|
|
596
|
-
"animation:mk-boot-line .
|
|
641
|
+
"animation:mk-boot-line .62s ease both}",
|
|
597
642
|
boot_stagger,
|
|
598
643
|
" #mk-boot::before{content:\"\";position:absolute;inset:0;"
|
|
599
644
|
"background:"
|
|
@@ -604,22 +649,59 @@ def motion_css():
|
|
|
604
649
|
"animation:mk-boot-fade .5s ease both}",
|
|
605
650
|
" #mk-boot-scan{position:absolute;left:0;right:0;top:0;height:1px;"
|
|
606
651
|
"background:linear-gradient(90deg,rgba(255,90,54,0),rgba(255,90,54,.55),"
|
|
607
|
-
"rgba(255,90,54,0));
|
|
652
|
+
"rgba(255,90,54,0));"
|
|
653
|
+
"animation:mk-boot-scan 1.45s linear .15s 2 both}",
|
|
654
|
+
|
|
655
|
+
# corner crosshairs: the veil is a plate, and a plate has trim marks
|
|
656
|
+
" #mk-boot .mk-x,#mk-boot-x0,#mk-boot-x1,#mk-boot-x2,#mk-boot-x3{"
|
|
657
|
+
"position:absolute;width:14px;height:14px;"
|
|
658
|
+
"animation:mk-boot-cross .5s cubic-bezier(.16,1,.3,1) both}",
|
|
659
|
+
" #mk-boot-x0{top:64px;left:40px;border-left:1px solid " + RULE_INK
|
|
660
|
+
+ ";border-top:1px solid " + RULE_INK + ";animation-delay:.08s}",
|
|
661
|
+
" #mk-boot-x1{top:64px;right:40px;border-right:1px solid " + RULE_INK
|
|
662
|
+
+ ";border-top:1px solid " + RULE_INK + ";animation-delay:.16s}",
|
|
663
|
+
" #mk-boot-x2{bottom:56px;left:40px;border-left:1px solid " + RULE_INK
|
|
664
|
+
+ ";border-bottom:1px solid " + RULE_INK + ";animation-delay:.24s}",
|
|
665
|
+
" #mk-boot-x3{bottom:56px;right:40px;border-right:1px solid " + RULE_INK
|
|
666
|
+
+ ";border-bottom:1px solid " + RULE_INK + ";animation-delay:.32s}",
|
|
667
|
+
|
|
668
|
+
# a ruler down the left margin, because this is a measured object
|
|
669
|
+
" #mk-boot-ruler{position:absolute;left:40px;top:50%;"
|
|
670
|
+
"transform:translateY(-50%);display:grid;row-gap:9px}",
|
|
671
|
+
" #mk-boot-ruler>*{width:9px;height:1px;background:" + RULE_INK + ";"
|
|
672
|
+
"transform-origin:0 50%;animation:mk-boot-tick .3s ease both}",
|
|
673
|
+
ruler_stagger,
|
|
674
|
+
" @media (max-width:1023px){#mk-boot-ruler{display:none}}",
|
|
675
|
+
|
|
676
|
+
# the log: four lines, each written out left to right
|
|
677
|
+
" #mk-boot-log{display:grid;row-gap:7px;justify-items:start;"
|
|
678
|
+
"font-family:" + MONO + ";font-size:10px;letter-spacing:.16em;"
|
|
679
|
+
"color:rgb(107,109,113);min-height:64px}",
|
|
680
|
+
" #mk-boot-log>*{animation:mk-boot-type .42s steps(24,end) both}",
|
|
681
|
+
log_stagger,
|
|
682
|
+
" #mk-boot-cursor{width:7px;height:11px;background:rgb(255,90,54);"
|
|
683
|
+
"animation:mk-boot-caret .62s steps(1,end) .5s infinite}",
|
|
684
|
+
|
|
685
|
+
# the stamp that says the document is complete
|
|
686
|
+
" #mk-boot-ready{border:1px solid rgb(191,68,40);padding:5px 12px;"
|
|
687
|
+
"font-family:" + MONO + ";font-size:10px;letter-spacing:.24em;"
|
|
688
|
+
"color:rgb(191,68,40);opacity:0;"
|
|
689
|
+
"animation:mk-boot-stamp .42s cubic-bezier(.16,1,.3,1) 2.42s both}",
|
|
608
690
|
" @media (max-width:767px){#mk-boot-num{font-size:62px}"
|
|
609
691
|
"#mk-boot-head,#mk-boot-foot{left:18px;right:18px}}",
|
|
610
692
|
" #mk-hl1-mask>*,#mk-hl2-mask>*{transform:translateY(112%);"
|
|
611
693
|
"animation:mk-linein .95s cubic-bezier(.16,1,.3,1) forwards}",
|
|
612
|
-
" #mk-hl1-mask>*{animation-delay:
|
|
613
|
-
" #mk-hl2-mask>*{animation-delay:
|
|
694
|
+
" #mk-hl1-mask>*{animation-delay:3.16s}",
|
|
695
|
+
" #mk-hl2-mask>*{animation-delay:3.28s}",
|
|
614
696
|
" #mk-mast-meta,#mk-mast-lede,#mk-mast-cta{opacity:0;"
|
|
615
697
|
"animation:mk-softin .8s cubic-bezier(.16,1,.3,1) forwards}",
|
|
616
|
-
" #mk-mast-meta{animation-delay:
|
|
617
|
-
" #mk-mast-lede{animation-delay:
|
|
618
|
-
" #mk-mast-cta{animation-delay:
|
|
698
|
+
" #mk-mast-meta{animation-delay:3.06s}",
|
|
699
|
+
" #mk-mast-lede{animation-delay:3.54s}",
|
|
700
|
+
" #mk-mast-cta{animation-delay:3.66s}",
|
|
619
701
|
" #mk-caret{animation:mk-caret 1.15s steps(1,end) infinite;"
|
|
620
|
-
"animation-delay:
|
|
702
|
+
"animation-delay:3.84s}",
|
|
621
703
|
" #mk-mast-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
|
|
622
|
-
"animation-delay:
|
|
704
|
+
"animation-delay:3.40s}",
|
|
623
705
|
" #mk-spec-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
|
|
624
706
|
"animation-delay:.9s}",
|
|
625
707
|
" #mk-cue{opacity:0;animation:mk-softin .8s ease forwards;"
|
|
@@ -1261,6 +1343,245 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1261
1343
|
]),
|
|
1262
1344
|
])])
|
|
1263
1345
|
|
|
1346
|
+
# ── the capability matrix ─────────────────────────────────────────────────────
|
|
1347
|
+
# The page could tell you what the studio does; a specification SHOWS you, in a form
|
|
1348
|
+
# you can read across. Filled, half and hollow marks carry the whole answer without
|
|
1349
|
+
# a sentence, and the density is the point - this is the one block on the page that
|
|
1350
|
+
# rewards being read as a grid rather than as a line.
|
|
1351
|
+
MATRIX_COLS = ["網站", "電商", "自動化"]
|
|
1352
|
+
MATRIX = [
|
|
1353
|
+
("客製化設計", "BESPOKE DESIGN", "●", "●", "◐"),
|
|
1354
|
+
("RWD 三段點", "RESPONSIVE", "●", "●", "○"),
|
|
1355
|
+
("SEO 結構化資料", "STRUCTURED DATA", "●", "●", "○"),
|
|
1356
|
+
("金流/物流串接", "PAYMENT / LOGISTICS", "○", "●", "◐"),
|
|
1357
|
+
("會員與訂單系統", "ACCOUNTS / ORDERS", "○", "●", "◐"),
|
|
1358
|
+
("n8n 流程自動化", "WORKFLOW AUTOMATION", "◐", "◐", "●"),
|
|
1359
|
+
("AI 內容與判讀", "AI CONTENT / REASONING", "◐", "◐", "●"),
|
|
1360
|
+
("上線後維運", "MAINTENANCE", "●", "●", "●"),
|
|
1361
|
+
]
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def mark(sym, i, j):
|
|
1365
|
+
"""One cell of the matrix. The symbol carries the meaning; the colour only
|
|
1366
|
+
reinforces it, so it still reads if the colour is gone."""
|
|
1367
|
+
colour = "--mk-ink" if sym == "●" else (
|
|
1368
|
+
"--mk-accent-ink" if sym == "◐" else "--mk-faint")
|
|
1369
|
+
return box("mk-mx-c-%d-%d" % (i, j),
|
|
1370
|
+
{"display": "flex", "justifyContent": "center"},
|
|
1371
|
+
[T("p", sym, color={"token": colour}, fontSize="13px",
|
|
1372
|
+
fontFamily=MONO, lineHeight="1")])
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
MATRIX_SEC = section("matrix", [wrap("mk-mx-in", [
|
|
1376
|
+
box("mk-mx-pad", {"paddingTop": "78px", "paddingBottom": "78px"},
|
|
1377
|
+
_m={"paddingTop": "52px", "paddingBottom": "52px"},
|
|
1378
|
+
children=[
|
|
1379
|
+
box("mk-mx-head",
|
|
1380
|
+
{"display": "flex", "justifyContent": "space-between",
|
|
1381
|
+
"columnGap": "20px", "rowGap": "8px", "alignItems": "baseline",
|
|
1382
|
+
"customStyles": "flex-wrap:wrap;"},
|
|
1383
|
+
[mono("TABLE 01 — CAPABILITY", size="10px",
|
|
1384
|
+
color="--mk-accent-ink", track="0.22em"),
|
|
1385
|
+
mono("● 標準 ◐ 選配 ○ 不含", size="10px",
|
|
1386
|
+
color="--mk-faint", track="0.12em")]),
|
|
1387
|
+
# the column header, then one row per capability
|
|
1388
|
+
box("mk-mx-cols",
|
|
1389
|
+
{"display": "grid", "gridCols": "1fr repeat(3, 78px)",
|
|
1390
|
+
"columnGap": "10px", "alignItems": "end", "marginTop": "26px",
|
|
1391
|
+
"paddingBottom": "10px",
|
|
1392
|
+
"customStyles": "border-bottom:1px solid " + RULE_INK + ";"},
|
|
1393
|
+
_m={"gridCols": "1fr repeat(3, 46px)", "columnGap": "6px"},
|
|
1394
|
+
children=[mono("CAPABILITY", size="10px", color="--mk-faint",
|
|
1395
|
+
track="0.18em")]
|
|
1396
|
+
+ [box("mk-mx-h-%d" % j,
|
|
1397
|
+
{"display": "flex", "justifyContent": "center"},
|
|
1398
|
+
[mono(c, size="11px", color="--mk-ink", track="0.06em")])
|
|
1399
|
+
for j, c in enumerate(MATRIX_COLS)]),
|
|
1400
|
+
box("mk-mx-rows", {}, [
|
|
1401
|
+
box("mk-mx-r-%d" % i,
|
|
1402
|
+
{"display": "grid", "gridCols": "1fr repeat(3, 78px)",
|
|
1403
|
+
"columnGap": "10px", "alignItems": "center",
|
|
1404
|
+
"paddingTop": "13px", "paddingBottom": "13px",
|
|
1405
|
+
"customStyles": "border-bottom:1px solid " + RULE + ";"},
|
|
1406
|
+
_m={"gridCols": "1fr repeat(3, 46px)", "columnGap": "6px",
|
|
1407
|
+
"paddingTop": "11px", "paddingBottom": "11px"},
|
|
1408
|
+
children=[
|
|
1409
|
+
box("mk-mx-n-%d" % i,
|
|
1410
|
+
{"display": "flex", "columnGap": "12px",
|
|
1411
|
+
"alignItems": "baseline", "rowGap": "2px",
|
|
1412
|
+
"customStyles": "flex-wrap:wrap;"},
|
|
1413
|
+
[T("p", zh, color={"token": "--mk-ink"},
|
|
1414
|
+
fontSize="14px", _m={"fontSize": "12.5px"}),
|
|
1415
|
+
mono(en, size="9px", color="--mk-faint",
|
|
1416
|
+
track="0.16em", _m={"display": "none"})]),
|
|
1417
|
+
] + [mark(sym, i, j) for j, sym in enumerate(marks)])
|
|
1418
|
+
for i, (zh, en, *marks) in enumerate(MATRIX)
|
|
1419
|
+
]),
|
|
1420
|
+
]),
|
|
1421
|
+
])], bg="--mk-panel")
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
# ── FIGURE 01: what a workflow actually looks like ────────────────────────────
|
|
1425
|
+
# The page had no graphic on it at all - every section was type and rules, which is
|
|
1426
|
+
# austere for eleven screens. A diagram is the one element that says something no
|
|
1427
|
+
# sentence here can, and drawn in hairline boxes it belongs to the same document
|
|
1428
|
+
# 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
|
+
|
|
1435
|
+
|
|
1436
|
+
def flow_cell(attr, zh, en):
|
|
1437
|
+
return box(attr,
|
|
1438
|
+
{"paddingTop": "14px", "paddingBottom": "14px",
|
|
1439
|
+
"paddingLeft": "16px", "paddingRight": "16px",
|
|
1440
|
+
"customStyles": "border:1px solid " + RULE + ";"},
|
|
1441
|
+
[mono(en, size="9px", color="--mk-faint", track="0.18em"),
|
|
1442
|
+
T("p", zh, color={"token": "--mk-ink"}, fontSize="14px",
|
|
1443
|
+
marginTop="6px", _m={"fontSize": "13px"})])
|
|
1444
|
+
|
|
1445
|
+
|
|
1446
|
+
def arrow(attr):
|
|
1447
|
+
return box(attr, {"display": "flex", "justifyContent": "center",
|
|
1448
|
+
"alignItems": "center"},
|
|
1449
|
+
[T("p", "→", color={"token": "--mk-accent-ink"}, fontSize="15px",
|
|
1450
|
+
fontFamily=MONO)],
|
|
1451
|
+
_m={"customStyles": "transform:rotate(90deg);"})
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
FIGURE_SEC = section("figure", [wrap("mk-fig2-in", [
|
|
1455
|
+
box("mk-fig2-pad", {"paddingTop": "82px", "paddingBottom": "82px"},
|
|
1456
|
+
_m={"paddingTop": "54px", "paddingBottom": "54px"},
|
|
1457
|
+
children=[
|
|
1458
|
+
box("mk-fig2-head",
|
|
1459
|
+
{"display": "flex", "justifyContent": "space-between",
|
|
1460
|
+
"columnGap": "20px", "rowGap": "8px",
|
|
1461
|
+
"customStyles": "flex-wrap:wrap;"},
|
|
1462
|
+
[mono("FIGURE 01 — A WORKFLOW", size="10px",
|
|
1463
|
+
color="--mk-accent-ink", track="0.22em"),
|
|
1464
|
+
mono("INPUT → PROCESS → OUTPUT", size="10px",
|
|
1465
|
+
color="--mk-faint", track="0.18em")]),
|
|
1466
|
+
box("mk-fig2-grid",
|
|
1467
|
+
{"display": "grid", "gridCols": "1fr 44px 1fr 44px 1fr",
|
|
1468
|
+
"columnGap": "0px", "rowGap": "16px", "marginTop": "34px"},
|
|
1469
|
+
_t={"gridCols": "1fr 34px 1fr 34px 1fr"},
|
|
1470
|
+
_m={"gridCols": "repeat(1, 1fr)", "rowGap": "8px",
|
|
1471
|
+
"marginTop": "24px"},
|
|
1472
|
+
children=[
|
|
1473
|
+
node for i, row in enumerate(FLOW) for node in (
|
|
1474
|
+
flow_cell("mk-fl-a-%d" % i, row[0], row[1]),
|
|
1475
|
+
arrow("mk-ar-a-%d" % i),
|
|
1476
|
+
flow_cell("mk-fl-b-%d" % i, row[2], row[3]),
|
|
1477
|
+
arrow("mk-ar-b-%d" % i),
|
|
1478
|
+
flow_cell("mk-fl-c-%d" % i, row[4], row[5]),
|
|
1479
|
+
)
|
|
1480
|
+
]),
|
|
1481
|
+
T("p", "同一條線可以接任何一端:表單、訂單、訊息進來,"
|
|
1482
|
+
"判斷與轉換在中間,結果送到你本來就在用的系統裡。",
|
|
1483
|
+
color={"token": "--mk-muted"}, fontSize="13px", lineHeight="1.95",
|
|
1484
|
+
marginTop="26px", maxWidth="42em", _m={"fontSize": "12.5px"}),
|
|
1485
|
+
]),
|
|
1486
|
+
])])
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
# ── PLATE 01: a statement ─────────────────────────────────────────────────────
|
|
1490
|
+
# Every clause on this page has the same rhythm - a numbered head, then hairline
|
|
1491
|
+
# rows. That is correct for a specification and monotonous for a page. A document
|
|
1492
|
+
# also has PLATES: unnumbered, full-bleed, one idea at a scale nothing else gets.
|
|
1493
|
+
# This one changes the ground, the measure and the type size all at once, so the
|
|
1494
|
+
# reader feels a chapter break rather than another row.
|
|
1495
|
+
PLATE = section("plate", [wrap("mk-plate-in", [
|
|
1496
|
+
box("mk-plate-pad",
|
|
1497
|
+
{"paddingTop": "104px", "paddingBottom": "104px",
|
|
1498
|
+
"display": "grid", "gridCols": "auto 1fr", "columnGap": "56px",
|
|
1499
|
+
"alignItems": "start"},
|
|
1500
|
+
_t={"columnGap": "36px"},
|
|
1501
|
+
_m={"gridCols": "repeat(1, 1fr)", "rowGap": "22px",
|
|
1502
|
+
"paddingTop": "62px", "paddingBottom": "62px"},
|
|
1503
|
+
children=[
|
|
1504
|
+
# the plate number, set as a hollow numeral - the one place on the page
|
|
1505
|
+
# where type is used as a shape rather than as reading matter
|
|
1506
|
+
box("mk-plate-num", {},
|
|
1507
|
+
[T("p", "01", fontFamily=DISPLAY, fontSize="132px",
|
|
1508
|
+
fontWeight="600", lineHeight=".82", letterSpacing="-0.05em",
|
|
1509
|
+
color="rgba(0,0,0,0)",
|
|
1510
|
+
customStyles="-webkit-text-stroke:1px " + RULE_INK + ";",
|
|
1511
|
+
_t={"fontSize": "104px"}, _m={"fontSize": "68px"})]),
|
|
1512
|
+
box("mk-plate-t", {}, [
|
|
1513
|
+
mono("PLATE 01 — POSITION", size="10px", color="--mk-accent-ink",
|
|
1514
|
+
track="0.22em"),
|
|
1515
|
+
ml("h2", "技術是拿來解決問題的,\n不是拿來炫耀的。",
|
|
1516
|
+
color={"token": "--mk-ink"}, fontSize="46px", fontWeight="600",
|
|
1517
|
+
letterSpacing="0.01em", lineHeight="1.42", fontFamily=DISPLAY,
|
|
1518
|
+
marginTop="22px",
|
|
1519
|
+
_t={"fontSize": "38px"}, _m={"fontSize": "26px"}),
|
|
1520
|
+
T("p", "每一個決定都要能被說明:為什麼用這個工具、"
|
|
1521
|
+
"為什麼是這個結構、為什麼值這個價錢。說不清楚的,就不做。",
|
|
1522
|
+
color={"token": "--mk-muted"}, fontSize="15px", lineHeight="2",
|
|
1523
|
+
marginTop="26px", maxWidth="34em", _m={"fontSize": "13.5px"}),
|
|
1524
|
+
box("mk-plate-sig",
|
|
1525
|
+
{"marginTop": "34px", "display": "flex", "columnGap": "12px",
|
|
1526
|
+
"alignItems": "center"},
|
|
1527
|
+
[box("mk-plate-dash", {}, []),
|
|
1528
|
+
mono("MOKSA WEB — TAICHUNG", size="10px", color="--mk-faint",
|
|
1529
|
+
track="0.2em")]),
|
|
1530
|
+
]),
|
|
1531
|
+
]),
|
|
1532
|
+
])], bg="--mk-panel")
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
# ── the type specimen ─────────────────────────────────────────────────────────
|
|
1536
|
+
# A studio that sets type should be willing to show the type. This is the system
|
|
1537
|
+
# itself on display - the scale it uses, the faces it pairs, the numerals it relies
|
|
1538
|
+
# on - inverted onto the ink ground so it reads as a plate rather than a section.
|
|
1539
|
+
SPECIMEN_ROWS = [
|
|
1540
|
+
("Aa", "72 / SPACE GROTESK 600", "72px", "54px", "40px", DISPLAY, "600"),
|
|
1541
|
+
("網站開發", "46 / NOTO SANS TC 500", "46px", "38px", "27px", CJK, "500"),
|
|
1542
|
+
("0123456789", "34 / IBM PLEX MONO 500", "34px", "27px", "20px", MONO, "500"),
|
|
1543
|
+
("automation", "22 / SPACE GROTESK 500", "22px", "19px", "16px", DISPLAY,
|
|
1544
|
+
"500"),
|
|
1545
|
+
]
|
|
1546
|
+
|
|
1547
|
+
SPECIMEN = section("specimen", [wrap("mk-spec2-in", [
|
|
1548
|
+
box("mk-spec2-pad", {"paddingTop": "88px", "paddingBottom": "88px"},
|
|
1549
|
+
_m={"paddingTop": "58px", "paddingBottom": "58px"},
|
|
1550
|
+
children=[
|
|
1551
|
+
box("mk-spec2-head",
|
|
1552
|
+
{"display": "flex", "justifyContent": "space-between",
|
|
1553
|
+
"columnGap": "20px", "rowGap": "8px",
|
|
1554
|
+
"customStyles": "flex-wrap:wrap;"},
|
|
1555
|
+
[mono("TYPE SPECIMEN", size="10px", color="--mk-accent",
|
|
1556
|
+
track="0.22em"),
|
|
1557
|
+
mono("THREE FACES / ONE SCALE", size="10px",
|
|
1558
|
+
color="rgb(140,142,150)", track="0.2em")]),
|
|
1559
|
+
box("mk-spec2-rows", {"marginTop": "40px"},
|
|
1560
|
+
_m={"marginTop": "26px"},
|
|
1561
|
+
children=[
|
|
1562
|
+
box("mk-spec2-r-%d" % i,
|
|
1563
|
+
{"display": "grid", "gridCols": "1fr auto",
|
|
1564
|
+
"columnGap": "28px", "alignItems": "baseline",
|
|
1565
|
+
"paddingTop": "20px", "paddingBottom": "20px",
|
|
1566
|
+
"customStyles":
|
|
1567
|
+
"border-top:1px solid " + RULE_DARK + ";"},
|
|
1568
|
+
_m={"gridCols": "1fr", "rowGap": "6px",
|
|
1569
|
+
"paddingTop": "14px", "paddingBottom": "14px"},
|
|
1570
|
+
children=[
|
|
1571
|
+
T("p", glyphs, color={"token": "--mk-paper"},
|
|
1572
|
+
fontSize=size, fontFamily=face, fontWeight=weight,
|
|
1573
|
+
lineHeight="1.1", letterSpacing="0.01em",
|
|
1574
|
+
_t={"fontSize": tsize}, _m={"fontSize": msize}),
|
|
1575
|
+
mono(label, size="10px", color="rgb(140,142,150)",
|
|
1576
|
+
track="0.16em"),
|
|
1577
|
+
])
|
|
1578
|
+
for i, (glyphs, label, size, tsize, msize, face, weight)
|
|
1579
|
+
in enumerate(SPECIMEN_ROWS)
|
|
1580
|
+
]),
|
|
1581
|
+
]),
|
|
1582
|
+
])], bg="--mk-ink")
|
|
1583
|
+
|
|
1584
|
+
|
|
1264
1585
|
# ── the entrance sequence ─────────────────────────────────────────────────────
|
|
1265
1586
|
# A boot screen, because the page is a specification document and this is what one
|
|
1266
1587
|
# looks like while it is being read off a machine. It is CSS only: a counter that is
|
|
@@ -1272,6 +1593,11 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1272
1593
|
# of the page's life, which is the only window in which any of this exists.
|
|
1273
1594
|
BOOT = box("mk-boot", {}, [
|
|
1274
1595
|
box("mk-boot-scan", {}, []),
|
|
1596
|
+
# trim marks on the plate itself, drawn corner by corner
|
|
1597
|
+
*[box("mk-boot-x%d" % i, {}, []) for i in range(4)],
|
|
1598
|
+
# a ruler up the left margin: the veil is a measured object, not a splash
|
|
1599
|
+
box("mk-boot-ruler", {}, [box("mk-boot-t%d" % i, {}, [])
|
|
1600
|
+
for i in range(RULER_TICKS)]),
|
|
1275
1601
|
box("mk-boot-head", {}, [
|
|
1276
1602
|
mono("MOKSA WEB — STUDIO PROFILE", size="10px", color="--mk-faint",
|
|
1277
1603
|
track="0.2em"),
|
|
@@ -1282,6 +1608,13 @@ BOOT = box("mk-boot", {}, [
|
|
|
1282
1608
|
box("mk-boot-pct", {}, [T("p", "%")]),
|
|
1283
1609
|
]),
|
|
1284
1610
|
box("mk-boot-track", {}, [box("mk-boot-fill", {}, [])]),
|
|
1611
|
+
# the log writes itself out a line at a time, with a cursor still blinking
|
|
1612
|
+
box("mk-boot-log", {},
|
|
1613
|
+
[box("mk-boot-l%d" % i, {}, [mono(line, size="10px", color="--mk-faint",
|
|
1614
|
+
track="0.16em")])
|
|
1615
|
+
for i, line in enumerate(BOOT_LOG)]
|
|
1616
|
+
+ [box("mk-boot-l%d" % len(BOOT_LOG), {},
|
|
1617
|
+
[box("mk-boot-cursor", {}, [])])]),
|
|
1285
1618
|
box("mk-boot-list", {},
|
|
1286
1619
|
# each clause gets its own box because the id is what the stagger
|
|
1287
1620
|
# targets, and `mono()` puts everything it is given into the style
|
|
@@ -1289,6 +1622,7 @@ BOOT = box("mk-boot", {}, [
|
|
|
1289
1622
|
[mono("§" + num + " " + name, size="10px", color="--mk-faint",
|
|
1290
1623
|
track="0.18em")])
|
|
1291
1624
|
for i, (num, name, _h, _t) in enumerate(CLAUSES)]),
|
|
1625
|
+
box("mk-boot-ready", {}, [T("p", "PROFILE READY")]),
|
|
1292
1626
|
box("mk-boot-foot", {}, [
|
|
1293
1627
|
mono("INITIALISING", size="10px", color="--mk-faint", track="0.2em"),
|
|
1294
1628
|
mono("TAICHUNG, TW", size="10px", color="--mk-faint", track="0.2em"),
|
|
@@ -1324,8 +1658,11 @@ CUE = box("mk-cue", {}, [
|
|
|
1324
1658
|
HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
|
|
1325
1659
|
"children": [BOOT, box("mk-doc", {}, [
|
|
1326
1660
|
MARKS, INDEX, CUE,
|
|
1327
|
-
|
|
1328
|
-
|
|
1661
|
+
# paper, panel, paper, ink, paper, ink, paper - the page
|
|
1662
|
+
# changes ground five times so it reads as chapters
|
|
1663
|
+
MASTHEAD, TICKER_BAND, PLATE, SERVICE_SEC, MATRIX_SEC,
|
|
1664
|
+
PROCESS_SEC, FIGURE_SEC, WORK_SEC, SPECIMEN, STACK_SEC,
|
|
1665
|
+
PRODUCT_SEC, VOICE_SEC, CONTACT,
|
|
1329
1666
|
])]}
|
|
1330
1667
|
|
|
1331
1668
|
SITE = {
|