Jose Salazar commited on
Commit ·
de1d694
1
Parent(s): 0ef022d
Maqueta de layout para UI con mockup data, actualizo a ultimas versiones todas las dependencias de los package.json
Browse files- backend/package.json +6 -6
- frontend/dist/assets/index-A_KP_t7E.js +0 -0
- frontend/dist/assets/index-CHfdQIIe.js +0 -0
- frontend/dist/assets/index-CIGW-MKW.css +0 -1
- frontend/dist/assets/index-xtYPhhTl.css +1 -0
- frontend/dist/index.html +214 -5
- frontend/index.html +212 -3
- frontend/package.json +6 -3
- frontend/src/api.js +73 -1
- frontend/src/app.js +623 -21
- frontend/src/charts.js +66 -8
- frontend/src/main.js +3 -1
- frontend/src/map.js +135 -10
- frontend/src/simulator.js +63 -7
- frontend/src/style.css +1061 -12
- package.json +1 -1
- polysignal_app_mockup.html +450 -0
backend/package.json
CHANGED
|
@@ -14,13 +14,13 @@
|
|
| 14 |
"db:studio": "prisma studio"
|
| 15 |
},
|
| 16 |
"dependencies": {
|
| 17 |
-
"express": "^
|
| 18 |
-
"socket.io": "^4.8.
|
| 19 |
-
"node-cron": "^
|
| 20 |
-
"@prisma/client": "^
|
| 21 |
-
"prisma": "^
|
| 22 |
},
|
| 23 |
"engines": {
|
| 24 |
-
"node": ">=
|
| 25 |
}
|
| 26 |
}
|
|
|
|
| 14 |
"db:studio": "prisma studio"
|
| 15 |
},
|
| 16 |
"dependencies": {
|
| 17 |
+
"express": "^5.2.1",
|
| 18 |
+
"socket.io": "^4.8.3",
|
| 19 |
+
"node-cron": "^4.2.1",
|
| 20 |
+
"@prisma/client": "^7.8.0",
|
| 21 |
+
"prisma": "^7.8.0"
|
| 22 |
},
|
| 23 |
"engines": {
|
| 24 |
+
"node": ">=26.0.0"
|
| 25 |
}
|
| 26 |
}
|
frontend/dist/assets/index-A_KP_t7E.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/dist/assets/index-CHfdQIIe.js
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/dist/assets/index-CIGW-MKW.css
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:transparent}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{width:1600px;height:1600px;-webkit-transform-origin:0 0}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{max-width:none!important;max-height:none!important;width:auto;padding:0}.leaflet-container img.leaflet-tile{mix-blend-mode:plus-lighter}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:rgba(51,181,229,.4)}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;-moz-box-sizing:border-box;box-sizing:border-box;z-index:800}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{position:relative;z-index:800;pointer-events:visiblePainted;pointer-events:auto}.leaflet-top,.leaflet-bottom{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-popup{opacity:0;-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}svg.leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{-webkit-transition:none;-moz-transition:none;transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{background:#ddd;outline-offset:1px}.leaflet-container a{color:#0078a8}.leaflet-zoom-box{border:2px dotted #38f;background:#ffffff80}.leaflet-container{font-family:Helvetica Neue,Arial,Helvetica,sans-serif;font-size:12px;font-size:.75rem;line-height:1.5}.leaflet-bar{box-shadow:0 1px 5px #000000a6;border-radius:4px}.leaflet-bar a{background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;display:block;text-align:center;text-decoration:none;color:#000}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50% 50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover,.leaflet-bar a:focus{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom:none}.leaflet-bar a.leaflet-disabled{cursor:default;background-color:#f4f4f4;color:#bbb}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{font:700 18px Lucida Console,Monaco,monospace;text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{box-shadow:0 1px 5px #0006;background:#fff;border-radius:5px}.leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{padding:6px 10px 6px 6px;color:#333;background:#fff}.leaflet-control-layers-scrollbar{overflow-y:scroll;overflow-x:hidden;padding-right:5px}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{display:block;font-size:13px;font-size:1.08333em}.leaflet-control-layers-separator{height:0;border-top:1px solid #ddd;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=)}.leaflet-container .leaflet-control-attribution{background:#fff;background:#fffc;margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{padding:0 5px;color:#333;line-height:1.4}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover,.leaflet-control-attribution a:focus{text-decoration:underline}.leaflet-attribution-flag{display:inline!important;vertical-align:baseline!important;width:1em;height:.6669em}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{border:2px solid #777;border-top:none;line-height:1.1;padding:2px 5px 1px;white-space:nowrap;-moz-box-sizing:border-box;box-sizing:border-box;background:#fffc;text-shadow:1px 1px #fff}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{box-shadow:none}.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{border:2px solid rgba(0,0,0,.2);background-clip:padding-box}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left;border-radius:12px}.leaflet-popup-content{margin:13px 24px 13px 20px;line-height:1.3;font-size:13px;font-size:1.08333em;min-height:1px}.leaflet-popup-content p{margin:1.3em 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-top:-1px;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;pointer-events:auto;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:#fff;color:#333;box-shadow:0 3px 14px #0006}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;border:none;text-align:center;width:24px;height:24px;font:16px/24px Tahoma,Verdana,sans-serif;color:#757575;text-decoration:none;background:transparent}.leaflet-container a.leaflet-popup-close-button:hover,.leaflet-container a.leaflet-popup-close-button:focus{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{width:24px;margin:0 auto;-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";filter:progid:DXImageTransform.Microsoft.Matrix(M11=.70710678,M12=.70710678,M21=-.70710678,M22=.70710678)}.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{position:absolute;padding:6px;background-color:#fff;border:1px solid #fff;border-radius:3px;color:#222;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;box-shadow:0 1px 3px #0006}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-top:before,.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{position:absolute;pointer-events:none;border:6px solid transparent;background:transparent;content:""}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{left:50%;margin-left:-6px}.leaflet-tooltip-top:before{bottom:0;margin-bottom:-12px;border-top-color:#fff}.leaflet-tooltip-bottom:before{top:0;margin-top:-12px;margin-left:-6px;border-bottom-color:#fff}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{top:50%;margin-top:-6px}.leaflet-tooltip-left:before{right:0;margin-right:-12px;border-left-color:#fff}.leaflet-tooltip-right:before{left:0;margin-left:-12px;border-right-color:#fff}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
|
|
|
|
|
|
frontend/dist/assets/index-xtYPhhTl.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@import "https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;500;600;700&display=swap";:root{--bg:#0a0c10;--bg2:#111318;--bg3:#181c24;--bg4:#1e232d;--border:#ffffff14;--border2:#ffffff21;--text:#e8eaf0;--text2:#8b90a0;--text3:#555b6e;--green:#22d37a;--green2:#0d6e3a;--green3:#052a17;--red:#f04040;--red2:#7a1a1a;--red3:#2d0808;--blue:#4a9eff;--blue2:#1a4a80;--blue3:#081830;--amber:#f0a020;--amber2:#7a4e08;--amber3:#2d1c02;--accent:#4a9eff;--sidebar-width:240px;--sidebar-collapsed:56px;--topbar-height:56px;--panel-gap:16px;--radius:10px;--radius-sm:6px;--fs-p:clamp(16px, 1.15vw, 18px);--fs-h1:clamp(28px, 2.8vw, 36px);--fs-h2:clamp(24px, 2.4vw, 30px);--fs-h3:clamp(20px, 1.9vw, 24px);--fs-h4:clamp(18px, 1.6vw, 20px);--fs-h5:clamp(16px, 1.3vw, 18px);--fs-h6:clamp(14px, 1.1vw, 16px)}*{box-sizing:border-box;margin:0;padding:0}html,body,#app{width:100%;height:100%;overflow:hidden}body{background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;font-family:Syne,sans-serif;font-size:16px;line-height:1.5}p{font-size:var(--fs-p);line-height:1.55}h1{font-size:var(--fs-h1);font-weight:700;line-height:1.15}h2{font-size:var(--fs-h2);font-weight:700;line-height:1.2}h3{font-size:var(--fs-h3);font-weight:600;line-height:1.25}h4{font-size:var(--fs-h4);font-weight:600;line-height:1.3}h5{font-size:var(--fs-h5);font-weight:500;line-height:1.35}h6{font-size:var(--fs-h6);font-weight:500;line-height:1.4}.layout{grid-template-areas:"topbar topbar""sidebar main";grid-template-columns:var(--sidebar-width) 1fr;grid-template-rows:var(--topbar-height) 1fr;width:100vw;height:100vh;transition:grid-template-columns .25s;display:grid}.layout.collapsed{grid-template-columns:var(--sidebar-collapsed) 1fr}.sidebar{background:var(--bg2);border-right:1px solid var(--border);flex-direction:column;grid-area:sidebar;transition:width .25s;display:flex;position:relative;overflow:hidden}.sidebar-toggle{background:var(--bg3);border:1px solid var(--border2);width:24px;height:24px;color:var(--text2);cursor:pointer;z-index:10;border-radius:50%;justify-content:center;align-items:center;font-size:14px;transition:transform .2s;display:flex;position:absolute;top:12px;right:-10px}.layout.collapsed .sidebar-toggle{right:-10px;transform:rotate(180deg)}.topbar-logo{align-items:center;gap:10px;display:flex;position:absolute;top:0;bottom:0;left:16px}.topbar-logo .logo-dot{background:var(--blue);border-radius:50%;flex-shrink:0;width:10px;height:10px;animation:2s ease-in-out infinite pulse}.topbar-logo .logo-text{color:var(--text);letter-spacing:-.3px;white-space:nowrap;font-size:18px;font-weight:700}.sidebar-nav{flex-direction:column;flex:1;gap:4px;padding:14px 8px;display:flex;overflow-y:auto}.nav-item{border-radius:var(--radius-sm);cursor:pointer;color:var(--text2);white-space:nowrap;align-items:center;gap:14px;padding:14px;font-size:16px;font-weight:500;transition:background .15s,color .15s;display:flex;overflow:hidden}.nav-item:hover{background:var(--bg3);color:var(--text)}.nav-item.active{background:var(--blue3);color:var(--blue);border:.5px solid var(--blue2)}.nav-icon{text-align:center;flex-shrink:0;width:20px;font-size:16px}.nav-label{opacity:1;transition:opacity .15s}.layout.collapsed .nav-label{opacity:0;width:0}.sidebar-footer{border-top:1px solid var(--border);color:var(--text3);text-align:center;white-space:nowrap;padding:14px;font-family:DM Mono,monospace;font-size:14px;overflow:hidden}.layout.collapsed .sidebar-footer{opacity:0}.topbar{background:var(--bg2);border-bottom:1px solid var(--border);padding:0 16px;padding-left:calc(var(--sidebar-width) + var(--panel-gap));grid-area:topbar;align-items:center;gap:16px;transition:padding-left .25s;display:flex;position:relative;overflow:hidden}.layout.collapsed .topbar{padding-left:calc(var(--sidebar-collapsed) + var(--panel-gap))}.live-badge{background:var(--green3);color:var(--green);border:.5px solid var(--green2);border-radius:4px;flex-shrink:0;align-items:center;gap:4px;padding:6px 12px;font-family:DM Mono,monospace;font-size:14px;display:flex}.live-dot{background:var(--green);border-radius:50%;width:8px;height:8px;animation:1.5s ease-in-out infinite pulse}.topbar-stats{flex:1;align-items:center;gap:20px;display:flex;overflow:hidden}.stat{flex-shrink:0;align-items:center;gap:14px;display:flex}.stat-label{color:var(--text3);text-transform:uppercase;letter-spacing:.06em;font-family:DM Mono,monospace;font-size:14px}.stat-val{color:var(--text);font-family:DM Mono,monospace;font-size:16px;font-weight:600}.stat-delta{font-family:DM Mono,monospace;font-size:14px}.stat-delta.up{color:var(--green)}.stat-delta.dn{color:var(--red)}.stat-delta.neutral{color:var(--text3)}.topbar-actions{flex-shrink:0;align-items:center;gap:16px;margin-left:auto;display:flex}.btn-ghost{color:var(--blue);border:.5px solid var(--blue2);background:var(--blue3);border-radius:var(--radius-sm);cursor:pointer;padding:8px 16px;font-family:Syne,sans-serif;font-size:15px;font-weight:500;transition:opacity .15s}.btn-ghost:hover{opacity:.85}.icon-btn{background:var(--bg3);border:.5px solid var(--border2);width:32px;height:32px;color:var(--text2);cursor:pointer;border-radius:50%;justify-content:center;align-items:center;font-size:16px;transition:color .15s,border-color .15s;display:flex}.icon-btn:hover{color:var(--text);border-color:var(--border2)}.main{padding:var(--panel-gap);background:var(--bg);grid-area:main;overflow:auto}.view{display:none}.view.active{height:100%;display:block}.dashboard-grid{gap:var(--panel-gap);grid-template-rows:1fr minmax(280px,40%);grid-template-columns:1fr 280px;height:100%;min-height:0;display:grid}.panel{background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);flex-direction:column;min-height:0;display:flex;overflow:hidden}.panel-header{border-bottom:.5px solid var(--border);cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;padding:14px;transition:background .15s;display:flex}.panel-header:hover{background:#ffffff05}.panel-title{color:var(--text3);text-transform:uppercase;letter-spacing:.08em;align-items:center;gap:14px;font-family:DM Mono,monospace;font-size:14px;display:flex}.panel-toggle{color:var(--text3);font-size:14px;transition:transform .2s}.panel.collapsed .panel-toggle{transform:rotate(-90deg)}.panel-body{flex:1;min-height:0;padding:12px;overflow:auto}.panel.collapsed .panel-body{display:none}.panel-subtitle{color:var(--text3);font-size:14px}.positions-separator{border-top:.5px solid var(--border);margin-top:10px;padding-top:10px}.panel-title.mb-sm{margin-bottom:8px}.panel.full-height{height:100%}.hidden{display:none}.map-panel{grid-area:1/1/2/2}#map-container{background:var(--bg3);border-radius:var(--radius-sm);width:100%;height:100%;min-height:300px;overflow:hidden}.signals-panel{grid-area:1/2/3/3}.signals-list{flex-direction:column;gap:16px;display:flex}.market-card{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;padding:14px;transition:border-color .15s,background .15s}.market-card:hover{border-color:var(--border2)}.market-card.active{border-color:var(--blue2);background:var(--blue3)}.market-cat{color:var(--text3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;font-family:DM Mono,monospace;font-size:14px}.market-q{color:var(--text);margin-bottom:8px;font-size:15px;font-weight:500;line-height:1.4}.market-footer{justify-content:space-between;align-items:center;gap:16px;display:flex}.prob-bar-wrap{flex:1;min-width:0}.prob-bar-bg{background:var(--bg4);border-radius:2px;height:5px;overflow:hidden}.prob-bar-fill{height:100%;width:var(--prob-width,0%);border-radius:2px;transition:width .8s}.prob-val{flex-shrink:0;font-family:DM Mono,monospace;font-size:15px;font-weight:600}.signal-badge{letter-spacing:.04em;border-radius:4px;flex-shrink:0;padding:2px 6px;font-family:DM Mono,monospace;font-size:14px;font-weight:600}.sig-bull{background:var(--green3);color:var(--green);border:.5px solid var(--green2)}.sig-bear{background:var(--red3);color:var(--red);border:.5px solid var(--red2)}.sig-neut{background:var(--bg4);color:var(--text2);border:.5px solid var(--border2)}.detail-panel{grid-area:2/1/3/2;max-height:100%;overflow:hidden}.detail-panel .panel-body{overflow-y:auto}.detail-header{justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:12px;display:flex}.detail-tag{color:var(--blue);text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px;font-family:DM Mono,monospace;font-size:14px}.detail-q{color:var(--text);font-size:16px;font-weight:600;line-height:1.5}.detail-meta{color:var(--text3);margin-top:2px;font-family:DM Mono,monospace;font-size:14px}.detail-metrics{flex-shrink:0;align-items:center;gap:16px;display:flex}.metric{text-align:right}.metric-label{color:var(--text3);margin-bottom:2px;font-family:DM Mono,monospace;font-size:14px}.metric-value{font-family:DM Mono,monospace;font-size:14px;font-weight:700}.metric-sep{background:var(--border);width:1px;height:40px}.outcomes-row{gap:16px;margin-bottom:12px;display:flex}.outcome-card{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-sm);text-align:center;flex:1;padding:14px}.outcome-name{color:var(--text2);margin-bottom:4px;font-family:DM Mono,monospace;font-size:14px}.outcome-price{color:var(--text);font-family:DM Mono,monospace;font-size:20px;font-weight:700}.outcome-delta{margin-top:2px;font-family:DM Mono,monospace;font-size:14px}.outcome-card.yes .outcome-price{color:var(--green)}.outcome-card.no .outcome-price{color:var(--red)}.chart-container{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-sm);flex:2;height:160px;min-height:120px;max-height:160px;padding:14px;overflow:hidden}.chart-container canvas{max-height:130px}.chart-label{color:var(--text3);margin-bottom:6px;font-family:DM Mono,monospace;font-size:14px}.ai-box{background:var(--bg3);border:.5px solid var(--blue2);border-radius:var(--radius-sm);align-items:flex-start;gap:14px;margin-bottom:10px;padding:14px;display:flex}.ai-icon{background:var(--blue3);border-radius:6px;flex-shrink:0;justify-content:center;align-items:center;width:32px;height:32px;font-size:14px;display:flex}.ai-label{color:var(--blue);text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px;font-family:DM Mono,monospace;font-size:14px}.ai-text{color:var(--text2);font-size:15px;line-height:1.5}.sim-row{flex-wrap:wrap;align-items:center;gap:16px;display:flex}.sim-label{color:var(--text3);font-family:DM Mono,monospace;font-size:15px}.sim-input{background:var(--bg3);border:.5px solid var(--border2);border-radius:var(--radius-sm);color:var(--text);outline:none;width:100px;padding:8px 14px;font-family:DM Mono,monospace;font-size:16px}.sim-input:focus{border-color:var(--blue2)}.sim-btn-yes{background:var(--green3);border:.5px solid var(--green2);color:var(--green);border-radius:var(--radius-sm);cursor:pointer;padding:8px 16px;font-family:DM Mono,monospace;font-size:15px;font-weight:600;transition:opacity .15s}.sim-btn-no{background:var(--red3);border:.5px solid var(--red2);color:var(--red);border-radius:var(--radius-sm);cursor:pointer;padding:8px 16px;font-family:DM Mono,monospace;font-size:15px;font-weight:600;transition:opacity .15s}.sim-btn-yes:hover,.sim-btn-no:hover{opacity:.85}.sim-disclaimer{color:var(--text3);font-family:DM Mono,monospace;font-size:14px}.legend{align-items:center;gap:16px;display:flex}.legend-item{color:var(--text3);align-items:center;gap:5px;font-family:DM Mono,monospace;font-size:14px;display:flex}.legend-dot{border-radius:50%;width:10px;height:10px}.legend-dot.green{background:var(--green)}.legend-dot.red{background:var(--red)}.legend-dot.gray{background:var(--text3)}.legend.end{margin-left:auto}.table-wrap{border:.5px solid var(--border);border-radius:var(--radius-sm);background:var(--bg3);overflow:auto}table{border-collapse:collapse;width:100%;font-size:16px}th,td{text-align:left;border-bottom:.5px solid var(--border);padding:14px}th{color:var(--text3);text-transform:uppercase;letter-spacing:.06em;background:var(--bg4);font-family:DM Mono,monospace;font-size:14px;font-weight:500;position:sticky;top:0}tr:hover td{background:#ffffff05}td{color:var(--text)}.td-mono{font-family:DM Mono,monospace}.td-green{color:var(--green)}.td-red{color:var(--red)}.td-blue{color:var(--blue)}.empty-state{text-align:center;color:var(--text3);padding:40px;font-size:16px}#app .leaflet-container{background:var(--bg3);font-family:DM Mono,monospace}#app .leaflet-popup-content-wrapper{background:var(--bg2);color:var(--text);border:.5px solid var(--border);border-radius:var(--radius-sm)}#app .leaflet-popup-tip{background:var(--bg2)}.sparkline{align-items:flex-end;gap:2px;height:32px;margin-top:6px;display:flex}.spark-bar{background:var(--blue2);border-radius:1px;width:3px;transition:height .3s}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text3)}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-start{justify-content:space-between;align-items:flex-start;display:flex}.flex-row{align-items:center;display:flex}.flex-wrap{flex-wrap:wrap}.gap-6{gap:14px}.gap-8{gap:16px}.text-green{color:var(--green)}.text-red{color:var(--red)}.text-blue{color:var(--blue)}.text-amber{color:var(--amber)}.text-neutral{color:var(--text3)}.bg-green{background:var(--green)}.bg-red{background:var(--red)}.bg-amber{background:var(--amber)}.flex-1{flex:1}.font-mono{font-family:DM Mono,monospace}.text-xs,.text-sm{font-size:14px}.text-base{font-size:15px}.text-lg{font-size:16px}.text-xl{font-size:18px}.font-semibold{font-weight:600}.font-bold{font-weight:700}.mb-4{margin-bottom:4px}.mb-6{margin-bottom:6px}.mb-8{margin-bottom:8px}.mt-4{margin-top:4px}.mt-6{margin-top:6px}.ml-auto{margin-left:auto}.divider{background:var(--border);height:1px;margin:8px 0}.empty-state-sm{text-align:center;color:var(--text3);padding:16px;font-size:16px}.map-popup{color:var(--text);max-width:200px;font-family:Syne,sans-serif;font-size:15px}.map-popup-cat{color:var(--text3);text-transform:uppercase;margin-bottom:4px;font-family:DM Mono,monospace;font-size:14px}.map-popup-q{margin-bottom:4px;font-weight:600;line-height:1.3}.map-popup-prices{gap:16px;font-family:DM Mono,monospace;font-size:15px;display:flex}.map-label-text{color:var(--text2);text-shadow:0 1px 2px #000;font-family:DM Mono,monospace;font-size:14px}@media (width<=1024px){.dashboard-grid{grid-template-rows:auto auto auto;grid-template-columns:1fr}.signals-panel{grid-area:auto;max-height:400px}.map-panel{grid-area:auto;min-height:300px}.detail-panel{grid-area:auto}}@media (width<=640px){.layout{grid-template-columns:0 1fr}.sidebar,.topbar-stats{display:none}.outcomes-row{flex-direction:column}}.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;top:0;left:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{-webkit-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:0 0}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{-webkit-transform-origin:0 0;width:1600px;height:1600px}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{width:auto;padding:0;max-width:none!important;max-height:none!important}.leaflet-container img.leaflet-tile{mix-blend-mode:plus-lighter}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:#33b5e566}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{box-sizing:border-box;z-index:800;width:0;height:0}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{z-index:800;pointer-events:visiblePainted;pointer-events:auto;position:relative}.leaflet-top,.leaflet-bottom{z-index:1000;pointer-events:none;position:absolute}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-popup{opacity:0;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{transform-origin:0 0}svg.leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{outline-offset:1px;background:#ddd}.leaflet-container a{color:#0078a8}.leaflet-zoom-box{background:#ffffff80;border:2px dotted #38f}.leaflet-container{font-family:Helvetica Neue,Arial,Helvetica,sans-serif;font-size:.75rem;line-height:1.5}.leaflet-bar{border-radius:4px;box-shadow:0 1px 5px #000000a6}.leaflet-bar a{text-align:center;color:#000;background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;text-decoration:none;display:block}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover,.leaflet-bar a:focus{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom:none;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.leaflet-bar a.leaflet-disabled{cursor:default;color:#bbb;background-color:#f4f4f4}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{text-indent:1px;font:700 18px Lucida Console,Monaco,monospace}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{background:#fff;border-radius:5px;box-shadow:0 1px 5px #0006}.leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{color:#333;background:#fff;padding:6px 10px 6px 6px}.leaflet-control-layers-scrollbar{padding-right:5px;overflow:hidden scroll}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{font-size:1.08333em;display:block}.leaflet-control-layers-separator{border-top:1px solid #ddd;height:0;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=)}.leaflet-container .leaflet-control-attribution{background:#fffc;margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{color:#333;padding:0 5px;line-height:1.4}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover,.leaflet-control-attribution a:focus{text-decoration:underline}.leaflet-attribution-flag{width:1em;height:.6669em;vertical-align:baseline!important;display:inline!important}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{white-space:nowrap;box-sizing:border-box;text-shadow:1px 1px #fff;background:#fffc;border:2px solid #777;border-top:none;padding:2px 5px 1px;line-height:1.1}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{box-shadow:none}.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{background-clip:padding-box;border:2px solid #0003}.leaflet-popup{text-align:center;margin-bottom:20px;position:absolute}.leaflet-popup-content-wrapper{text-align:left;border-radius:12px;padding:1px}.leaflet-popup-content{min-height:1px;margin:13px 24px 13px 20px;font-size:1.08333em;line-height:1.3}.leaflet-popup-content p{margin:1.3em 0}.leaflet-popup-tip-container{pointer-events:none;width:40px;height:20px;margin-top:-1px;margin-left:-20px;position:absolute;left:50%;overflow:hidden}.leaflet-popup-tip{pointer-events:auto;width:17px;height:17px;margin:-10px auto 0;padding:1px;transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{color:#333;background:#fff;box-shadow:0 3px 14px #0006}.leaflet-container a.leaflet-popup-close-button{text-align:center;color:#757575;background:0 0;border:none;width:24px;height:24px;font:16px/24px Tahoma,Verdana,sans-serif;text-decoration:none;position:absolute;top:0;right:0}.leaflet-container a.leaflet-popup-close-button:hover,.leaflet-container a.leaflet-popup-close-button:focus{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";width:24px;filter:progid:DXImageTransform.Microsoft.Matrix(M11=.707107, M12=.707107, M21=-.707107, M22=.707107);margin:0 auto}.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{color:#222;white-space:nowrap;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:#fff;border:1px solid #fff;border-radius:3px;padding:6px;position:absolute;box-shadow:0 1px 3px #0006}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-top:before,.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{pointer-events:none;content:"";background:0 0;border:6px solid #0000;position:absolute}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{margin-left:-6px;left:50%}.leaflet-tooltip-top:before{border-top-color:#fff;margin-bottom:-12px;bottom:0}.leaflet-tooltip-bottom:before{border-bottom-color:#fff;margin-top:-12px;margin-left:-6px;top:0}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{margin-top:-6px;top:50%}.leaflet-tooltip-left:before{border-left-color:#fff;margin-right:-12px;right:0}.leaflet-tooltip-right:before{border-right-color:#fff;margin-left:-12px;left:0}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
|
frontend/dist/index.html
CHANGED
|
@@ -5,13 +5,222 @@
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>PolySignal — Dashboard de Inteligencia de Mercados</title>
|
| 7 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 8 |
-
<script type="module" crossorigin src="/assets/index-
|
| 9 |
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
-
<div id="app">
|
| 13 |
-
|
| 14 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
</div>
|
| 16 |
|
| 17 |
</body>
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>PolySignal — Dashboard de Inteligencia de Mercados</title>
|
| 7 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 8 |
+
<script type="module" crossorigin src="/assets/index-A_KP_t7E.js"></script>
|
| 9 |
+
<link rel="stylesheet" crossorigin href="/assets/index-xtYPhhTl.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
+
<div id="app" class="layout">
|
| 13 |
+
|
| 14 |
+
<!-- Sidebar -->
|
| 15 |
+
<aside class="sidebar" id="sidebar">
|
| 16 |
+
<div class="sidebar-toggle" id="sidebar-toggle" title="Colapsar sidebar">◀</div>
|
| 17 |
+
<nav class="sidebar-nav">
|
| 18 |
+
<div class="nav-item active" data-view="dashboard">
|
| 19 |
+
<span class="nav-icon">◈</span>
|
| 20 |
+
<span class="nav-label">Panel</span>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="nav-item" data-view="positions">
|
| 23 |
+
<span class="nav-icon">◫</span>
|
| 24 |
+
<span class="nav-label">Posiciones</span>
|
| 25 |
+
</div>
|
| 26 |
+
<div class="nav-item" data-view="watchlist">
|
| 27 |
+
<span class="nav-icon">☆</span>
|
| 28 |
+
<span class="nav-label">Seguimiento</span>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="nav-item" data-view="alerts">
|
| 31 |
+
<span class="nav-icon">⚡</span>
|
| 32 |
+
<span class="nav-label">Alertas</span>
|
| 33 |
+
</div>
|
| 34 |
+
</nav>
|
| 35 |
+
<div class="sidebar-footer">
|
| 36 |
+
v0.1.0 · HF Spaces
|
| 37 |
+
</div>
|
| 38 |
+
</aside>
|
| 39 |
+
|
| 40 |
+
<!-- Topbar -->
|
| 41 |
+
<header class="topbar" id="topbar">
|
| 42 |
+
<div class="topbar-logo">
|
| 43 |
+
<div class="logo-dot"></div>
|
| 44 |
+
<span class="logo-text">PolySignal</span>
|
| 45 |
+
</div>
|
| 46 |
+
<div class="live-badge">
|
| 47 |
+
<div class="live-dot"></div>
|
| 48 |
+
EN VIVO
|
| 49 |
+
</div>
|
| 50 |
+
<div class="topbar-stats">
|
| 51 |
+
<div class="stat">
|
| 52 |
+
<span class="stat-label">Mercados</span>
|
| 53 |
+
<span class="stat-val" id="stat-markets">2.847</span>
|
| 54 |
+
</div>
|
| 55 |
+
<div class="stat">
|
| 56 |
+
<span class="stat-label">Volumen 24h</span>
|
| 57 |
+
<span class="stat-val" id="stat-volume">€4,2M</span>
|
| 58 |
+
<span class="stat-delta up" id="stat-volume-delta">+12,4%</span>
|
| 59 |
+
</div>
|
| 60 |
+
<div class="stat">
|
| 61 |
+
<span class="stat-label">Señales IA</span>
|
| 62 |
+
<span class="stat-val" id="stat-signals">183</span>
|
| 63 |
+
<span class="stat-delta up" id="stat-signals-delta">alcista</span>
|
| 64 |
+
</div>
|
| 65 |
+
<div class="stat">
|
| 66 |
+
<span class="stat-label">Alertas enviadas</span>
|
| 67 |
+
<span class="stat-val" id="stat-alerts">47</span>
|
| 68 |
+
<span class="stat-delta neutral">hoy</span>
|
| 69 |
+
</div>
|
| 70 |
+
<div class="legend end">
|
| 71 |
+
<div class="legend-item"><div class="legend-dot green"></div>alcista</div>
|
| 72 |
+
<div class="legend-item"><div class="legend-dot red"></div>bajista</div>
|
| 73 |
+
<div class="legend-item"><div class="legend-dot gray"></div>neutral</div>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="topbar-actions">
|
| 77 |
+
<button class="btn-ghost" id="btn-telegram">Alertas Telegram</button>
|
| 78 |
+
<button class="icon-btn" id="btn-notif" title="Notificaciones">◉</button>
|
| 79 |
+
</div>
|
| 80 |
+
</header>
|
| 81 |
+
|
| 82 |
+
<!-- Main content area -->
|
| 83 |
+
<main class="main" id="main">
|
| 84 |
+
|
| 85 |
+
<!-- DASHBOARD VIEW -->
|
| 86 |
+
<section class="view active" id="view-dashboard">
|
| 87 |
+
<div class="dashboard-grid">
|
| 88 |
+
|
| 89 |
+
<!-- Map Panel -->
|
| 90 |
+
<div class="panel map-panel" id="panel-map">
|
| 91 |
+
<div class="panel-header" data-panel="map">
|
| 92 |
+
<div class="panel-title">
|
| 93 |
+
<span>◈</span>
|
| 94 |
+
Mapa global de predicciones
|
| 95 |
+
<span class="panel-subtitle">· tamaño = volumen · color = señal IA</span>
|
| 96 |
+
</div>
|
| 97 |
+
<span class="panel-toggle">▼</span>
|
| 98 |
+
</div>
|
| 99 |
+
<div class="panel-body">
|
| 100 |
+
<div id="map-container"></div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<!-- Signals Panel -->
|
| 105 |
+
<div class="panel signals-panel" id="panel-signals">
|
| 106 |
+
<div class="panel-header" data-panel="signals">
|
| 107 |
+
<div class="panel-title">
|
| 108 |
+
<span>◈</span>
|
| 109 |
+
Señales IA — mercados top
|
| 110 |
+
</div>
|
| 111 |
+
<span class="panel-toggle">▼</span>
|
| 112 |
+
</div>
|
| 113 |
+
<div class="panel-body">
|
| 114 |
+
<div class="signals-list" id="signals-list"></div>
|
| 115 |
+
<div class="positions-separator">
|
| 116 |
+
<div class="panel-title mb-sm">Mis posiciones</div>
|
| 117 |
+
<div id="mini-positions"></div>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<!-- Detail Panel -->
|
| 123 |
+
<div class="panel detail-panel" id="panel-detail">
|
| 124 |
+
<div class="panel-header" data-panel="detail">
|
| 125 |
+
<div class="panel-title">
|
| 126 |
+
<span>◈</span>
|
| 127 |
+
Detalle del mercado
|
| 128 |
+
</div>
|
| 129 |
+
<span class="panel-toggle">▼</span>
|
| 130 |
+
</div>
|
| 131 |
+
<div class="panel-body" id="detail-body">
|
| 132 |
+
<!-- Dynamic content -->
|
| 133 |
+
</div>
|
| 134 |
+
</div>
|
| 135 |
+
|
| 136 |
+
</div>
|
| 137 |
+
</section>
|
| 138 |
+
|
| 139 |
+
<!-- POSITIONS VIEW -->
|
| 140 |
+
<section class="view" id="view-positions">
|
| 141 |
+
<div class="panel full-height">
|
| 142 |
+
<div class="panel-header">
|
| 143 |
+
<div class="panel-title"><span>◫</span> Simulador — Posiciones abiertas</div>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="panel-body">
|
| 146 |
+
<div class="table-wrap">
|
| 147 |
+
<table id="positions-table">
|
| 148 |
+
<thead>
|
| 149 |
+
<tr>
|
| 150 |
+
<th>Mercado</th>
|
| 151 |
+
<th>Resultado</th>
|
| 152 |
+
<th>Cantidad</th>
|
| 153 |
+
<th>Entrada</th>
|
| 154 |
+
<th>Actual</th>
|
| 155 |
+
<th>G&P</th>
|
| 156 |
+
<th>Kelly</th>
|
| 157 |
+
<th>Abierta</th>
|
| 158 |
+
<th></th>
|
| 159 |
+
</tr>
|
| 160 |
+
</thead>
|
| 161 |
+
<tbody></tbody>
|
| 162 |
+
</table>
|
| 163 |
+
</div>
|
| 164 |
+
<div class="empty-state hidden" id="positions-empty">No hay posiciones abiertas. Ve al Panel para simular una operación.</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
</section>
|
| 168 |
+
|
| 169 |
+
<!-- WATCHLIST VIEW -->
|
| 170 |
+
<section class="view" id="view-watchlist">
|
| 171 |
+
<div class="panel full-height">
|
| 172 |
+
<div class="panel-header">
|
| 173 |
+
<div class="panel-title"><span>☆</span> Lista de seguimiento</div>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="panel-body">
|
| 176 |
+
<div class="table-wrap">
|
| 177 |
+
<table id="watchlist-table">
|
| 178 |
+
<thead>
|
| 179 |
+
<tr>
|
| 180 |
+
<th>Mercado</th>
|
| 181 |
+
<th>Categoría</th>
|
| 182 |
+
<th>Sí</th>
|
| 183 |
+
<th>No</th>
|
| 184 |
+
<th>Señal</th>
|
| 185 |
+
<th>Volumen</th>
|
| 186 |
+
<th>Umbral de alerta</th>
|
| 187 |
+
<th></th>
|
| 188 |
+
</tr>
|
| 189 |
+
</thead>
|
| 190 |
+
<tbody></tbody>
|
| 191 |
+
</table>
|
| 192 |
+
</div>
|
| 193 |
+
<div class="empty-state hidden" id="watchlist-empty">Tu lista de seguimiento está vacía. Añade mercados desde el Panel.</div>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</section>
|
| 197 |
+
|
| 198 |
+
<!-- ALERTS VIEW -->
|
| 199 |
+
<section class="view" id="view-alerts">
|
| 200 |
+
<div class="panel full-height">
|
| 201 |
+
<div class="panel-header">
|
| 202 |
+
<div class="panel-title"><span>⚡</span> Historial de alertas</div>
|
| 203 |
+
</div>
|
| 204 |
+
<div class="panel-body">
|
| 205 |
+
<div class="table-wrap">
|
| 206 |
+
<table id="alerts-table">
|
| 207 |
+
<thead>
|
| 208 |
+
<tr>
|
| 209 |
+
<th>Hora</th>
|
| 210 |
+
<th>Mercado</th>
|
| 211 |
+
<th>Tipo</th>
|
| 212 |
+
<th>Mensaje</th>
|
| 213 |
+
</tr>
|
| 214 |
+
</thead>
|
| 215 |
+
<tbody></tbody>
|
| 216 |
+
</table>
|
| 217 |
+
</div>
|
| 218 |
+
<div class="empty-state hidden" id="alerts-empty">Aún no se han enviado alertas.</div>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
</section>
|
| 222 |
+
|
| 223 |
+
</main>
|
| 224 |
</div>
|
| 225 |
|
| 226 |
</body>
|
frontend/index.html
CHANGED
|
@@ -7,9 +7,218 @@
|
|
| 7 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
-
<div id="app">
|
| 11 |
-
|
| 12 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</div>
|
| 14 |
|
| 15 |
<script type="module" src="/src/main.js"></script>
|
|
|
|
| 7 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
+
<div id="app" class="layout">
|
| 11 |
+
|
| 12 |
+
<!-- Sidebar -->
|
| 13 |
+
<aside class="sidebar" id="sidebar">
|
| 14 |
+
<div class="sidebar-toggle" id="sidebar-toggle" title="Colapsar sidebar">◀</div>
|
| 15 |
+
<nav class="sidebar-nav">
|
| 16 |
+
<div class="nav-item active" data-view="dashboard">
|
| 17 |
+
<span class="nav-icon">◈</span>
|
| 18 |
+
<span class="nav-label">Panel</span>
|
| 19 |
+
</div>
|
| 20 |
+
<div class="nav-item" data-view="positions">
|
| 21 |
+
<span class="nav-icon">◫</span>
|
| 22 |
+
<span class="nav-label">Posiciones</span>
|
| 23 |
+
</div>
|
| 24 |
+
<div class="nav-item" data-view="watchlist">
|
| 25 |
+
<span class="nav-icon">☆</span>
|
| 26 |
+
<span class="nav-label">Seguimiento</span>
|
| 27 |
+
</div>
|
| 28 |
+
<div class="nav-item" data-view="alerts">
|
| 29 |
+
<span class="nav-icon">⚡</span>
|
| 30 |
+
<span class="nav-label">Alertas</span>
|
| 31 |
+
</div>
|
| 32 |
+
</nav>
|
| 33 |
+
<div class="sidebar-footer">
|
| 34 |
+
v0.1.0 · HF Spaces
|
| 35 |
+
</div>
|
| 36 |
+
</aside>
|
| 37 |
+
|
| 38 |
+
<!-- Topbar -->
|
| 39 |
+
<header class="topbar" id="topbar">
|
| 40 |
+
<div class="topbar-logo">
|
| 41 |
+
<div class="logo-dot"></div>
|
| 42 |
+
<span class="logo-text">PolySignal</span>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="live-badge">
|
| 45 |
+
<div class="live-dot"></div>
|
| 46 |
+
EN VIVO
|
| 47 |
+
</div>
|
| 48 |
+
<div class="topbar-stats">
|
| 49 |
+
<div class="stat">
|
| 50 |
+
<span class="stat-label">Mercados</span>
|
| 51 |
+
<span class="stat-val" id="stat-markets">2.847</span>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="stat">
|
| 54 |
+
<span class="stat-label">Volumen 24h</span>
|
| 55 |
+
<span class="stat-val" id="stat-volume">€4,2M</span>
|
| 56 |
+
<span class="stat-delta up" id="stat-volume-delta">+12,4%</span>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="stat">
|
| 59 |
+
<span class="stat-label">Señales IA</span>
|
| 60 |
+
<span class="stat-val" id="stat-signals">183</span>
|
| 61 |
+
<span class="stat-delta up" id="stat-signals-delta">alcista</span>
|
| 62 |
+
</div>
|
| 63 |
+
<div class="stat">
|
| 64 |
+
<span class="stat-label">Alertas enviadas</span>
|
| 65 |
+
<span class="stat-val" id="stat-alerts">47</span>
|
| 66 |
+
<span class="stat-delta neutral">hoy</span>
|
| 67 |
+
</div>
|
| 68 |
+
<div class="legend end">
|
| 69 |
+
<div class="legend-item"><div class="legend-dot green"></div>alcista</div>
|
| 70 |
+
<div class="legend-item"><div class="legend-dot red"></div>bajista</div>
|
| 71 |
+
<div class="legend-item"><div class="legend-dot gray"></div>neutral</div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
<div class="topbar-actions">
|
| 75 |
+
<button class="btn-ghost" id="btn-telegram">Alertas Telegram</button>
|
| 76 |
+
<button class="icon-btn" id="btn-notif" title="Notificaciones">◉</button>
|
| 77 |
+
</div>
|
| 78 |
+
</header>
|
| 79 |
+
|
| 80 |
+
<!-- Main content area -->
|
| 81 |
+
<main class="main" id="main">
|
| 82 |
+
|
| 83 |
+
<!-- DASHBOARD VIEW -->
|
| 84 |
+
<section class="view active" id="view-dashboard">
|
| 85 |
+
<div class="dashboard-grid">
|
| 86 |
+
|
| 87 |
+
<!-- Map Panel -->
|
| 88 |
+
<div class="panel map-panel" id="panel-map">
|
| 89 |
+
<div class="panel-header" data-panel="map">
|
| 90 |
+
<div class="panel-title">
|
| 91 |
+
<span>◈</span>
|
| 92 |
+
Mapa global de predicciones
|
| 93 |
+
<span class="panel-subtitle">· tamaño = volumen · color = señal IA</span>
|
| 94 |
+
</div>
|
| 95 |
+
<span class="panel-toggle">▼</span>
|
| 96 |
+
</div>
|
| 97 |
+
<div class="panel-body">
|
| 98 |
+
<div id="map-container"></div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
<!-- Signals Panel -->
|
| 103 |
+
<div class="panel signals-panel" id="panel-signals">
|
| 104 |
+
<div class="panel-header" data-panel="signals">
|
| 105 |
+
<div class="panel-title">
|
| 106 |
+
<span>◈</span>
|
| 107 |
+
Señales IA — mercados top
|
| 108 |
+
</div>
|
| 109 |
+
<span class="panel-toggle">▼</span>
|
| 110 |
+
</div>
|
| 111 |
+
<div class="panel-body">
|
| 112 |
+
<div class="signals-list" id="signals-list"></div>
|
| 113 |
+
<div class="positions-separator">
|
| 114 |
+
<div class="panel-title mb-sm">Mis posiciones</div>
|
| 115 |
+
<div id="mini-positions"></div>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<!-- Detail Panel -->
|
| 121 |
+
<div class="panel detail-panel" id="panel-detail">
|
| 122 |
+
<div class="panel-header" data-panel="detail">
|
| 123 |
+
<div class="panel-title">
|
| 124 |
+
<span>◈</span>
|
| 125 |
+
Detalle del mercado
|
| 126 |
+
</div>
|
| 127 |
+
<span class="panel-toggle">▼</span>
|
| 128 |
+
</div>
|
| 129 |
+
<div class="panel-body" id="detail-body">
|
| 130 |
+
<!-- Dynamic content -->
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
</div>
|
| 135 |
+
</section>
|
| 136 |
+
|
| 137 |
+
<!-- POSITIONS VIEW -->
|
| 138 |
+
<section class="view" id="view-positions">
|
| 139 |
+
<div class="panel full-height">
|
| 140 |
+
<div class="panel-header">
|
| 141 |
+
<div class="panel-title"><span>◫</span> Simulador — Posiciones abiertas</div>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="panel-body">
|
| 144 |
+
<div class="table-wrap">
|
| 145 |
+
<table id="positions-table">
|
| 146 |
+
<thead>
|
| 147 |
+
<tr>
|
| 148 |
+
<th>Mercado</th>
|
| 149 |
+
<th>Resultado</th>
|
| 150 |
+
<th>Cantidad</th>
|
| 151 |
+
<th>Entrada</th>
|
| 152 |
+
<th>Actual</th>
|
| 153 |
+
<th>G&P</th>
|
| 154 |
+
<th>Kelly</th>
|
| 155 |
+
<th>Abierta</th>
|
| 156 |
+
<th></th>
|
| 157 |
+
</tr>
|
| 158 |
+
</thead>
|
| 159 |
+
<tbody></tbody>
|
| 160 |
+
</table>
|
| 161 |
+
</div>
|
| 162 |
+
<div class="empty-state hidden" id="positions-empty">No hay posiciones abiertas. Ve al Panel para simular una operación.</div>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</section>
|
| 166 |
+
|
| 167 |
+
<!-- WATCHLIST VIEW -->
|
| 168 |
+
<section class="view" id="view-watchlist">
|
| 169 |
+
<div class="panel full-height">
|
| 170 |
+
<div class="panel-header">
|
| 171 |
+
<div class="panel-title"><span>☆</span> Lista de seguimiento</div>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="panel-body">
|
| 174 |
+
<div class="table-wrap">
|
| 175 |
+
<table id="watchlist-table">
|
| 176 |
+
<thead>
|
| 177 |
+
<tr>
|
| 178 |
+
<th>Mercado</th>
|
| 179 |
+
<th>Categoría</th>
|
| 180 |
+
<th>Sí</th>
|
| 181 |
+
<th>No</th>
|
| 182 |
+
<th>Señal</th>
|
| 183 |
+
<th>Volumen</th>
|
| 184 |
+
<th>Umbral de alerta</th>
|
| 185 |
+
<th></th>
|
| 186 |
+
</tr>
|
| 187 |
+
</thead>
|
| 188 |
+
<tbody></tbody>
|
| 189 |
+
</table>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="empty-state hidden" id="watchlist-empty">Tu lista de seguimiento está vacía. Añade mercados desde el Panel.</div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</section>
|
| 195 |
+
|
| 196 |
+
<!-- ALERTS VIEW -->
|
| 197 |
+
<section class="view" id="view-alerts">
|
| 198 |
+
<div class="panel full-height">
|
| 199 |
+
<div class="panel-header">
|
| 200 |
+
<div class="panel-title"><span>⚡</span> Historial de alertas</div>
|
| 201 |
+
</div>
|
| 202 |
+
<div class="panel-body">
|
| 203 |
+
<div class="table-wrap">
|
| 204 |
+
<table id="alerts-table">
|
| 205 |
+
<thead>
|
| 206 |
+
<tr>
|
| 207 |
+
<th>Hora</th>
|
| 208 |
+
<th>Mercado</th>
|
| 209 |
+
<th>Tipo</th>
|
| 210 |
+
<th>Mensaje</th>
|
| 211 |
+
</tr>
|
| 212 |
+
</thead>
|
| 213 |
+
<tbody></tbody>
|
| 214 |
+
</table>
|
| 215 |
+
</div>
|
| 216 |
+
<div class="empty-state hidden" id="alerts-empty">Aún no se han enviado alertas.</div>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
</section>
|
| 220 |
+
|
| 221 |
+
</main>
|
| 222 |
</div>
|
| 223 |
|
| 224 |
<script type="module" src="/src/main.js"></script>
|
frontend/package.json
CHANGED
|
@@ -9,11 +9,14 @@
|
|
| 9 |
"preview": "vite preview"
|
| 10 |
},
|
| 11 |
"devDependencies": {
|
| 12 |
-
"vite": "^
|
|
|
|
|
|
|
|
|
|
| 13 |
},
|
| 14 |
"dependencies": {
|
| 15 |
-
"chart.js": "^4.
|
| 16 |
"leaflet": "^1.9.4",
|
| 17 |
-
"socket.io-client": "^4.8.
|
| 18 |
}
|
| 19 |
}
|
|
|
|
| 9 |
"preview": "vite preview"
|
| 10 |
},
|
| 11 |
"devDependencies": {
|
| 12 |
+
"vite": "^8.0.13"
|
| 13 |
+
},
|
| 14 |
+
"engines": {
|
| 15 |
+
"node": ">=26.0.0"
|
| 16 |
},
|
| 17 |
"dependencies": {
|
| 18 |
+
"chart.js": "^4.5.1",
|
| 19 |
"leaflet": "^1.9.4",
|
| 20 |
+
"socket.io-client": "^4.8.3"
|
| 21 |
}
|
| 22 |
}
|
frontend/src/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
/**
|
| 2 |
* Cliente HTTP para consumir la API REST del backend.
|
| 3 |
*
|
| 4 |
-
*
|
| 5 |
* - getMarkets(), getMarket(id), getSignal(id)
|
| 6 |
* - createPosition(data), getPositions(), closePosition(id)
|
| 7 |
* - addToWatchlist(marketId), removeFromWatchlist(marketId), getWatchlist()
|
|
@@ -9,3 +9,75 @@
|
|
| 9 |
*
|
| 10 |
* Base URL: /api/v1 (mismo dominio, sin CORS en producción HF Spaces).
|
| 11 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
* Cliente HTTP para consumir la API REST del backend.
|
| 3 |
*
|
| 4 |
+
* Endpoints:
|
| 5 |
* - getMarkets(), getMarket(id), getSignal(id)
|
| 6 |
* - createPosition(data), getPositions(), closePosition(id)
|
| 7 |
* - addToWatchlist(marketId), removeFromWatchlist(marketId), getWatchlist()
|
|
|
|
| 9 |
*
|
| 10 |
* Base URL: /api/v1 (mismo dominio, sin CORS en producción HF Spaces).
|
| 11 |
*/
|
| 12 |
+
|
| 13 |
+
const BASE = '/api/v1'
|
| 14 |
+
|
| 15 |
+
async function fetchJson(url, opts = {}) {
|
| 16 |
+
const res = await fetch(url, {
|
| 17 |
+
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
| 18 |
+
...opts,
|
| 19 |
+
})
|
| 20 |
+
if (!res.ok) {
|
| 21 |
+
const text = await res.text().catch(() => '')
|
| 22 |
+
throw new Error(`HTTP ${res.status}: ${text}`)
|
| 23 |
+
}
|
| 24 |
+
if (res.status === 204) return null
|
| 25 |
+
return res.json()
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/* ─── Markets ─── */
|
| 29 |
+
export async function getMarkets(params = {}) {
|
| 30 |
+
const qs = new URLSearchParams(params).toString()
|
| 31 |
+
return fetchJson(`${BASE}/markets${qs ? '?' + qs : ''}`)
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export async function getMarket(id) {
|
| 35 |
+
return fetchJson(`${BASE}/markets/${id}`)
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/* ─── Signals ─── */
|
| 39 |
+
export async function getSignal(marketId) {
|
| 40 |
+
return fetchJson(`${BASE}/markets/${marketId}/signal`)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/* ─── Positions ─── */
|
| 44 |
+
export async function getPositions() {
|
| 45 |
+
return fetchJson(`${BASE}/positions`)
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
export async function createPosition(data) {
|
| 49 |
+
return fetchJson(`${BASE}/positions`, {
|
| 50 |
+
method: 'POST',
|
| 51 |
+
body: JSON.stringify(data),
|
| 52 |
+
})
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
export async function closePosition(id) {
|
| 56 |
+
return fetchJson(`${BASE}/positions/${id}`, { method: 'DELETE' })
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/* ─── Watchlist ─── */
|
| 60 |
+
export async function getWatchlist() {
|
| 61 |
+
return fetchJson(`${BASE}/watchlist`)
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
export async function addToWatchlist(marketId, alertThreshold) {
|
| 65 |
+
return fetchJson(`${BASE}/watchlist`, {
|
| 66 |
+
method: 'POST',
|
| 67 |
+
body: JSON.stringify({ marketId, alertThreshold }),
|
| 68 |
+
})
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
export async function removeFromWatchlist(marketId) {
|
| 72 |
+
return fetchJson(`${BASE}/watchlist/${marketId}`, { method: 'DELETE' })
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/* ─── Alerts ─── */
|
| 76 |
+
export async function getAlerts() {
|
| 77 |
+
return fetchJson(`${BASE}/alerts`)
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* ─── Stats ─── */
|
| 81 |
+
export async function getStats() {
|
| 82 |
+
return fetchJson(`${BASE}/stats`)
|
| 83 |
+
}
|
frontend/src/app.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
| 1 |
/**
|
| 2 |
-
*
|
| 3 |
*
|
| 4 |
* Responsabilidades:
|
| 5 |
-
* -
|
| 6 |
-
*
|
| 7 |
-
* -
|
| 8 |
-
* -
|
| 9 |
-
*
|
| 10 |
-
* Se ejecuta al cargar index.html.
|
| 11 |
*/
|
| 12 |
|
| 13 |
import { io } from 'socket.io-client'
|
|
@@ -16,22 +14,626 @@ import * as charts from './charts.js'
|
|
| 16 |
import * as map from './map.js'
|
| 17 |
import * as simulator from './simulator.js'
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
|
| 25 |
-
socket.on('market_update', (data) => {
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
socket.on('ai_signal', (data) => {
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
socket.on('price_alert', (data) => {
|
| 34 |
-
|
| 35 |
-
|
|
|
|
| 36 |
|
| 37 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Lógica principal de la SPA PolySignal.
|
| 3 |
*
|
| 4 |
* Responsabilidades:
|
| 5 |
+
* - Routing de vistas (dashboard, positions, watchlist, alerts)
|
| 6 |
+
* - Sidebar y paneles colapsables
|
| 7 |
+
* - Carga inicial de datos y actualizaciones en tiempo real
|
| 8 |
+
* - Integración con api.js, map.js, charts.js, simulator.js
|
|
|
|
|
|
|
| 9 |
*/
|
| 10 |
|
| 11 |
import { io } from 'socket.io-client'
|
|
|
|
| 14 |
import * as map from './map.js'
|
| 15 |
import * as simulator from './simulator.js'
|
| 16 |
|
| 17 |
+
/* ─── Estado global ─── */
|
| 18 |
+
let state = {
|
| 19 |
+
view: 'dashboard',
|
| 20 |
+
activeMarketId: null,
|
| 21 |
+
markets: [],
|
| 22 |
+
signals: [],
|
| 23 |
+
positions: [],
|
| 24 |
+
watchlist: [],
|
| 25 |
+
alerts: [],
|
| 26 |
+
collapsedPanels: new Set(),
|
| 27 |
+
sidebarCollapsed: false,
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* ─── Datos mock para demo (fallback si backend no responde) ─── */
|
| 31 |
+
const MOCK_MARKETS = [
|
| 32 |
+
{
|
| 33 |
+
id: 'usa-001',
|
| 34 |
+
question: '¿Trump firmará la ley fiscal antes de junio de 2026?',
|
| 35 |
+
category: 'política',
|
| 36 |
+
countryCode: 'US',
|
| 37 |
+
yesPrice: 0.73,
|
| 38 |
+
noPrice: 0.27,
|
| 39 |
+
volumeEur: 1240000,
|
| 40 |
+
liquidityEur: 340000,
|
| 41 |
+
status: 'active',
|
| 42 |
+
closesAt: '2026-06-01T00:00:00Z',
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
id: 'eur-001',
|
| 46 |
+
question: '¿El BCE recortará tipos en junio de 2026?',
|
| 47 |
+
category: 'economía',
|
| 48 |
+
countryCode: 'DE',
|
| 49 |
+
yesPrice: 0.34,
|
| 50 |
+
noPrice: 0.66,
|
| 51 |
+
volumeEur: 890000,
|
| 52 |
+
liquidityEur: 210000,
|
| 53 |
+
status: 'active',
|
| 54 |
+
closesAt: '2026-06-12T00:00:00Z',
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
id: 'chn-001',
|
| 58 |
+
question: '¿Se alcanzará un acuerdo arancelario EE.UU.-China antes del T3 2026?',
|
| 59 |
+
category: 'comercio',
|
| 60 |
+
countryCode: 'CN',
|
| 61 |
+
yesPrice: 0.51,
|
| 62 |
+
noPrice: 0.49,
|
| 63 |
+
volumeEur: 2100000,
|
| 64 |
+
liquidityEur: 580000,
|
| 65 |
+
status: 'active',
|
| 66 |
+
closesAt: '2026-07-01T00:00:00Z',
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
id: 'bra-001',
|
| 70 |
+
question: '¿Bitcoin superará los $120.000 antes de julio de 2026?',
|
| 71 |
+
category: 'cripto',
|
| 72 |
+
countryCode: 'BR',
|
| 73 |
+
yesPrice: 0.61,
|
| 74 |
+
noPrice: 0.39,
|
| 75 |
+
volumeEur: 3400000,
|
| 76 |
+
liquidityEur: 920000,
|
| 77 |
+
status: 'active',
|
| 78 |
+
closesAt: '2026-07-01T00:00:00Z',
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
id: 'uk-001',
|
| 82 |
+
question: '¿La inflación del Reino Unido bajará del 2% en 2026?',
|
| 83 |
+
category: 'economía',
|
| 84 |
+
countryCode: 'GB',
|
| 85 |
+
yesPrice: 0.45,
|
| 86 |
+
noPrice: 0.55,
|
| 87 |
+
volumeEur: 620000,
|
| 88 |
+
liquidityEur: 180000,
|
| 89 |
+
status: 'active',
|
| 90 |
+
closesAt: '2026-09-01T00:00:00Z',
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
id: 'ind-001',
|
| 94 |
+
question: '¿El crecimiento del PIB de India superará el 7% en el AF2026?',
|
| 95 |
+
category: 'economía',
|
| 96 |
+
countryCode: 'IN',
|
| 97 |
+
yesPrice: 0.68,
|
| 98 |
+
noPrice: 0.32,
|
| 99 |
+
volumeEur: 450000,
|
| 100 |
+
liquidityEur: 120000,
|
| 101 |
+
status: 'active',
|
| 102 |
+
closesAt: '2026-03-31T00:00:00Z',
|
| 103 |
+
},
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
const MOCK_SIGNALS = {
|
| 107 |
+
'usa-001': {
|
| 108 |
+
signal: 'bullish',
|
| 109 |
+
confidence: 0.87,
|
| 110 |
+
summary: 'El liderazgo republicano confirmó 3 de 4 votos de comité asegurados. El sentimiento de noticias de 8 fuentes es positivo (+0,72). La línea temporal del Congreso sugiere que la ventana de firma se abre del 28 al 31 de mayo.',
|
| 111 |
+
keyRisk: 'El proceso de enmiendas en el Senado podría retrasar más allá del 1 de junio.',
|
| 112 |
+
},
|
| 113 |
+
'eur-001': {
|
| 114 |
+
signal: 'bearish',
|
| 115 |
+
confidence: 0.74,
|
| 116 |
+
summary: 'La inflación de la zona euro sorprendió al alza al 2,4% en abril. Las recientes declaraciones de los miembros del consejo de gobierno del BCE señalan que es más probable una pausa.',
|
| 117 |
+
keyRisk: 'La valoración del mercado de bonos implica solo un 22% de probabilidad de recorte en junio.',
|
| 118 |
+
},
|
| 119 |
+
'chn-001': {
|
| 120 |
+
signal: 'neutral',
|
| 121 |
+
confidence: 0.52,
|
| 122 |
+
summary: 'Las negociaciones continúan pero no se ha acordado un marco formal. Los flujos comerciales muestran patrones tempranos de evasión arancelaria, sugiriendo que ambas partes ganan tiempo.',
|
| 123 |
+
keyRisk: 'Demasiadas variables geopolíticas. Se monitorea la declaración del USTR como señal clave.',
|
| 124 |
+
},
|
| 125 |
+
'bra-001': {
|
| 126 |
+
signal: 'bullish',
|
| 127 |
+
confidence: 0.79,
|
| 128 |
+
summary: 'BTC actualmente en $103.400. Las métricas on-chain muestran acumulación de grandes carteras. Entradas en ETF +$820M esta semana. El mercado de opciones implica 68% de probabilidad de $120K a finales de junio.',
|
| 129 |
+
keyRisk: 'Las condiciones de liquidez macro podrían cambiar si la Fed mantiene una postura hawkish.',
|
| 130 |
+
},
|
| 131 |
+
'uk-001': {
|
| 132 |
+
signal: 'neutral',
|
| 133 |
+
confidence: 0.58,
|
| 134 |
+
summary: 'Las previsiones del BoE sugieren una caída gradual pero los shocks externos de precios de energía siguen siendo una incógnita. La inflación de servicios se mantiene en el 5,2%.',
|
| 135 |
+
keyRisk: 'Las interrupciones geopolíticas en la cadena de suministro podrían reavivar la inflación de bienes.',
|
| 136 |
+
},
|
| 137 |
+
'ind-001': {
|
| 138 |
+
signal: 'bullish',
|
| 139 |
+
confidence: 0.71,
|
| 140 |
+
summary: 'Dato del T4 AF25 en 7,1% con expansión del PMI manufacturero. El impulso del gasto público en infraestructura probablemente sostenga el momentum en el S1 AF26.',
|
| 141 |
+
keyRisk: 'El impacto de El Niño en la producción agrícola podría reducir 30-40 pb del dato principal.',
|
| 142 |
+
},
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
const MOCK_POSITIONS = [
|
| 146 |
+
{
|
| 147 |
+
id: 1,
|
| 148 |
+
marketId: 'usa-001',
|
| 149 |
+
outcome: 'SÍ',
|
| 150 |
+
amountEur: 100,
|
| 151 |
+
entryPrice: 0.68,
|
| 152 |
+
currentPrice: 0.73,
|
| 153 |
+
pnl: 14.20,
|
| 154 |
+
kellyFraction: 0.25,
|
| 155 |
+
openedAt: '2026-05-10T14:30:00Z',
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
id: 2,
|
| 159 |
+
marketId: 'eur-001',
|
| 160 |
+
outcome: 'NO',
|
| 161 |
+
amountEur: 80,
|
| 162 |
+
entryPrice: 0.62,
|
| 163 |
+
currentPrice: 0.66,
|
| 164 |
+
pnl: -3.80,
|
| 165 |
+
kellyFraction: 0.18,
|
| 166 |
+
openedAt: '2026-05-11T09:15:00Z',
|
| 167 |
+
},
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
const MOCK_ALERTS = [
|
| 171 |
+
{
|
| 172 |
+
id: 1,
|
| 173 |
+
marketId: 'usa-001',
|
| 174 |
+
type: 'cambio_señal',
|
| 175 |
+
message: 'La señal cambió a ALCISTA (87%) en ley fiscal de Trump',
|
| 176 |
+
sentAt: '2026-05-14T08:23:00Z',
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
id: 2,
|
| 180 |
+
marketId: 'bra-001',
|
| 181 |
+
type: 'umbral_precio',
|
| 182 |
+
message: 'El mercado BTC $120K SÍ cruzó el umbral de 60¢',
|
| 183 |
+
sentAt: '2026-05-14T07:45:00Z',
|
| 184 |
+
},
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
/* ─── Helpers ─── */
|
| 188 |
+
function formatCurrency(n) {
|
| 189 |
+
if (n >= 1e6) return '€' + (n / 1e6).toFixed(1) + 'M'
|
| 190 |
+
if (n >= 1e3) return '€' + (n / 1e3).toFixed(1) + 'K'
|
| 191 |
+
return '€' + n.toFixed(0)
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
function formatPrice(p) {
|
| 195 |
+
return Math.round(p * 100) + '¢'
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
function formatDate(iso) {
|
| 199 |
+
const d = new Date(iso)
|
| 200 |
+
return d.toLocaleDateString('es-ES', { month: 'short', day: 'numeric', year: 'numeric' })
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
function signalColorClass(signal) {
|
| 204 |
+
if (signal === 'bullish') return 'green'
|
| 205 |
+
if (signal === 'bearish') return 'red'
|
| 206 |
+
return 'amber'
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function getSignalBadgeClass(signal) {
|
| 210 |
+
if (signal === 'bullish') return 'sig-bull'
|
| 211 |
+
if (signal === 'bearish') return 'sig-bear'
|
| 212 |
+
return 'sig-neut'
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
function getSignalLabel(signal) {
|
| 216 |
+
if (signal === 'bullish') return 'ALC'
|
| 217 |
+
if (signal === 'bearish') return 'BAJ'
|
| 218 |
+
return 'NEUT'
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
function translateSignal(signal) {
|
| 222 |
+
if (signal === 'bullish') return 'alcista'
|
| 223 |
+
if (signal === 'bearish') return 'bajista'
|
| 224 |
+
return 'neutral'
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
/* ─── Routing de vistas ─── */
|
| 228 |
+
function switchView(viewName) {
|
| 229 |
+
state.view = viewName
|
| 230 |
+
document.querySelectorAll('.view').forEach((el) => el.classList.toggle('active', el.id === `view-${viewName}`))
|
| 231 |
+
document.querySelectorAll('.nav-item').forEach((el) => el.classList.toggle('active', el.dataset.view === viewName))
|
| 232 |
+
if (viewName === 'positions') renderPositions()
|
| 233 |
+
if (viewName === 'watchlist') renderWatchlist()
|
| 234 |
+
if (viewName === 'alerts') renderAlerts()
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
/* ─── Sidebar toggle ─── */
|
| 238 |
+
function toggleSidebar() {
|
| 239 |
+
state.sidebarCollapsed = !state.sidebarCollapsed
|
| 240 |
+
document.getElementById('app').classList.toggle('collapsed', state.sidebarCollapsed)
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
/* ─── Panel toggle ─── */
|
| 244 |
+
function togglePanel(panelId) {
|
| 245 |
+
const panel = document.getElementById(`panel-${panelId}`)
|
| 246 |
+
if (!panel) return
|
| 247 |
+
const isCollapsed = panel.classList.toggle('collapsed')
|
| 248 |
+
if (isCollapsed) state.collapsedPanels.add(panelId)
|
| 249 |
+
else state.collapsedPanels.delete(panelId)
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
/* ─── Render signals list ─── */
|
| 253 |
+
function renderSignals() {
|
| 254 |
+
const container = document.getElementById('signals-list')
|
| 255 |
+
if (!container) return
|
| 256 |
+
container.innerHTML = state.markets
|
| 257 |
+
.map((m) => {
|
| 258 |
+
const sig = state.signals.find((s) => s.marketId === m.id) || MOCK_SIGNALS[m.id] || { signal: 'neutral', confidence: 0.5 }
|
| 259 |
+
const isActive = state.activeMarketId === m.id
|
| 260 |
+
const cls = signalColorClass(sig.signal)
|
| 261 |
+
const badgeClass = getSignalBadgeClass(sig.signal)
|
| 262 |
+
return `
|
| 263 |
+
<div class="market-card ${isActive ? 'active' : ''}" data-market="${m.id}">
|
| 264 |
+
<div class="market-cat">${m.category || 'General'} · ${m.countryCode || 'GL'}</div>
|
| 265 |
+
<div class="market-q">${m.question}</div>
|
| 266 |
+
<div class="market-footer">
|
| 267 |
+
<div class="prob-bar-wrap">
|
| 268 |
+
<div class="prob-bar-bg">
|
| 269 |
+
<div class="prob-bar-fill bg-${cls}" style="--prob-width:${Math.round(m.yesPrice * 100)}%"></div>
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
+
<span class="prob-val text-${cls}">${formatPrice(m.yesPrice)}</span>
|
| 273 |
+
<span class="signal-badge ${badgeClass}">${getSignalLabel(sig.signal)}</span>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
`
|
| 277 |
+
})
|
| 278 |
+
.join('')
|
| 279 |
+
|
| 280 |
+
container.querySelectorAll('.market-card').forEach((card) => {
|
| 281 |
+
card.addEventListener('click', () => selectMarket(card.dataset.market))
|
| 282 |
+
})
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
/* ─── Render mini positions in sidebar ─── */
|
| 286 |
+
function renderMiniPositions() {
|
| 287 |
+
const container = document.getElementById('mini-positions')
|
| 288 |
+
if (!container) return
|
| 289 |
+
if (state.positions.length === 0) {
|
| 290 |
+
container.innerHTML = '<div class="empty-state empty-state-sm">Aún sin posiciones</div>'
|
| 291 |
+
return
|
| 292 |
+
}
|
| 293 |
+
let netPnl = 0
|
| 294 |
+
const items = state.positions
|
| 295 |
+
.map((p) => {
|
| 296 |
+
const m = state.markets.find((x) => x.id === p.marketId) || { question: p.marketId }
|
| 297 |
+
netPnl += p.pnl
|
| 298 |
+
const cls = p.pnl >= 0 ? 'green' : 'red'
|
| 299 |
+
const sign = p.pnl >= 0 ? '+' : ''
|
| 300 |
+
return `
|
| 301 |
+
<div class="flex-between mb-6">
|
| 302 |
+
<span class="text-sm text-neutral font-mono">${m.question.substring(0, 32)}${m.question.length > 32 ? '…' : ''} ${p.outcome}</span>
|
| 303 |
+
<span class="text-base font-semibold text-${cls} font-mono">${sign}€${p.pnl.toFixed(2)}</span>
|
| 304 |
+
</div>
|
| 305 |
+
`
|
| 306 |
+
})
|
| 307 |
+
.join('')
|
| 308 |
+
|
| 309 |
+
const netCls = netPnl >= 0 ? 'green' : 'red'
|
| 310 |
+
const netSign = netPnl >= 0 ? '+' : ''
|
| 311 |
+
container.innerHTML = `
|
| 312 |
+
${items}
|
| 313 |
+
<div class="divider"></div>
|
| 314 |
+
<div class="flex-between">
|
| 315 |
+
<span class="text-sm text-neutral font-mono">G&P Neto</span>
|
| 316 |
+
<span class="text-lg font-bold text-${netCls} font-mono">${netSign}€${netPnl.toFixed(2)}</span>
|
| 317 |
+
</div>
|
| 318 |
+
`
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
/* ─── Render detail panel ─── */
|
| 322 |
+
function renderDetail() {
|
| 323 |
+
const container = document.getElementById('detail-body')
|
| 324 |
+
if (!container) return
|
| 325 |
+
const m = state.markets.find((x) => x.id === state.activeMarketId)
|
| 326 |
+
if (!m) {
|
| 327 |
+
container.innerHTML = '<div class="empty-state">Selecciona un mercado para ver detalles</div>'
|
| 328 |
+
return
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
const sig = state.signals.find((s) => s.marketId === m.id) || MOCK_SIGNALS[m.id] || { signal: 'neutral', confidence: 0.5, summary: 'Aún no hay análisis de IA disponible.', keyRisk: '' }
|
| 332 |
+
const delta = ((m.yesPrice - 0.5) * 20).toFixed(1)
|
| 333 |
+
const deltaCls = m.yesPrice > 0.5 ? 'green' : 'red'
|
| 334 |
+
const deltaSign = m.yesPrice > 0.5 ? '+' : ''
|
| 335 |
+
|
| 336 |
+
container.innerHTML = `
|
| 337 |
+
<div class="detail-header">
|
| 338 |
+
<div>
|
| 339 |
+
<div class="detail-tag">${m.countryCode || 'GL'} · ${m.category || 'General'} · Polymarket</div>
|
| 340 |
+
<div class="detail-q">${m.question}</div>
|
| 341 |
+
<div class="detail-meta">Vol: ${formatCurrency(m.volumeEur || 0)} · Liq: ${formatCurrency(m.liquidityEur || 0)} · Cierra: ${formatDate(m.closesAt)}</div>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="detail-metrics">
|
| 344 |
+
<div class="metric">
|
| 345 |
+
<div class="metric-label">Cambio 24h</div>
|
| 346 |
+
<div class="metric-value text-${deltaCls}">${deltaSign}${delta}%</div>
|
| 347 |
+
</div>
|
| 348 |
+
<div class="metric-sep"></div>
|
| 349 |
+
<div class="metric">
|
| 350 |
+
<div class="metric-label">Confianza</div>
|
| 351 |
+
<div class="metric-value text-blue">${Math.round(sig.confidence * 100)}%</div>
|
| 352 |
+
</div>
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
+
|
| 356 |
+
<div class="outcomes-row">
|
| 357 |
+
<div class="outcome-card yes">
|
| 358 |
+
<div class="outcome-name">SÍ</div>
|
| 359 |
+
<div class="outcome-price">${formatPrice(m.yesPrice)}</div>
|
| 360 |
+
<div class="outcome-delta td-green">▲ ${(m.yesPrice * 0.05).toFixed(1)}¢</div>
|
| 361 |
+
<div class="sparkline" id="spark-yes"></div>
|
| 362 |
+
</div>
|
| 363 |
+
<div class="outcome-card no">
|
| 364 |
+
<div class="outcome-name">NO</div>
|
| 365 |
+
<div class="outcome-price">${formatPrice(m.noPrice)}</div>
|
| 366 |
+
<div class="outcome-delta td-red">▼ ${(m.noPrice * 0.05).toFixed(1)}¢</div>
|
| 367 |
+
<div class="sparkline" id="spark-no"></div>
|
| 368 |
+
</div>
|
| 369 |
+
<div class="chart-container">
|
| 370 |
+
<div class="chart-label">Historial de precios 7d</div>
|
| 371 |
+
<canvas id="detail-chart"></canvas>
|
| 372 |
+
</div>
|
| 373 |
+
</div>
|
| 374 |
+
|
| 375 |
+
<div class="ai-box">
|
| 376 |
+
<div class="ai-icon">◈</div>
|
| 377 |
+
<div class="flex-1">
|
| 378 |
+
<div class="flex-row gap-8 mb-4 flex-wrap">
|
| 379 |
+
<div class="ai-label">Análisis IA · HuggingFace Qwen3-8B</div>
|
| 380 |
+
<span class="signal-badge ${getSignalBadgeClass(sig.signal)}">${translateSignal(sig.signal).toUpperCase()} · ${Math.round(sig.confidence * 100)}%</span>
|
| 381 |
+
<span class="text-xs text-neutral font-mono ml-auto">actualizado hace 2m</span>
|
| 382 |
+
</div>
|
| 383 |
+
<div class="ai-text">${sig.summary}${sig.keyRisk ? ' <strong>Riesgo clave:</strong> ' + sig.keyRisk : ''}</div>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
|
| 387 |
+
<div class="sim-row">
|
| 388 |
+
<span class="sim-label">Simular posición →</span>
|
| 389 |
+
<input class="sim-input" type="number" id="sim-amount" value="100" min="1" placeholder="€"/>
|
| 390 |
+
<button class="sim-btn-yes" id="sim-yes">COMPRAR SÍ ↗</button>
|
| 391 |
+
<button class="sim-btn-no" id="sim-no">COMPRAR NO</button>
|
| 392 |
+
<span class="sim-disclaimer">Simulado · sin trading real</span>
|
| 393 |
+
</div>
|
| 394 |
+
`
|
| 395 |
+
|
| 396 |
+
// Bind simulator buttons
|
| 397 |
+
document.getElementById('sim-yes')?.addEventListener('click', () => simulator.openPosition(m.id, 'SÍ', document.getElementById('sim-amount').value))
|
| 398 |
+
document.getElementById('sim-no')?.addEventListener('click', () => simulator.openPosition(m.id, 'NO', document.getElementById('sim-amount').value))
|
| 399 |
+
|
| 400 |
+
// Render chart
|
| 401 |
+
charts.renderDetailChart('detail-chart', m.yesPrice)
|
| 402 |
+
charts.renderSparkline('spark-yes', m.yesPrice, 'yes')
|
| 403 |
+
charts.renderSparkline('spark-no', m.noPrice, 'no')
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
/* ─── Select market ─── */
|
| 407 |
+
function selectMarket(marketId) {
|
| 408 |
+
state.activeMarketId = marketId
|
| 409 |
+
renderSignals()
|
| 410 |
+
renderDetail()
|
| 411 |
+
map.highlightMarket(marketId)
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/* ─── Render positions view ─── */
|
| 415 |
+
function renderPositions() {
|
| 416 |
+
const tbody = document.querySelector('#positions-table tbody')
|
| 417 |
+
const empty = document.getElementById('positions-empty')
|
| 418 |
+
if (!tbody) return
|
| 419 |
+
if (state.positions.length === 0) {
|
| 420 |
+
tbody.innerHTML = ''
|
| 421 |
+
empty.classList.remove('hidden')
|
| 422 |
+
return
|
| 423 |
+
}
|
| 424 |
+
empty.classList.add('hidden')
|
| 425 |
+
tbody.innerHTML = state.positions
|
| 426 |
+
.map((p) => {
|
| 427 |
+
const m = state.markets.find((x) => x.id === p.marketId) || { question: p.marketId }
|
| 428 |
+
const pnlColor = p.pnl >= 0 ? 'td-green' : 'td-red'
|
| 429 |
+
const sign = p.pnl >= 0 ? '+' : ''
|
| 430 |
+
return `
|
| 431 |
+
<tr>
|
| 432 |
+
<td>${m.question.substring(0, 40)}${m.question.length > 40 ? '…' : ''}</td>
|
| 433 |
+
<td class="td-mono ${p.outcome === 'SÍ' ? 'td-green' : 'td-red'}">${p.outcome}</td>
|
| 434 |
+
<td class="td-mono">€${p.amountEur.toFixed(0)}</td>
|
| 435 |
+
<td class="td-mono">${formatPrice(p.entryPrice)}</td>
|
| 436 |
+
<td class="td-mono">${formatPrice(p.currentPrice)}</td>
|
| 437 |
+
<td class="td-mono ${pnlColor}">${sign}€${p.pnl.toFixed(2)}</td>
|
| 438 |
+
<td class="td-mono td-blue">${((p.kellyFraction || 0) * 100).toFixed(0)}%</td>
|
| 439 |
+
<td class="td-mono">${formatDate(p.openedAt)}</td>
|
| 440 |
+
<td><button class="btn-ghost" onclick="closePositionById(${p.id})">Cerrar</button></td>
|
| 441 |
+
</tr>
|
| 442 |
+
`
|
| 443 |
+
})
|
| 444 |
+
.join('')
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
window.closePositionById = async (id) => {
|
| 448 |
+
await simulator.closePosition(id)
|
| 449 |
+
await loadPositions()
|
| 450 |
+
renderPositions()
|
| 451 |
+
renderMiniPositions()
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
/* ─── Render watchlist view ─── */
|
| 455 |
+
function renderWatchlist() {
|
| 456 |
+
const tbody = document.querySelector('#watchlist-table tbody')
|
| 457 |
+
const empty = document.getElementById('watchlist-empty')
|
| 458 |
+
if (!tbody) return
|
| 459 |
+
if (state.watchlist.length === 0) {
|
| 460 |
+
tbody.innerHTML = ''
|
| 461 |
+
empty.classList.remove('hidden')
|
| 462 |
+
return
|
| 463 |
+
}
|
| 464 |
+
empty.classList.add('hidden')
|
| 465 |
+
tbody.innerHTML = state.watchlist
|
| 466 |
+
.map((w) => {
|
| 467 |
+
const m = state.markets.find((x) => x.id === w.marketId) || { question: w.marketId, category: '-', yesPrice: 0, noPrice: 0, volumeEur: 0 }
|
| 468 |
+
const sig = state.signals.find((s) => s.marketId === w.marketId) || { signal: 'neutral' }
|
| 469 |
+
return `
|
| 470 |
+
<tr>
|
| 471 |
+
<td>${m.question.substring(0, 40)}${m.question.length > 40 ? '…' : ''}</td>
|
| 472 |
+
<td>${m.category || '-'}</td>
|
| 473 |
+
<td class="td-mono td-green">${formatPrice(m.yesPrice)}</td>
|
| 474 |
+
<td class="td-mono td-red">${formatPrice(m.noPrice)}</td>
|
| 475 |
+
<td><span class="signal-badge ${getSignalBadgeClass(sig.signal)}">${getSignalLabel(sig.signal)}</span></td>
|
| 476 |
+
<td class="td-mono">${formatCurrency(m.volumeEur || 0)}</td>
|
| 477 |
+
<td class="td-mono">${w.alertThreshold ? formatPrice(w.alertThreshold) : '-'}</td>
|
| 478 |
+
<td><button class="btn-ghost" onclick="removeFromWatchlistById('${w.marketId}')">Eliminar</button></td>
|
| 479 |
+
</tr>
|
| 480 |
+
`
|
| 481 |
+
})
|
| 482 |
+
.join('')
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
window.removeFromWatchlistById = async (marketId) => {
|
| 486 |
+
try { await api.removeFromWatchlist(marketId) } catch (e) { console.warn(e) }
|
| 487 |
+
state.watchlist = state.watchlist.filter((w) => w.marketId !== marketId)
|
| 488 |
+
renderWatchlist()
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
/* ─── Render alerts view ─── */
|
| 492 |
+
function renderAlerts() {
|
| 493 |
+
const tbody = document.querySelector('#alerts-table tbody')
|
| 494 |
+
const empty = document.getElementById('alerts-empty')
|
| 495 |
+
if (!tbody) return
|
| 496 |
+
if (state.alerts.length === 0) {
|
| 497 |
+
tbody.innerHTML = ''
|
| 498 |
+
empty.classList.remove('hidden')
|
| 499 |
+
return
|
| 500 |
+
}
|
| 501 |
+
empty.classList.add('hidden')
|
| 502 |
+
tbody.innerHTML = state.alerts
|
| 503 |
+
.map((a) => {
|
| 504 |
+
const m = state.markets.find((x) => x.id === a.marketId) || { question: a.marketId }
|
| 505 |
+
return `
|
| 506 |
+
<tr>
|
| 507 |
+
<td class="td-mono">${new Date(a.sentAt).toLocaleString('es-ES')}</td>
|
| 508 |
+
<td>${m.question.substring(0, 35)}${m.question.length > 35 ? '…' : ''}</td>
|
| 509 |
+
<td><span class="signal-badge sig-neut">${a.type}</span></td>
|
| 510 |
+
<td>${a.message}</td>
|
| 511 |
+
</tr>
|
| 512 |
+
`
|
| 513 |
+
})
|
| 514 |
+
.join('')
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
/* ─── Carga de datos ─── */
|
| 518 |
+
async function loadMarkets() {
|
| 519 |
+
try {
|
| 520 |
+
const data = await api.getMarkets({ limit: 50 })
|
| 521 |
+
state.markets = data.length ? data : MOCK_MARKETS
|
| 522 |
+
} catch (e) {
|
| 523 |
+
console.warn('API mercados no disponible, usando datos de prueba')
|
| 524 |
+
state.markets = MOCK_MARKETS
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
async function loadSignals() {
|
| 529 |
+
try {
|
| 530 |
+
const promises = state.markets.map((m) => api.getSignal(m.id).catch(() => null))
|
| 531 |
+
const results = await Promise.all(promises)
|
| 532 |
+
state.signals = results.filter(Boolean).map((r, i) => ({ ...r, marketId: state.markets[i].id }))
|
| 533 |
+
} catch (e) {
|
| 534 |
+
console.warn('API señales no disponible, usando datos de prueba')
|
| 535 |
+
state.signals = Object.entries(MOCK_SIGNALS).map(([marketId, s]) => ({ ...s, marketId }))
|
| 536 |
+
}
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
async function loadPositions() {
|
| 540 |
+
try {
|
| 541 |
+
state.positions = await api.getPositions()
|
| 542 |
+
} catch (e) {
|
| 543 |
+
state.positions = MOCK_POSITIONS
|
| 544 |
+
}
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
async function loadWatchlist() {
|
| 548 |
+
try {
|
| 549 |
+
state.watchlist = await api.getWatchlist()
|
| 550 |
+
} catch (e) {
|
| 551 |
+
state.watchlist = []
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
async function loadAlerts() {
|
| 556 |
+
try {
|
| 557 |
+
state.alerts = await api.getAlerts()
|
| 558 |
+
} catch (e) {
|
| 559 |
+
state.alerts = MOCK_ALERTS
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
/* ─── Inicialización ─── */
|
| 564 |
+
export async function init() {
|
| 565 |
+
// Sidebar toggle
|
| 566 |
+
document.getElementById('sidebar-toggle')?.addEventListener('click', toggleSidebar)
|
| 567 |
+
|
| 568 |
+
// Nav routing
|
| 569 |
+
document.querySelectorAll('.nav-item').forEach((el) => {
|
| 570 |
+
el.addEventListener('click', () => switchView(el.dataset.view))
|
| 571 |
+
})
|
| 572 |
+
|
| 573 |
+
// Panel toggles
|
| 574 |
+
document.querySelectorAll('.panel-header[data-panel]').forEach((el) => {
|
| 575 |
+
el.addEventListener('click', (e) => {
|
| 576 |
+
// Evitar colapsar al clicar elementos interactivos
|
| 577 |
+
if (e.target.closest('button, input, a')) return
|
| 578 |
+
togglePanel(el.dataset.panel)
|
| 579 |
+
})
|
| 580 |
+
})
|
| 581 |
+
|
| 582 |
+
// Load initial data
|
| 583 |
+
await loadMarkets()
|
| 584 |
+
await loadSignals()
|
| 585 |
+
await loadPositions()
|
| 586 |
+
await loadWatchlist()
|
| 587 |
+
await loadAlerts()
|
| 588 |
+
|
| 589 |
+
// Init modules
|
| 590 |
+
map.init('map-container', state.markets, state.signals, selectMarket)
|
| 591 |
+
simulator.init(state)
|
| 592 |
+
|
| 593 |
+
// Initial render
|
| 594 |
+
state.activeMarketId = state.markets[0]?.id || null
|
| 595 |
+
renderSignals()
|
| 596 |
+
renderDetail()
|
| 597 |
+
renderMiniPositions()
|
| 598 |
|
| 599 |
+
// Socket.io
|
| 600 |
+
const socket = io()
|
| 601 |
+
socket.on('connect', () => console.log('Socket.io conectado'))
|
| 602 |
|
| 603 |
+
socket.on('market_update', (data) => {
|
| 604 |
+
const m = state.markets.find((x) => x.id === data.marketId)
|
| 605 |
+
if (m) {
|
| 606 |
+
Object.assign(m, data)
|
| 607 |
+
if (state.activeMarketId === data.marketId) renderDetail()
|
| 608 |
+
renderSignals()
|
| 609 |
+
map.updateBubble(data.marketId, data.yesPrice)
|
| 610 |
+
}
|
| 611 |
+
})
|
| 612 |
|
| 613 |
+
socket.on('ai_signal', (data) => {
|
| 614 |
+
const idx = state.signals.findIndex((s) => s.marketId === data.marketId)
|
| 615 |
+
if (idx >= 0) state.signals[idx] = data
|
| 616 |
+
else state.signals.push(data)
|
| 617 |
+
renderSignals()
|
| 618 |
+
if (state.activeMarketId === data.marketId) renderDetail()
|
| 619 |
+
})
|
| 620 |
|
| 621 |
+
socket.on('price_alert', (data) => {
|
| 622 |
+
state.alerts.unshift(data)
|
| 623 |
+
if (state.view === 'alerts') renderAlerts()
|
| 624 |
+
})
|
| 625 |
|
| 626 |
+
// Stats animation mock
|
| 627 |
+
setInterval(() => {
|
| 628 |
+
const el = document.getElementById('stat-markets')
|
| 629 |
+
if (el) {
|
| 630 |
+
const n = parseInt(el.textContent.replace(/\./g, '')) + Math.floor(Math.random() * 3)
|
| 631 |
+
el.textContent = n.toLocaleString('es-ES')
|
| 632 |
+
}
|
| 633 |
+
const el2 = document.getElementById('stat-signals')
|
| 634 |
+
if (el2 && Math.random() > 0.7) {
|
| 635 |
+
const n = parseInt(el2.textContent) + 1
|
| 636 |
+
el2.textContent = n
|
| 637 |
+
}
|
| 638 |
+
}, 3000)
|
| 639 |
+
}
|
frontend/src/charts.js
CHANGED
|
@@ -1,14 +1,72 @@
|
|
| 1 |
/**
|
| 2 |
-
* Módulo de gráficos
|
| 3 |
-
*
|
| 4 |
-
* Tecnología: Chart.js
|
| 5 |
*
|
| 6 |
* Funciones:
|
| 7 |
-
* -
|
| 8 |
-
* -
|
| 9 |
-
* - Opcional: mostrar punto de entrada de posiciones abiertas
|
| 10 |
-
*
|
| 11 |
-
* Se vincula al panel de detalle de mercado.
|
| 12 |
*/
|
| 13 |
|
| 14 |
import { Chart } from 'chart.js/auto'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Módulo de gráficos usando Chart.js.
|
|
|
|
|
|
|
| 3 |
*
|
| 4 |
* Funciones:
|
| 5 |
+
* - renderDetailChart(canvasId, currentPrice)
|
| 6 |
+
* - renderSparkline(containerId, price, side)
|
|
|
|
|
|
|
|
|
|
| 7 |
*/
|
| 8 |
|
| 9 |
import { Chart } from 'chart.js/auto'
|
| 10 |
+
|
| 11 |
+
let detailChartInstance = null
|
| 12 |
+
|
| 13 |
+
export function renderDetailChart(canvasId, currentPrice) {
|
| 14 |
+
const ctx = document.getElementById(canvasId)
|
| 15 |
+
if (!ctx) return
|
| 16 |
+
if (detailChartInstance) detailChartInstance.destroy()
|
| 17 |
+
|
| 18 |
+
const base = currentPrice * 100
|
| 19 |
+
const pts = Array.from({ length: 8 }, (_, i) => {
|
| 20 |
+
const noise = (Math.random() - 0.5) * 8
|
| 21 |
+
return Math.max(5, Math.min(95, base - 12 + (i / 7) * 12 + noise))
|
| 22 |
+
})
|
| 23 |
+
pts[pts.length - 1] = base
|
| 24 |
+
|
| 25 |
+
const col = base > 50 ? '#22d37a' : base < 40 ? '#f04040' : '#f0a020'
|
| 26 |
+
|
| 27 |
+
detailChartInstance = new Chart(ctx, {
|
| 28 |
+
type: 'line',
|
| 29 |
+
data: {
|
| 30 |
+
labels: ['7d', '6d', '5d', '4d', '3d', '2d', '1d', 'now'],
|
| 31 |
+
datasets: [
|
| 32 |
+
{
|
| 33 |
+
data: pts,
|
| 34 |
+
borderColor: col,
|
| 35 |
+
borderWidth: 1.5,
|
| 36 |
+
pointRadius: 0,
|
| 37 |
+
fill: false,
|
| 38 |
+
tension: 0.4,
|
| 39 |
+
},
|
| 40 |
+
],
|
| 41 |
+
},
|
| 42 |
+
options: {
|
| 43 |
+
responsive: true,
|
| 44 |
+
maintainAspectRatio: false,
|
| 45 |
+
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
| 46 |
+
scales: { x: { display: false }, y: { display: false } },
|
| 47 |
+
animation: { duration: 600 },
|
| 48 |
+
},
|
| 49 |
+
})
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
export function renderSparkline(containerId, price, side) {
|
| 53 |
+
const el = document.getElementById(containerId)
|
| 54 |
+
if (!el) return
|
| 55 |
+
el.innerHTML = ''
|
| 56 |
+
el.className = 'sparkline'
|
| 57 |
+
|
| 58 |
+
const base = price * 100
|
| 59 |
+
for (let i = 0; i < 12; i++) {
|
| 60 |
+
const h = Math.max(4, Math.min(24, base / 4 + (Math.random() - 0.5) * 8))
|
| 61 |
+
const d = document.createElement('div')
|
| 62 |
+
d.className = 'spark-bar'
|
| 63 |
+
d.style.height = h + 'px'
|
| 64 |
+
d.style.background = side === 'yes' ? '#0d6e3a' : '#7a1a1a'
|
| 65 |
+
el.appendChild(d)
|
| 66 |
+
}
|
| 67 |
+
const last = document.createElement('div')
|
| 68 |
+
last.className = 'spark-bar'
|
| 69 |
+
last.style.height = Math.min(28, base / 3.5) + 'px'
|
| 70 |
+
last.style.background = side === 'yes' ? '#22d37a' : '#f04040'
|
| 71 |
+
el.appendChild(last)
|
| 72 |
+
}
|
frontend/src/main.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
import './style.css'
|
| 2 |
-
import './app.js'
|
|
|
|
|
|
|
|
|
| 1 |
import './style.css'
|
| 2 |
+
import { init } from './app.js'
|
| 3 |
+
|
| 4 |
+
init().catch((err) => console.error('Failed to initialize app:', err))
|
frontend/src/map.js
CHANGED
|
@@ -1,17 +1,142 @@
|
|
| 1 |
/**
|
| 2 |
-
* Módulo de visualización del mapa mundial interactivo.
|
| 3 |
-
*
|
| 4 |
-
* Tecnología: Leaflet.js
|
| 5 |
*
|
| 6 |
* Funciones:
|
| 7 |
-
* -
|
| 8 |
-
* -
|
| 9 |
-
* -
|
| 10 |
-
* - Tamaño proporcional al volumen o liquidez del mercado
|
| 11 |
-
* - Popup al hacer click con detalle del mercado
|
| 12 |
-
*
|
| 13 |
-
* Recibe actualizaciones en tiempo real vía Socket.io desde app.js.
|
| 14 |
*/
|
| 15 |
|
| 16 |
import L from 'leaflet'
|
| 17 |
import 'leaflet/dist/leaflet.css'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Módulo de visualización del mapa mundial interactivo con Leaflet.js.
|
|
|
|
|
|
|
| 3 |
*
|
| 4 |
* Funciones:
|
| 5 |
+
* - init(containerId, markets, signals, onSelect)
|
| 6 |
+
* - updateBubble(marketId, newPrice)
|
| 7 |
+
* - highlightMarket(marketId)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
*/
|
| 9 |
|
| 10 |
import L from 'leaflet'
|
| 11 |
import 'leaflet/dist/leaflet.css'
|
| 12 |
+
|
| 13 |
+
// Coordenadas aproximadas por countryCode ISO2
|
| 14 |
+
const COORDS = {
|
| 15 |
+
US: [37.09, -95.71],
|
| 16 |
+
DE: [51.16, 10.45],
|
| 17 |
+
GB: [55.37, -3.43],
|
| 18 |
+
BR: [-14.23, -51.92],
|
| 19 |
+
CN: [35.86, 104.19],
|
| 20 |
+
IN: [20.59, 78.96],
|
| 21 |
+
KR: [35.90, 127.76],
|
| 22 |
+
SA: [23.88, 45.07],
|
| 23 |
+
FR: [46.22, 2.21],
|
| 24 |
+
JP: [36.20, 138.25],
|
| 25 |
+
AU: [-25.27, 133.77],
|
| 26 |
+
CA: [56.13, -106.34],
|
| 27 |
+
RU: [61.52, 105.31],
|
| 28 |
+
MX: [23.63, -102.55],
|
| 29 |
+
ZA: [-30.55, 22.93],
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
let mapInstance = null
|
| 33 |
+
let bubbles = {} // marketId -> circle marker
|
| 34 |
+
|
| 35 |
+
function getCoords(countryCode) {
|
| 36 |
+
return COORDS[countryCode?.toUpperCase()] || [20, 0]
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function getSignalColor(signal) {
|
| 40 |
+
if (signal === 'bullish') return '#22d37a'
|
| 41 |
+
if (signal === 'bearish') return '#f04040'
|
| 42 |
+
return '#f0a020'
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function getRadius(volumeEur) {
|
| 46 |
+
const v = volumeEur || 0
|
| 47 |
+
if (v > 2e6) return 18
|
| 48 |
+
if (v > 1e6) return 14
|
| 49 |
+
if (v > 500000) return 11
|
| 50 |
+
if (v > 200000) return 8
|
| 51 |
+
return 6
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export function init(containerId, markets, signals, onSelect) {
|
| 55 |
+
const container = document.getElementById(containerId)
|
| 56 |
+
if (!container) return
|
| 57 |
+
|
| 58 |
+
mapInstance = L.map(container, {
|
| 59 |
+
zoomControl: false,
|
| 60 |
+
attributionControl: false,
|
| 61 |
+
minZoom: 2,
|
| 62 |
+
maxZoom: 6,
|
| 63 |
+
worldCopyJump: true,
|
| 64 |
+
}).setView([25, 10], 2)
|
| 65 |
+
|
| 66 |
+
// Dark tile layer (CartoDB Dark Matter)
|
| 67 |
+
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
| 68 |
+
attribution: '©OpenStreetMap ©CartoDB',
|
| 69 |
+
subdomains: 'abcd',
|
| 70 |
+
maxZoom: 19,
|
| 71 |
+
}).addTo(mapInstance)
|
| 72 |
+
|
| 73 |
+
markets.forEach((m) => {
|
| 74 |
+
const sig = signals.find((s) => s.marketId === m.id) || { signal: 'neutral' }
|
| 75 |
+
const color = getSignalColor(sig.signal)
|
| 76 |
+
const coords = getCoords(m.countryCode)
|
| 77 |
+
const radius = getRadius(m.volumeEur)
|
| 78 |
+
|
| 79 |
+
const circle = L.circleMarker(coords, {
|
| 80 |
+
radius,
|
| 81 |
+
fillColor: color,
|
| 82 |
+
color: color,
|
| 83 |
+
weight: 1.5,
|
| 84 |
+
opacity: 0.6,
|
| 85 |
+
fillOpacity: 0.22,
|
| 86 |
+
}).addTo(mapInstance)
|
| 87 |
+
|
| 88 |
+
const inner = L.circleMarker(coords, {
|
| 89 |
+
radius: Math.max(3, radius * 0.45),
|
| 90 |
+
fillColor: color,
|
| 91 |
+
color: 'transparent',
|
| 92 |
+
fillOpacity: 0.8,
|
| 93 |
+
}).addTo(mapInstance)
|
| 94 |
+
|
| 95 |
+
const label = L.marker(coords, {
|
| 96 |
+
icon: L.divIcon({
|
| 97 |
+
className: 'map-label',
|
| 98 |
+
html: `<span class="map-label-text">${m.countryCode || 'GL'}</span>`,
|
| 99 |
+
iconSize: [40, 14],
|
| 100 |
+
iconAnchor: [20, -radius - 4],
|
| 101 |
+
}),
|
| 102 |
+
interactive: false,
|
| 103 |
+
}).addTo(mapInstance)
|
| 104 |
+
|
| 105 |
+
const popupContent = `
|
| 106 |
+
<div class="map-popup">
|
| 107 |
+
<div class="map-popup-cat">${m.category || 'General'} · ${m.countryCode || 'GL'}</div>
|
| 108 |
+
<div class="map-popup-q">${m.question}</div>
|
| 109 |
+
<div class="map-popup-prices">
|
| 110 |
+
<span class="text-green">SÍ ${Math.round((m.yesPrice || 0) * 100)}¢</span>
|
| 111 |
+
<span class="text-red">NO ${Math.round((m.noPrice || 0) * 100)}¢</span>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
`
|
| 115 |
+
circle.bindPopup(popupContent, { closeButton: false, offset: [0, -4] })
|
| 116 |
+
|
| 117 |
+
circle.on('click', () => {
|
| 118 |
+
onSelect(m.id)
|
| 119 |
+
})
|
| 120 |
+
|
| 121 |
+
bubbles[m.id] = { circle, inner, label, color }
|
| 122 |
+
})
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
export function updateBubble(marketId, newPrice) {
|
| 126 |
+
const b = bubbles[marketId]
|
| 127 |
+
if (!b) return
|
| 128 |
+
// Slightly adjust radius based on new activity (mock behavior)
|
| 129 |
+
const newRadius = b.circle.getRadius() + (Math.random() > 0.5 ? 0.5 : -0.5)
|
| 130 |
+
b.circle.setRadius(Math.max(5, Math.min(22, newRadius)))
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
export function highlightMarket(marketId) {
|
| 134 |
+
Object.values(bubbles).forEach((b) => {
|
| 135 |
+
b.circle.setStyle({ weight: 1.5, opacity: 0.6 })
|
| 136 |
+
})
|
| 137 |
+
const b = bubbles[marketId]
|
| 138 |
+
if (b) {
|
| 139 |
+
b.circle.setStyle({ weight: 3, opacity: 1, color: '#4a9eff' })
|
| 140 |
+
b.circle.openPopup()
|
| 141 |
+
}
|
| 142 |
+
}
|
frontend/src/simulator.js
CHANGED
|
@@ -1,11 +1,67 @@
|
|
| 1 |
/**
|
| 2 |
-
* Módulo
|
| 3 |
*
|
| 4 |
* Funciones:
|
| 5 |
-
* -
|
| 6 |
-
* -
|
| 7 |
-
* -
|
| 8 |
-
* - Botón de cierre de posición y cálculo de resultado final
|
| 9 |
-
*
|
| 10 |
-
* Usa userId=1 fijo para la demo.
|
| 11 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Módulo del simulador de posiciones virtuales.
|
| 3 |
*
|
| 4 |
* Funciones:
|
| 5 |
+
* - init(state)
|
| 6 |
+
* - openPosition(marketId, outcome, amount)
|
| 7 |
+
* - closePosition(positionId)
|
|
|
|
|
|
|
|
|
|
| 8 |
*/
|
| 9 |
+
|
| 10 |
+
import * as api from './api.js'
|
| 11 |
+
|
| 12 |
+
let appState = null
|
| 13 |
+
|
| 14 |
+
export function init(state) {
|
| 15 |
+
appState = state
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export async function openPosition(marketId, outcome, amount) {
|
| 19 |
+
const amt = parseFloat(amount)
|
| 20 |
+
if (!amt || amt <= 0) {
|
| 21 |
+
alert('Introduce una cantidad válida')
|
| 22 |
+
return
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
const m = appState.markets.find((x) => x.id === marketId)
|
| 26 |
+
if (!m) return
|
| 27 |
+
|
| 28 |
+
const entryPrice = outcome === 'YES' ? m.yesPrice : m.noPrice
|
| 29 |
+
const data = {
|
| 30 |
+
marketId,
|
| 31 |
+
outcome,
|
| 32 |
+
amountEur: amt,
|
| 33 |
+
entryPrice,
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
try {
|
| 37 |
+
const created = await api.createPosition(data)
|
| 38 |
+
appState.positions.push(created)
|
| 39 |
+
} catch (e) {
|
| 40 |
+
// Fallback: create locally if API unavailable
|
| 41 |
+
const fakeId = Date.now()
|
| 42 |
+
appState.positions.push({
|
| 43 |
+
id: fakeId,
|
| 44 |
+
marketId,
|
| 45 |
+
outcome,
|
| 46 |
+
amountEur: amt,
|
| 47 |
+
entryPrice,
|
| 48 |
+
currentPrice: entryPrice,
|
| 49 |
+
pnl: 0,
|
| 50 |
+
kellyFraction: 0.2,
|
| 51 |
+
openedAt: new Date().toISOString(),
|
| 52 |
+
})
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Trigger re-render via app.js if needed
|
| 56 |
+
document.dispatchEvent(new CustomEvent('positions:changed'))
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
export async function closePosition(positionId) {
|
| 60 |
+
try {
|
| 61 |
+
await api.closePosition(positionId)
|
| 62 |
+
} catch (e) {
|
| 63 |
+
console.warn('API closePosition failed, removing locally')
|
| 64 |
+
}
|
| 65 |
+
appState.positions = appState.positions.filter((p) => p.id !== positionId)
|
| 66 |
+
document.dispatchEvent(new CustomEvent('positions:changed'))
|
| 67 |
+
}
|
frontend/src/style.css
CHANGED
|
@@ -1,12 +1,1061 @@
|
|
| 1 |
-
/
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;500;600;700&display=swap');
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--bg: #0a0c10;
|
| 5 |
+
--bg2: #111318;
|
| 6 |
+
--bg3: #181c24;
|
| 7 |
+
--bg4: #1e232d;
|
| 8 |
+
--border: rgba(255,255,255,0.08);
|
| 9 |
+
--border2: rgba(255,255,255,0.13);
|
| 10 |
+
--text: #e8eaf0;
|
| 11 |
+
--text2: #8b90a0;
|
| 12 |
+
--text3: #555b6e;
|
| 13 |
+
--green: #22d37a;
|
| 14 |
+
--green2: #0d6e3a;
|
| 15 |
+
--green3: #052a17;
|
| 16 |
+
--red: #f04040;
|
| 17 |
+
--red2: #7a1a1a;
|
| 18 |
+
--red3: #2d0808;
|
| 19 |
+
--blue: #4a9eff;
|
| 20 |
+
--blue2: #1a4a80;
|
| 21 |
+
--blue3: #081830;
|
| 22 |
+
--amber: #f0a020;
|
| 23 |
+
--amber2: #7a4e08;
|
| 24 |
+
--amber3: #2d1c02;
|
| 25 |
+
--accent: #4a9eff;
|
| 26 |
+
|
| 27 |
+
--sidebar-width: 240px;
|
| 28 |
+
--sidebar-collapsed: 56px;
|
| 29 |
+
--topbar-height: 56px;
|
| 30 |
+
--panel-gap: 16px;
|
| 31 |
+
--radius: 10px;
|
| 32 |
+
--radius-sm: 6px;
|
| 33 |
+
|
| 34 |
+
/* Fluid typography scale */
|
| 35 |
+
--fs-p: clamp(16px, 1.15vw, 18px);
|
| 36 |
+
--fs-h1: clamp(28px, 2.8vw, 36px);
|
| 37 |
+
--fs-h2: clamp(24px, 2.4vw, 30px);
|
| 38 |
+
--fs-h3: clamp(20px, 1.9vw, 24px);
|
| 39 |
+
--fs-h4: clamp(18px, 1.6vw, 20px);
|
| 40 |
+
--fs-h5: clamp(16px, 1.3vw, 18px);
|
| 41 |
+
--fs-h6: clamp(14px, 1.1vw, 16px);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
* {
|
| 45 |
+
box-sizing: border-box;
|
| 46 |
+
margin: 0;
|
| 47 |
+
padding: 0;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
html, body, #app {
|
| 51 |
+
height: 100%;
|
| 52 |
+
width: 100%;
|
| 53 |
+
overflow: hidden;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
body {
|
| 57 |
+
background: var(--bg);
|
| 58 |
+
color: var(--text);
|
| 59 |
+
font-family: 'Syne', sans-serif;
|
| 60 |
+
font-size: 16px;
|
| 61 |
+
line-height: 1.5;
|
| 62 |
+
-webkit-font-smoothing: antialiased;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/* Semantic type hierarchy (fluid) */
|
| 66 |
+
p { font-size: var(--fs-p); line-height: 1.55; }
|
| 67 |
+
h1 { font-size: var(--fs-h1); line-height: 1.15; font-weight: 700; }
|
| 68 |
+
h2 { font-size: var(--fs-h2); line-height: 1.2; font-weight: 700; }
|
| 69 |
+
h3 { font-size: var(--fs-h3); line-height: 1.25; font-weight: 600; }
|
| 70 |
+
h4 { font-size: var(--fs-h4); line-height: 1.3; font-weight: 600; }
|
| 71 |
+
h5 { font-size: var(--fs-h5); line-height: 1.35; font-weight: 500; }
|
| 72 |
+
h6 { font-size: var(--fs-h6); line-height: 1.4; font-weight: 500; }
|
| 73 |
+
|
| 74 |
+
/* ─── Layout principal ─── */
|
| 75 |
+
.layout {
|
| 76 |
+
display: grid;
|
| 77 |
+
grid-template-areas:
|
| 78 |
+
"topbar topbar"
|
| 79 |
+
"sidebar main";
|
| 80 |
+
grid-template-columns: var(--sidebar-width) 1fr;
|
| 81 |
+
grid-template-rows: var(--topbar-height) 1fr;
|
| 82 |
+
height: 100vh;
|
| 83 |
+
width: 100vw;
|
| 84 |
+
transition: grid-template-columns 0.25s ease;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.layout.collapsed {
|
| 88 |
+
grid-template-columns: var(--sidebar-collapsed) 1fr;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* ─── Sidebar ─── */
|
| 92 |
+
.sidebar {
|
| 93 |
+
grid-area: sidebar;
|
| 94 |
+
background: var(--bg2);
|
| 95 |
+
border-right: 1px solid var(--border);
|
| 96 |
+
display: flex;
|
| 97 |
+
flex-direction: column;
|
| 98 |
+
overflow: hidden;
|
| 99 |
+
transition: width 0.25s ease;
|
| 100 |
+
position: relative;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.sidebar-toggle {
|
| 104 |
+
position: absolute;
|
| 105 |
+
top: 12px;
|
| 106 |
+
right: -10px;
|
| 107 |
+
width: 24px;
|
| 108 |
+
height: 24px;
|
| 109 |
+
border-radius: 50%;
|
| 110 |
+
background: var(--bg3);
|
| 111 |
+
border: 1px solid var(--border2);
|
| 112 |
+
color: var(--text2);
|
| 113 |
+
display: flex;
|
| 114 |
+
align-items: center;
|
| 115 |
+
justify-content: center;
|
| 116 |
+
cursor: pointer;
|
| 117 |
+
font-size: 14px;
|
| 118 |
+
z-index: 10;
|
| 119 |
+
transition: transform 0.2s;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.layout.collapsed .sidebar-toggle {
|
| 123 |
+
transform: rotate(180deg);
|
| 124 |
+
right: -10px;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/* ─── Topbar Logo ─── */
|
| 128 |
+
.topbar-logo {
|
| 129 |
+
position: absolute;
|
| 130 |
+
left: 16px;
|
| 131 |
+
top: 0;
|
| 132 |
+
bottom: 0;
|
| 133 |
+
display: flex;
|
| 134 |
+
align-items: center;
|
| 135 |
+
gap: 10px;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.topbar-logo .logo-dot {
|
| 139 |
+
width: 10px;
|
| 140 |
+
height: 10px;
|
| 141 |
+
border-radius: 50%;
|
| 142 |
+
background: var(--blue);
|
| 143 |
+
animation: pulse 2s ease-in-out infinite;
|
| 144 |
+
flex-shrink: 0;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.topbar-logo .logo-text {
|
| 148 |
+
font-size: 18px;
|
| 149 |
+
font-weight: 700;
|
| 150 |
+
color: var(--text);
|
| 151 |
+
letter-spacing: -0.3px;
|
| 152 |
+
white-space: nowrap;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.sidebar-nav {
|
| 156 |
+
flex: 1;
|
| 157 |
+
padding: 14px 8px;
|
| 158 |
+
display: flex;
|
| 159 |
+
flex-direction: column;
|
| 160 |
+
gap: 4px;
|
| 161 |
+
overflow-y: auto;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.nav-item {
|
| 165 |
+
display: flex;
|
| 166 |
+
align-items: center;
|
| 167 |
+
gap: 14px;
|
| 168 |
+
padding: 14px 14px;
|
| 169 |
+
border-radius: var(--radius-sm);
|
| 170 |
+
cursor: pointer;
|
| 171 |
+
color: var(--text2);
|
| 172 |
+
font-size: 16px;
|
| 173 |
+
font-weight: 500;
|
| 174 |
+
transition: background 0.15s, color 0.15s;
|
| 175 |
+
white-space: nowrap;
|
| 176 |
+
overflow: hidden;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.nav-item:hover {
|
| 180 |
+
background: var(--bg3);
|
| 181 |
+
color: var(--text);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.nav-item.active {
|
| 185 |
+
background: var(--blue3);
|
| 186 |
+
color: var(--blue);
|
| 187 |
+
border: 0.5px solid var(--blue2);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.nav-icon {
|
| 191 |
+
font-size: 16px;
|
| 192 |
+
width: 20px;
|
| 193 |
+
text-align: center;
|
| 194 |
+
flex-shrink: 0;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.nav-label {
|
| 198 |
+
opacity: 1;
|
| 199 |
+
transition: opacity 0.15s;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
.layout.collapsed .nav-label {
|
| 203 |
+
opacity: 0;
|
| 204 |
+
width: 0;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.sidebar-footer {
|
| 208 |
+
padding: 14px;
|
| 209 |
+
border-top: 1px solid var(--border);
|
| 210 |
+
font-size: 14px;
|
| 211 |
+
color: var(--text3);
|
| 212 |
+
font-family: 'DM Mono', monospace;
|
| 213 |
+
text-align: center;
|
| 214 |
+
white-space: nowrap;
|
| 215 |
+
overflow: hidden;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.layout.collapsed .sidebar-footer {
|
| 219 |
+
opacity: 0;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* ─── Topbar ─── */
|
| 223 |
+
.topbar {
|
| 224 |
+
grid-area: topbar;
|
| 225 |
+
background: var(--bg2);
|
| 226 |
+
border-bottom: 1px solid var(--border);
|
| 227 |
+
display: flex;
|
| 228 |
+
align-items: center;
|
| 229 |
+
padding: 0 16px;
|
| 230 |
+
padding-left: calc(var(--sidebar-width) + var(--panel-gap));
|
| 231 |
+
gap: 16px;
|
| 232 |
+
overflow: hidden;
|
| 233 |
+
position: relative;
|
| 234 |
+
transition: padding-left 0.25s ease;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.layout.collapsed .topbar {
|
| 238 |
+
padding-left: calc(var(--sidebar-collapsed) + var(--panel-gap));
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.live-badge {
|
| 242 |
+
font-family: 'DM Mono', monospace;
|
| 243 |
+
font-size: 14px;
|
| 244 |
+
background: var(--green3);
|
| 245 |
+
color: var(--green);
|
| 246 |
+
border: 0.5px solid var(--green2);
|
| 247 |
+
padding: 6px 12px;
|
| 248 |
+
border-radius: 4px;
|
| 249 |
+
display: flex;
|
| 250 |
+
align-items: center;
|
| 251 |
+
gap: 4px;
|
| 252 |
+
flex-shrink: 0;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.live-dot {
|
| 256 |
+
width: 8px;
|
| 257 |
+
height: 8px;
|
| 258 |
+
border-radius: 50%;
|
| 259 |
+
background: var(--green);
|
| 260 |
+
animation: pulse 1.5s ease-in-out infinite;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.topbar-stats {
|
| 264 |
+
display: flex;
|
| 265 |
+
align-items: center;
|
| 266 |
+
gap: 20px;
|
| 267 |
+
flex: 1;
|
| 268 |
+
overflow: hidden;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.stat {
|
| 272 |
+
display: flex;
|
| 273 |
+
align-items: center;
|
| 274 |
+
gap: 14px;
|
| 275 |
+
flex-shrink: 0;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.stat-label {
|
| 279 |
+
font-size: 14px;
|
| 280 |
+
color: var(--text3);
|
| 281 |
+
font-family: 'DM Mono', monospace;
|
| 282 |
+
text-transform: uppercase;
|
| 283 |
+
letter-spacing: 0.06em;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.stat-val {
|
| 287 |
+
font-size: 16px;
|
| 288 |
+
font-weight: 600;
|
| 289 |
+
color: var(--text);
|
| 290 |
+
font-family: 'DM Mono', monospace;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
.stat-delta {
|
| 294 |
+
font-size: 14px;
|
| 295 |
+
font-family: 'DM Mono', monospace;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.stat-delta.up { color: var(--green); }
|
| 299 |
+
.stat-delta.dn { color: var(--red); }
|
| 300 |
+
.stat-delta.neutral { color: var(--text3); }
|
| 301 |
+
|
| 302 |
+
.topbar-actions {
|
| 303 |
+
margin-left: auto;
|
| 304 |
+
display: flex;
|
| 305 |
+
align-items: center;
|
| 306 |
+
gap: 16px;
|
| 307 |
+
flex-shrink: 0;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
.btn-ghost {
|
| 311 |
+
font-size: 15px;
|
| 312 |
+
font-weight: 500;
|
| 313 |
+
color: var(--blue);
|
| 314 |
+
border: 0.5px solid var(--blue2);
|
| 315 |
+
background: var(--blue3);
|
| 316 |
+
padding: 8px 16px;
|
| 317 |
+
border-radius: var(--radius-sm);
|
| 318 |
+
cursor: pointer;
|
| 319 |
+
font-family: 'Syne', sans-serif;
|
| 320 |
+
transition: opacity 0.15s;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.btn-ghost:hover {
|
| 324 |
+
opacity: 0.85;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.icon-btn {
|
| 328 |
+
width: 32px;
|
| 329 |
+
height: 32px;
|
| 330 |
+
border-radius: 50%;
|
| 331 |
+
background: var(--bg3);
|
| 332 |
+
border: 0.5px solid var(--border2);
|
| 333 |
+
color: var(--text2);
|
| 334 |
+
display: flex;
|
| 335 |
+
align-items: center;
|
| 336 |
+
justify-content: center;
|
| 337 |
+
cursor: pointer;
|
| 338 |
+
font-size: 16px;
|
| 339 |
+
transition: color 0.15s, border-color 0.15s;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.icon-btn:hover {
|
| 343 |
+
color: var(--text);
|
| 344 |
+
border-color: var(--border2);
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
/* ─── Main content ─── */
|
| 348 |
+
.main {
|
| 349 |
+
grid-area: main;
|
| 350 |
+
overflow: auto;
|
| 351 |
+
padding: var(--panel-gap);
|
| 352 |
+
background: var(--bg);
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
/* Vistas */
|
| 356 |
+
.view { display: none; }
|
| 357 |
+
.view.active { display: block; height: 100%; }
|
| 358 |
+
|
| 359 |
+
/* ─── Dashboard layout ─── */
|
| 360 |
+
.dashboard-grid {
|
| 361 |
+
display: grid;
|
| 362 |
+
grid-template-columns: 1fr 280px;
|
| 363 |
+
grid-template-rows: 1fr minmax(280px, 40%);
|
| 364 |
+
gap: var(--panel-gap);
|
| 365 |
+
height: 100%;
|
| 366 |
+
min-height: 0;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.panel {
|
| 370 |
+
background: var(--bg2);
|
| 371 |
+
border: 0.5px solid var(--border);
|
| 372 |
+
border-radius: var(--radius);
|
| 373 |
+
display: flex;
|
| 374 |
+
flex-direction: column;
|
| 375 |
+
overflow: hidden;
|
| 376 |
+
min-height: 0;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.panel-header {
|
| 380 |
+
display: flex;
|
| 381 |
+
align-items: center;
|
| 382 |
+
justify-content: space-between;
|
| 383 |
+
padding: 14px 14px;
|
| 384 |
+
border-bottom: 0.5px solid var(--border);
|
| 385 |
+
cursor: pointer;
|
| 386 |
+
user-select: none;
|
| 387 |
+
transition: background 0.15s;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.panel-header:hover {
|
| 391 |
+
background: rgba(255,255,255,0.02);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.panel-title {
|
| 395 |
+
font-size: 14px;
|
| 396 |
+
color: var(--text3);
|
| 397 |
+
font-family: 'DM Mono', monospace;
|
| 398 |
+
text-transform: uppercase;
|
| 399 |
+
letter-spacing: 0.08em;
|
| 400 |
+
display: flex;
|
| 401 |
+
align-items: center;
|
| 402 |
+
gap: 14px;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.panel-toggle {
|
| 406 |
+
font-size: 14px;
|
| 407 |
+
color: var(--text3);
|
| 408 |
+
transition: transform 0.2s;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.panel.collapsed .panel-toggle {
|
| 412 |
+
transform: rotate(-90deg);
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.panel-body {
|
| 416 |
+
flex: 1;
|
| 417 |
+
overflow: auto;
|
| 418 |
+
padding: 12px;
|
| 419 |
+
min-height: 0;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
.panel.collapsed .panel-body {
|
| 423 |
+
display: none;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
.panel-subtitle {
|
| 427 |
+
font-size: 14px;
|
| 428 |
+
color: var(--text3);
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.positions-separator {
|
| 432 |
+
margin-top: 10px;
|
| 433 |
+
padding-top: 10px;
|
| 434 |
+
border-top: 0.5px solid var(--border);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.panel-title.mb-sm {
|
| 438 |
+
margin-bottom: 8px;
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
.panel.full-height {
|
| 442 |
+
height: 100%;
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
.hidden {
|
| 446 |
+
display: none;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
/* Panel mapa */
|
| 450 |
+
.map-panel {
|
| 451 |
+
grid-row: 1 / 2;
|
| 452 |
+
grid-column: 1 / 2;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
#map-container {
|
| 456 |
+
width: 100%;
|
| 457 |
+
height: 100%;
|
| 458 |
+
min-height: 300px;
|
| 459 |
+
background: var(--bg3);
|
| 460 |
+
border-radius: var(--radius-sm);
|
| 461 |
+
overflow: hidden;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* Panel señales */
|
| 465 |
+
.signals-panel {
|
| 466 |
+
grid-row: 1 / 3;
|
| 467 |
+
grid-column: 2 / 3;
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.signals-list {
|
| 471 |
+
display: flex;
|
| 472 |
+
flex-direction: column;
|
| 473 |
+
gap: 16px;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.market-card {
|
| 477 |
+
background: var(--bg3);
|
| 478 |
+
border: 0.5px solid var(--border);
|
| 479 |
+
border-radius: var(--radius-sm);
|
| 480 |
+
padding: 14px;
|
| 481 |
+
cursor: pointer;
|
| 482 |
+
transition: border-color 0.15s, background 0.15s;
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.market-card:hover {
|
| 486 |
+
border-color: var(--border2);
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.market-card.active {
|
| 490 |
+
border-color: var(--blue2);
|
| 491 |
+
background: var(--blue3);
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.market-cat {
|
| 495 |
+
font-size: 14px;
|
| 496 |
+
font-family: 'DM Mono', monospace;
|
| 497 |
+
color: var(--text3);
|
| 498 |
+
text-transform: uppercase;
|
| 499 |
+
letter-spacing: 0.06em;
|
| 500 |
+
margin-bottom: 4px;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
.market-q {
|
| 504 |
+
font-size: 15px;
|
| 505 |
+
color: var(--text);
|
| 506 |
+
line-height: 1.4;
|
| 507 |
+
font-weight: 500;
|
| 508 |
+
margin-bottom: 8px;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.market-footer {
|
| 512 |
+
display: flex;
|
| 513 |
+
align-items: center;
|
| 514 |
+
justify-content: space-between;
|
| 515 |
+
gap: 16px;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
.prob-bar-wrap {
|
| 519 |
+
flex: 1;
|
| 520 |
+
min-width: 0;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.prob-bar-bg {
|
| 524 |
+
height: 5px;
|
| 525 |
+
background: var(--bg4);
|
| 526 |
+
border-radius: 2px;
|
| 527 |
+
overflow: hidden;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.prob-bar-fill {
|
| 531 |
+
height: 100%;
|
| 532 |
+
border-radius: 2px;
|
| 533 |
+
width: var(--prob-width, 0%);
|
| 534 |
+
transition: width 0.8s ease;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
.prob-val {
|
| 538 |
+
font-size: 15px;
|
| 539 |
+
font-weight: 600;
|
| 540 |
+
font-family: 'DM Mono', monospace;
|
| 541 |
+
flex-shrink: 0;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.signal-badge {
|
| 545 |
+
font-size: 14px;
|
| 546 |
+
font-weight: 600;
|
| 547 |
+
padding: 2px 6px;
|
| 548 |
+
border-radius: 4px;
|
| 549 |
+
font-family: 'DM Mono', monospace;
|
| 550 |
+
letter-spacing: 0.04em;
|
| 551 |
+
flex-shrink: 0;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
.sig-bull { background: var(--green3); color: var(--green); border: 0.5px solid var(--green2); }
|
| 555 |
+
.sig-bear { background: var(--red3); color: var(--red); border: 0.5px solid var(--red2); }
|
| 556 |
+
.sig-neut { background: var(--bg4); color: var(--text2); border: 0.5px solid var(--border2); }
|
| 557 |
+
|
| 558 |
+
/* Panel detalle */
|
| 559 |
+
.detail-panel {
|
| 560 |
+
grid-row: 2 / 3;
|
| 561 |
+
grid-column: 1 / 2;
|
| 562 |
+
max-height: 100%;
|
| 563 |
+
overflow: hidden;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.detail-panel .panel-body {
|
| 567 |
+
overflow-y: auto;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.detail-header {
|
| 571 |
+
display: flex;
|
| 572 |
+
align-items: flex-start;
|
| 573 |
+
justify-content: space-between;
|
| 574 |
+
margin-bottom: 12px;
|
| 575 |
+
gap: 16px;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.detail-tag {
|
| 579 |
+
font-size: 14px;
|
| 580 |
+
color: var(--blue);
|
| 581 |
+
font-family: 'DM Mono', monospace;
|
| 582 |
+
text-transform: uppercase;
|
| 583 |
+
letter-spacing: 0.08em;
|
| 584 |
+
margin-bottom: 4px;
|
| 585 |
+
}
|
| 586 |
+
|
| 587 |
+
.detail-q {
|
| 588 |
+
font-size: 16px;
|
| 589 |
+
font-weight: 600;
|
| 590 |
+
color: var(--text);
|
| 591 |
+
line-height: 1.5;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.detail-meta {
|
| 595 |
+
font-size: 14px;
|
| 596 |
+
color: var(--text3);
|
| 597 |
+
font-family: 'DM Mono', monospace;
|
| 598 |
+
margin-top: 2px;
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.detail-metrics {
|
| 602 |
+
display: flex;
|
| 603 |
+
gap: 16px;
|
| 604 |
+
align-items: center;
|
| 605 |
+
flex-shrink: 0;
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
.metric {
|
| 609 |
+
text-align: right;
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
.metric-label {
|
| 613 |
+
font-size: 14px;
|
| 614 |
+
color: var(--text3);
|
| 615 |
+
font-family: 'DM Mono', monospace;
|
| 616 |
+
margin-bottom: 2px;
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
.metric-value {
|
| 620 |
+
font-size: 14px;
|
| 621 |
+
font-weight: 700;
|
| 622 |
+
font-family: 'DM Mono', monospace;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.metric-sep {
|
| 626 |
+
width: 1px;
|
| 627 |
+
height: 40px;
|
| 628 |
+
background: var(--border);
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
.outcomes-row {
|
| 632 |
+
display: flex;
|
| 633 |
+
gap: 16px;
|
| 634 |
+
margin-bottom: 12px;
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
.outcome-card {
|
| 638 |
+
flex: 1;
|
| 639 |
+
background: var(--bg3);
|
| 640 |
+
border: 0.5px solid var(--border);
|
| 641 |
+
border-radius: var(--radius-sm);
|
| 642 |
+
padding: 14px;
|
| 643 |
+
text-align: center;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
.outcome-name {
|
| 647 |
+
font-size: 14px;
|
| 648 |
+
color: var(--text2);
|
| 649 |
+
margin-bottom: 4px;
|
| 650 |
+
font-family: 'DM Mono', monospace;
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
.outcome-price {
|
| 654 |
+
font-size: 20px;
|
| 655 |
+
font-weight: 700;
|
| 656 |
+
color: var(--text);
|
| 657 |
+
font-family: 'DM Mono', monospace;
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
.outcome-delta {
|
| 661 |
+
font-size: 14px;
|
| 662 |
+
font-family: 'DM Mono', monospace;
|
| 663 |
+
margin-top: 2px;
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
.outcome-card.yes .outcome-price { color: var(--green); }
|
| 667 |
+
.outcome-card.no .outcome-price { color: var(--red); }
|
| 668 |
+
|
| 669 |
+
.chart-container {
|
| 670 |
+
flex: 2;
|
| 671 |
+
background: var(--bg3);
|
| 672 |
+
border: 0.5px solid var(--border);
|
| 673 |
+
border-radius: var(--radius-sm);
|
| 674 |
+
padding: 14px;
|
| 675 |
+
min-height: 120px;
|
| 676 |
+
height: 160px;
|
| 677 |
+
max-height: 160px;
|
| 678 |
+
overflow: hidden;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.chart-container canvas {
|
| 682 |
+
max-height: 130px;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
.chart-label {
|
| 686 |
+
font-size: 14px;
|
| 687 |
+
color: var(--text3);
|
| 688 |
+
font-family: 'DM Mono', monospace;
|
| 689 |
+
margin-bottom: 6px;
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
.ai-box {
|
| 693 |
+
background: var(--bg3);
|
| 694 |
+
border: 0.5px solid var(--blue2);
|
| 695 |
+
border-radius: var(--radius-sm);
|
| 696 |
+
padding: 14px;
|
| 697 |
+
display: flex;
|
| 698 |
+
gap: 14px;
|
| 699 |
+
align-items: flex-start;
|
| 700 |
+
margin-bottom: 10px;
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
.ai-icon {
|
| 704 |
+
width: 32px;
|
| 705 |
+
height: 32px;
|
| 706 |
+
border-radius: 6px;
|
| 707 |
+
background: var(--blue3);
|
| 708 |
+
display: flex;
|
| 709 |
+
align-items: center;
|
| 710 |
+
justify-content: center;
|
| 711 |
+
flex-shrink: 0;
|
| 712 |
+
font-size: 14px;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
.ai-label {
|
| 716 |
+
font-size: 14px;
|
| 717 |
+
color: var(--blue);
|
| 718 |
+
font-family: 'DM Mono', monospace;
|
| 719 |
+
text-transform: uppercase;
|
| 720 |
+
letter-spacing: 0.06em;
|
| 721 |
+
margin-bottom: 3px;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
.ai-text {
|
| 725 |
+
font-size: 15px;
|
| 726 |
+
color: var(--text2);
|
| 727 |
+
line-height: 1.5;
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
.sim-row {
|
| 731 |
+
display: flex;
|
| 732 |
+
gap: 16px;
|
| 733 |
+
align-items: center;
|
| 734 |
+
flex-wrap: wrap;
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
.sim-label {
|
| 738 |
+
font-size: 15px;
|
| 739 |
+
color: var(--text3);
|
| 740 |
+
font-family: 'DM Mono', monospace;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
.sim-input {
|
| 744 |
+
background: var(--bg3);
|
| 745 |
+
border: 0.5px solid var(--border2);
|
| 746 |
+
border-radius: var(--radius-sm);
|
| 747 |
+
padding: 8px 14px;
|
| 748 |
+
color: var(--text);
|
| 749 |
+
font-size: 16px;
|
| 750 |
+
font-family: 'DM Mono', monospace;
|
| 751 |
+
width: 100px;
|
| 752 |
+
outline: none;
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
.sim-input:focus {
|
| 756 |
+
border-color: var(--blue2);
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
.sim-btn-yes {
|
| 760 |
+
background: var(--green3);
|
| 761 |
+
border: 0.5px solid var(--green2);
|
| 762 |
+
color: var(--green);
|
| 763 |
+
font-size: 15px;
|
| 764 |
+
font-weight: 600;
|
| 765 |
+
padding: 8px 16px;
|
| 766 |
+
border-radius: var(--radius-sm);
|
| 767 |
+
cursor: pointer;
|
| 768 |
+
font-family: 'DM Mono', monospace;
|
| 769 |
+
transition: opacity 0.15s;
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
.sim-btn-no {
|
| 773 |
+
background: var(--red3);
|
| 774 |
+
border: 0.5px solid var(--red2);
|
| 775 |
+
color: var(--red);
|
| 776 |
+
font-size: 15px;
|
| 777 |
+
font-weight: 600;
|
| 778 |
+
padding: 8px 16px;
|
| 779 |
+
border-radius: var(--radius-sm);
|
| 780 |
+
cursor: pointer;
|
| 781 |
+
font-family: 'DM Mono', monospace;
|
| 782 |
+
transition: opacity 0.15s;
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
.sim-btn-yes:hover, .sim-btn-no:hover {
|
| 786 |
+
opacity: 0.85;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
.sim-disclaimer {
|
| 790 |
+
font-size: 14px;
|
| 791 |
+
color: var(--text3);
|
| 792 |
+
font-family: 'DM Mono', monospace;
|
| 793 |
+
}
|
| 794 |
+
|
| 795 |
+
/* ─── Legend inline ─── */
|
| 796 |
+
.legend {
|
| 797 |
+
display: flex;
|
| 798 |
+
gap: 16px;
|
| 799 |
+
align-items: center;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
.legend-item {
|
| 803 |
+
display: flex;
|
| 804 |
+
align-items: center;
|
| 805 |
+
gap: 5px;
|
| 806 |
+
font-size: 14px;
|
| 807 |
+
color: var(--text3);
|
| 808 |
+
font-family: 'DM Mono', monospace;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
.legend-dot {
|
| 812 |
+
width: 10px;
|
| 813 |
+
height: 10px;
|
| 814 |
+
border-radius: 50%;
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
.legend-dot.green { background: var(--green); }
|
| 818 |
+
.legend-dot.red { background: var(--red); }
|
| 819 |
+
.legend-dot.gray { background: var(--text3); }
|
| 820 |
+
|
| 821 |
+
.legend.end { margin-left: auto; }
|
| 822 |
+
|
| 823 |
+
/* ─── Positions / Watchlist views ─── */
|
| 824 |
+
.table-wrap {
|
| 825 |
+
overflow: auto;
|
| 826 |
+
border: 0.5px solid var(--border);
|
| 827 |
+
border-radius: var(--radius-sm);
|
| 828 |
+
background: var(--bg3);
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
table {
|
| 832 |
+
width: 100%;
|
| 833 |
+
border-collapse: collapse;
|
| 834 |
+
font-size: 16px;
|
| 835 |
+
}
|
| 836 |
+
|
| 837 |
+
th, td {
|
| 838 |
+
padding: 14px 14px;
|
| 839 |
+
text-align: left;
|
| 840 |
+
border-bottom: 0.5px solid var(--border);
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
th {
|
| 844 |
+
font-family: 'DM Mono', monospace;
|
| 845 |
+
font-size: 14px;
|
| 846 |
+
color: var(--text3);
|
| 847 |
+
text-transform: uppercase;
|
| 848 |
+
letter-spacing: 0.06em;
|
| 849 |
+
font-weight: 500;
|
| 850 |
+
background: var(--bg4);
|
| 851 |
+
position: sticky;
|
| 852 |
+
top: 0;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
tr:hover td {
|
| 856 |
+
background: rgba(255,255,255,0.02);
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
td {
|
| 860 |
+
color: var(--text);
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
.td-mono {
|
| 864 |
+
font-family: 'DM Mono', monospace;
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
.td-green { color: var(--green); }
|
| 868 |
+
.td-red { color: var(--red); }
|
| 869 |
+
.td-blue { color: var(--blue); }
|
| 870 |
+
|
| 871 |
+
.empty-state {
|
| 872 |
+
padding: 40px;
|
| 873 |
+
text-align: center;
|
| 874 |
+
color: var(--text3);
|
| 875 |
+
font-size: 16px;
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
/* ─── Leaflet overrides ─── */
|
| 879 |
+
#app .leaflet-container {
|
| 880 |
+
background: var(--bg3);
|
| 881 |
+
font-family: 'DM Mono', monospace;
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
#app .leaflet-popup-content-wrapper {
|
| 885 |
+
background: var(--bg2);
|
| 886 |
+
color: var(--text);
|
| 887 |
+
border: 0.5px solid var(--border);
|
| 888 |
+
border-radius: var(--radius-sm);
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
#app .leaflet-popup-tip {
|
| 892 |
+
background: var(--bg2);
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
/* ─── Sparklines ─── */
|
| 896 |
+
.sparkline {
|
| 897 |
+
display: flex;
|
| 898 |
+
align-items: flex-end;
|
| 899 |
+
gap: 2px;
|
| 900 |
+
height: 32px;
|
| 901 |
+
margin-top: 6px;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
.spark-bar {
|
| 905 |
+
width: 3px;
|
| 906 |
+
border-radius: 1px;
|
| 907 |
+
background: var(--blue2);
|
| 908 |
+
transition: height 0.3s;
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
/* ─── Animations ─── */
|
| 912 |
+
@keyframes pulse {
|
| 913 |
+
0%, 100% { opacity: 1; }
|
| 914 |
+
50% { opacity: 0.4; }
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
/* ─── Scrollbar ─── */
|
| 918 |
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 919 |
+
::-webkit-scrollbar-track { background: transparent; }
|
| 920 |
+
::-webkit-scrollbar-thumb { background: var(--bg4); border-radius: 3px; }
|
| 921 |
+
::-webkit-scrollbar-thumb:hover { background: var(--text3); }
|
| 922 |
+
|
| 923 |
+
/* ─── Utilities ─── */
|
| 924 |
+
.flex-between {
|
| 925 |
+
display: flex;
|
| 926 |
+
justify-content: space-between;
|
| 927 |
+
align-items: center;
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
.flex-start {
|
| 931 |
+
display: flex;
|
| 932 |
+
align-items: flex-start;
|
| 933 |
+
justify-content: space-between;
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
.flex-row {
|
| 937 |
+
display: flex;
|
| 938 |
+
align-items: center;
|
| 939 |
+
}
|
| 940 |
+
|
| 941 |
+
.flex-wrap {
|
| 942 |
+
flex-wrap: wrap;
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
.gap-6 {
|
| 946 |
+
gap: 14px;
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
.gap-8 {
|
| 950 |
+
gap: 16px;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
.text-green { color: var(--green); }
|
| 954 |
+
.text-red { color: var(--red); }
|
| 955 |
+
.text-blue { color: var(--blue); }
|
| 956 |
+
.text-amber { color: var(--amber); }
|
| 957 |
+
.text-neutral { color: var(--text3); }
|
| 958 |
+
|
| 959 |
+
.bg-green { background: var(--green); }
|
| 960 |
+
.bg-red { background: var(--red); }
|
| 961 |
+
.bg-amber { background: var(--amber); }
|
| 962 |
+
|
| 963 |
+
.flex-1 { flex: 1; }
|
| 964 |
+
|
| 965 |
+
.font-mono { font-family: 'DM Mono', monospace; }
|
| 966 |
+
|
| 967 |
+
.text-xs { font-size: 14px; }
|
| 968 |
+
.text-sm { font-size: 14px; }
|
| 969 |
+
.text-base { font-size: 15px; }
|
| 970 |
+
.text-lg { font-size: 16px; }
|
| 971 |
+
.text-xl { font-size: 18px; }
|
| 972 |
+
|
| 973 |
+
.font-semibold { font-weight: 600; }
|
| 974 |
+
.font-bold { font-weight: 700; }
|
| 975 |
+
|
| 976 |
+
.mb-4 { margin-bottom: 4px; }
|
| 977 |
+
.mb-6 { margin-bottom: 6px; }
|
| 978 |
+
.mb-8 { margin-bottom: 8px; }
|
| 979 |
+
.mt-4 { margin-top: 4px; }
|
| 980 |
+
.mt-6 { margin-top: 6px; }
|
| 981 |
+
.ml-auto { margin-left: auto; }
|
| 982 |
+
|
| 983 |
+
.divider {
|
| 984 |
+
height: 1px;
|
| 985 |
+
background: var(--border);
|
| 986 |
+
margin: 8px 0;
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
.empty-state-sm {
|
| 990 |
+
padding: 16px;
|
| 991 |
+
text-align: center;
|
| 992 |
+
color: var(--text3);
|
| 993 |
+
font-size: 16px;
|
| 994 |
+
}
|
| 995 |
+
|
| 996 |
+
/* Map popup */
|
| 997 |
+
.map-popup {
|
| 998 |
+
font-family: 'Syne', sans-serif;
|
| 999 |
+
font-size: 15px;
|
| 1000 |
+
color: var(--text);
|
| 1001 |
+
max-width: 200px;
|
| 1002 |
+
}
|
| 1003 |
+
|
| 1004 |
+
.map-popup-cat {
|
| 1005 |
+
font-size: 14px;
|
| 1006 |
+
color: var(--text3);
|
| 1007 |
+
font-family: 'DM Mono', monospace;
|
| 1008 |
+
margin-bottom: 4px;
|
| 1009 |
+
text-transform: uppercase;
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
.map-popup-q {
|
| 1013 |
+
font-weight: 600;
|
| 1014 |
+
margin-bottom: 4px;
|
| 1015 |
+
line-height: 1.3;
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
.map-popup-prices {
|
| 1019 |
+
display: flex;
|
| 1020 |
+
gap: 16px;
|
| 1021 |
+
font-family: 'DM Mono', monospace;
|
| 1022 |
+
font-size: 15px;
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
.map-label-text {
|
| 1026 |
+
color: var(--text2);
|
| 1027 |
+
font-family: 'DM Mono', monospace;
|
| 1028 |
+
font-size: 14px;
|
| 1029 |
+
text-shadow: 0 1px 2px #000;
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
/* ─── Responsive ─── */
|
| 1033 |
+
@media (max-width: 1024px) {
|
| 1034 |
+
.dashboard-grid {
|
| 1035 |
+
grid-template-columns: 1fr;
|
| 1036 |
+
grid-template-rows: auto auto auto;
|
| 1037 |
+
}
|
| 1038 |
+
.signals-panel {
|
| 1039 |
+
grid-row: auto;
|
| 1040 |
+
grid-column: auto;
|
| 1041 |
+
max-height: 400px;
|
| 1042 |
+
}
|
| 1043 |
+
.map-panel {
|
| 1044 |
+
grid-row: auto;
|
| 1045 |
+
grid-column: auto;
|
| 1046 |
+
min-height: 300px;
|
| 1047 |
+
}
|
| 1048 |
+
.detail-panel {
|
| 1049 |
+
grid-row: auto;
|
| 1050 |
+
grid-column: auto;
|
| 1051 |
+
}
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
+
@media (max-width: 640px) {
|
| 1055 |
+
.layout {
|
| 1056 |
+
grid-template-columns: 0 1fr;
|
| 1057 |
+
}
|
| 1058 |
+
.sidebar { display: none; }
|
| 1059 |
+
.topbar-stats { display: none; }
|
| 1060 |
+
.outcomes-row { flex-direction: column; }
|
| 1061 |
+
}
|
package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
| 19 |
"db:studio": "npm run db:studio --workspace=backend"
|
| 20 |
},
|
| 21 |
"engines": {
|
| 22 |
-
"node": ">=
|
| 23 |
},
|
| 24 |
"keywords": [
|
| 25 |
"polymarket",
|
|
|
|
| 19 |
"db:studio": "npm run db:studio --workspace=backend"
|
| 20 |
},
|
| 21 |
"engines": {
|
| 22 |
+
"node": ">=26.0.0"
|
| 23 |
},
|
| 24 |
"keywords": [
|
| 25 |
"polymarket",
|
polysignal_app_mockup.html
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
<h2 class="sr-only">Mockup interactivo de PolySignal — dashboard con mapa global de predicciones y panel de señales IA</h2>
|
| 3 |
+
<style>
|
| 4 |
+
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@500;600;700&display=swap');
|
| 5 |
+
*{box-sizing:border-box;margin:0;padding:0}
|
| 6 |
+
:root{
|
| 7 |
+
--bg:#0a0c10;--bg2:#111318;--bg3:#181c24;--bg4:#1e232d;
|
| 8 |
+
--border:#ffffff14;--border2:#ffffff22;
|
| 9 |
+
--text:#e8eaf0;--text2:#8b90a0;--text3:#555b6e;
|
| 10 |
+
--green:#22d37a;--green2:#0d6e3a;--green3:#052a17;
|
| 11 |
+
--red:#f04040;--red2:#7a1a1a;--red3:#2d0808;
|
| 12 |
+
--blue:#4a9eff;--blue2:#1a4a80;--blue3:#081830;
|
| 13 |
+
--amber:#f0a020;--amber2:#7a4e08;--amber3:#2d1c02;
|
| 14 |
+
--accent:#4a9eff;
|
| 15 |
+
}
|
| 16 |
+
.app{background:var(--bg);border-radius:16px;border:0.5px solid var(--border2);overflow:hidden;font-family:'Syne',sans-serif}
|
| 17 |
+
.topbar{background:var(--bg2);border-bottom:0.5px solid var(--border);padding:10px 16px;display:flex;align-items:center;gap:12px}
|
| 18 |
+
.logo{font-size:15px;font-weight:700;color:var(--text);letter-spacing:-0.3px;display:flex;align-items:center;gap:6px}
|
| 19 |
+
.logo-dot{width:8px;height:8px;border-radius:50%;background:var(--blue);animation:pulse 2s ease-in-out infinite}
|
| 20 |
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
| 21 |
+
.live-badge{font-family:'DM Mono',monospace;font-size:10px;background:var(--green3);color:var(--green);border:0.5px solid var(--green2);padding:3px 8px;border-radius:4px;display:flex;align-items:center;gap:4px}
|
| 22 |
+
.live-dot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:pulse 1.5s ease-in-out infinite}
|
| 23 |
+
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px}
|
| 24 |
+
.tg-btn{font-size:11px;font-weight:500;color:var(--blue);border:0.5px solid var(--blue2);background:var(--blue3);padding:4px 10px;border-radius:6px;cursor:pointer;font-family:'Syne',sans-serif}
|
| 25 |
+
.stat-row{background:var(--bg2);border-bottom:0.5px solid var(--border);padding:8px 16px;display:flex;gap:24px}
|
| 26 |
+
.stat{display:flex;align-items:center;gap:6px}
|
| 27 |
+
.stat-label{font-size:10px;color:var(--text3);font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:0.06em}
|
| 28 |
+
.stat-val{font-size:13px;font-weight:600;color:var(--text);font-family:'DM Mono',monospace}
|
| 29 |
+
.stat-delta{font-size:10px;font-family:'DM Mono',monospace}
|
| 30 |
+
.stat-delta.up{color:var(--green)}
|
| 31 |
+
.stat-delta.dn{color:var(--red)}
|
| 32 |
+
.main{display:grid;grid-template-columns:1fr 220px}
|
| 33 |
+
.map-area{padding:16px;border-right:0.5px solid var(--border)}
|
| 34 |
+
.map-title{font-size:11px;color:var(--text3);font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:10px;display:flex;align-items:center;gap:8px}
|
| 35 |
+
.map-wrap{background:var(--bg3);border-radius:10px;border:0.5px solid var(--border);overflow:hidden;position:relative}
|
| 36 |
+
.map-svg{width:100%;display:block}
|
| 37 |
+
.signal-panel{padding:12px}
|
| 38 |
+
.panel-title{font-size:10px;color:var(--text3);font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:10px}
|
| 39 |
+
.market-card{background:var(--bg3);border:0.5px solid var(--border);border-radius:8px;padding:10px;margin-bottom:8px;cursor:pointer;transition:border-color 0.15s}
|
| 40 |
+
.market-card:hover{border-color:var(--border2)}
|
| 41 |
+
.market-card.active{border-color:var(--blue2);background:var(--blue3)}
|
| 42 |
+
.market-cat{font-size:9px;font-family:'DM Mono',monospace;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:4px}
|
| 43 |
+
.market-q{font-size:11px;color:var(--text);line-height:1.4;font-weight:500;margin-bottom:8px}
|
| 44 |
+
.market-footer{display:flex;align-items:center;justify-content:space-between}
|
| 45 |
+
.prob-bar-wrap{flex:1;margin-right:8px}
|
| 46 |
+
.prob-bar-bg{height:3px;background:var(--bg4);border-radius:2px;overflow:hidden}
|
| 47 |
+
.prob-bar-fill{height:100%;border-radius:2px;transition:width 0.8s ease}
|
| 48 |
+
.prob-val{font-size:11px;font-weight:600;font-family:'DM Mono',monospace}
|
| 49 |
+
.signal-badge{font-size:9px;font-weight:600;padding:2px 6px;border-radius:4px;font-family:'DM Mono',monospace;letter-spacing:0.04em}
|
| 50 |
+
.sig-bull{background:var(--green3);color:var(--green);border:0.5px solid var(--green2)}
|
| 51 |
+
.sig-bear{background:var(--red3);color:var(--red);border:0.5px solid var(--red2)}
|
| 52 |
+
.sig-neut{background:var(--bg4);color:var(--text2);border:0.5px solid var(--border2)}
|
| 53 |
+
.detail-section{background:var(--bg2);border-top:0.5px solid var(--border);padding:14px 16px}
|
| 54 |
+
.detail-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px}
|
| 55 |
+
.detail-q{font-size:13px;font-weight:600;color:var(--text);line-height:1.5;max-width:380px}
|
| 56 |
+
.detail-meta{font-size:10px;color:var(--text3);font-family:'DM Mono',monospace;margin-top:2px}
|
| 57 |
+
.outcomes-row{display:flex;gap:8px;margin-bottom:12px}
|
| 58 |
+
.outcome-card{flex:1;background:var(--bg3);border:0.5px solid var(--border);border-radius:8px;padding:10px;text-align:center}
|
| 59 |
+
.outcome-name{font-size:10px;color:var(--text2);margin-bottom:4px;font-family:'DM Mono',monospace}
|
| 60 |
+
.outcome-price{font-size:20px;font-weight:700;color:var(--text);font-family:'DM Mono',monospace}
|
| 61 |
+
.outcome-delta{font-size:10px;font-family:'DM Mono',monospace;margin-top:2px}
|
| 62 |
+
.outcome-card.yes .outcome-price{color:var(--green)}
|
| 63 |
+
.outcome-card.no .outcome-price{color:var(--red)}
|
| 64 |
+
.ai-box{background:var(--bg3);border:0.5px solid var(--blue2);border-radius:8px;padding:10px;display:flex;gap:10px;align-items:flex-start}
|
| 65 |
+
.ai-icon{width:28px;height:28px;border-radius:6px;background:var(--blue3);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:14px}
|
| 66 |
+
.ai-label{font-size:9px;color:var(--blue);font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:0.06em;margin-bottom:3px}
|
| 67 |
+
.ai-text{font-size:11px;color:var(--text2);line-height:1.5}
|
| 68 |
+
.sim-row{display:flex;gap:8px;margin-top:10px;align-items:center}
|
| 69 |
+
.sim-input{background:var(--bg3);border:0.5px solid var(--border2);border-radius:6px;padding:6px 10px;color:var(--text);font-size:12px;font-family:'DM Mono',monospace;width:80px;outline:none}
|
| 70 |
+
.sim-btn-yes{background:var(--green3);border:0.5px solid var(--green2);color:var(--green);font-size:11px;font-weight:600;padding:6px 14px;border-radius:6px;cursor:pointer;font-family:'DM Mono',monospace}
|
| 71 |
+
.sim-btn-no{background:var(--red3);border:0.5px solid var(--red2);color:var(--red);font-size:11px;font-weight:600;padding:6px 14px;border-radius:6px;cursor:pointer;font-family:'DM Mono',monospace}
|
| 72 |
+
.sim-label{font-size:11px;color:var(--text3);font-family:'DM Mono',monospace}
|
| 73 |
+
.legend{display:flex;gap:12px;align-items:center;margin-top:6px}
|
| 74 |
+
.legend-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--text3);font-family:'DM Mono',monospace}
|
| 75 |
+
.legend-dot{width:8px;height:8px;border-radius:50%}
|
| 76 |
+
@keyframes float{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-3px) scale(1.05)}}
|
| 77 |
+
.bubble{cursor:pointer;transition:opacity 0.2s}
|
| 78 |
+
.bubble:hover{opacity:0.85}
|
| 79 |
+
.bubble.active-b circle{stroke-width:2;stroke:#4a9eff}
|
| 80 |
+
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
|
| 81 |
+
.sparkline{display:flex;align-items:flex-end;gap:1.5px;height:28px;margin-top:4px}
|
| 82 |
+
.spark-bar{width:3px;border-radius:1px;background:var(--blue2);transition:height 0.3s}
|
| 83 |
+
</style>
|
| 84 |
+
|
| 85 |
+
<div class="app">
|
| 86 |
+
|
| 87 |
+
<div class="topbar">
|
| 88 |
+
<div class="logo">
|
| 89 |
+
<div class="logo-dot"></div>
|
| 90 |
+
PolySignal
|
| 91 |
+
</div>
|
| 92 |
+
<div class="live-badge">
|
| 93 |
+
<div class="live-dot"></div>
|
| 94 |
+
LIVE
|
| 95 |
+
</div>
|
| 96 |
+
<div style="font-size:10px;color:var(--text3);font-family:'DM Mono',monospace">Polymarket · Finnhub · HuggingFace</div>
|
| 97 |
+
<div class="topbar-right">
|
| 98 |
+
<div class="tg-btn"><i class="ti ti-send" style="font-size:11px;margin-right:3px" aria-hidden="true"></i>Telegram alerts</div>
|
| 99 |
+
<div style="width:28px;height:28px;border-radius:50%;background:var(--bg3);border:0.5px solid var(--border2);display:flex;align-items:center;justify-content:center">
|
| 100 |
+
<i class="ti ti-bell" style="font-size:13px;color:var(--text2)" aria-hidden="true"></i>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div class="stat-row">
|
| 106 |
+
<div class="stat">
|
| 107 |
+
<span class="stat-label">Markets</span>
|
| 108 |
+
<span class="stat-val" id="mkt-count">2,847</span>
|
| 109 |
+
</div>
|
| 110 |
+
<div class="stat">
|
| 111 |
+
<span class="stat-label">Volume 24h</span>
|
| 112 |
+
<span class="stat-val">$4.2M</span>
|
| 113 |
+
<span class="stat-delta up">+12.4%</span>
|
| 114 |
+
</div>
|
| 115 |
+
<div class="stat">
|
| 116 |
+
<span class="stat-label">AI signals</span>
|
| 117 |
+
<span class="stat-val" id="sig-count">183</span>
|
| 118 |
+
<span class="stat-delta up">bullish</span>
|
| 119 |
+
</div>
|
| 120 |
+
<div class="stat">
|
| 121 |
+
<span class="stat-label">Alerts sent</span>
|
| 122 |
+
<span class="stat-val">47</span>
|
| 123 |
+
<span class="stat-delta" style="color:var(--text3)">today</span>
|
| 124 |
+
</div>
|
| 125 |
+
<div style="margin-left:auto;display:flex;align-items:center;gap:6px">
|
| 126 |
+
<div class="legend">
|
| 127 |
+
<div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div>bullish</div>
|
| 128 |
+
<div class="legend-item"><div class="legend-dot" style="background:var(--red)"></div>bearish</div>
|
| 129 |
+
<div class="legend-item"><div class="legend-dot" style="background:var(--text3)"></div>neutral</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<div class="main">
|
| 135 |
+
<div class="map-area">
|
| 136 |
+
<div class="map-title">
|
| 137 |
+
<i class="ti ti-world" style="font-size:13px" aria-hidden="true"></i>
|
| 138 |
+
Global prediction map
|
| 139 |
+
<span style="font-size:9px;color:var(--text3)">· bubble size = volume · color = AI signal</span>
|
| 140 |
+
</div>
|
| 141 |
+
<div class="map-wrap">
|
| 142 |
+
<svg class="map-svg" viewBox="0 0 440 240" id="world-map">
|
| 143 |
+
<rect width="440" height="240" fill="#0e1218"/>
|
| 144 |
+
<g fill="#1e2530" stroke="#2a3040" stroke-width="0.4">
|
| 145 |
+
<path d="M35 90 Q55 75 90 80 Q120 85 130 100 Q140 115 125 125 Q110 135 85 130 Q60 125 45 115 Q30 105 35 90Z"/>
|
| 146 |
+
<path d="M88 100 Q115 95 135 105 Q155 115 160 130 Q165 145 145 155 Q125 165 105 158 Q85 150 80 135 Q75 120 88 100Z"/>
|
| 147 |
+
<path d="M148 88 Q175 80 195 92 Q215 104 218 118 Q221 132 205 140 Q189 148 168 142 Q147 136 142 122 Q137 108 148 88Z"/>
|
| 148 |
+
<path d="M195 78 Q225 68 255 76 Q280 84 288 98 Q296 112 285 124 Q274 136 248 138 Q222 140 204 128 Q186 116 185 102 Q184 88 195 78Z"/>
|
| 149 |
+
<path d="M250 80 Q278 70 305 80 Q330 90 335 108 Q340 126 320 136 Q300 146 275 140 Q250 134 242 118 Q234 102 250 80Z"/>
|
| 150 |
+
<path d="M310 72 Q345 62 375 75 Q400 88 405 105 Q410 122 392 132 Q374 142 345 138 Q316 134 305 118 Q294 102 310 72Z"/>
|
| 151 |
+
<path d="M45 145 Q70 138 90 148 Q110 158 112 172 Q114 186 96 192 Q78 198 58 190 Q38 182 36 168 Q34 154 45 145Z"/>
|
| 152 |
+
<path d="M100 140 Q130 132 155 145 Q175 158 176 175 Q177 192 158 198 Q139 204 116 196 Q93 188 90 173 Q87 158 100 140Z"/>
|
| 153 |
+
<path d="M320 135 Q345 128 365 140 Q382 152 380 168 Q378 184 360 190 Q342 196 325 187 Q308 178 308 163 Q308 148 320 135Z"/>
|
| 154 |
+
<path d="M200 150 Q228 142 250 155 Q268 168 266 185 Q264 202 244 208 Q224 214 206 204 Q188 194 187 177 Q186 160 200 150Z"/>
|
| 155 |
+
<path d="M380 65 Q400 60 415 68 Q428 76 425 88 Q422 100 408 104 Q394 108 382 100 Q370 92 372 80 Q374 68 380 65Z"/>
|
| 156 |
+
</g>
|
| 157 |
+
<g id="bubbles">
|
| 158 |
+
<g class="bubble active-b" id="b-usa" onclick="selectMarket('usa')">
|
| 159 |
+
<circle cx="95" cy="105" r="14" fill="#22d37a" fill-opacity="0.22" stroke="#22d37a" stroke-width="1" stroke-opacity="0.6"/>
|
| 160 |
+
<circle cx="95" cy="105" r="7" fill="#22d37a" fill-opacity="0.8"/>
|
| 161 |
+
<text x="95" y="128" text-anchor="middle" font-size="8" fill="#8b90a0" font-family="DM Mono,monospace">USA</text>
|
| 162 |
+
</g>
|
| 163 |
+
<g class="bubble" id="b-eur" onclick="selectMarket('eur')">
|
| 164 |
+
<circle cx="215" cy="98" r="11" fill="#f04040" fill-opacity="0.22" stroke="#f04040" stroke-width="1" stroke-opacity="0.6"/>
|
| 165 |
+
<circle cx="215" cy="98" r="5.5" fill="#f04040" fill-opacity="0.8"/>
|
| 166 |
+
<text x="215" y="118" text-anchor="middle" font-size="8" fill="#8b90a0" font-family="DM Mono,monospace">EU</text>
|
| 167 |
+
</g>
|
| 168 |
+
<g class="bubble" id="b-uk" onclick="selectMarket('uk')">
|
| 169 |
+
<circle cx="172" cy="85" r="8" fill="#8b90a0" fill-opacity="0.22" stroke="#8b90a0" stroke-width="1" stroke-opacity="0.5"/>
|
| 170 |
+
<circle cx="172" cy="85" r="4" fill="#8b90a0" fill-opacity="0.7"/>
|
| 171 |
+
<text x="172" y="102" text-anchor="middle" font-size="7" fill="#8b90a0" font-family="DM Mono,monospace">UK</text>
|
| 172 |
+
</g>
|
| 173 |
+
<g class="bubble" id="b-bra" onclick="selectMarket('bra')">
|
| 174 |
+
<circle cx="130" cy="175" r="9" fill="#22d37a" fill-opacity="0.22" stroke="#22d37a" stroke-width="1" stroke-opacity="0.5"/>
|
| 175 |
+
<circle cx="130" cy="175" r="4.5" fill="#22d37a" fill-opacity="0.75"/>
|
| 176 |
+
<text x="130" y="193" text-anchor="middle" font-size="7" fill="#8b90a0" font-family="DM Mono,monospace">BRA</text>
|
| 177 |
+
</g>
|
| 178 |
+
<g class="bubble" id="b-chn" onclick="selectMarket('chn')">
|
| 179 |
+
<circle cx="340" cy="105" r="16" fill="#f0a020" fill-opacity="0.22" stroke="#f0a020" stroke-width="1" stroke-opacity="0.6"/>
|
| 180 |
+
<circle cx="340" cy="105" r="8" fill="#f0a020" fill-opacity="0.8"/>
|
| 181 |
+
<text x="340" y="130" text-anchor="middle" font-size="8" fill="#8b90a0" font-family="DM Mono,monospace">CHN</text>
|
| 182 |
+
</g>
|
| 183 |
+
<g class="bubble" id="b-ind" onclick="selectMarket('ind')">
|
| 184 |
+
<circle cx="295" cy="130" r="10" fill="#22d37a" fill-opacity="0.22" stroke="#22d37a" stroke-width="1" stroke-opacity="0.5"/>
|
| 185 |
+
<circle cx="295" cy="130" r="5" fill="#22d37a" fill-opacity="0.75"/>
|
| 186 |
+
<text x="295" y="149" text-anchor="middle" font-size="7" fill="#8b90a0" font-family="DM Mono,monospace">IND</text>
|
| 187 |
+
</g>
|
| 188 |
+
<g class="bubble" id="b-kor" onclick="selectMarket('kor')">
|
| 189 |
+
<circle cx="378" cy="98" r="7" fill="#f04040" fill-opacity="0.2" stroke="#f04040" stroke-width="1" stroke-opacity="0.5"/>
|
| 190 |
+
<circle cx="378" cy="98" r="3.5" fill="#f04040" fill-opacity="0.75"/>
|
| 191 |
+
<text x="378" y="113" text-anchor="middle" font-size="7" fill="#8b90a0" font-family="DM Mono,monospace">KOR</text>
|
| 192 |
+
</g>
|
| 193 |
+
<g class="bubble" id="b-sau" onclick="selectMarket('sau')">
|
| 194 |
+
<circle cx="248" cy="128" r="8" fill="#8b90a0" fill-opacity="0.2" stroke="#8b90a0" stroke-width="1" stroke-opacity="0.4"/>
|
| 195 |
+
<circle cx="248" cy="128" r="4" fill="#8b90a0" fill-opacity="0.65"/>
|
| 196 |
+
<text x="248" y="144" text-anchor="middle" font-size="7" fill="#8b90a0" font-family="DM Mono,monospace">SAU</text>
|
| 197 |
+
</g>
|
| 198 |
+
</g>
|
| 199 |
+
<g>
|
| 200 |
+
<rect x="8" y="8" width="62" height="14" rx="3" fill="#0a0c10" fill-opacity="0.8"/>
|
| 201 |
+
<text x="12" y="18" font-size="8" fill="#555b6e" font-family="DM Mono,monospace">SELECTED: USA</text>
|
| 202 |
+
</g>
|
| 203 |
+
</svg>
|
| 204 |
+
</div>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<div class="signal-panel">
|
| 208 |
+
<div class="panel-title">AI signals — top markets</div>
|
| 209 |
+
|
| 210 |
+
<div class="market-card active" onclick="selectMarket('usa')">
|
| 211 |
+
<div class="market-cat">Politics · USA</div>
|
| 212 |
+
<div class="market-q">Will Trump sign the tax bill before June 2026?</div>
|
| 213 |
+
<div class="market-footer">
|
| 214 |
+
<div class="prob-bar-wrap">
|
| 215 |
+
<div class="prob-bar-bg"><div class="prob-bar-fill" style="width:73%;background:var(--green)"></div></div>
|
| 216 |
+
</div>
|
| 217 |
+
<span class="prob-val" style="color:var(--green);margin-right:6px">73¢</span>
|
| 218 |
+
<span class="signal-badge sig-bull">BULL</span>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
|
| 222 |
+
<div class="market-card" onclick="selectMarket('eur')">
|
| 223 |
+
<div class="market-cat">Economy · EU</div>
|
| 224 |
+
<div class="market-q">ECB rate cut in June 2026?</div>
|
| 225 |
+
<div class="market-footer">
|
| 226 |
+
<div class="prob-bar-wrap">
|
| 227 |
+
<div class="prob-bar-bg"><div class="prob-bar-fill" style="width:34%;background:var(--red)"></div></div>
|
| 228 |
+
</div>
|
| 229 |
+
<span class="prob-val" style="color:var(--red);margin-right:6px">34¢</span>
|
| 230 |
+
<span class="signal-badge sig-bear">BEAR</span>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
|
| 234 |
+
<div class="market-card" onclick="selectMarket('chn')">
|
| 235 |
+
<div class="market-cat">Trade · CHN</div>
|
| 236 |
+
<div class="market-q">US-China tariff deal before Q3 2026?</div>
|
| 237 |
+
<div class="market-footer">
|
| 238 |
+
<div class="prob-bar-wrap">
|
| 239 |
+
<div class="prob-bar-bg"><div class="prob-bar-fill" style="width:51%;background:var(--amber)"></div></div>
|
| 240 |
+
</div>
|
| 241 |
+
<span class="prob-val" style="color:var(--amber);margin-right:6px">51¢</span>
|
| 242 |
+
<span class="signal-badge sig-neut">NEUT</span>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
<div class="market-card" onclick="selectMarket('bra')">
|
| 247 |
+
<div class="market-cat">Crypto · Global</div>
|
| 248 |
+
<div class="market-q">Bitcoin above $120k before July?</div>
|
| 249 |
+
<div class="market-footer">
|
| 250 |
+
<div class="prob-bar-wrap">
|
| 251 |
+
<div class="prob-bar-bg"><div class="prob-bar-fill" style="width:61%;background:var(--green)"></div></div>
|
| 252 |
+
</div>
|
| 253 |
+
<span class="prob-val" style="color:var(--green);margin-right:6px">61¢</span>
|
| 254 |
+
<span class="signal-badge sig-bull">BULL</span>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
<div style="margin-top:10px;padding-top:10px;border-top:0.5px solid var(--border)">
|
| 259 |
+
<div class="panel-title">My positions</div>
|
| 260 |
+
<div style="background:var(--bg3);border-radius:8px;border:0.5px solid var(--border);padding:10px">
|
| 261 |
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
| 262 |
+
<span style="font-size:10px;color:var(--text2);font-family:'DM Mono',monospace">Trump tax bill YES</span>
|
| 263 |
+
<span style="font-size:11px;font-weight:600;color:var(--green);font-family:'DM Mono',monospace">+$14.20</span>
|
| 264 |
+
</div>
|
| 265 |
+
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 266 |
+
<span style="font-size:10px;color:var(--text2);font-family:'DM Mono',monospace">ECB cut NO</span>
|
| 267 |
+
<span style="font-size:11px;font-weight:600;color:var(--red);font-family:'DM Mono',monospace">-$3.80</span>
|
| 268 |
+
</div>
|
| 269 |
+
<div style="height:1px;background:var(--border);margin:8px 0"></div>
|
| 270 |
+
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 271 |
+
<span style="font-size:10px;color:var(--text3);font-family:'DM Mono',monospace">Net P&L</span>
|
| 272 |
+
<span style="font-size:13px;font-weight:700;color:var(--green);font-family:'DM Mono',monospace">+$10.40</span>
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
+
|
| 279 |
+
<div class="detail-section">
|
| 280 |
+
<div class="detail-header">
|
| 281 |
+
<div>
|
| 282 |
+
<div style="font-size:9px;color:var(--blue);font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px">
|
| 283 |
+
<i class="ti ti-map-pin" style="font-size:10px" aria-hidden="true"></i> USA · Politics · Polymarket
|
| 284 |
+
</div>
|
| 285 |
+
<div class="detail-q" id="detail-q">Will Trump sign the tax bill before June 2026?</div>
|
| 286 |
+
<div class="detail-meta" id="detail-meta">Vol: $1.24M · Liq: $340K · Closes: Jun 1 2026</div>
|
| 287 |
+
</div>
|
| 288 |
+
<div style="display:flex;gap:6px;align-items:center">
|
| 289 |
+
<div style="text-align:right">
|
| 290 |
+
<div style="font-size:9px;color:var(--text3);font-family:'DM Mono',monospace;margin-bottom:2px">24h change</div>
|
| 291 |
+
<div style="font-size:14px;font-weight:700;color:var(--green);font-family:'DM Mono',monospace">+4.2%</div>
|
| 292 |
+
</div>
|
| 293 |
+
<div style="width:1px;height:32px;background:var(--border);margin:0 4px"></div>
|
| 294 |
+
<div style="text-align:right">
|
| 295 |
+
<div style="font-size:9px;color:var(--text3);font-family:'DM Mono',monospace;margin-bottom:2px">Confidence</div>
|
| 296 |
+
<div style="font-size:14px;font-weight:700;color:var(--blue);font-family:'DM Mono',monospace">87%</div>
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
+
</div>
|
| 300 |
+
|
| 301 |
+
<div class="outcomes-row">
|
| 302 |
+
<div class="outcome-card yes">
|
| 303 |
+
<div class="outcome-name">YES</div>
|
| 304 |
+
<div class="outcome-price">73¢</div>
|
| 305 |
+
<div class="outcome-delta up" style="color:var(--green)">▲ 3.1¢</div>
|
| 306 |
+
<div class="sparkline" id="spark-yes"></div>
|
| 307 |
+
</div>
|
| 308 |
+
<div class="outcome-card no">
|
| 309 |
+
<div class="outcome-name">NO</div>
|
| 310 |
+
<div class="outcome-price">27¢</div>
|
| 311 |
+
<div class="outcome-delta" style="color:var(--red)">▼ 3.1¢</div>
|
| 312 |
+
<div class="sparkline" id="spark-no"></div>
|
| 313 |
+
</div>
|
| 314 |
+
<div style="flex:2;background:var(--bg3);border:0.5px solid var(--border);border-radius:8px;padding:10px">
|
| 315 |
+
<div style="font-size:9px;color:var(--text3);font-family:'DM Mono',monospace;margin-bottom:6px">Price history 7d</div>
|
| 316 |
+
<canvas id="mini-chart" height="48"></canvas>
|
| 317 |
+
</div>
|
| 318 |
+
</div>
|
| 319 |
+
|
| 320 |
+
<div class="ai-box">
|
| 321 |
+
<div class="ai-icon">
|
| 322 |
+
<i class="ti ti-brain" style="font-size:15px;color:var(--blue)" aria-hidden="true"></i>
|
| 323 |
+
</div>
|
| 324 |
+
<div style="flex:1">
|
| 325 |
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
| 326 |
+
<div class="ai-label">AI analysis · HuggingFace Mistral-7B</div>
|
| 327 |
+
<span class="signal-badge sig-bull">BULLISH · 87%</span>
|
| 328 |
+
<span style="font-size:9px;color:var(--text3);font-family:'DM Mono',monospace;margin-left:auto">updated 2m ago</span>
|
| 329 |
+
</div>
|
| 330 |
+
<div class="ai-text" id="ai-text">Republican leadership confirmed 3 of 4 committee votes secured. News sentiment from 8 sources is positive (+0.72). Congressional timeline suggests signing window opens May 28-31. Key risk: Senate amendment process could delay beyond June 1.</div>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
<div class="sim-row">
|
| 335 |
+
<span class="sim-label">Simulate position →</span>
|
| 336 |
+
<input class="sim-input" type="number" value="100" min="1" placeholder="$USDC"/>
|
| 337 |
+
<button class="sim-btn-yes" onclick="sendPrompt('¿Cuánto ganaría si pongo $100 en YES del mercado Trump tax bill al precio actual de 73¢?')">BUY YES ↗</button>
|
| 338 |
+
<button class="sim-btn-no">BUY NO</button>
|
| 339 |
+
<span style="font-size:10px;color:var(--text3);font-family:'DM Mono',monospace;margin-left:4px">Simulated · no real trading</span>
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
| 342 |
+
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
|
| 346 |
+
<script>
|
| 347 |
+
const markets = {
|
| 348 |
+
usa: {
|
| 349 |
+
q: 'Will Trump sign the tax bill before June 2026?',
|
| 350 |
+
meta: 'Vol: $1.24M · Liq: $340K · Closes: Jun 1 2026',
|
| 351 |
+
cat: 'USA · Politics · Polymarket',
|
| 352 |
+
yes: 73, no: 27, delta: '+4.2%', conf: '87%', signal: 'BULLISH',
|
| 353 |
+
ai: 'Republican leadership confirmed 3 of 4 committee votes secured. News sentiment from 8 sources is positive (+0.72). Congressional timeline suggests signing window opens May 28–31. Key risk: Senate amendment process could delay beyond June 1.'
|
| 354 |
+
},
|
| 355 |
+
eur: {
|
| 356 |
+
q: 'Will the ECB cut rates in June 2026?',
|
| 357 |
+
meta: 'Vol: $890K · Liq: $210K · Closes: Jun 12 2026',
|
| 358 |
+
cat: 'EU · Economy · Polymarket',
|
| 359 |
+
yes: 34, no: 66, delta: '-2.1%', conf: '74%', signal: 'BEARISH',
|
| 360 |
+
ai: 'Eurozone inflation surprised to the upside at 2.4% in April. ECB governing council members\'s recent statements signal a pause is more likely. Bond market pricing implies only 22% probability of a June cut vs Polymarket\'s 34% — divergence is notable.'
|
| 361 |
+
},
|
| 362 |
+
chn: {
|
| 363 |
+
q: 'Will a US-China tariff deal be reached before Q3 2026?',
|
| 364 |
+
meta: 'Vol: $2.1M · Liq: $580K · Closes: Jul 1 2026',
|
| 365 |
+
cat: 'CHN · Trade · Polymarket',
|
| 366 |
+
yes: 51, no: 49, delta: '+0.8%', conf: '52%', signal: 'NEUTRAL',
|
| 367 |
+
ai: 'Negotiations ongoing but no formal framework agreed. Trade flows show early tariff evasion patterns suggesting both sides are playing for time. AI confidence is low — too many geopolitical variables. Monitoring for USTR statement as key signal.'
|
| 368 |
+
},
|
| 369 |
+
bra: {
|
| 370 |
+
q: 'Will Bitcoin exceed $120,000 before July 2026?',
|
| 371 |
+
meta: 'Vol: $3.4M · Liq: $920K · Closes: Jul 1 2026',
|
| 372 |
+
cat: 'Crypto · Global · Polymarket',
|
| 373 |
+
yes: 61, no: 39, delta: '+6.3%', conf: '79%', signal: 'BULLISH',
|
| 374 |
+
ai: 'BTC currently at $103,400. On-chain metrics show accumulation from large wallets. ETF inflows +$820M this week. Options market implies 68% probability of $120K by end of June. Sentiment score: 0.81 (strongly positive across 14 news sources).'
|
| 375 |
+
}
|
| 376 |
+
};
|
| 377 |
+
|
| 378 |
+
let activeMarket = 'usa';
|
| 379 |
+
let chartInst = null;
|
| 380 |
+
|
| 381 |
+
function selectMarket(id) {
|
| 382 |
+
activeMarket = id;
|
| 383 |
+
const m = markets[id];
|
| 384 |
+
document.getElementById('detail-q').textContent = m.q;
|
| 385 |
+
document.getElementById('detail-meta').textContent = m.meta;
|
| 386 |
+
document.getElementById('ai-text').textContent = m.ai;
|
| 387 |
+
document.querySelectorAll('.market-card').forEach((c,i) => {
|
| 388 |
+
const ids = ['usa','eur','chn','bra'];
|
| 389 |
+
c.classList.toggle('active', ids[i] === id);
|
| 390 |
+
});
|
| 391 |
+
document.querySelectorAll('.bubble').forEach(b => b.classList.remove('active-b'));
|
| 392 |
+
const bel = document.getElementById('b-'+id);
|
| 393 |
+
if(bel) bel.classList.add('active-b');
|
| 394 |
+
document.querySelector('#world-map text').textContent = 'SELECTED: '+id.toUpperCase();
|
| 395 |
+
buildChart(m);
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
function buildChart(m) {
|
| 399 |
+
const ctx = document.getElementById('mini-chart').getContext('2d');
|
| 400 |
+
if(chartInst) chartInst.destroy();
|
| 401 |
+
const base = m.yes;
|
| 402 |
+
const pts = Array.from({length:8},(_,i)=>{
|
| 403 |
+
const noise = (Math.random()-0.5)*8;
|
| 404 |
+
return Math.max(5,Math.min(95, base - 12 + (i/7)*12 + noise));
|
| 405 |
+
});
|
| 406 |
+
pts[pts.length-1] = base;
|
| 407 |
+
const col = base > 50 ? '#22d37a' : base < 40 ? '#f04040' : '#f0a020';
|
| 408 |
+
chartInst = new Chart(ctx,{
|
| 409 |
+
type:'line',
|
| 410 |
+
data:{
|
| 411 |
+
labels:['7d','6d','5d','4d','3d','2d','1d','now'],
|
| 412 |
+
datasets:[{data:pts,borderColor:col,borderWidth:1.5,pointRadius:0,fill:false,tension:0.4}]
|
| 413 |
+
},
|
| 414 |
+
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{enabled:false}},scales:{x:{display:false},y:{display:false}},animation:{duration:600}}
|
| 415 |
+
});
|
| 416 |
+
buildSparklines(m);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
function buildSparklines(m) {
|
| 420 |
+
['yes','no'].forEach(side => {
|
| 421 |
+
const el = document.getElementById('spark-'+side);
|
| 422 |
+
el.innerHTML = '';
|
| 423 |
+
const base = side==='yes' ? m.yes : m.no;
|
| 424 |
+
for(let i=0;i<12;i++){
|
| 425 |
+
const h = Math.max(4, Math.min(24, base/4 + (Math.random()-0.5)*8));
|
| 426 |
+
const d = document.createElement('div');
|
| 427 |
+
d.className='spark-bar';
|
| 428 |
+
d.style.height = h+'px';
|
| 429 |
+
d.style.background = side==='yes'?'#0d6e3a':'#7a1a1a';
|
| 430 |
+
el.appendChild(d);
|
| 431 |
+
}
|
| 432 |
+
const last = document.createElement('div');
|
| 433 |
+
last.className='spark-bar';
|
| 434 |
+
last.style.height = Math.min(28,base/3.5)+'px';
|
| 435 |
+
last.style.background = side==='yes'?'#22d37a':'#f04040';
|
| 436 |
+
el.appendChild(last);
|
| 437 |
+
});
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
let counter = 2847;
|
| 441 |
+
let sigCounter = 183;
|
| 442 |
+
setInterval(()=>{
|
| 443 |
+
counter += Math.floor(Math.random()*3);
|
| 444 |
+
sigCounter += Math.random()>0.7 ? 1 : 0;
|
| 445 |
+
document.getElementById('mkt-count').textContent = counter.toLocaleString();
|
| 446 |
+
document.getElementById('sig-count').textContent = sigCounter;
|
| 447 |
+
}, 3000);
|
| 448 |
+
|
| 449 |
+
buildChart(markets[activeMarket]);
|
| 450 |
+
</script>
|