mosaic-headless 1.3.0 → 1.5.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 +5 -1
- package/README.ko.md +5 -1
- package/README.md +5 -1
- package/README.zh-TW.md +5 -1
- package/SKILL.md +50 -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 +15 -1
- package/data/browser-verification.csv +45 -0
- package/data/interaction-verification.csv +8 -0
- package/data/intro-verification.csv +20 -0
- package/data/style-state-verification.csv +53 -0
- package/package.json +1 -1
- package/references/interactions.md +53 -8
- package/sites/_moksa.py +148 -13
- package/sites/moksa.json +528 -6
- package/tools/mo.py +35 -6
- package/tools/sweep_interactions.py +352 -0
- package/tools/sweep_style_states.py +225 -0
- package/tools/verify_intro.py +324 -0
|
@@ -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
|
@@ -72,10 +72,22 @@ def T(tag, text, _t=None, _m=None, **st):
|
|
|
72
72
|
"style": bp(st, _t, _m) if (st or _t or _m) else None}
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
|
|
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):
|
|
76
86
|
s = bp(style, _t, _m) or {"&": {"_": {}}}
|
|
77
87
|
if hover:
|
|
78
88
|
s["hover"] = {"_": hover}
|
|
89
|
+
if focus:
|
|
90
|
+
s["focus-visible"] = {"_": focus}
|
|
79
91
|
return {"type": "div", "data": {"attrID": attr}, "style": s, "children": children}
|
|
80
92
|
|
|
81
93
|
|
|
@@ -364,6 +376,10 @@ def motion_css():
|
|
|
364
376
|
for i, (_g, items) in enumerate(STACK)
|
|
365
377
|
for j in range(len(items)))
|
|
366
378
|
marks = ",".join("#mk-mark-%d" % i for i in range(4))
|
|
379
|
+
# the boot list lights one clause at a time
|
|
380
|
+
boot_stagger = "\n".join(
|
|
381
|
+
" #mk-boot-c%d{animation-delay:%dms}" % (i, 260 + i * 120)
|
|
382
|
+
for i in range(len(CLAUSES)))
|
|
367
383
|
# a named view timeline per section, declared on the section and consumed by its
|
|
368
384
|
# index entry - `timeline-scope` on #mk-doc is what lets the name cross between
|
|
369
385
|
# two elements that are not ancestor and descendant
|
|
@@ -516,21 +532,94 @@ def motion_css():
|
|
|
516
532
|
"@media (max-width:1079px){#mk-cue{right:28px}}",
|
|
517
533
|
"@media (max-width:767px){#mk-cue{right:18px;bottom:16px}}",
|
|
518
534
|
|
|
535
|
+
# ── the entrance sequence ────────────────────────────────────────────
|
|
536
|
+
# A page-load animation has one catastrophic failure mode: an overlay that
|
|
537
|
+
# covers the document and never leaves. So the veil is `display:none` in the
|
|
538
|
+
# BASE rule and is only switched on inside the motion query, alongside the
|
|
539
|
+
# animation that removes it. If motion is reduced, or the query never matches,
|
|
540
|
+
# or the stylesheet is truncated, the overlay does not exist at all - the
|
|
541
|
+
# failure direction is "no intro", never "no page".
|
|
542
|
+
"#mk-boot{display:none}",
|
|
543
|
+
# An integer that can be animated, and read back out of the element - which
|
|
544
|
+
# is what makes the counter verifiable rather than merely visible.
|
|
545
|
+
# `inherits:true` is load-bearing, and the reason is not obvious. The
|
|
546
|
+
# animation runs on #mk-boot-num, but the digits are drawn by its ::after
|
|
547
|
+
# through `counter(n)`. With `inherits:false` the pseudo-element never sees
|
|
548
|
+
# the animated value: measured mid-run, --mk-n was 18 on the element and 0
|
|
549
|
+
# on its ::after, so the counter sat at 0 for the whole sequence while the
|
|
550
|
+
# property underneath it animated perfectly.
|
|
551
|
+
"@property --mk-n{syntax:'<integer>';initial-value:0;inherits:true}",
|
|
552
|
+
"@keyframes mk-count{to{--mk-n:100}}",
|
|
553
|
+
"@keyframes mk-boot-out{"
|
|
554
|
+
"0%{clip-path:inset(0 0 0 0)}"
|
|
555
|
+
"100%{clip-path:inset(0 0 100% 0);visibility:hidden}}",
|
|
556
|
+
"@keyframes mk-boot-fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}",
|
|
557
|
+
"@keyframes mk-boot-line{from{opacity:.14}"
|
|
558
|
+
"60%{opacity:1;color:rgb(255,90,54)}to{opacity:.55;color:rgb(107,109,113)}}",
|
|
559
|
+
"@keyframes mk-boot-scan{from{transform:translateY(0)}"
|
|
560
|
+
"to{transform:translateY(100vh)}}",
|
|
561
|
+
"@keyframes mk-boot-fade{from{opacity:0}to{opacity:1}}",
|
|
562
|
+
|
|
519
563
|
# ── motion, all of it opt-out-able ───────────────────────────────────
|
|
520
564
|
"@media (prefers-reduced-motion:no-preference){",
|
|
565
|
+
" #mk-boot{display:grid;position:fixed;inset:0;z-index:999;"
|
|
566
|
+
"background:rgb(250,250,247);align-content:center;justify-items:center;"
|
|
567
|
+
"row-gap:22px;overflow:hidden;"
|
|
568
|
+
"animation:mk-boot-out .5s cubic-bezier(.7,0,.3,1) 1.55s forwards}",
|
|
569
|
+
" #mk-boot-head,#mk-boot-foot{position:absolute;left:40px;right:40px;"
|
|
570
|
+
"display:flex;justify-content:space-between;font-family:" + MONO + ";"
|
|
571
|
+
"font-size:10px;letter-spacing:.2em;color:rgb(107,109,113);"
|
|
572
|
+
"animation:mk-boot-fade .4s ease both}",
|
|
573
|
+
" #mk-boot-head{top:34px}",
|
|
574
|
+
" #mk-boot-foot{bottom:30px;animation-delay:.1s}",
|
|
575
|
+
" #mk-boot-num{font-family:" + MONO + ";font-size:96px;font-weight:500;"
|
|
576
|
+
"line-height:1;letter-spacing:-.04em;color:rgb(22,24,28);"
|
|
577
|
+
"font-variant-numeric:tabular-nums;min-width:3ch;text-align:right;"
|
|
578
|
+
"animation:mk-count 1.2s cubic-bezier(.3,0,0,1) .12s both}",
|
|
579
|
+
' #mk-boot-num::after{counter-reset:n var(--mk-n);content:counter(n)}',
|
|
580
|
+
" #mk-boot-row{display:flex;align-items:flex-end;column-gap:8px;"
|
|
581
|
+
"line-height:1}",
|
|
582
|
+
# the darkened cut, not the fill orange: 18px of rgb(255,90,54) on paper is
|
|
583
|
+
# 2.97:1 and the design audit refuses it. The progress rule beside it keeps
|
|
584
|
+
# the bright colour, because a 1px hairline is not text.
|
|
585
|
+
" #mk-boot-pct{font-family:" + MONO + ";font-size:18px;"
|
|
586
|
+
"color:rgb(191,68,40);padding-bottom:12px}",
|
|
587
|
+
" #mk-boot-track{width:min(420px,62vw);height:1px;"
|
|
588
|
+
"background:rgba(22,24,28,.14);position:relative}",
|
|
589
|
+
" #mk-boot-fill{position:absolute;inset:0;background:rgb(255,90,54);"
|
|
590
|
+
"transform-origin:0 50%;"
|
|
591
|
+
"animation:mk-boot-fill 1.2s cubic-bezier(.3,0,0,1) .12s both}",
|
|
592
|
+
" #mk-boot-list{display:flex;column-gap:18px;row-gap:8px;"
|
|
593
|
+
"flex-wrap:wrap;justify-content:center;font-family:" + MONO + ";"
|
|
594
|
+
"font-size:10px;letter-spacing:.18em}",
|
|
595
|
+
" #mk-boot-list>*{opacity:.14;"
|
|
596
|
+
"animation:mk-boot-line .5s ease both}",
|
|
597
|
+
boot_stagger,
|
|
598
|
+
" #mk-boot::before{content:\"\";position:absolute;inset:0;"
|
|
599
|
+
"background:"
|
|
600
|
+
"repeating-linear-gradient(90deg,rgba(22,24,28,.045) 0 1px,"
|
|
601
|
+
"transparent 1px 72px),"
|
|
602
|
+
"repeating-linear-gradient(0deg,rgba(22,24,28,.045) 0 1px,"
|
|
603
|
+
"transparent 1px 72px);"
|
|
604
|
+
"animation:mk-boot-fade .5s ease both}",
|
|
605
|
+
" #mk-boot-scan{position:absolute;left:0;right:0;top:0;height:1px;"
|
|
606
|
+
"background:linear-gradient(90deg,rgba(255,90,54,0),rgba(255,90,54,.55),"
|
|
607
|
+
"rgba(255,90,54,0));animation:mk-boot-scan 1.6s linear .1s both}",
|
|
608
|
+
" @media (max-width:767px){#mk-boot-num{font-size:62px}"
|
|
609
|
+
"#mk-boot-head,#mk-boot-foot{left:18px;right:18px}}",
|
|
521
610
|
" #mk-hl1-mask>*,#mk-hl2-mask>*{transform:translateY(112%);"
|
|
522
611
|
"animation:mk-linein .95s cubic-bezier(.16,1,.3,1) forwards}",
|
|
523
|
-
" #mk-hl1-mask>*{animation-delay
|
|
524
|
-
" #mk-hl2-mask>*{animation-delay
|
|
612
|
+
" #mk-hl1-mask>*{animation-delay:1.72s}",
|
|
613
|
+
" #mk-hl2-mask>*{animation-delay:1.84s}",
|
|
525
614
|
" #mk-mast-meta,#mk-mast-lede,#mk-mast-cta{opacity:0;"
|
|
526
615
|
"animation:mk-softin .8s cubic-bezier(.16,1,.3,1) forwards}",
|
|
527
|
-
" #mk-mast-meta{animation-delay
|
|
528
|
-
" #mk-mast-lede{animation-delay
|
|
529
|
-
" #mk-mast-cta{animation-delay
|
|
616
|
+
" #mk-mast-meta{animation-delay:1.62s}",
|
|
617
|
+
" #mk-mast-lede{animation-delay:2.10s}",
|
|
618
|
+
" #mk-mast-cta{animation-delay:2.22s}",
|
|
530
619
|
" #mk-caret{animation:mk-caret 1.15s steps(1,end) infinite;"
|
|
531
|
-
"animation-delay
|
|
620
|
+
"animation-delay:2.4s}",
|
|
532
621
|
" #mk-mast-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
|
|
533
|
-
"animation-delay
|
|
622
|
+
"animation-delay:1.96s}",
|
|
534
623
|
" #mk-spec-rule{animation:mk-drawx .9s cubic-bezier(.2,.7,.3,1) forwards;"
|
|
535
624
|
"animation-delay:.9s}",
|
|
536
625
|
" #mk-cue{opacity:0;animation:mk-softin .8s ease forwards;"
|
|
@@ -632,7 +721,8 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
632
721
|
"transitionAll": "160ms ease",
|
|
633
722
|
"cursor": "pointer"},
|
|
634
723
|
"_m": {"fontSize": "12px"}},
|
|
635
|
-
"hover": {"_": {"color": {"token": "--mk-accent"}}}
|
|
724
|
+
"hover": {"_": {"color": {"token": "--mk-accent"}}},
|
|
725
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
636
726
|
"text": text}
|
|
637
727
|
for i, (text, href) in enumerate(NAV)
|
|
638
728
|
]},
|
|
@@ -656,7 +746,8 @@ HEADER = box("mk-shell-top", {}, [
|
|
|
656
746
|
"color": "rgb(255,90,54)"},
|
|
657
747
|
"transitionAll": "180ms ease"}},
|
|
658
748
|
"hover": {"_": {"backgroundColor": {"token": "--mk-accent"},
|
|
659
|
-
"color": {"token": "--mk-ink"}}}
|
|
749
|
+
"color": {"token": "--mk-ink"}}},
|
|
750
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
660
751
|
"text": "START A PROJECT"},
|
|
661
752
|
]}
|
|
662
753
|
])]),
|
|
@@ -790,6 +881,7 @@ MASTHEAD = section("mk-mast", [wrap("mk-mast-in", [
|
|
|
790
881
|
"border-bottom-color:rgb(22,24,28);"},
|
|
791
882
|
fontSize="13px", fontFamily=MONO, letterSpacing="0.1em",
|
|
792
883
|
radius="0px", cursor="pointer", transitionAll="180ms ease")},
|
|
884
|
+
"focus-visible": {"_": FOCUS_RING},
|
|
793
885
|
"hover": {"_": ({"backgroundColor": {"token": "--mk-ink"},
|
|
794
886
|
"customStyles": "border:1px solid rgb(22,24,28);"}
|
|
795
887
|
if n == 1
|
|
@@ -937,7 +1029,15 @@ STACK_SEC = section("stack", [wrap("mk-stack-in", [
|
|
|
937
1029
|
{"display": "grid", "gridCols": "160px 1fr", "columnGap": "24px",
|
|
938
1030
|
"alignItems": "baseline",
|
|
939
1031
|
"paddingTop": "18px", "paddingBottom": "18px",
|
|
940
|
-
|
|
1032
|
+
# Each row arrives on its own, staggered by pushing its
|
|
1033
|
+
# animation-range further down the scroll rather than by a
|
|
1034
|
+
# delay: a scroll-driven animation has no clock to delay
|
|
1035
|
+
# against, so the offset has to live in the range.
|
|
1036
|
+
"customStyles":
|
|
1037
|
+
"border-top:1px solid " + RULE + ";"
|
|
1038
|
+
"animation:mk-rise both;animation-timeline:view();"
|
|
1039
|
+
"animation-range:entry %d%% cover %d%%;"
|
|
1040
|
+
% (2 + i * 4, 18 + i * 4)},
|
|
941
1041
|
_m={"gridCols": "1fr", "rowGap": "10px",
|
|
942
1042
|
"paddingTop": "16px", "paddingBottom": "16px"},
|
|
943
1043
|
children=[
|
|
@@ -1161,6 +1261,40 @@ CONTACT = section("contact", [wrap("mk-contact-in", [
|
|
|
1161
1261
|
]),
|
|
1162
1262
|
])])
|
|
1163
1263
|
|
|
1264
|
+
# ── the entrance sequence ─────────────────────────────────────────────────────
|
|
1265
|
+
# A boot screen, because the page is a specification document and this is what one
|
|
1266
|
+
# looks like while it is being read off a machine. It is CSS only: a counter that is
|
|
1267
|
+
# a real animated integer (`@property --mk-n`), a rule that fills with it, the seven
|
|
1268
|
+
# clauses reporting in one at a time, and a scan line. Then the whole panel clips
|
|
1269
|
+
# upward and is gone.
|
|
1270
|
+
#
|
|
1271
|
+
# Every id here is deliberate. `verify_intro.py` samples them over the first seconds
|
|
1272
|
+
# of the page's life, which is the only window in which any of this exists.
|
|
1273
|
+
BOOT = box("mk-boot", {}, [
|
|
1274
|
+
box("mk-boot-scan", {}, []),
|
|
1275
|
+
box("mk-boot-head", {}, [
|
|
1276
|
+
mono("MOKSA WEB — STUDIO PROFILE", size="10px", color="--mk-faint",
|
|
1277
|
+
track="0.2em"),
|
|
1278
|
+
mono("REV. 2026.09", size="10px", color="--mk-faint", track="0.2em"),
|
|
1279
|
+
]),
|
|
1280
|
+
box("mk-boot-row", {}, [
|
|
1281
|
+
box("mk-boot-num", {}, []),
|
|
1282
|
+
box("mk-boot-pct", {}, [T("p", "%")]),
|
|
1283
|
+
]),
|
|
1284
|
+
box("mk-boot-track", {}, [box("mk-boot-fill", {}, [])]),
|
|
1285
|
+
box("mk-boot-list", {},
|
|
1286
|
+
# each clause gets its own box because the id is what the stagger
|
|
1287
|
+
# targets, and `mono()` puts everything it is given into the style
|
|
1288
|
+
[box("mk-boot-c%d" % i, {},
|
|
1289
|
+
[mono("§" + num + " " + name, size="10px", color="--mk-faint",
|
|
1290
|
+
track="0.18em")])
|
|
1291
|
+
for i, (num, name, _h, _t) in enumerate(CLAUSES)]),
|
|
1292
|
+
box("mk-boot-foot", {}, [
|
|
1293
|
+
mono("INITIALISING", size="10px", color="--mk-faint", track="0.2em"),
|
|
1294
|
+
mono("TAICHUNG, TW", size="10px", color="--mk-faint", track="0.2em"),
|
|
1295
|
+
]),
|
|
1296
|
+
])
|
|
1297
|
+
|
|
1164
1298
|
# Registration marks. A spec sheet is a printed object and these are how one is
|
|
1165
1299
|
# trimmed; here they simply say the page has edges that were decided.
|
|
1166
1300
|
MARKS = box("mk-marks", {}, [box("mk-mark-%d" % i, {}, []) for i in range(4)])
|
|
@@ -1176,7 +1310,8 @@ INDEX = box("mk-index", {}, [
|
|
|
1176
1310
|
"fontFamily": MONO, "fontSize": "10px",
|
|
1177
1311
|
"letterSpacing": "0.14em",
|
|
1178
1312
|
"transitionAll": "220ms ease"}},
|
|
1179
|
-
"hover": {"_": {"color": {"token": "--mk-ink"}}}
|
|
1313
|
+
"hover": {"_": {"color": {"token": "--mk-ink"}}},
|
|
1314
|
+
"focus-visible": {"_": FOCUS_RING}},
|
|
1180
1315
|
"text": "§" + num + " " + name}
|
|
1181
1316
|
for i, (num, name, href, _tl) in enumerate(CLAUSES)
|
|
1182
1317
|
])
|
|
@@ -1187,7 +1322,7 @@ CUE = box("mk-cue", {}, [
|
|
|
1187
1322
|
])
|
|
1188
1323
|
|
|
1189
1324
|
HOME_TREE = {"type": "div", "data": {"attrID": "mk-home"},
|
|
1190
|
-
"children": [box("mk-doc", {}, [
|
|
1325
|
+
"children": [BOOT, box("mk-doc", {}, [
|
|
1191
1326
|
MARKS, INDEX, CUE,
|
|
1192
1327
|
MASTHEAD, TICKER_BAND, SERVICE_SEC, PROCESS_SEC, WORK_SEC,
|
|
1193
1328
|
STACK_SEC, PRODUCT_SEC, VOICE_SEC, CONTACT,
|