@stocksharp/trading-controls 0.1.0 → 1.1.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 (77) hide show
  1. package/README.md +92 -6
  2. package/dist/esm/active-orders-widget.js +9 -0
  3. package/dist/esm/active-orders-widget.js.map +1 -1
  4. package/dist/esm/control-types.js +3 -0
  5. package/dist/esm/control-types.js.map +1 -1
  6. package/dist/esm/grid-menu.js +54 -0
  7. package/dist/esm/grid-menu.js.map +1 -0
  8. package/dist/esm/index.js +8 -0
  9. package/dist/esm/index.js.map +1 -1
  10. package/dist/esm/order-entry-widget.js +654 -0
  11. package/dist/esm/order-entry-widget.js.map +1 -0
  12. package/dist/esm/orderbook-depth.js +51 -0
  13. package/dist/esm/orderbook-depth.js.map +1 -0
  14. package/dist/esm/orderbook-widget.js +792 -0
  15. package/dist/esm/orderbook-widget.js.map +1 -0
  16. package/dist/esm/positions-widget.js +9 -0
  17. package/dist/esm/positions-widget.js.map +1 -1
  18. package/dist/esm/trade-history-widget.js +8 -0
  19. package/dist/esm/trade-history-widget.js.map +1 -1
  20. package/dist/esm/tradefeed-aggregator.js +58 -0
  21. package/dist/esm/tradefeed-aggregator.js.map +1 -0
  22. package/dist/esm/tradefeed-bubbles.js +131 -0
  23. package/dist/esm/tradefeed-bubbles.js.map +1 -0
  24. package/dist/esm/tradefeed-widget.js +692 -0
  25. package/dist/esm/tradefeed-widget.js.map +1 -0
  26. package/dist/esm/trading-host.js +2 -0
  27. package/dist/esm/trading-host.js.map +1 -1
  28. package/dist/esm/watchlist-widget.js +2 -0
  29. package/dist/esm/watchlist-widget.js.map +1 -1
  30. package/dist/sstradingcontrols.js +3346 -25
  31. package/dist/sstradingcontrols.js.map +3 -3
  32. package/dist/types/active-orders-widget.d.ts.map +1 -1
  33. package/dist/types/control-types.d.ts +3 -0
  34. package/dist/types/control-types.d.ts.map +1 -1
  35. package/dist/types/grid-menu.d.ts +4 -0
  36. package/dist/types/grid-menu.d.ts.map +1 -0
  37. package/dist/types/index.d.ts +13 -0
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/order-entry-widget.d.ts +86 -0
  40. package/dist/types/order-entry-widget.d.ts.map +1 -0
  41. package/dist/types/orderbook-depth.d.ts +19 -0
  42. package/dist/types/orderbook-depth.d.ts.map +1 -0
  43. package/dist/types/orderbook-widget.d.ts +105 -0
  44. package/dist/types/orderbook-widget.d.ts.map +1 -0
  45. package/dist/types/positions-widget.d.ts.map +1 -1
  46. package/dist/types/trade-history-widget.d.ts.map +1 -1
  47. package/dist/types/tradefeed-aggregator.d.ts +15 -0
  48. package/dist/types/tradefeed-aggregator.d.ts.map +1 -0
  49. package/dist/types/tradefeed-bubbles.d.ts +39 -0
  50. package/dist/types/tradefeed-bubbles.d.ts.map +1 -0
  51. package/dist/types/tradefeed-widget.d.ts +78 -0
  52. package/dist/types/tradefeed-widget.d.ts.map +1 -0
  53. package/dist/types/trading-data.d.ts +22 -0
  54. package/dist/types/trading-data.d.ts.map +1 -1
  55. package/dist/types/trading-host.d.ts +8 -0
  56. package/dist/types/trading-host.d.ts.map +1 -1
  57. package/dist/types/watchlist-widget.d.ts.map +1 -1
  58. package/package.json +21 -2
  59. package/screenshots/panels.jpg +0 -0
  60. package/src/active-orders-widget.ts +9 -0
  61. package/src/control-types.ts +3 -0
  62. package/src/grid-menu.ts +67 -0
  63. package/src/index.ts +31 -0
  64. package/src/order-entry-widget.ts +733 -0
  65. package/src/orderbook-depth.ts +99 -0
  66. package/src/orderbook-widget.ts +896 -0
  67. package/src/positions-widget.ts +9 -0
  68. package/src/trade-history-widget.ts +8 -0
  69. package/src/tradefeed-aggregator.ts +95 -0
  70. package/src/tradefeed-bubbles.ts +194 -0
  71. package/src/tradefeed-widget.ts +744 -0
  72. package/src/trading-data.ts +168 -121
  73. package/src/trading-host.ts +326 -300
  74. package/src/watchlist-widget.ts +2 -0
  75. package/styles/theme.css +23 -0
  76. package/styles/trading-controls.css +911 -0
  77. package/translation-keys.json +103 -2
@@ -308,6 +308,12 @@
308
308
  color: var(--t-text-dim);
309
309
  }
310
310
 
311
+ /* The grid marks selected rows (the blotters switch selection on); a tint
312
+ under the cells' own colours, so a red Sell stays red while marked. */
313
+ .terminal-table tbody tr.is-selected td {
314
+ background: color-mix(in srgb, var(--t-accent) 16%, transparent);
315
+ }
316
+
311
317
  /* Spelled the way Bootstrap spells it. A page that already loads Bootstrap has
312
318
  this rule twice with the same meaning, which is harmless; a page that does
313
319
  not still gets accessible headers. */
@@ -323,6 +329,140 @@
323
329
  border: 0;
324
330
  }
325
331
 
332
+ /* The grid's context menu and its filter rule dialog. The grid draws them at
333
+ the pointer and names their parts; the controls are what put them on screen
334
+ (see grid-menu.ts), so their look ships here with everything else the
335
+ controls emit. Position and coordinates are the grid's inline business —
336
+ these rules only dress the box. */
337
+ .grid-menu {
338
+ z-index: 100;
339
+ min-width: 190px;
340
+ padding: 4px;
341
+ background: var(--t-panel);
342
+ border: 1px solid var(--t-border);
343
+ border-radius: var(--t-radius);
344
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
345
+ font-family: var(--t-font);
346
+ font-size: 12px;
347
+ color: var(--t-text);
348
+ }
349
+
350
+ .grid-menu-item {
351
+ padding: 5px 10px;
352
+ border-radius: var(--t-radius);
353
+ cursor: pointer;
354
+ white-space: nowrap;
355
+ overflow: hidden;
356
+ text-overflow: ellipsis;
357
+ max-width: 320px;
358
+ }
359
+
360
+ .grid-menu-item:hover { background: var(--t-hover); color: var(--t-text-bright); }
361
+
362
+ .grid-menu-item.is-disabled { color: var(--t-text-dim); opacity: 0.6; cursor: default; }
363
+ .grid-menu-item.is-disabled:hover { background: transparent; color: var(--t-text-dim); }
364
+
365
+ .grid-menu-item.is-checked::before { content: '· '; color: var(--t-accent); }
366
+
367
+ .grid-menu-separator {
368
+ height: 1px;
369
+ margin: 4px 6px;
370
+ background: var(--t-border);
371
+ }
372
+
373
+ .grid-filter-dialog {
374
+ z-index: 100;
375
+ display: flex;
376
+ flex-direction: column;
377
+ gap: 8px;
378
+ min-width: 240px;
379
+ padding: 10px;
380
+ background: var(--t-panel);
381
+ border: 1px solid var(--t-border);
382
+ border-radius: var(--t-radius);
383
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
384
+ font-family: var(--t-font);
385
+ font-size: 12px;
386
+ color: var(--t-text);
387
+ }
388
+
389
+ .grid-filter-dialog-title {
390
+ font-size: 11px;
391
+ font-weight: 600;
392
+ color: var(--t-text-dim);
393
+ text-transform: uppercase;
394
+ letter-spacing: 0.05em;
395
+ }
396
+
397
+ .grid-filter-dialog-row { display: flex; align-items: center; gap: 6px; }
398
+
399
+ .grid-filter-dialog-op,
400
+ .grid-filter-dialog-value {
401
+ padding: 4px 8px;
402
+ background: var(--t-bg);
403
+ border: 1px solid var(--t-border);
404
+ border-radius: var(--t-radius);
405
+ color: var(--t-text);
406
+ font-family: var(--t-font);
407
+ font-size: 12px;
408
+ }
409
+
410
+ .grid-filter-dialog-value { flex: 1; min-width: 0; }
411
+
412
+ .grid-filter-dialog-op:focus,
413
+ .grid-filter-dialog-value:focus {
414
+ outline: none;
415
+ border-color: var(--t-accent);
416
+ }
417
+
418
+ /* The set filter's pick list: every value the column holds, scrollable when
419
+ the column holds many. */
420
+ .grid-filter-dialog-values {
421
+ display: flex;
422
+ flex-direction: column;
423
+ gap: 2px;
424
+ max-height: 200px;
425
+ overflow-y: auto;
426
+ }
427
+
428
+ .grid-filter-dialog-values-item {
429
+ display: flex;
430
+ align-items: center;
431
+ gap: 6px;
432
+ padding: 3px 6px;
433
+ border-radius: var(--t-radius);
434
+ cursor: pointer;
435
+ }
436
+
437
+ .grid-filter-dialog-values-item:hover { background: var(--t-hover); }
438
+
439
+ .grid-filter-dialog-actions {
440
+ display: flex;
441
+ justify-content: flex-end;
442
+ gap: 6px;
443
+ }
444
+
445
+ .grid-filter-dialog-actions button {
446
+ padding: 4px 12px;
447
+ background: transparent;
448
+ border: 1px solid var(--t-border);
449
+ border-radius: var(--t-radius);
450
+ color: var(--t-text);
451
+ font-family: var(--t-font);
452
+ font-size: 12px;
453
+ cursor: pointer;
454
+ }
455
+
456
+ .grid-filter-dialog-actions button:hover { border-color: var(--t-accent); color: var(--t-accent); }
457
+
458
+ .grid-filter-dialog-apply {
459
+ background: var(--t-accent);
460
+ border-color: var(--t-accent);
461
+ color: var(--t-accent-text);
462
+ }
463
+
464
+ .grid-filter-dialog-apply:hover { background: var(--t-accent-hover); color: var(--t-accent-text); }
465
+
326
466
  /* ---------------------------------------------------------------- watchlist */
327
467
 
328
468
  .watchlist-panel {
@@ -467,3 +607,774 @@
467
607
  .wl-chg { text-align: right; font-weight: 600; }
468
608
  .wl-chg.up { color: var(--t-green); }
469
609
  .wl-chg.down { color: var(--t-red); }
610
+
611
+ /* --------------------------------------------------------------- order book */
612
+
613
+ .orderbook-panel {
614
+ display: flex;
615
+ flex-direction: column;
616
+ min-height: 0;
617
+ overflow: hidden;
618
+ }
619
+
620
+ /* Every setting the ladder has lives in its header, so the header packs from
621
+ the left and the two buttons at the end take the room that is left. */
622
+ .orderbook-panel > .panel-header {
623
+ justify-content: flex-start;
624
+ gap: 6px;
625
+ }
626
+
627
+ .ob-symbol-label {
628
+ cursor: pointer;
629
+ font-weight: 600;
630
+ color: var(--t-text-bright);
631
+ border-bottom: 1px dashed var(--t-text-dim);
632
+ padding-bottom: 1px;
633
+ }
634
+
635
+ .ob-symbol-label:hover {
636
+ color: var(--t-accent);
637
+ border-bottom-color: var(--t-accent);
638
+ }
639
+
640
+ .ob-add-btn { margin-left: auto; }
641
+
642
+ /* Depth, row layout, side order and the depth chart share one skin, because
643
+ they are one row of settings rather than four kinds of button. */
644
+ .ob-depth-selector,
645
+ .ob-view-selector {
646
+ display: flex;
647
+ gap: 2px;
648
+ }
649
+
650
+ .btn-ob-depth,
651
+ .btn-ob-view,
652
+ .btn-ob-invert,
653
+ .btn-ob-depthtoggle {
654
+ display: inline-flex;
655
+ align-items: center;
656
+ justify-content: center;
657
+ min-width: 22px;
658
+ background: transparent;
659
+ border: 1px solid var(--t-border);
660
+ color: var(--t-text-dim);
661
+ font-family: var(--t-mono);
662
+ font-size: 9px;
663
+ line-height: 1.6;
664
+ padding: 0 5px;
665
+ border-radius: var(--t-radius);
666
+ cursor: pointer;
667
+ transition: all var(--t-transition);
668
+ }
669
+
670
+ .btn-ob-view .bi,
671
+ .btn-ob-invert .bi,
672
+ .btn-ob-depthtoggle .bi { font-size: 11px; line-height: 1; }
673
+
674
+ .btn-ob-depth:hover,
675
+ .btn-ob-view:hover,
676
+ .btn-ob-invert:hover,
677
+ .btn-ob-depthtoggle:hover { color: var(--t-text); border-color: var(--t-text-dim); }
678
+
679
+ .btn-ob-depth.active,
680
+ .btn-ob-view.active,
681
+ .btn-ob-invert.active,
682
+ .btn-ob-depthtoggle.active {
683
+ background: var(--t-accent);
684
+ color: var(--t-accent-text);
685
+ border-color: var(--t-accent);
686
+ }
687
+
688
+ .spread-label { font-weight: 400; font-size: 10px; color: var(--t-text-dim); }
689
+
690
+ /* The depth curve. Decoration, not interaction, so clicks fall through to the
691
+ ladder underneath it. */
692
+ .ob-depth-chart {
693
+ display: block;
694
+ width: 100%;
695
+ height: 80px;
696
+ pointer-events: none;
697
+ background: transparent;
698
+ }
699
+
700
+ .ob-hide-depth .ob-depth-chart { display: none; }
701
+
702
+ /* Captions above the levels, on the same three columns as a row so each one
703
+ stays over the figures it names. */
704
+ .ob-col-headers {
705
+ display: grid;
706
+ grid-template-columns: 1fr 1fr 1fr;
707
+ padding: 4px 8px;
708
+ font-size: 10px;
709
+ letter-spacing: 0.5px;
710
+ text-transform: uppercase;
711
+ color: var(--t-text-dim);
712
+ background: var(--t-panel);
713
+ border-bottom: 1px solid var(--t-border);
714
+ }
715
+
716
+ .ob-col-price { text-align: left; }
717
+ .ob-col-qty,
718
+ .ob-col-total { text-align: right; }
719
+
720
+ .orderbook-content {
721
+ flex: 1;
722
+ display: flex;
723
+ flex-direction: column;
724
+ min-height: 0;
725
+ overflow: hidden;
726
+ font-family: var(--t-mono);
727
+ font-size: 11px;
728
+ }
729
+
730
+ /* Natural height, so the best ask sits just above the mid line and the best bid
731
+ just below it. Centring each block inside its half leaves a dead band above a
732
+ shallow book. */
733
+ .orderbook-asks,
734
+ .orderbook-bids {
735
+ flex: 0 0 auto;
736
+ display: flex;
737
+ flex-direction: column;
738
+ min-height: 0;
739
+ max-height: calc(50% - 12px);
740
+ overflow-y: auto;
741
+ }
742
+
743
+ /* One level. The volume bar sits behind the figures, absolutely positioned, so
744
+ its width does not compete with the grid for room; the tint behind the whole
745
+ row separates a wall from its neighbours. Both widths are measured per level
746
+ by the control and arrive as custom properties. */
747
+ .ob-row {
748
+ display: grid;
749
+ grid-template-columns: 1fr 1fr 1fr;
750
+ padding: 1px 8px;
751
+ line-height: 1.7;
752
+ position: relative;
753
+ cursor: pointer;
754
+ transition: background 0.1s ease;
755
+ }
756
+
757
+ .ob-row.ask { background: color-mix(in srgb, var(--t-red) var(--t-ob-heat), transparent); }
758
+ .ob-row.bid { background: color-mix(in srgb, var(--t-green) var(--t-ob-heat), transparent); }
759
+ .ob-row:hover { background: var(--t-hover); }
760
+
761
+ .ob-row .price { text-align: left; font-weight: 600; position: relative; z-index: 1; }
762
+ .ob-row .qty { text-align: right; font-weight: 500; color: var(--t-text-dim); position: relative; z-index: 1; }
763
+ .ob-row .total { text-align: right; font-weight: 500; color: var(--t-text-dim); position: relative; z-index: 1; }
764
+ .ob-row.ask .price { color: var(--t-red); }
765
+ .ob-row.bid .price { color: var(--t-green); }
766
+
767
+ .ob-row .bar {
768
+ position: absolute;
769
+ top: 0;
770
+ bottom: 0;
771
+ width: var(--t-ob-bar);
772
+ opacity: 0.28;
773
+ z-index: 0;
774
+ pointer-events: none;
775
+ transition: width 0.3s ease;
776
+ }
777
+
778
+ .ob-row.ask .bar {
779
+ right: 0;
780
+ background: linear-gradient(to left, color-mix(in srgb, var(--t-red) 55%, transparent), transparent);
781
+ }
782
+
783
+ .ob-row.bid .bar {
784
+ left: 0;
785
+ background: linear-gradient(to right, color-mix(in srgb, var(--t-green) 55%, transparent), transparent);
786
+ }
787
+
788
+ .ob-row.flash-green { animation: flashGreen 0.5s ease; }
789
+ .ob-row.flash-red { animation: flashRed 0.5s ease; }
790
+
791
+ /* A level this session has size resting on: a stripe down the outside of the
792
+ row, and the resting quantity inside the size cell. */
793
+ .ob-row.own.bid { box-shadow: inset 3px 0 0 var(--t-green); }
794
+ .ob-row.own.ask { box-shadow: inset -3px 0 0 var(--t-red); }
795
+
796
+ .ob-row .own-badge {
797
+ display: inline-block;
798
+ margin: 0 4px;
799
+ padding: 0 4px;
800
+ border-radius: 2px;
801
+ background: color-mix(in srgb, var(--t-text-bright) 8%, transparent);
802
+ color: var(--t-text);
803
+ font-size: 10px;
804
+ font-weight: 600;
805
+ line-height: 1.4;
806
+ }
807
+
808
+ .orderbook-mid {
809
+ position: relative;
810
+ text-align: center;
811
+ padding: 4px;
812
+ font-weight: 700;
813
+ font-size: 13px;
814
+ letter-spacing: -0.3px;
815
+ color: var(--t-text-bright);
816
+ background: var(--t-bg);
817
+ border-top: 1px solid var(--t-border);
818
+ border-bottom: 1px solid var(--t-border);
819
+ flex-shrink: 0;
820
+ }
821
+
822
+ .ob-mid-price { font-weight: inherit; }
823
+
824
+ /* The spread reads to the right of the divider while the mid price stays
825
+ centred on it. */
826
+ .ob-mid-spread {
827
+ position: absolute;
828
+ right: 8px;
829
+ top: 50%;
830
+ transform: translateY(-50%);
831
+ font-weight: 400;
832
+ font-size: 10px;
833
+ letter-spacing: 0;
834
+ color: var(--t-text-dim);
835
+ }
836
+
837
+ /* Diagonal layout — the classic two-sided book. Same three columns, but the
838
+ size cell moves to the outside of each side with the price centred between
839
+ them; the cumulative column and the captions go, because neither reads
840
+ unambiguously once the two sides are mirrored. */
841
+ .orderbook-panel.ob-view-diagonal .ob-row { grid-template-areas: "left center right"; }
842
+ .orderbook-panel.ob-view-diagonal .ob-row .price { grid-area: center; text-align: center; }
843
+ .orderbook-panel.ob-view-diagonal .ob-row.ask .qty { grid-area: left; text-align: left; }
844
+ .orderbook-panel.ob-view-diagonal .ob-row.bid .qty { grid-area: right; text-align: right; }
845
+ .orderbook-panel.ob-view-diagonal .ob-row .total { display: none; }
846
+ .orderbook-panel.ob-view-diagonal .ob-col-headers { display: none; }
847
+
848
+ /* Stacked is the layout the rules above already describe; naming it is what
849
+ lets a panel switch back out of diagonal. */
850
+ .orderbook-panel.ob-view-stacked .ob-row { grid-template-areas: none; }
851
+
852
+ /* Inverted sides — the bid block floats above the mid line. Each block reverses
853
+ internally as well, so the levels nearest the spread stay nearest the mid
854
+ line in both orders. */
855
+ .orderbook-panel.ob-invert .orderbook-content { flex-direction: column-reverse; }
856
+ .orderbook-panel.ob-invert .orderbook-asks,
857
+ .orderbook-panel.ob-invert .orderbook-bids { flex-direction: column-reverse; }
858
+
859
+ /* Buy-versus-sell strip along the bottom. One measured property splits it — the
860
+ bid half takes it, the ask half takes what is left — so the two can never
861
+ disagree about how much of the strip is spoken for. */
862
+ .ob-sentiment {
863
+ display: flex;
864
+ height: 18px;
865
+ font-size: 10px;
866
+ font-weight: 600;
867
+ border-top: 1px solid var(--t-border);
868
+ overflow: hidden;
869
+ /* Informational, so it never squeezes the levels above it. */
870
+ flex: 0 0 18px;
871
+ }
872
+
873
+ .ob-sent-bid,
874
+ .ob-sent-ask {
875
+ text-align: center;
876
+ line-height: 18px;
877
+ overflow: hidden;
878
+ white-space: nowrap;
879
+ color: var(--t-text-bright);
880
+ transition: width 0.3s ease;
881
+ }
882
+
883
+ .ob-sent-bid {
884
+ width: var(--t-ob-sent);
885
+ background: color-mix(in srgb, var(--t-green) 55%, transparent);
886
+ }
887
+
888
+ .ob-sent-ask {
889
+ width: calc(100% - var(--t-ob-sent));
890
+ background: color-mix(in srgb, var(--t-red) 55%, transparent);
891
+ }
892
+
893
+ .ob-sent-label { opacity: 0.85; margin: 0 4px; }
894
+
895
+ /* Tabular figures, so a percentage ticking between 9% and 10% does not shove
896
+ the letter beside it sideways. */
897
+ .ob-sent-bid-pct,
898
+ .ob-sent-ask-pct { font-variant-numeric: tabular-nums; }
899
+
900
+ /* On a phone the ladder is most of the screen, so the settings that only tune
901
+ it step aside and the rows tighten. What actually shortens the book is the
902
+ host answering a smaller `maxDepth()`; this makes the rows that remain fit. */
903
+ @media (max-width: 768px), (max-height: 500px) {
904
+ .ob-depth-selector,
905
+ .ob-view-selector,
906
+ .ob-symbol-label { display: none; }
907
+
908
+ .orderbook-content { justify-content: center; }
909
+ .orderbook-asks,
910
+ .orderbook-bids { overflow-y: hidden; }
911
+ .orderbook-content .ob-row { line-height: 1.4; padding: 0 6px; font-size: 10px; }
912
+ .orderbook-content .orderbook-mid { padding: 2px 6px; font-size: 12px; }
913
+ }
914
+
915
+ /* --------------------------------------------------------------- trade feed */
916
+
917
+ /* The panel is the tooltip's containing block: the bubble tooltip is placed in
918
+ canvas coordinates and would otherwise anchor to the page. */
919
+ .tradefeed-panel {
920
+ display: flex;
921
+ flex-direction: column;
922
+ position: relative;
923
+ }
924
+
925
+ /* Market / My trades. Two panes and one canvas share the body, and which of
926
+ them is showing is two class names the control puts on the root — so the
927
+ rules below are the whole of that decision, and a narrow viewport can
928
+ override it further down without the control being told. */
929
+ .tf-tab-market .tf-my,
930
+ .tf-tab-my .tf-market,
931
+ .tf-tab-my .tf-bubbles,
932
+ .tf-tab-my .tf-view-toggle,
933
+ .tf-tab-market.tf-view-bubbles .tf-market,
934
+ .tf-tab-market.tf-view-list .tf-bubbles { display: none; }
935
+
936
+ .tf-tabs {
937
+ display: flex;
938
+ gap: 2px;
939
+ padding: 2px 8px;
940
+ border-bottom: 1px solid var(--t-border);
941
+ flex-shrink: 0;
942
+ }
943
+
944
+ .tf-tab {
945
+ background: transparent;
946
+ border: none;
947
+ border-bottom: 2px solid transparent;
948
+ color: var(--t-text-dim);
949
+ font-family: var(--t-font);
950
+ font-size: 10px;
951
+ font-weight: 600;
952
+ text-transform: uppercase;
953
+ letter-spacing: 0.4px;
954
+ padding: 2px 8px;
955
+ border-radius: var(--t-radius);
956
+ cursor: pointer;
957
+ transition: all var(--t-transition);
958
+ }
959
+
960
+ .tf-tab:hover { color: var(--t-text-bright); }
961
+
962
+ .tf-tab.active {
963
+ color: var(--t-accent);
964
+ border-bottom-color: var(--t-accent);
965
+ }
966
+
967
+ /* List of prints. Scrolls on its own so the header, the tab strip and the
968
+ pinned-extras ribbon stay put. */
969
+ .tradefeed-content {
970
+ flex: 1;
971
+ overflow-y: auto;
972
+ font-family: var(--t-mono);
973
+ font-size: 11px;
974
+ min-height: 0;
975
+ }
976
+
977
+ /* Each tape is a DataGrid over the shared table skin. The direction class the
978
+ host answered with sits on the row, so the price and the side inherit its
979
+ colour while the time and the qty state their own. The grid stamps every
980
+ body cell with its column key — that is what the alignments key off. */
981
+ .tradefeed-table td[data-col="time"] { color: var(--t-text-dim); }
982
+ .tradefeed-table td[data-col="symbol"] { color: var(--t-text-bright); font-weight: 600; font-size: 11px; }
983
+ .tradefeed-table td[data-col="price"] { text-align: right; font-weight: 500; }
984
+ .tradefeed-table td[data-col="quantity"] { text-align: right; color: var(--t-text-dim); }
985
+ .tradefeed-table td[data-col="side"] { text-align: right; font-weight: 600; font-size: 10px; }
986
+
987
+ /* A print that just landed (the grid's flashNewClass names it), and one well
988
+ above the recent average size. */
989
+ .tradefeed-table tbody tr.flash-new { animation: flashGreen 0.6s ease; }
990
+ .tradefeed-table tbody tr.side-sell.flash-new { animation: flashRed 0.6s ease; }
991
+ .tradefeed-table tbody tr.large-trade td { font-size: 12px; font-weight: 600; }
992
+ .tradefeed-table tbody tr.large-trade td[data-col="quantity"] { color: var(--t-text-bright); }
993
+
994
+ /* List | bubbles, in the panel header. */
995
+ .tradefeed-view-toggle {
996
+ display: inline-flex;
997
+ gap: 2px;
998
+ margin-left: auto;
999
+ }
1000
+
1001
+ .tradefeed-view-toggle .tf-view-btn {
1002
+ background: transparent;
1003
+ border: 1px solid var(--t-border);
1004
+ color: var(--t-text-dim);
1005
+ padding: 1px 6px;
1006
+ height: 18px;
1007
+ line-height: 1;
1008
+ font-size: 10px;
1009
+ border-radius: var(--t-radius);
1010
+ cursor: pointer;
1011
+ transition: all var(--t-transition);
1012
+ }
1013
+
1014
+ .tradefeed-view-toggle .tf-view-btn:hover {
1015
+ color: var(--t-text-bright);
1016
+ border-color: var(--t-text-dim);
1017
+ }
1018
+
1019
+ .tradefeed-view-toggle .tf-view-btn.active {
1020
+ background: var(--t-accent);
1021
+ color: var(--t-accent-text);
1022
+ border-color: var(--t-accent);
1023
+ }
1024
+
1025
+ /* Pinned extras — one chip per instrument this feed watches on top of the
1026
+ page's own. Absent from the markup until there is one. */
1027
+ .tf-extras {
1028
+ display: flex;
1029
+ flex-wrap: wrap;
1030
+ gap: 4px;
1031
+ padding: 4px 8px;
1032
+ border-bottom: 1px solid var(--t-border);
1033
+ flex-shrink: 0;
1034
+ }
1035
+
1036
+ .tf-extras[hidden] { display: none; }
1037
+
1038
+ .tf-extra-chip {
1039
+ display: inline-flex;
1040
+ align-items: center;
1041
+ gap: 4px;
1042
+ padding: 2px 4px 2px 8px;
1043
+ font-size: 11px;
1044
+ font-weight: 600;
1045
+ color: var(--t-text-bright);
1046
+ background: var(--t-hover);
1047
+ border: 1px solid var(--t-border);
1048
+ border-radius: 10px;
1049
+ }
1050
+
1051
+ .tf-extra-sym { letter-spacing: 0.3px; }
1052
+
1053
+ .tf-extra-rm {
1054
+ display: inline-flex;
1055
+ align-items: center;
1056
+ justify-content: center;
1057
+ width: 16px;
1058
+ height: 16px;
1059
+ padding: 0;
1060
+ line-height: 1;
1061
+ color: var(--t-text-dim);
1062
+ background: transparent;
1063
+ border: none;
1064
+ border-radius: 50%;
1065
+ cursor: pointer;
1066
+ }
1067
+
1068
+ .tf-extra-rm i { font-size: 10px; }
1069
+
1070
+ .tf-extra-rm:hover {
1071
+ color: var(--t-red);
1072
+ background: color-mix(in srgb, var(--t-red) 12%, transparent);
1073
+ }
1074
+
1075
+ /* The bubble chart. Takes whatever height is left below the chrome, full
1076
+ width; the control sizes its backing store to the device's pixels. */
1077
+ .tradefeed-bubbles {
1078
+ flex: 1;
1079
+ width: 100%;
1080
+ min-height: 0;
1081
+ display: block;
1082
+ }
1083
+
1084
+ /* Placed by the control in canvas coordinates, which is why this is the one
1085
+ thing here carrying a `left` / `top` it does not declare. */
1086
+ .tf-bubble-tooltip {
1087
+ position: absolute;
1088
+ z-index: 20;
1089
+ min-width: 120px;
1090
+ padding: 6px 8px;
1091
+ background: var(--t-panel);
1092
+ border: 1px solid var(--t-border);
1093
+ border-radius: 4px;
1094
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
1095
+ font-family: var(--t-mono);
1096
+ font-size: 11px;
1097
+ line-height: 1.5;
1098
+ color: var(--t-text-bright);
1099
+ pointer-events: none;
1100
+ }
1101
+
1102
+ .tf-bubble-tooltip[hidden] { display: none; }
1103
+ .tf-bbtt-row { display: flex; justify-content: space-between; gap: 12px; }
1104
+ .tf-bbtt-label { color: var(--t-text-dim); }
1105
+ .tf-bbtt-value { font-weight: 600; }
1106
+
1107
+ /* A phone has no room for two renderings of the same tape, or for a second
1108
+ instrument pinned beside the first: the list wins and the rest steps aside.
1109
+ Later in the file than the state rules above, and at the same specificity,
1110
+ so it overrides them whatever the control's own state says. */
1111
+ @media (max-width: 768px), (max-height: 500px) {
1112
+ .tf-tab-market.tf-view-bubbles .tf-market { display: block; }
1113
+ .tradefeed-panel .tf-bubbles,
1114
+ .tradefeed-panel .tf-view-toggle,
1115
+ .tradefeed-panel .tf-extras,
1116
+ .tradefeed-panel .tf-add-symbol-btn { display: none; }
1117
+ }
1118
+
1119
+ /* ----------------------------------------------------------- order entry */
1120
+
1121
+ .order-entry-content {
1122
+ padding: 8px;
1123
+ flex: 1;
1124
+ display: flex;
1125
+ flex-direction: column;
1126
+ min-height: 0;
1127
+ gap: 6px;
1128
+ /* Backstop for viewports and locales whose labels push the form past the
1129
+ height the panel was given. */
1130
+ overflow-y: auto;
1131
+ }
1132
+
1133
+ /* Nothing can be sent while the host says the account is out of reach. The form
1134
+ says so by going quiet rather than by disappearing, so the numbers in it stay
1135
+ readable. */
1136
+ .order-entry-content.oe-offline {
1137
+ opacity: 0.5;
1138
+ pointer-events: none;
1139
+ }
1140
+
1141
+ /* Two pairs of type buttons, so each pair sits over the column it applies to:
1142
+ the outer gap matches the column gap and the inner one is tighter. */
1143
+ .order-type-tabs {
1144
+ display: grid;
1145
+ grid-template-columns: 1fr 1fr;
1146
+ gap: 12px;
1147
+ }
1148
+
1149
+ .ot-pair {
1150
+ display: flex;
1151
+ gap: 4px;
1152
+ min-width: 0;
1153
+ }
1154
+
1155
+ .btn-ot {
1156
+ flex: 1;
1157
+ padding: 4px 8px;
1158
+ font-size: 11px;
1159
+ font-weight: 600;
1160
+ background: transparent;
1161
+ border: 1px solid var(--t-border);
1162
+ color: var(--t-text-dim);
1163
+ border-radius: var(--t-radius);
1164
+ cursor: pointer;
1165
+ transition: all var(--t-transition);
1166
+ font-family: var(--t-font);
1167
+ min-width: 0;
1168
+ }
1169
+
1170
+ .btn-ot.active { background: var(--t-accent); color: var(--t-accent-text); border-color: var(--t-accent); }
1171
+ .btn-ot:hover:not(.active) { background: var(--t-hover); color: var(--t-text); border-color: var(--t-text-dim); }
1172
+
1173
+ .order-entry-split {
1174
+ flex: 1;
1175
+ min-height: 0;
1176
+ display: grid;
1177
+ grid-template-columns: 1fr 1fr;
1178
+ gap: 12px;
1179
+ }
1180
+
1181
+ .oe-col {
1182
+ display: flex;
1183
+ flex-direction: column;
1184
+ gap: 5px;
1185
+ padding: 6px 8px;
1186
+ background: var(--t-panel);
1187
+ border: 1px solid var(--t-border);
1188
+ border-radius: var(--t-radius);
1189
+ min-width: 0;
1190
+ }
1191
+
1192
+ /* The direction shows at the edge only. The submit button carries the full
1193
+ colour, and two saturated panels side by side read as an alarm. */
1194
+ .oe-col-buy { border-color: color-mix(in srgb, var(--t-green) 25%, transparent); }
1195
+ .oe-col-sell { border-color: color-mix(in srgb, var(--t-red) 25%, transparent); }
1196
+
1197
+ .oe-label {
1198
+ font-size: 10px;
1199
+ color: var(--t-text-dim);
1200
+ text-transform: uppercase;
1201
+ letter-spacing: 0.4px;
1202
+ font-family: var(--t-font);
1203
+ }
1204
+
1205
+ .oe-value {
1206
+ font-family: var(--t-mono);
1207
+ font-size: 11px;
1208
+ color: var(--t-text);
1209
+ text-align: right;
1210
+ white-space: nowrap;
1211
+ overflow: hidden;
1212
+ text-overflow: ellipsis;
1213
+ }
1214
+
1215
+ .oe-avbl,
1216
+ .oe-total,
1217
+ .oe-foot {
1218
+ display: flex;
1219
+ justify-content: space-between;
1220
+ align-items: baseline;
1221
+ gap: 6px;
1222
+ font-size: 10px;
1223
+ }
1224
+
1225
+ .oe-field {
1226
+ display: flex;
1227
+ flex-direction: column;
1228
+ gap: 2px;
1229
+ min-width: 0;
1230
+ }
1231
+
1232
+ /* A field the current order type does not use, and the take-profit block while
1233
+ it is switched off. The control toggles this class rather than a display
1234
+ style, so what a field looks like when it is away stays here. */
1235
+ .oe-field-hidden { display: none; }
1236
+
1237
+ .oe-input-grp {
1238
+ display: flex;
1239
+ gap: 4px;
1240
+ align-items: stretch;
1241
+ }
1242
+
1243
+ .oe-input-grp .oe-input { flex: 1; min-width: 0; }
1244
+
1245
+ .oe-input {
1246
+ background: var(--t-bg);
1247
+ border: 1px solid var(--t-border);
1248
+ color: var(--t-text-bright);
1249
+ font-family: var(--t-mono);
1250
+ font-size: 12px;
1251
+ padding: 4px 6px;
1252
+ border-radius: var(--t-radius);
1253
+ transition: border-color var(--t-transition);
1254
+ width: 100%;
1255
+ min-width: 0;
1256
+ }
1257
+
1258
+ .oe-input:focus {
1259
+ outline: none;
1260
+ border-color: var(--t-accent);
1261
+ box-shadow: 0 0 0 2px var(--t-accent-glow-soft);
1262
+ }
1263
+
1264
+ .btn-bbo {
1265
+ background: var(--t-bg);
1266
+ border: 1px solid var(--t-border);
1267
+ color: var(--t-text-dim);
1268
+ font-size: 10px;
1269
+ font-weight: 600;
1270
+ padding: 0 8px;
1271
+ border-radius: var(--t-radius);
1272
+ cursor: pointer;
1273
+ font-family: var(--t-font);
1274
+ transition: all var(--t-transition);
1275
+ }
1276
+
1277
+ .btn-bbo:hover {
1278
+ color: var(--t-accent-text);
1279
+ background: var(--t-accent);
1280
+ border-color: var(--t-accent);
1281
+ }
1282
+
1283
+ .oe-pct-row {
1284
+ display: flex;
1285
+ gap: 3px;
1286
+ }
1287
+
1288
+ .btn-oe-pct {
1289
+ flex: 1;
1290
+ padding: 2px 0;
1291
+ font-size: 10px;
1292
+ font-weight: 500;
1293
+ font-family: var(--t-mono);
1294
+ background: var(--t-bg);
1295
+ border: 1px solid var(--t-border);
1296
+ color: var(--t-text-dim);
1297
+ border-radius: 2px;
1298
+ cursor: pointer;
1299
+ transition: all var(--t-transition);
1300
+ }
1301
+
1302
+ .btn-oe-pct:hover {
1303
+ background: var(--t-hover);
1304
+ color: var(--t-text-bright);
1305
+ }
1306
+
1307
+ .oe-tpsl-toggle {
1308
+ display: flex;
1309
+ align-items: center;
1310
+ gap: 6px;
1311
+ font-size: 10px;
1312
+ color: var(--t-text-dim);
1313
+ cursor: pointer;
1314
+ user-select: none;
1315
+ font-family: var(--t-font);
1316
+ text-transform: uppercase;
1317
+ letter-spacing: 0.4px;
1318
+ }
1319
+
1320
+ .oe-tpsl {
1321
+ display: flex;
1322
+ flex-direction: column;
1323
+ gap: 4px;
1324
+ padding: 4px 0;
1325
+ border-top: 1px dashed var(--t-border);
1326
+ }
1327
+
1328
+ /* Pushed to the bottom of the column, so both buttons line up however tall the
1329
+ two forms above them ended up. */
1330
+ .btn-oe-submit {
1331
+ margin-top: auto;
1332
+ flex: none;
1333
+ padding: 10px 6px;
1334
+ font-weight: 700;
1335
+ font-size: 13px;
1336
+ text-transform: uppercase;
1337
+ border: none;
1338
+ border-radius: var(--t-radius);
1339
+ cursor: pointer;
1340
+ color: var(--t-on-direction);
1341
+ transition: all var(--t-transition);
1342
+ font-family: var(--t-font);
1343
+ letter-spacing: 0.3px;
1344
+ position: relative;
1345
+ overflow: hidden;
1346
+ }
1347
+
1348
+ .btn-oe-submit.btn-buy { background: var(--t-green); }
1349
+ .btn-oe-submit.btn-buy:hover { background: var(--t-green-hover); box-shadow: 0 0 12px color-mix(in srgb, var(--t-green) 30%, transparent); }
1350
+ .btn-oe-submit.btn-sell { background: var(--t-red); }
1351
+ .btn-oe-submit.btn-sell:hover { background: var(--t-red-hover); box-shadow: 0 0 12px color-mix(in srgb, var(--t-red) 30%, transparent); }
1352
+ .btn-oe-submit:disabled { opacity: 0.45; cursor: not-allowed; }
1353
+ .btn-oe-submit:active { transform: scale(0.97); }
1354
+
1355
+ /* The side a host-level gesture aimed at — a click on a book level, a chart
1356
+ price. A ring rather than a colour change: the button already says which
1357
+ direction it is. */
1358
+ .btn-oe-submit.btn-buy.btn-preselected { box-shadow: 0 0 0 2px var(--t-green), 0 0 12px color-mix(in srgb, var(--t-green) 30%, transparent); }
1359
+ .btn-oe-submit.btn-sell.btn-preselected { box-shadow: 0 0 0 2px var(--t-red), 0 0 12px color-mix(in srgb, var(--t-red) 30%, transparent); }
1360
+
1361
+ .hotkey-hint {
1362
+ font-size: 9px;
1363
+ font-weight: 400;
1364
+ opacity: 0.6;
1365
+ margin-left: 4px;
1366
+ font-family: var(--t-mono);
1367
+ }
1368
+
1369
+ /* The order's value, and the reason it cannot be sent, in the same line: they
1370
+ are never both true, and giving them one line keeps the button from moving
1371
+ when a message appears. */
1372
+ .oe-estimate {
1373
+ font-size: 10px;
1374
+ font-family: var(--t-mono);
1375
+ color: var(--t-text-dim);
1376
+ min-height: 12px;
1377
+ text-align: center;
1378
+ }
1379
+
1380
+ .oe-estimate.oe-estimate-error { color: var(--t-red); }