@conduction/docusaurus-preset 3.32.0 → 3.34.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.
Files changed (31) hide show
  1. package/MISSING_COMPONENTS.md +7 -6
  2. package/package.json +1 -1
  3. package/src/components/AgentTrace/AgentTrace.jsx +26 -5
  4. package/src/components/AgentTrace/AgentTrace.module.css +41 -2
  5. package/src/components/AgentTrace/__tests__/AgentTrace.render.test.js +108 -0
  6. package/src/components/AppMock/AppMock.jsx +14 -5
  7. package/src/components/AppMock/AppMock.module.css +439 -0
  8. package/src/components/AppMock/__tests__/AppMock.render.test.js +181 -0
  9. package/src/components/AppMock/variants/LarpingAppMock.jsx +23 -4
  10. package/src/components/AppMock/variants/LaunchPadBiMock.jsx +12 -5
  11. package/src/components/AppMock/variants/LaunchPadTilesMock.jsx +6 -1
  12. package/src/components/AppMock/variants/LaunchPadWidgetsMock.jsx +17 -8
  13. package/src/components/AppMock/variants/NLDesignMock.jsx +15 -4
  14. package/src/components/AppMock/variants/PipelinQMock.jsx +12 -2
  15. package/src/components/AppMock/variants/PortaliqMock.jsx +21 -9
  16. package/src/components/AppMock/variants/ScholiqMock.jsx +12 -6
  17. package/src/components/FlowMock/FlowMock.jsx +5 -3
  18. package/src/components/FlowMock/FlowMock.module.css +36 -9
  19. package/src/components/FlowMock/__tests__/FlowMock.render.test.js +83 -0
  20. package/src/components/KanbanMock/KanbanMock.jsx +35 -5
  21. package/src/components/KanbanMock/KanbanMock.module.css +120 -28
  22. package/src/components/KanbanMock/__tests__/KanbanMock.render.test.js +114 -0
  23. package/src/components/LeafMock/LeafMock.jsx +23 -8
  24. package/src/components/LeafMock/LeafMock.module.css +123 -0
  25. package/src/components/LeafMock/__tests__/LeafMock.render.test.js +105 -0
  26. package/src/components/MockScene/MockScene.jsx +16 -2
  27. package/src/components/MockScene/MockScene.module.css +22 -2
  28. package/src/components/MockScene/__tests__/MockScene.render.test.js +101 -0
  29. package/src/components/WidgetShelf/WidgetShelf.jsx +321 -28
  30. package/src/components/WidgetShelf/WidgetShelf.module.css +193 -1
  31. package/src/components/WidgetShelf/__tests__/WidgetShelf.render.test.js +149 -0
@@ -30,6 +30,10 @@
30
30
  .am .figure { margin: 0; display: inline-flex; flex-direction: column; gap: var(--space-2); width: 100%; max-width: 100%; }
31
31
 
32
32
  .am .frame {
33
+ /* Shared animation timeline token. Every variant keyframe below runs
34
+ on var(--am-dur); a variant overrides it on its own body class
35
+ (e.g. `.nldesign { --am-dur: 6s; }`). The KanbanMock convention. */
36
+ --am-dur: 8s;
33
37
  width: 100%;
34
38
  max-width: 720px;
35
39
  aspect-ratio: 16 / 10;
@@ -700,3 +704,438 @@
700
704
  .am .procest .timeline .step.todo .h { background: var(--c-cobalt-100); }
701
705
  .am .procest .timeline .step.now .h { background: var(--c-orange-knvb); }
702
706
  .am .procest .timeline .step .label { height: 3px; width: 60%; background: var(--c-cobalt-400); border-radius: 1px; }
707
+
708
+ /* ======================================================================
709
+ Variant animations
710
+ ======================================================================
711
+
712
+ Every timeline runs on var(--am-dur) (declared on `.am .frame`,
713
+ overridden per variant body class). Conventions, copied from
714
+ KanbanMock.module.css:
715
+
716
+ - Base styles are the animation's meaningful END state, so freezing
717
+ (running={false} → `.static` on the frame, or the user's
718
+ prefers-reduced-motion) is `animation: none` plus a couple of
719
+ explicit overrides.
720
+ - Loops reset via opacity swaps (fade out at ~92–98%, re-enter from
721
+ the 0% frame), never a visible rewind. 0% and 100% frames are both
722
+ hidden or identical, so per-element `animation-delay` staggers stay
723
+ seamless in steady state.
724
+ - `animation-fill-mode: both` keeps delayed elements on their hidden
725
+ 0% frame during the initial delay.
726
+ - Tokens only; no new orange is introduced by any animation. */
727
+
728
+ /* ---- Portaliq — a visitor fills the form, submits, the message
729
+ lands in the inbox. Timeline (8s): focus+fill field 1 (3–19%),
730
+ field 2 (21–37%), field 3 (39–55%), submit press (58–63%), mint
731
+ confirm sweep across the form (63–72%), inbox row arrives (74–80%),
732
+ hold, fade-reset (94–100%). ---- */
733
+
734
+ .am .portaliq .field {
735
+ position: relative;
736
+ height: 14px;
737
+ background: white;
738
+ border: 1px solid var(--c-cobalt-200);
739
+ border-radius: 3px;
740
+ overflow: hidden;
741
+ }
742
+ .am .portaliq .field.tall { height: 26px; }
743
+ .am .portaliq .fieldFill {
744
+ position: absolute;
745
+ left: 3px;
746
+ top: 4px;
747
+ height: 4px;
748
+ border-radius: 1px;
749
+ background: var(--c-cobalt-300);
750
+ transform-origin: left center;
751
+ }
752
+ .am .portaliq .field-1 .fieldFill { width: 62%; animation: amPqFill1 var(--am-dur) linear infinite both; }
753
+ .am .portaliq .field-2 .fieldFill { width: 48%; animation: amPqFill2 var(--am-dur) linear infinite both; }
754
+ .am .portaliq .field-3 .fieldFill { width: 78%; animation: amPqFill3 var(--am-dur) linear infinite both; }
755
+ .am .portaliq .field-1 { animation: amPqFocus1 var(--am-dur) linear infinite; }
756
+ .am .portaliq .field-2 { animation: amPqFocus2 var(--am-dur) linear infinite; }
757
+ .am .portaliq .field-3 { animation: amPqFocus3 var(--am-dur) linear infinite; }
758
+
759
+ @keyframes amPqFocus1 {
760
+ 0%, 3% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
761
+ 6%, 17% { border-color: var(--c-blue-cobalt); box-shadow: 0 0 0 2px var(--c-cobalt-100); }
762
+ 20%, 100% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
763
+ }
764
+ @keyframes amPqFocus2 {
765
+ 0%, 21% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
766
+ 24%, 35% { border-color: var(--c-blue-cobalt); box-shadow: 0 0 0 2px var(--c-cobalt-100); }
767
+ 38%, 100% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
768
+ }
769
+ @keyframes amPqFocus3 {
770
+ 0%, 39% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
771
+ 42%, 53% { border-color: var(--c-blue-cobalt); box-shadow: 0 0 0 2px var(--c-cobalt-100); }
772
+ 56%, 100% { border-color: var(--c-cobalt-200); box-shadow: 0 0 0 0 rgba(31, 68, 156, 0); }
773
+ }
774
+ @keyframes amPqFill1 {
775
+ 0%, 8% { transform: scaleX(0); opacity: 1; }
776
+ 19%, 93% { transform: scaleX(1); opacity: 1; }
777
+ 98% { transform: scaleX(1); opacity: 0; }
778
+ 100% { transform: scaleX(0); opacity: 0; }
779
+ }
780
+ @keyframes amPqFill2 {
781
+ 0%, 26% { transform: scaleX(0); opacity: 1; }
782
+ 37%, 93% { transform: scaleX(1); opacity: 1; }
783
+ 98% { transform: scaleX(1); opacity: 0; }
784
+ 100% { transform: scaleX(0); opacity: 0; }
785
+ }
786
+ @keyframes amPqFill3 {
787
+ 0%, 44% { transform: scaleX(0); opacity: 1; }
788
+ 55%, 93% { transform: scaleX(1); opacity: 1; }
789
+ 98% { transform: scaleX(1); opacity: 0; }
790
+ 100% { transform: scaleX(0); opacity: 0; }
791
+ }
792
+
793
+ .am .portaliq .submit { animation: amPqPress var(--am-dur) ease-in-out infinite; }
794
+ @keyframes amPqPress {
795
+ 0%, 58% { transform: scale(1); }
796
+ 60% { transform: scale(0.92); }
797
+ 63%, 100% { transform: scale(1); }
798
+ }
799
+
800
+ /* Mint confirmation sweep — a soft light band crossing the form panel
801
+ right after the submit press. Hidden at rest and in static frames. */
802
+ .am .portaliq .formPanel { position: relative; overflow: hidden; }
803
+ .am .portaliq .confirmSweep {
804
+ position: absolute;
805
+ inset: 0;
806
+ background: linear-gradient(100deg, transparent 32%, var(--c-mint-300) 50%, transparent 68%);
807
+ opacity: 0;
808
+ transform: translateX(-120%);
809
+ pointer-events: none;
810
+ animation: amPqSweep var(--am-dur) linear infinite;
811
+ }
812
+ @keyframes amPqSweep {
813
+ 0%, 63% { transform: translateX(-120%); opacity: 0; }
814
+ 64% { opacity: 0.9; }
815
+ 71% { opacity: 0.9; }
816
+ 72%, 100% { transform: translateX(120%); opacity: 0; }
817
+ }
818
+
819
+ /* The arriving inbox row (its default mint avatar reads as the
820
+ delivered/confirmed state). Visible in static frames. */
821
+ .am .portaliq .newItem { animation: amPqRow var(--am-dur) ease-out infinite both; }
822
+ @keyframes amPqRow {
823
+ 0%, 74% { opacity: 0; transform: translateY(-5px); }
824
+ 80%, 93% { opacity: 1; transform: none; }
825
+ 98%, 100% { opacity: 0; transform: none; }
826
+ }
827
+
828
+ /* ---- NLDesign — a theme swatch is selected, the components retheme
829
+ in a cascade (button, then status pill), and the type scale
830
+ restretches. Timeline (6s): ring on the lavender swatch (5–10%),
831
+ button recolours (12–20%), pill recolours (14–22%), type bars
832
+ restretch (30–50%), hold, revert crossfade (84–100%). ---- */
833
+
834
+ .am .nldesign { --am-dur: 6s; }
835
+
836
+ .am .nldesign .swatch { display: inline-block; width: 22px; height: 22px; border-radius: 4px; }
837
+ .am .nldesign .swatchSel { animation: amNldSel var(--am-dur) ease-in-out infinite; }
838
+ @keyframes amNldSel {
839
+ 0%, 5% { box-shadow: 0 0 0 0 white, 0 0 0 0 rgba(31, 68, 156, 0); }
840
+ 10%, 86% { box-shadow: 0 0 0 2px white, 0 0 0 4px var(--c-blue-cobalt); }
841
+ 94%, 100% { box-shadow: 0 0 0 0 white, 0 0 0 0 rgba(31, 68, 156, 0); }
842
+ }
843
+
844
+ .am .nldesign .actions .btn:not(.ghost) { animation: amNldBtn var(--am-dur) ease-in-out infinite; }
845
+ @keyframes amNldBtn {
846
+ 0%, 12% { background-color: var(--c-blue-cobalt); }
847
+ 20%, 84% { background-color: var(--c-lavender-500); }
848
+ 92%, 100% { background-color: var(--c-blue-cobalt); }
849
+ }
850
+
851
+ .am .nldesign .statusPill:not(.beta) { animation: amNldPillBg var(--am-dur) ease-in-out infinite; }
852
+ .am .nldesign .statusPill:not(.beta) .h,
853
+ .am .nldesign .statusPill:not(.beta) .t { animation: amNldPillInk var(--am-dur) ease-in-out infinite; }
854
+ @keyframes amNldPillBg {
855
+ 0%, 14% { background-color: var(--c-mint-300); }
856
+ 22%, 84% { background-color: var(--c-lavender-300); }
857
+ 92%, 100% { background-color: var(--c-mint-300); }
858
+ }
859
+ @keyframes amNldPillInk {
860
+ 0%, 14% { background-color: var(--c-mint-500); }
861
+ 22%, 84% { background-color: var(--c-lavender-500); }
862
+ 92%, 100% { background-color: var(--c-mint-500); }
863
+ }
864
+
865
+ .am .nldesign .typeRow > * {
866
+ transform-origin: left center;
867
+ animation: amNldType var(--am-dur) ease-in-out infinite;
868
+ }
869
+ .am .nldesign .typeRow > *:nth-child(2) { animation-delay: 0.05s; }
870
+ .am .nldesign .typeRow > *:nth-child(3) { animation-delay: 0.1s; }
871
+ .am .nldesign .typeRow > *:nth-child(4) { animation-delay: 0.15s; }
872
+ .am .nldesign .typeRow > *:nth-child(5) { animation-delay: 0.2s; }
873
+ @keyframes amNldType {
874
+ 0%, 30% { transform: scaleX(1); }
875
+ 42% { transform: scaleX(1.32); }
876
+ 50%, 84% { transform: scaleX(1.18); }
877
+ 94%, 100% { transform: scaleX(1); }
878
+ }
879
+
880
+ /* ---- Scholiq — course progress bars fill in a stagger, then the
881
+ completed course's credential chip pops with an overshoot.
882
+ Timeline (6s): bars fill (3–25% + per-bar delay), chip pop
883
+ (44–57%), hold, fade-reset (91–100%). Per-bar delays are inline
884
+ (i × 250ms) so the keyframes stay shared. ---- */
885
+
886
+ .am .scholiq { --am-dur: 6s; }
887
+
888
+ .am .scholiq .progress { height: 4px; background: var(--c-cobalt-100); border-radius: 2px; }
889
+ .am .scholiq .progressFill {
890
+ height: 4px;
891
+ background: var(--c-forest-500);
892
+ border-radius: 2px;
893
+ transform-origin: left center;
894
+ animation: amSchFill var(--am-dur) cubic-bezier(0.2, 0.7, 0.3, 1) infinite both;
895
+ }
896
+ @keyframes amSchFill {
897
+ 0%, 3% { transform: scaleX(0); opacity: 1; }
898
+ 25%, 91% { transform: scaleX(1); opacity: 1; }
899
+ 97% { transform: scaleX(1); opacity: 0; }
900
+ 100% { transform: scaleX(0); opacity: 0; }
901
+ }
902
+
903
+ .am .scholiq .credChip {
904
+ display: inline-flex;
905
+ align-items: center;
906
+ gap: 3px;
907
+ padding: 2px 6px;
908
+ border-radius: 999px;
909
+ background: var(--c-forest-300);
910
+ align-self: flex-start;
911
+ transform-origin: left center;
912
+ animation: amSchChip var(--am-dur) ease-in-out infinite both;
913
+ }
914
+ .am .scholiq .credChip .credHex { width: 6px; height: 7px; clip-path: var(--hex-pointy-top); background: var(--c-forest-500); }
915
+ .am .scholiq .credChip .credBar { height: 3px; width: 22px; background: var(--c-forest-500); border-radius: 1px; }
916
+ @keyframes amSchChip {
917
+ 0%, 44% { transform: scale(0); opacity: 0; }
918
+ 52% { transform: scale(1.18); opacity: 1; }
919
+ 57%, 91% { transform: scale(1); opacity: 1; }
920
+ 97%, 100% { transform: scale(1); opacity: 0; }
921
+ }
922
+
923
+ /* ---- LaunchPad · Widgets — the dashboard assembles: widgets arrive
924
+ one by one on a 900ms stagger (inline delays), hold, fade-reset.
925
+ Timeline (10s). ---- */
926
+
927
+ .am .lpWidgets { --am-dur: 10s; }
928
+ .am .lpWidgets .w { animation: amLpwArrive var(--am-dur) ease-out infinite both; }
929
+ @keyframes amLpwArrive {
930
+ 0% { opacity: 0; transform: translateY(10px) scale(0.98); }
931
+ 4% { opacity: 1; transform: none; }
932
+ 93% { opacity: 1; transform: none; }
933
+ 98%, 100% { opacity: 0; transform: none; }
934
+ }
935
+
936
+ /* ---- LaunchPad · Tiles — the sub-grid tile is pressed and unfolds
937
+ its four sub-tiles in a stagger. Timeline (7s): press (5–11%),
938
+ cells unfold (10–20% + nth-child delay), hold, fade-reset. ---- */
939
+
940
+ .am .lpTiles { --am-dur: 7s; }
941
+ .am .lpTiles .w-subgrid { transform-origin: center; animation: amLptPress var(--am-dur) ease-in-out infinite; }
942
+ @keyframes amLptPress {
943
+ 0%, 5% { transform: scale(1); }
944
+ 8% { transform: scale(0.96); }
945
+ 11%, 100% { transform: scale(1); }
946
+ }
947
+ .am .lpTiles .w-subgrid .cell { animation: amLptCell var(--am-dur) cubic-bezier(0.2, 0.7, 0.3, 1) infinite both; }
948
+ .am .lpTiles .w-subgrid .cell:nth-child(2) { animation-delay: 0.12s; }
949
+ .am .lpTiles .w-subgrid .cell:nth-child(3) { animation-delay: 0.24s; }
950
+ .am .lpTiles .w-subgrid .cell:nth-child(4) { animation-delay: 0.36s; }
951
+ @keyframes amLptCell {
952
+ 0%, 10% { opacity: 0; transform: scale(0.4); }
953
+ 16% { opacity: 1; transform: scale(1.08); }
954
+ 20%, 92% { opacity: 1; transform: scale(1); }
955
+ 97%, 100% { opacity: 0; transform: scale(1); }
956
+ }
957
+
958
+ /* ---- LaunchPad · BI — the charts draw themselves: bars grow from
959
+ the axis in a stagger, the line draws along its path and its end
960
+ dot pops, the donut sweeps in, the KPI deltas fade up. Timeline
961
+ (9s): bars (2–16% + nth-child delay), donut (8–26%), line draw
962
+ (10–38%), dot pop (38–49%), deltas (46–54% + inline delay), hold,
963
+ fade-reset (92–100%). The line's stroke path carries
964
+ pathLength="1" so the dash arithmetic is geometry-independent
965
+ (the FlowMock run-line technique). ---- */
966
+
967
+ .am .lpBi { --am-dur: 9s; }
968
+
969
+ .am .lpBi .w-graph-bar .bar {
970
+ transform-origin: bottom;
971
+ animation: amBiBar var(--am-dur) cubic-bezier(0.2, 0.7, 0.3, 1) infinite both;
972
+ }
973
+ .am .lpBi .w-graph-bar .bar:nth-child(2) { animation-delay: 0.12s; }
974
+ .am .lpBi .w-graph-bar .bar:nth-child(3) { animation-delay: 0.24s; }
975
+ .am .lpBi .w-graph-bar .bar:nth-child(4) { animation-delay: 0.36s; }
976
+ .am .lpBi .w-graph-bar .bar:nth-child(5) { animation-delay: 0.48s; }
977
+ .am .lpBi .w-graph-bar .bar:nth-child(6) { animation-delay: 0.6s; }
978
+ @keyframes amBiBar {
979
+ 0%, 2% { transform: scaleY(0); opacity: 1; }
980
+ 16%, 92% { transform: scaleY(1); opacity: 1; }
981
+ 97% { transform: scaleY(1); opacity: 0; }
982
+ 100% { transform: scaleY(0); opacity: 0; }
983
+ }
984
+
985
+ .am .lpBi .w-graph-line .stroke {
986
+ stroke-dasharray: 1;
987
+ animation: amBiLine var(--am-dur) ease-in-out infinite both;
988
+ }
989
+ @keyframes amBiLine {
990
+ 0%, 10% { stroke-dashoffset: 1; opacity: 1; }
991
+ 38%, 92% { stroke-dashoffset: 0; opacity: 1; }
992
+ 97% { stroke-dashoffset: 0; opacity: 0; }
993
+ 100% { stroke-dashoffset: 1; opacity: 0; }
994
+ }
995
+ .am .lpBi .w-graph-line .fill { animation: amBiFill var(--am-dur) linear infinite both; }
996
+ @keyframes amBiFill {
997
+ 0%, 30% { opacity: 0; }
998
+ 44%, 92% { opacity: 1; }
999
+ 98%, 100% { opacity: 0; }
1000
+ }
1001
+ .am .lpBi .w-graph-line .dot {
1002
+ transform-box: fill-box;
1003
+ transform-origin: center;
1004
+ animation: amBiDot var(--am-dur) ease-out infinite both;
1005
+ }
1006
+ @keyframes amBiDot {
1007
+ 0%, 38% { opacity: 0; transform: scale(0); }
1008
+ 44% { opacity: 1; transform: scale(1.7); }
1009
+ 49%, 92% { opacity: 1; transform: scale(1); }
1010
+ 98%, 100% { opacity: 0; transform: scale(1); }
1011
+ }
1012
+
1013
+ .am .lpBi .donut { animation: amBiDonut var(--am-dur) ease-out infinite both; }
1014
+ @keyframes amBiDonut {
1015
+ 0%, 8% { opacity: 0; transform: rotate(-80deg) scale(0.85); }
1016
+ 26%, 92% { opacity: 1; transform: none; }
1017
+ 98%, 100% { opacity: 0; transform: none; }
1018
+ }
1019
+
1020
+ .am .lpBi .biDelta { animation: amBiDelta var(--am-dur) ease-out infinite both; }
1021
+ @keyframes amBiDelta {
1022
+ 0%, 46% { opacity: 0; transform: translateY(4px); }
1023
+ 54%, 92% { opacity: 1; transform: none; }
1024
+ 98%, 100% { opacity: 0; transform: none; }
1025
+ }
1026
+
1027
+ /* ---- PipelinQ — a deal closes: the proposal-column card collapses,
1028
+ the won card scales in with its mint bar, and the pipeline-value
1029
+ KPI widens. Timeline (7s): collapse (14–22%), won pop (17–29%),
1030
+ KPI widen (26–34%), hold, opacity-swap reset (91–100%). Static
1031
+ frames show the closed state (won card in place, proposal card
1032
+ gone). ---- */
1033
+
1034
+ .am .plq { --am-dur: 7s; }
1035
+
1036
+ .am .plq .dealGone { transform-origin: center; animation: amPlqGone var(--am-dur) ease-in-out infinite; }
1037
+ @keyframes amPlqGone {
1038
+ 0%, 14% { opacity: 1; transform: scaleY(1); }
1039
+ 22%, 91% { opacity: 0; transform: scaleY(0); }
1040
+ 97%, 100% { opacity: 1; transform: scaleY(1); }
1041
+ }
1042
+
1043
+ .am .plq .dealWon {
1044
+ border-left-color: var(--c-mint-500);
1045
+ transform-origin: center;
1046
+ animation: amPlqWon var(--am-dur) ease-out infinite both;
1047
+ }
1048
+ @keyframes amPlqWon {
1049
+ 0%, 17% { opacity: 0; transform: scale(0.5); }
1050
+ 25% { opacity: 1; transform: scale(1.1); }
1051
+ 29%, 91% { opacity: 1; transform: scale(1); }
1052
+ 97%, 100% { opacity: 0; transform: scale(1); }
1053
+ }
1054
+
1055
+ .am .plq .kpiRow .kpi:first-child .num {
1056
+ transform-origin: left center;
1057
+ animation: amPlqKpi var(--am-dur) ease-in-out infinite;
1058
+ }
1059
+ @keyframes amPlqKpi {
1060
+ 0%, 26% { transform: scaleX(1); }
1061
+ 34%, 90% { transform: scaleX(1.3); }
1062
+ 97%, 100% { transform: scaleX(1); }
1063
+ }
1064
+
1065
+ /* ======================================================================
1066
+ Static frames — running={false} adds `.static` next to `.am .frame`;
1067
+ the media query below mirrors it for prefers-reduced-motion. Selectors
1068
+ repeat the animated ones with `.static` inserted so they always win
1069
+ on specificity. Base styles are the meaningful end state, so beyond
1070
+ `animation: none` only a few explicit overrides are needed.
1071
+ ====================================================================== */
1072
+
1073
+ .am .static .portaliq .field-1,
1074
+ .am .static .portaliq .field-2,
1075
+ .am .static .portaliq .field-3,
1076
+ .am .static .portaliq .field-1 .fieldFill,
1077
+ .am .static .portaliq .field-2 .fieldFill,
1078
+ .am .static .portaliq .field-3 .fieldFill,
1079
+ .am .static .portaliq .submit,
1080
+ .am .static .portaliq .confirmSweep,
1081
+ .am .static .portaliq .newItem,
1082
+ .am .static .nldesign .swatchSel,
1083
+ .am .static .nldesign .actions .btn:not(.ghost),
1084
+ .am .static .nldesign .statusPill:not(.beta),
1085
+ .am .static .nldesign .statusPill:not(.beta) .h,
1086
+ .am .static .nldesign .statusPill:not(.beta) .t,
1087
+ .am .static .nldesign .typeRow > *,
1088
+ .am .static .scholiq .progressFill,
1089
+ .am .static .scholiq .credChip,
1090
+ .am .static .lpWidgets .w,
1091
+ .am .static .lpTiles .w-subgrid,
1092
+ .am .static .lpTiles .w-subgrid .cell,
1093
+ .am .static .lpBi .w-graph-bar .bar,
1094
+ .am .static .lpBi .w-graph-line .stroke,
1095
+ .am .static .lpBi .w-graph-line .fill,
1096
+ .am .static .lpBi .w-graph-line .dot,
1097
+ .am .static .lpBi .donut,
1098
+ .am .static .lpBi .biDelta,
1099
+ .am .static .plq .dealGone,
1100
+ .am .static .plq .dealWon,
1101
+ .am .static .plq .kpiRow .kpi:first-child .num {
1102
+ animation: none;
1103
+ }
1104
+ .am .static .lpBi .w-graph-line .stroke { stroke-dasharray: none; stroke-dashoffset: 0; }
1105
+ .am .static .plq .dealGone { display: none; }
1106
+
1107
+ @media (prefers-reduced-motion: reduce) {
1108
+ .am .portaliq .field-1,
1109
+ .am .portaliq .field-2,
1110
+ .am .portaliq .field-3,
1111
+ .am .portaliq .field-1 .fieldFill,
1112
+ .am .portaliq .field-2 .fieldFill,
1113
+ .am .portaliq .field-3 .fieldFill,
1114
+ .am .portaliq .submit,
1115
+ .am .portaliq .confirmSweep,
1116
+ .am .portaliq .newItem,
1117
+ .am .nldesign .swatchSel,
1118
+ .am .nldesign .actions .btn:not(.ghost),
1119
+ .am .nldesign .statusPill:not(.beta),
1120
+ .am .nldesign .statusPill:not(.beta) .h,
1121
+ .am .nldesign .statusPill:not(.beta) .t,
1122
+ .am .nldesign .typeRow > *,
1123
+ .am .scholiq .progressFill,
1124
+ .am .scholiq .credChip,
1125
+ .am .lpWidgets .w,
1126
+ .am .lpTiles .w-subgrid,
1127
+ .am .lpTiles .w-subgrid .cell,
1128
+ .am .lpBi .w-graph-bar .bar,
1129
+ .am .lpBi .w-graph-line .stroke,
1130
+ .am .lpBi .w-graph-line .fill,
1131
+ .am .lpBi .w-graph-line .dot,
1132
+ .am .lpBi .donut,
1133
+ .am .lpBi .biDelta,
1134
+ .am .plq .dealGone,
1135
+ .am .plq .dealWon,
1136
+ .am .plq .kpiRow .kpi:first-child .num {
1137
+ animation: none;
1138
+ }
1139
+ .am .lpBi .w-graph-line .stroke { stroke-dasharray: none; stroke-dashoffset: 0; }
1140
+ .am .plq .dealGone { display: none; }
1141
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * AppMock.render.test.js — renders the real <AppMock> JSX to static
3
+ * markup (the SSR path Docusaurus takes at build time) and asserts on
4
+ * the output: every variant slug renders, the P0 `running` prop
5
+ * plumbing puts `.static` on the frame, the animated variants expose
6
+ * their animation hooks, the `openwoo` slug aliases to the
7
+ * OpenCatalogi mock, and no rendered inline style reaches for the
8
+ * reserved coral/gold families.
9
+ *
10
+ * Uses the same esbuild-bundle-then-renderToStaticMarkup technique as
11
+ * KanbanMock.render.test.js. CSS modules are stubbed with an identity
12
+ * proxy (styles.foo → 'foo'), so class assertions read as the source
13
+ * class names.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const test = require('node:test');
19
+ const {before, after} = test;
20
+ const assert = require('node:assert/strict');
21
+ const path = require('node:path');
22
+ const fs = require('node:fs/promises');
23
+ const {build} = require('esbuild');
24
+ const React = require('react');
25
+ const {renderToStaticMarkup} = require('react-dom/server');
26
+
27
+ const COMPONENT = path.resolve(__dirname, '..', 'AppMock.jsx');
28
+ const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
29
+ const SCRATCH = path.join(PRESET_ROOT, '.tmp-app-mock-test');
30
+
31
+ /* Must stay in sync with the VARIANTS map in AppMock.jsx. */
32
+ const SLUGS = [
33
+ 'launchpad', 'launchpad-tiles', 'launchpad-bi', 'launchpad-widgets',
34
+ 'openbuild', 'opencatalogi', 'openconnector', 'openregister',
35
+ 'procest', 'decidesk', 'docudesk', 'larpingapp', 'nldesign',
36
+ 'openwoo', 'pipelinq', 'softwarecatalog', 'zaakafhandelapp',
37
+ 'hermiq', 'portaliq', 'scholiq', 'shillinq', 'doriath', 'planix',
38
+ 'hrmq', 'app-versions',
39
+ ];
40
+
41
+ const cssModuleStub = {
42
+ name: 'css-module-stub',
43
+ setup(b) {
44
+ b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
45
+ b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
46
+ contents: 'export default new Proxy({}, {get: (_, p) => p});',
47
+ loader: 'js',
48
+ }));
49
+ },
50
+ };
51
+
52
+ let AppMock;
53
+
54
+ before(async () => {
55
+ await fs.mkdir(SCRATCH, {recursive: true});
56
+ const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
57
+ const outFile = path.join(tmpDir, 'bundle.cjs');
58
+ await build({
59
+ entryPoints: [COMPONENT],
60
+ outfile: outFile,
61
+ bundle: true,
62
+ format: 'cjs',
63
+ jsx: 'automatic',
64
+ jsxImportSource: 'react',
65
+ tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
66
+ platform: 'node',
67
+ external: ['react'],
68
+ plugins: [cssModuleStub],
69
+ logLevel: 'warning',
70
+ });
71
+ delete require.cache[require.resolve(outFile)];
72
+ AppMock = require(outFile).default;
73
+ });
74
+
75
+ after(async () => {
76
+ await fs.rm(SCRATCH, {recursive: true, force: true});
77
+ });
78
+
79
+ function render(props) {
80
+ return renderToStaticMarkup(React.createElement(AppMock, props));
81
+ }
82
+
83
+ test('SSR smoke: every variant slug renders a frame, none falls through to Unknown app', () => {
84
+ for (const app of SLUGS) {
85
+ const html = render({app});
86
+ assert.match(html, /class="frame size-md"/, `${app}: frame missing`);
87
+ assert.doesNotMatch(html, /Unknown app/, `${app}: fell through to the unknown-variant branch`);
88
+ }
89
+ });
90
+
91
+ test('P0 plumbing: running={false} adds the static class to the frame; default omits it', () => {
92
+ assert.match(render({app: 'portaliq', running: false}), /class="frame size-md static"/);
93
+ assert.doesNotMatch(render({app: 'portaliq'}), /\bstatic\b/);
94
+ });
95
+
96
+ test('P0 plumbing: static frame works on every variant slug', () => {
97
+ for (const app of SLUGS) {
98
+ assert.match(render({app, running: false}), /class="frame size-md static"/, `${app}: no static frame`);
99
+ }
100
+ });
101
+
102
+ test('openwoo slug is an alias for the OpenCatalogi mock', () => {
103
+ const openwoo = render({app: 'openwoo'});
104
+ const opencatalogi = render({app: 'opencatalogi'});
105
+ assert.equal(openwoo, opencatalogi);
106
+ assert.equal(
107
+ render({app: 'openwoo', caption: true}).match(/<figcaption[^>]*>([^<]*)</)[1],
108
+ 'OpenCatalogi'
109
+ );
110
+ });
111
+
112
+ test('portaliq: form fields, fills, submit, confirm sweep, and the arriving inbox row are all present', () => {
113
+ const html = render({app: 'portaliq'});
114
+ assert.match(html, /class="body decidesk portaliq"/);
115
+ assert.match(html, /field field-1/);
116
+ assert.match(html, /field field-2/);
117
+ assert.match(html, /field field-3 tall/);
118
+ assert.equal((html.match(/class="fieldFill"/g) || []).length, 3);
119
+ assert.match(html, /btn submit/);
120
+ assert.match(html, /class="confirmSweep"/);
121
+ assert.match(html, /item newItem/);
122
+ });
123
+
124
+ test('scholiq: staggered progress fills (inline delays) and the credential chip', () => {
125
+ const html = render({app: 'scholiq'});
126
+ assert.equal((html.match(/class="progressFill"/g) || []).length, 6);
127
+ for (const d of [0, 250, 500, 750, 1000, 1250]) {
128
+ assert.match(html, new RegExp(`animation-delay:${d}ms`), `missing ${d}ms bar delay`);
129
+ }
130
+ assert.match(html, /class="credChip"/);
131
+ });
132
+
133
+ test('launchpad-widgets: seven widgets arrive on a 900ms stagger', () => {
134
+ const html = render({app: 'launchpad-widgets'});
135
+ assert.match(html, /class="body launchpad lpWidgets"/);
136
+ for (const d of [0, 900, 1800, 2700, 3600, 4500, 5400]) {
137
+ assert.match(html, new RegExp(`animation-delay:${d}ms`), `missing ${d}ms widget delay`);
138
+ }
139
+ });
140
+
141
+ test('launchpad-bi: the line chart stroke carries pathLength="1" for the draw animation', () => {
142
+ const html = render({app: 'launchpad-bi'});
143
+ assert.match(html, /class="body launchpad lpBi"/);
144
+ assert.match(html, /class="stroke" pathLength="1"/);
145
+ assert.equal((html.match(/class="biDelta"/g) || []).length, 3);
146
+ });
147
+
148
+ test('pipelinq: the closing deal (dealGone) and the won card (dealWon) are both in the board', () => {
149
+ const html = render({app: 'pipelinq'});
150
+ assert.match(html, /class="body decidesk plq"/);
151
+ assert.match(html, /card dealGone/);
152
+ assert.match(html, /card dealWon/);
153
+ });
154
+
155
+ test('nldesign: selected swatch, type row, and the actions wrapper around the preview buttons', () => {
156
+ const html = render({app: 'nldesign'});
157
+ assert.match(html, /class="body openregister nldesign"/);
158
+ assert.equal((html.match(/class="swatch(?: |")/g) || []).length, 8);
159
+ assert.match(html, /swatch swatchSel/);
160
+ assert.match(html, /class="typeRow"/);
161
+ // The preview buttons need the .actions parent to be styled at all.
162
+ assert.match(html, /class="actions"[^>]*><div class="btn"><\/div><div class="btn ghost">/);
163
+ });
164
+
165
+ test('larpingapp: the scene timeline renders five steps (was an empty shell)', () => {
166
+ const html = render({app: 'larpingapp'});
167
+ assert.match(html, /class="procest"/);
168
+ assert.match(html, /class="timeline"/);
169
+ assert.equal((html.match(/class="step[ "]/g) || []).length, 5);
170
+ assert.equal((html.match(/step now/g) || []).length, 1, 'exactly one active (orange) step');
171
+ });
172
+
173
+ test('reserved accent families: no variant inline-styles coral or gold', () => {
174
+ // SKILL.md reserves the coral and gold families; the one-orange rule
175
+ // is enforced per component by review, but coral/gold must never
176
+ // appear at all. Class-based colours live in the stylesheet; this
177
+ // guards the inline styles the variants set directly.
178
+ for (const app of SLUGS) {
179
+ assert.doesNotMatch(render({app}), /--c-coral|--c-gold/, `${app}: reserved family in inline style`);
180
+ }
181
+ });
@@ -33,10 +33,29 @@ export default function LarpingAppMock() {
33
33
  ))}
34
34
  </div>
35
35
  <div className={styles.col}>
36
- {/* Scene timeline (one done, one active in orange, three to-do) */}
37
- <div className={[styles.w, styles['w-graph-bar']].join(' ')} style={{minHeight: 0}}>
38
- <div className={styles.wHead}>
39
- <div className={styles.h}></div><div className={styles.t}></div>
36
+ {/* Scene timeline (one done, one active in orange the
37
+ variant's single orange — three to-do). Reuses the
38
+ procest timeline atoms; the wrapper div provides the
39
+ `.procest` scope those styles require. The previous
40
+ markup here was an empty w-graph-bar shell that rendered
41
+ nothing but its header. */}
42
+ <div className={styles.procest}>
43
+ <div className={styles.timeline}>
44
+ <div className={styles.step}>
45
+ <div className={styles.h}></div><div className={styles.label}></div>
46
+ </div>
47
+ <div className={[styles.step, styles.now].join(' ')}>
48
+ <div className={styles.h}></div><div className={styles.label}></div>
49
+ </div>
50
+ <div className={[styles.step, styles.todo].join(' ')}>
51
+ <div className={styles.h}></div><div className={styles.label}></div>
52
+ </div>
53
+ <div className={[styles.step, styles.todo].join(' ')}>
54
+ <div className={styles.h}></div><div className={styles.label}></div>
55
+ </div>
56
+ <div className={[styles.step, styles.todo].join(' ')}>
57
+ <div className={styles.h}></div><div className={styles.label}></div>
58
+ </div>
40
59
  </div>
41
60
  </div>
42
61
  {/* Character grid */}