mosaic-headless 1.20.0 → 1.20.1

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.
@@ -2,16 +2,17 @@
2
2
  /**
3
3
  * Write package.json's version into every other place that carries one.
4
4
  *
5
- * node bin/sync-version.mjs
5
+ * node scripts/sync-version.mjs
6
6
  *
7
7
  * Runs from the `version` npm lifecycle script, after npm has bumped package.json
8
8
  * and before it makes the commit and the tag - so the tagged commit already has all
9
- * three versions agreeing, and `npm version patch` is the only command anyone has to
9
+ * four versions agreeing, and `npm version patch` is the only command anyone has to
10
10
  * remember.
11
11
  *
12
12
  * Targets:
13
13
  * SKILL.md frontmatter `version:`
14
14
  * assets/templates/platforms/*.json frontmatter.version, where present
15
+ * .claude-plugin/plugin.json version (the Claude plugin directory reads it)
15
16
  *
16
17
  * check-release.mjs asserts afterwards that this worked, so a silent miss here
17
18
  * cannot reach npm.
@@ -60,4 +61,15 @@ for (const f of fs.readdirSync(platDir).filter((x) => x.endsWith(".json"))) {
60
61
  }
61
62
  }
62
63
 
64
+ // Claude plugin manifest - the directory listing and Claude Code's version pinning read it
65
+ const manPath = path.join(ROOT, ".claude-plugin", "plugin.json");
66
+ if (fs.existsSync(manPath)) {
67
+ const man = JSON.parse(fs.readFileSync(manPath, "utf8"));
68
+ if (man.version !== v) {
69
+ man.version = v;
70
+ fs.writeFileSync(manPath, JSON.stringify(man, null, 2) + "\n");
71
+ touched.push(".claude-plugin/plugin.json");
72
+ }
73
+ }
74
+
63
75
  console.log(`synced v${v} into ${touched.length} file(s): ${touched.join(", ") || "none"}`);
package/sites/_moksa.py CHANGED
@@ -26,6 +26,7 @@ Run from this directory: python _moksa.py
26
26
  """
27
27
  import json
28
28
  import os
29
+ import uuid
29
30
 
30
31
  # Monospace leads. The grotesque is the display face and appears at three sizes only;
31
32
  # Noto Sans TC carries running Chinese text.
@@ -748,6 +749,24 @@ def motion_css():
748
749
  "#mk-mark-3{bottom:20px;right:40px;border-right:1px solid " + RULE_INK
749
750
  + ";border-bottom:1px solid " + RULE_INK + "}",
750
751
  "@media (max-width:1279px){" + marks + "{display:none}}",
752
+ # the colophon card's own trim marks, inside it rather than fixed to the
753
+ # viewport - the card is a leaf of the same document
754
+ "#mk-colophon-mark-tl,#mk-colophon-mark-br{position:absolute;"
755
+ "width:11px;height:11px;pointer-events:none}",
756
+ "#mk-colophon-mark-tl{top:9px;left:9px;border-left:1px solid " + RULE_INK
757
+ + ";border-top:1px solid " + RULE_INK + "}",
758
+ "#mk-colophon-mark-br{bottom:9px;right:9px;border-right:1px solid " + RULE_INK
759
+ + ";border-bottom:1px solid " + RULE_INK + "}",
760
+ # the dialog itself: Mosaic lays the host out as a centred grid, so the
761
+ # card only has to say how wide it is. The entrance is the card's, not the
762
+ # veil's - a 1px rule sliding up reads as a page being laid down.
763
+ "#mk-colophon[open] #mk-colophon-card{animation:mk-colophon-in .42s "
764
+ "cubic-bezier(.2,.7,.3,1) both}",
765
+ "@keyframes mk-colophon-in{from{opacity:0;transform:translateY(14px)}"
766
+ "to{opacity:1;transform:none}}",
767
+ "@media (prefers-reduced-motion:reduce){"
768
+ "#mk-colophon[open] #mk-colophon-card{animation:none}}",
769
+
751
770
 
752
771
  # the clause index. It needs room outside the document margin, so it only
753
772
  # appears once the viewport is wide enough to have that room.
@@ -2526,11 +2545,155 @@ INDEX = box("mk-index", {}, [
2526
2545
  for i, (num, name, href, _tl) in enumerate(CLAUSES)
2527
2546
  ])
2528
2547
 
2548
+ # The colophon modal's own node id. A modalClose action targets a NODE id, so the
2549
+ # button inside the modal has to know it before either row is written - which is
2550
+ # why it is a constant here rather than something build_page mints.
2551
+ #
2552
+ # Minted fresh on every generation, never hard-coded: wp_mosaic_nodes' primary key
2553
+ # is (ID, themeID), so a fixed id collides with the row the PREVIOUS build of this
2554
+ # same page left in the same theme, and the commit dies on a duplicate key.
2555
+ COLOPHON_NODE_ID = str(uuid.uuid4())
2556
+
2529
2557
  CUE = box("mk-cue", {}, [
2530
2558
  mono("SCROLL", size="10px", color="--mk-faint", track="0.22em"),
2531
2559
  box("mk-cue-rail", {}, []),
2532
2560
  ])
2533
2561
 
2562
+
2563
+ # ── §9 the colophon modal ─────────────────────────────────────────────────────
2564
+ # Mosaic 1.0.9's dialog, in this document's voice rather than a popup's: paper
2565
+ # ground, hairline border, trim marks at two corners, mono label, no radius, the
2566
+ # accent on one action only. It is the last page of a printed document - the
2567
+ # colophon - shown to a reader who is about to leave.
2568
+ #
2569
+ # Nothing on the page points at it. The modal names its own trigger in
2570
+ # `interactionShorthand`, which Mosaic expands at render into a real interaction
2571
+ # carrying the element's own `modalOpen` - the one element type that offers the
2572
+ # shorthand is the modal, because "open this when ..." is the only thing it would
2573
+ # ever mean. The cap rule beside it is what keeps the thing civil: once a session,
2574
+ # remembered under a name of its own, and `exitIntent` never fires on a touch
2575
+ # device at all, so a phone reader is never shown it.
2576
+ #
2577
+ # `closedby: "everything"` means Esc and a click on the overlay both close it; the
2578
+ # overlay child is also what makes Mosaic emit `aria-modal="true"`, because the
2579
+ # overlay is what actually blocks the page. The close control is a real button so
2580
+ # it is in the tab order, and the whole thing is reachable and dismissable from
2581
+ # the keyboard alone - measured, not assumed (tools/verify_dialog.py).
2582
+ COLOPHON = {
2583
+ "type": "modal",
2584
+ "data": {
2585
+ "attrID": "mk-colophon",
2586
+ "accessibleLabel": "開始一個專案",
2587
+ "closedby": "everything",
2588
+ "interactionShorthand": {
2589
+ "type": "exitIntent",
2590
+ "exitIntentOptions": {
2591
+ "settings": {"awayFor": "500ms"},
2592
+ "runs": {"rules": [{
2593
+ "uuid": "6f1d3a92-5c84-4e71-9b20-7d8a1f4c2e55",
2594
+ "type": "cap",
2595
+ "capOptions": {"max": "1", "remember": "session",
2596
+ "name": "mk-colophon"}}]}}},
2597
+ },
2598
+ "style": bp({"paddingLeft": "40px", "paddingRight": "40px"},
2599
+ None, {"paddingLeft": "18px", "paddingRight": "18px"}),
2600
+ "children": [
2601
+ {"type": "modal-overlay", "data": {"attrID": "mk-colophon-veil"},
2602
+ "style": {"&": {"_": {"backgroundColor": "rgba(22,24,28,.62)"}}}},
2603
+ # a modal-window, not a plain div: the window is what Mosaic scrolls and
2604
+ # focus-traps, and a modal without one is a dialog with loose children
2605
+ dict(box("mk-colophon-card",
2606
+ {"backgroundColor": {"token": "--mk-paper"},
2607
+ "maxWidth": "520px", "width": "100%",
2608
+ "flexDirection": "column", "rowGap": "0px",
2609
+ "position": "relative",
2610
+ "paddingTop": "34px", "paddingBottom": "30px",
2611
+ "paddingLeft": "34px", "paddingRight": "34px",
2612
+ "customDeclarations": "border:1px solid rgb(22,24,28);"},
2613
+ [
2614
+ # the same trim marks the document uses, so the card reads as a
2615
+ # leaf of it rather than something the site threw at the reader
2616
+ box("mk-colophon-mark-tl", {}, []),
2617
+ box("mk-colophon-mark-br", {}, []),
2618
+ box("mk-colophon-head",
2619
+ {"display": "flex", "justifyContent": "space-between",
2620
+ "alignItems": "baseline", "columnGap": "16px",
2621
+ "paddingBottom": "14px",
2622
+ "customDeclarations": "border-bottom:1px solid " + RULE + ";"},
2623
+ [mono("COLOPHON", size="10px", color="--mk-faint", track="0.26em"),
2624
+ mono("09 / 離開前", size="10px", color="--mk-faint", track="0.18em")]),
2625
+ ml("h2", "還沒找到\n要的東西?",
2626
+ fontFamily=DISPLAY, fontWeight="600", fontSize="34px",
2627
+ # positive, not tight: a Han glyph sits on a full em body and is
2628
+ # already as close as it is meant to be. The design audit fails a
2629
+ # CJK run below -0.15px, and caught this line at -0.68px.
2630
+ lineHeight="1.12", letterSpacing="0.005em",
2631
+ color={"token": "--mk-ink"}, marginTop="20px",
2632
+ _m={"fontSize": "28px"}),
2633
+ T("p", "把想做的事寫三行給我,兩個工作天內會收到一份能直接討論的提案——"
2634
+ "範圍、做法、時間,不是報價單。",
2635
+ fontFamily=CJK, fontSize="14px", lineHeight="1.85",
2636
+ color={"token": "--mk-muted"}, marginTop="14px"),
2637
+ box("mk-colophon-cta",
2638
+ {"display": "flex", "columnGap": "14px", "rowGap": "10px",
2639
+ "marginTop": "26px", "flexWrap": "wrap",
2640
+ "alignItems": "center"},
2641
+ [
2642
+ {"type": "button",
2643
+ "data": {"attrID": "mk-colophon-go", "url": "#contact"},
2644
+ "style": {"&": {"_": {
2645
+ "backgroundColor": {"token": "--mk-accent"},
2646
+ "color": {"token": "--mk-ink"},
2647
+ "fontFamily": MONO, "fontSize": "11px",
2648
+ "letterSpacing": "0.18em", "fontWeight": "500",
2649
+ "paddingLeft": "22px", "paddingRight": "22px",
2650
+ "paddingTop": "13px", "paddingBottom": "13px",
2651
+ "customDeclarations": "border:1px solid rgb(255,90,54);"}},
2652
+ "hover": {"_": {"backgroundColor": {"token": "--mk-ink"},
2653
+ "color": {"token": "--mk-paper"},
2654
+ "customDeclarations":
2655
+ "border:1px solid rgb(22,24,28);"}},
2656
+ "focus-visible": {"_": dict(FOCUS_RING)}},
2657
+ "children": [{"type": "wysiwyg-text",
2658
+ "data": {"text": "START A PROJECT"}}]},
2659
+ # closes the dialog and nothing else: a modalClose action
2660
+ # aimed at the modal's own node id
2661
+ {"type": "button",
2662
+ "data": {"attrID": "mk-colophon-stay",
2663
+ "interactions": [{
2664
+ "type": "click",
2665
+ "uuid": "8b2e5d14-9af3-4c60-8e71-3c5d9b07a4f2",
2666
+ "clickOptions": {
2667
+ "name": "close", "ID":
2668
+ "c41a7e69-0b52-4d38-9f84-61e0ad3b5c77",
2669
+ "actionSlots": {"click": {"actions": [{
2670
+ "type": "modalClose",
2671
+ "uuid": "2d8f4b31-7c06-4e95-a3b2-58d1f9e60c84",
2672
+ "settings": {"target": {
2673
+ "type": "element",
2674
+ "uuid": COLOPHON_NODE_ID}}}]}}}}]},
2675
+ "style": {"&": {"_": {
2676
+ "backgroundColor": "rgba(0,0,0,0)",
2677
+ "color": {"token": "--mk-muted"},
2678
+ "fontFamily": MONO, "fontSize": "11px",
2679
+ "letterSpacing": "0.18em", "fontWeight": "400",
2680
+ "paddingLeft": "4px", "paddingRight": "4px",
2681
+ "paddingTop": "13px", "paddingBottom": "13px",
2682
+ "cursor": "pointer"}},
2683
+ "hover": {"_": {"color": {"token": "--mk-ink"}}},
2684
+ "focus-visible": {"_": dict(FOCUS_RING)}},
2685
+ "children": [{"type": "wysiwyg-text",
2686
+ "data": {"text": "繼續閱讀"}}]},
2687
+ ]),
2688
+ ]), type="modal-window"),
2689
+ ],
2690
+ # The node id is pinned because the close button has to name it, and both are
2691
+ # written in the same commit - there is no second pass in which to learn it.
2692
+ # Unique per THEME, not per document: wp_mosaic_nodes' key is (ID, themeID).
2693
+ "nodeID": COLOPHON_NODE_ID,
2694
+ }
2695
+
2696
+
2534
2697
  HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
2535
2698
  "children": [BOOT, box("mk-doc", {}, [
2536
2699
  MARKS, INDEX, CUE, LOOP_WINDOW, box("mk-sweep", {}, []),
@@ -2538,7 +2701,7 @@ HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
2538
2701
  # changes ground five times so it reads as chapters
2539
2702
  MASTHEAD, TICKER_BAND, PLATE, SERVICE_SEC, MATRIX_SEC,
2540
2703
  PROCESS_SEC, FIGURE_SEC, WORK_SEC, PAN, SPECIMEN, STACK_SEC,
2541
- BOARD, PRODUCT_SEC, VOICE_SEC, CONTACT,
2704
+ BOARD, PRODUCT_SEC, VOICE_SEC, CONTACT, COLOPHON,
2542
2705
  ])]}
2543
2706
 
2544
2707