@tekkare/romulus 0.1.254 → 0.1.256

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.253",
7
+ "version": "0.1.255",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1 +1 @@
1
- :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-rail-wide-w:240px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto;scrollbar-gutter:stable}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);transition:width .2s;width:var(--rm-rail-w)}.rm-sb--right.rm-sb--wide{border-right:1px solid var(--rm-shell-border);width:var(--rm-rail-wide-w)}.rm-rail__head{align-items:center;border-bottom:1px solid var(--rm-shell-border);color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;font-size:15px;font-weight:600;height:var(--rm-bar-h);padding:0 var(--space-16);white-space:nowrap}.rm-sb--wide .rm-rail__item{align-items:center;border-radius:8px;flex-direction:row;gap:var(--space-10);justify-content:flex-start;margin:1px 8px;padding:9px var(--space-12);width:auto}.rm-sb--wide .rm-rail__item:first-of-type{margin-top:8px}.rm-sb--wide .rm-rail__caption{color:var(--rm-shell-text-dark);flex:1;font-size:var(--font-13);font-weight:500;line-height:18px;text-align:left}.rm-sb--wide .rm-rail__tile{color:var(--rm-shell-text-muted);display:contents}.rm-sb--wide .rm-rail__badge,.rm-sb--wide .rm-rail__dot{order:1;position:static}.rm-sb--wide .rm-rail__chev{order:2}.rm-sb--wide .rm-rail__dot{border:none;height:8px;width:8px}.rm-sb--wide .rm-rail__item:hover{background:var(--rm-shell-subtle)}.rm-sb--wide .rm-rail__item.rm-rail__item--active .rm-rail__dot{border-color:color-mix(in srgb,var(--rm-shell-accent) 8%,var(--rm-shell-surface))}.rm-sb--wide .rm-rail__avatar{font-size:var(--font-10);height:22px;width:22px}.rm-sb--wide .rm-rail__item--active{background:color-mix(in srgb,var(--rm-shell-accent) 8%,transparent)}.rm-sb--wide .rm-rail__item--active .rm-rail__caption,.rm-sb--wide .rm-rail__item--active .rm-rail__chev,.rm-sb--wide .rm-rail__item.rm-rail__item--active .rm-rail__tile{background:none;color:var(--rm-shell-accent)}.rm-sb--wide .rm-rail__item--active .rm-rail__caption{font-weight:600}.rm-rail__chev{font-size:var(--font-14);opacity:0}.rm-sb--wide .rm-rail__item--active .rm-rail__chev{opacity:1}.rm-sb--wide .rm-rail__item--active .rm-rail__badge{background:var(--rm-shell-accent);color:#fff}.rm-sb--wide .rm-rail__bottom{align-items:stretch;gap:var(--space-6);padding:0 var(--space-8) var(--space-12)}.rm-sb--wide .rm-lang{display:block}.rm-sb--wide .rm-lang__current{border-radius:8px;height:32px;justify-content:flex-start;padding:0 var(--space-10);width:100%}.rm-lang__name{color:var(--rm-shell-text-dark);font-size:var(--font-13)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:var(--space-8);line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:var(--space-16);overflow-y:auto;padding:var(--space-16)}.rm-sb--collapsed .rm-sb__nav{padding-left:var(--space-8);padding-right:var(--space-8)}.rm-sb__group{flex-direction:column}.rm-sb__group,.rm-sb__label{display:flex;gap:var(--space-4)}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);font-family:inherit;font-size:var(--font-10);font-weight:500;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 var(--space-2);text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:var(--space-8);height:26px;margin:0;padding:var(--space-2);text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:var(--font-14);line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:var(--font-14);line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:var(--space-2) var(--space-4);text-transform:uppercase}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__tip{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:6px;box-shadow:0 4px 12px rgba(30,41,59,.1);color:var(--rm-shell-text);font-size:var(--font-12);line-height:1.3;padding:5px 9px;pointer-events:none;position:fixed;transform:translateY(-50%);white-space:nowrap;z-index:60}.rm-sb__sub{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-2) 0 var(--space-2) 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:var(--font-14);line-height:1.625;overflow:hidden;padding:var(--space-2) var(--space-4);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:var(--space-4);margin:0;padding:var(--space-12) 0 var(--space-8);text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:var(--font-14);height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:var(--font-16);height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__item.rm-rail__item--disabled{cursor:default}.rm-rail__item.rm-rail__item--disabled .rm-rail__caption,.rm-rail__item.rm-rail__item--disabled i{opacity:.4}.rm-rail__item.rm-rail__item--disabled:hover .rm-rail__tile{background:var(--rm-shell-subtle)}.rm-rail__tile{position:relative}.rm-rail__badge{background:var(--rm-shell-accent);border-radius:9999px;color:#fff;font-size:var(--font-10);font-weight:700;height:16px;line-height:16px;min-width:16px;padding:0 var(--space-4);position:absolute;right:-4px;text-align:center;top:-4px}.rm-rail__item--active .rm-rail__badge{background:#fff;color:var(--rm-shell-accent)}.rm-rail__dot{animation:rm-rail-dot-pulse 2s ease-out infinite;background:var(--color-success,#16a34a);border:2px solid var(--rm-shell-surface);border-radius:9999px;height:10px;position:absolute;right:-2px;top:-2px;width:10px}@keyframes rm-rail-dot-pulse{0%{box-shadow:0 0 0 0 rgba(22,163,74,.55)}70%{box-shadow:0 0 0 6px rgba(22,163,74,0)}to{box-shadow:0 0 0 0 rgba(22,163,74,0)}}@media (prefers-reduced-motion:reduce){.rm-rail__dot{animation:none}}.rm-rail__item--active .rm-rail__dot{border-color:var(--rm-shell-accent)}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:var(--font-10);line-height:1;max-width:100%;overflow:hidden;padding:0 var(--space-2);text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{align-items:center;display:flex;flex-direction:column;gap:var(--space-10);margin-top:auto;padding-bottom:var(--space-16)}.rm-dns{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:var(--bg-app,#f5f5f9);border:1px solid var(--rm-shell-border);border-radius:9999px;box-shadow:inset 0 1px 2px rgba(30,41,59,.06);box-sizing:border-box;cursor:pointer;display:flex;flex:0 0 auto;flex-direction:column;height:64px;margin:0;padding:var(--space-2);position:relative;width:32px}.rm-dns__thumb{background:#fff;border-radius:9999px;box-shadow:0 1px 3px rgba(30,41,59,.15);height:calc(50% - 2px);left:2px;position:absolute;top:2px;transition:top .24s cubic-bezier(.2,0,0,1);width:calc(100% - 4px);z-index:1}.rm-dns__half{align-items:center;border-radius:9999px;display:flex;flex:1;font-size:var(--font-14);justify-content:center;line-height:1;position:relative;transition:color .2s ease;z-index:2}.rm-dns__half[data-dns=day]{color:#f59e0b}.rm-dns__half[data-dns=night]{color:var(--text-muted,#8a90a8)}.rm-dns:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}body.night .rm-dns{background:#14141c;border-color:#2f2f40;box-shadow:inset 0 1px 2px rgba(0,0,0,.4)}body.night .rm-dns__thumb{background:var(--color-accent-soft);box-shadow:0 1px 4px rgba(180,133,255,.5);top:50%}body.night .rm-dns__half[data-dns=day]{color:#525670}body.night .rm-dns__half[data-dns=night]{color:var(--bg-surface)}.rm-dns--wide{flex-direction:row;height:32px;width:100%}.rm-dns--wide .rm-dns__thumb{height:calc(100% - 4px);left:2px;top:2px;transition:left .24s cubic-bezier(.2,0,0,1);width:calc(50% - 2px)}body.night .rm-dns--wide .rm-dns__thumb{left:50%;top:2px}.rm-dns--wide .rm-dns__half{font-size:var(--font-12);gap:var(--space-6)}.rm-dns__text{font-weight:600}@media (prefers-reduced-motion:reduce){.rm-dns__thumb{transition:none}}.rm-lang__current span,.rm-lang__item-flag{font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif}.rm-lang{display:flex;flex:0 0 auto;position:relative}.rm-lang__current{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;box-sizing:border-box;cursor:pointer;display:flex;font-size:17px;gap:var(--space-8);height:32px;justify-content:center;line-height:1;margin:0;padding:0;transition:border-color .15s,background-color .15s;width:32px}.rm-lang__current:hover{background:var(--rm-shell-subtle)}.rm-lang__current:hover,.rm-lang__current[aria-expanded=true]{border-color:var(--rm-shell-accent)}.rm-lang__current:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}.rm-lang__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;bottom:0;box-shadow:0 8px 24px rgba(var(--shadow-rgb,30,41,59),.12);min-width:160px;padding:var(--space-4);position:absolute;right:calc(100% + 8px);z-index:300}.rm-lang__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-sizing:border-box;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14);gap:var(--space-8);line-height:1.5;margin:0;padding:var(--space-4) var(--space-6);text-align:left;white-space:nowrap;width:100%}.rm-lang__item:hover{background:var(--rm-shell-subtle)}.rm-lang__item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-lang__item-flag{flex-shrink:0;font-size:17px;line-height:1}.rm-lang-enter-active,.rm-lang-leave-active{transition:opacity .14s ease,transform .14s ease}.rm-lang-enter-from,.rm-lang-leave-to{opacity:0;transform:translateX(4px)}@media (prefers-reduced-motion:reduce){.rm-lang-enter-active,.rm-lang-leave-active{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:var(--space-16);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:var(--font-14);gap:var(--space-4);min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:var(--space-6);text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:var(--font-14);margin:0;padding:var(--space-2) var(--space-4);transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:var(--font-14);gap:var(--space-8);line-height:1.5;overflow:hidden;padding:var(--space-4) var(--space-6);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:var(--font-10);font-weight:500;letter-spacing:.025em;line-height:1.6;padding:var(--space-8) var(--space-6) var(--space-2);text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:var(--space-2)}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:var(--font-14);line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:var(--space-4);padding:var(--space-4) var(--space-12);width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:var(--font-14);line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:var(--font-14);height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:var(--space-16);padding:0 var(--space-16)}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:var(--font-14);font-weight:700;line-height:1.6;margin:0;padding:var(--space-4) var(--space-4) var(--space-8);white-space:nowrap}.rm-hd__tab-label{align-items:center;display:inline-flex;gap:var(--space-6)}.rm-hd__tab-count{background:color-mix(in srgb,currentColor 12%,transparent);border-radius:var(--radius-pill);font-size:var(--font-12);font-weight:600;line-height:1.5;padding:0 var(--space-6)}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:min(var(--rm-panel-w,640px),calc(100vw - 160px));z-index:401}.rm-panel__veil{backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:var(--rm-panel-veil,rgba(100,116,139,.12));cursor:default;inset:0;position:absolute;z-index:400}.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:opacity .22s ease}.rm-panel-veil-enter-from,.rm-panel-veil-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:none}}.rm-sb--right{position:relative;z-index:402}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:var(--font-14);font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__header:has(+.rm-panel__tabs){border-bottom:none}.rm-panel__tabs{border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);overflow-x:auto;padding:0 var(--space-16)}.rm-panel__tab{background:none;border:none;border-bottom:3px solid transparent;border-radius:8px 8px 0 0;color:var(--text-muted);cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:500;line-height:1.6;margin-bottom:-1px;padding:var(--space-8) var(--space-16) var(--space-10);transition:background-color .3s,color .15s;white-space:nowrap}.rm-panel__tab:hover{color:var(--color-primary)}.rm-panel__tab.rm-panel__tab--disabled,.rm-panel__tab.rm-panel__tab--disabled:hover{color:var(--text-muted);cursor:default;opacity:.45}.rm-panel__tab--active{background:color-mix(in srgb,var(--color-primary) 6%,transparent);border-bottom-color:var(--color-primary);color:var(--color-primary);font-weight:600}.rm-panel__tab-count{background:color-mix(in srgb,currentColor 12%,transparent);border-radius:var(--radius-pill);display:inline-block;font-size:var(--font-12);font-weight:600;line-height:1.5;margin-left:var(--space-6);padding:0 var(--space-6)}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:var(--space-16)}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
1
+ :root{--rm-shell-bg:#f9f9fa;--rm-shell-surface:#fff;--rm-shell-border:#dbdef0;--rm-shell-subtle:#f5f5f9;--rm-shell-text:#1e293b;--rm-shell-text-dark:#475569;--rm-shell-text-muted:#878ca6;--rm-shell-accent:#2017ad;--rm-shell-inset:8px;--rm-shell-radius:12px;--rm-bar-h:56px;--rm-nav-w:240px;--rm-nav-collapsed-w:64px;--rm-rail-w:56px;--rm-rail-wide-w:240px;--rm-dots-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAcUlEQVR42u3TwQmAMAxAUdfQfUoSZ3OBTNMFHEqLVBEtCB6M8B8EevwktOsAAACAl0SkNzNXtaw6zut7SikNYeJq2Hksh4gs27rG7ZH+eWA7bttigMC78x4TIdBbceX8fJKnkSL1s5RY8zBxAAAA+LUF0ula10BxVDoAAAAASUVORK5CYII=");--rm-dots-size:12px;--rm-dots-opacity:0.1}body.night{--rm-shell-bg:var(--bg-app);--rm-shell-surface:var(--bg-surface,#1a1a24);--rm-shell-border:var(--border-default);--rm-shell-subtle:var(--bg-subtle,#22222e);--rm-shell-text:var(--text-primary);--rm-shell-text-dark:var(--text-secondary);--rm-shell-text-muted:var(--text-muted);--rm-shell-accent:var(--color-primary);--rm-dots-opacity:0.06}.rm-shell{background:var(--rm-shell-bg);color:var(--rm-shell-text);font-family:var(--font-sans,"Figtree",sans-serif);inset:0;overflow:hidden;position:fixed}.rm-shell__dots{background-image:var(--rm-dots-image);background-repeat:repeat;background-size:var(--rm-dots-size) var(--rm-dots-size);inset:0;opacity:var(--rm-dots-opacity);pointer-events:none;position:absolute}.rm-shell__frame{border:1px solid var(--rm-shell-border);border-radius:var(--rm-shell-radius);display:flex;inset:var(--rm-shell-inset);overflow:hidden;position:absolute}.rm-shell__body{display:flex;flex:1;flex-direction:column;min-width:0}.rm-shell__main{flex:1;min-width:0;overflow-y:auto;scrollbar-gutter:stable}.rm-sb{background:var(--rm-shell-surface);display:flex;flex-direction:column;flex-shrink:0;height:100%}.rm-sb--left{border-right:1px solid var(--rm-shell-border);position:relative;transition:width .2s;width:var(--rm-nav-w);z-index:200}.rm-sb--left.rm-sb--collapsed{width:var(--rm-nav-collapsed-w)}.rm-sb--right{border-left:1px solid var(--rm-shell-border);transition:width .2s;width:var(--rm-rail-w)}.rm-sb--right.rm-sb--wide{border-right:1px solid var(--rm-shell-border);width:var(--rm-rail-wide-w)}.rm-rail__head{align-items:center;border-bottom:1px solid var(--rm-shell-border);color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;font-size:15px;font-weight:600;height:var(--rm-bar-h);padding:0 var(--space-16);white-space:nowrap}.rm-sb--wide .rm-rail__item{align-items:center;border-radius:8px;flex-direction:row;gap:var(--space-10);justify-content:flex-start;margin:1px 8px;padding:9px var(--space-12);width:auto}.rm-sb--wide .rm-rail__item:first-of-type{margin-top:8px}.rm-sb--wide .rm-rail__caption{color:var(--rm-shell-text-dark);flex:1;font-size:var(--font-13);font-weight:500;line-height:18px;text-align:left}.rm-sb--wide .rm-rail__tile{color:var(--rm-shell-text-muted);display:contents}.rm-sb--wide .rm-rail__badge,.rm-sb--wide .rm-rail__dot{order:1;position:static}.rm-sb--wide .rm-rail__chev{order:2}.rm-sb--wide .rm-rail__dot{border:none;height:8px;width:8px}.rm-sb--wide .rm-rail__item:hover{background:var(--rm-shell-subtle)}.rm-sb--wide .rm-rail__item.rm-rail__item--active .rm-rail__dot{border-color:color-mix(in srgb,var(--rm-shell-accent) 8%,var(--rm-shell-surface))}.rm-sb--wide .rm-rail__avatar{font-size:var(--font-10);height:22px;width:22px}.rm-sb--wide .rm-rail__item--active{background:color-mix(in srgb,var(--rm-shell-accent) 8%,transparent)}.rm-sb--wide .rm-rail__item--active .rm-rail__caption,.rm-sb--wide .rm-rail__item--active .rm-rail__chev,.rm-sb--wide .rm-rail__item.rm-rail__item--active .rm-rail__tile{background:none;color:var(--rm-shell-accent)}.rm-sb--wide .rm-rail__item--active .rm-rail__caption{font-weight:600}.rm-rail__chev{font-size:var(--font-14);opacity:0}.rm-sb--wide .rm-rail__item--active .rm-rail__chev{opacity:1}.rm-sb--wide .rm-rail__item--active .rm-rail__badge{background:var(--rm-shell-accent);color:#fff}.rm-sb--wide .rm-rail__bottom{align-items:stretch;gap:var(--space-6);padding:0 var(--space-8) var(--space-12)}.rm-sb--wide .rm-lang{display:block}.rm-sb--wide .rm-lang__current{border-radius:8px;height:32px;justify-content:flex-start;padding:0 var(--space-10);width:100%}.rm-lang__name{color:var(--rm-shell-text-dark);font-size:var(--font-13)}.rm-sb__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-sb--collapsed .rm-sb__header{justify-content:center;padding:0}.rm-sb__logo{align-items:center;color:var(--rm-shell-text);display:flex;flex:1;font-size:20px;font-weight:700;gap:var(--space-8);line-height:1.4;min-width:0;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__toggle{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-sb__toggle:hover{color:var(--rm-shell-text)}.rm-sb__toggle svg{display:block;height:100%;width:100%}.rm-sb__nav{display:flex;flex:1;flex-direction:column;gap:var(--space-16);overflow-y:auto;padding:var(--space-16)}.rm-sb--collapsed .rm-sb__nav{padding-left:var(--space-8);padding-right:var(--space-8)}.rm-sb__group{flex-direction:column}.rm-sb__group,.rm-sb__label{display:flex;gap:var(--space-4)}.rm-sb__label{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);font-family:inherit;font-size:var(--font-10);font-weight:500;letter-spacing:.025em;line-height:1.6;margin:0;padding:0 var(--space-2);text-align:left;text-transform:uppercase;width:100%}button.rm-sb__label{cursor:pointer}button.rm-sb__label:hover{color:var(--rm-shell-text)}.rm-sb__label-text{flex:1;min-width:0}.rm-sb__chev{display:block;flex-shrink:0;height:12px;transition:transform .2s;width:12px}.rm-sb__label[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-sb__rule{background:var(--rm-shell-border);height:1px;margin:4px}.rm-mi{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;gap:var(--space-8);height:26px;margin:0;padding:var(--space-2);text-align:left;text-decoration:none;transition:background-color .15s;width:100%}.rm-mi:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-mi.rm-mi--active{background:var(--rm-shell-subtle)}.rm-mi.rm-mi--current{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent)}.rm-mi.rm-mi--current .rm-mi__icon{background:var(--rm-shell-accent);color:#fff}.rm-mi.rm-mi--current .rm-mi__icon svg{stroke:#fff}.rm-mi__icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-mi__icon svg{display:block;height:14px;width:14px}.rm-mi__icon i{font-size:var(--font-14);line-height:1}.rm-mi__label{color:var(--rm-shell-text);flex:1;font-size:var(--font-14);line-height:1.625;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-mi__badge{background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-muted);flex-shrink:0;font-size:9px;font-weight:700;letter-spacing:.025em;padding:var(--space-2) var(--space-4);text-transform:uppercase}.rm-mi-parent{display:flex;flex-direction:column;gap:var(--space-4)}.rm-mi-row{align-items:center;display:flex;gap:var(--space-2)}.rm-mi-row>.rm-mi{flex:1;min-width:0}.rm-mi__toggle{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;color:var(--rm-shell-text-muted);cursor:pointer;display:flex;flex-shrink:0;height:22px;justify-content:center;padding:0;width:22px}.rm-mi__toggle:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent);color:var(--rm-shell-text)}.rm-mi__toggle[aria-expanded=true] .rm-sb__chev{transform:rotate(90deg)}.rm-mi.rm-mi--within .rm-mi__label{font-weight:600}.rm-sb__children{border-left:1px solid var(--rm-shell-border);display:flex;flex-direction:column;gap:var(--space-4);margin-left:13px;padding-left:var(--space-8)}.rm-sb--collapsed .rm-mi{justify-content:center}.rm-sb--collapsed .rm-mi>:not(.rm-mi__icon),.rm-sb--collapsed .rm-sb__sub{display:none}.rm-sb__tip{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:6px;box-shadow:0 4px 12px rgba(30,41,59,.1);color:var(--rm-shell-text);font-size:var(--font-12);line-height:1.3;padding:5px 9px;pointer-events:none;position:fixed;transform:translateY(-50%);white-space:nowrap;z-index:60}.rm-sb__sub{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-2) 0 var(--space-2) 28px}.rm-sb__sub-item{border-radius:4px;color:var(--rm-shell-text);display:block;font-size:var(--font-14);line-height:1.625;overflow:hidden;padding:var(--space-2) var(--space-4);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-sb__sub-item:hover{background:color-mix(in srgb,var(--rm-shell-subtle) 50%,transparent)}.rm-rail__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;cursor:pointer;display:flex;flex-direction:column;font-family:inherit;gap:var(--space-4);margin:0;padding:var(--space-12) 0 var(--space-8);text-decoration:none;width:100%}.rm-rail__avatar{align-items:center;border:2px solid var(--rm-shell-surface);border-radius:9999px;color:#fff;display:flex;font-size:var(--font-14);height:32px;justify-content:center;transition:opacity .15s;width:32px}.rm-rail__item:hover .rm-rail__avatar{opacity:.8}.rm-rail__tile{align-items:center;background:var(--rm-shell-subtle);border-radius:8px;color:var(--rm-shell-text-dark);display:flex;font-size:var(--font-16);height:32px;justify-content:center;transition:background-color .15s,color .15s;width:32px}.rm-rail__item:hover .rm-rail__tile{background:var(--rm-shell-border)}.rm-rail__item.rm-rail__item--active .rm-rail__tile{background:var(--rm-shell-accent);color:#fff}.rm-rail__item.rm-rail__item--disabled{cursor:default}.rm-rail__item.rm-rail__item--disabled .rm-rail__caption,.rm-rail__item.rm-rail__item--disabled i{opacity:.4}.rm-rail__item.rm-rail__item--disabled:hover .rm-rail__tile{background:var(--rm-shell-subtle)}.rm-rail__tile{position:relative}.rm-rail__badge{background:var(--rm-shell-accent);border-radius:9999px;color:#fff;font-size:var(--font-10);font-weight:700;height:16px;line-height:16px;min-width:16px;padding:0 var(--space-4);position:absolute;right:-4px;text-align:center;top:-4px}.rm-rail__item--active .rm-rail__badge{background:#fff;color:var(--rm-shell-accent)}.rm-rail__dot{animation:rm-rail-dot-pulse 2s ease-out infinite;background:var(--color-success,#16a34a);border:2px solid var(--rm-shell-surface);border-radius:9999px;height:10px;position:absolute;right:-2px;top:-2px;width:10px}@keyframes rm-rail-dot-pulse{0%{box-shadow:0 0 0 0 rgba(22,163,74,.55)}70%{box-shadow:0 0 0 6px rgba(22,163,74,0)}to{box-shadow:0 0 0 0 rgba(22,163,74,0)}}@media (prefers-reduced-motion:reduce){.rm-rail__dot{animation:none}}.rm-rail__item--active .rm-rail__dot{border-color:var(--rm-shell-accent)}.rm-rail__caption{color:var(--rm-shell-text-muted);font-size:var(--font-10);line-height:1;max-width:100%;overflow:hidden;padding:0 var(--space-2);text-overflow:ellipsis;transition:color .15s;white-space:nowrap}.rm-rail__item--active .rm-rail__caption,.rm-rail__item:hover .rm-rail__caption{color:var(--rm-shell-text)}.rm-rail__bottom{align-items:center;display:flex;flex-direction:column;gap:var(--space-10);margin-top:auto;padding-bottom:var(--space-16)}.rm-dns{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:var(--bg-app,#f5f5f9);border:1px solid var(--rm-shell-border);border-radius:9999px;box-shadow:inset 0 1px 2px rgba(30,41,59,.06);box-sizing:border-box;cursor:pointer;display:flex;flex:0 0 auto;flex-direction:column;height:64px;margin:0;padding:var(--space-2);position:relative;width:32px}.rm-dns__thumb{background:#fff;border-radius:9999px;box-shadow:0 1px 3px rgba(30,41,59,.15);height:calc(50% - 2px);left:2px;position:absolute;top:2px;transition:top .24s cubic-bezier(.2,0,0,1);width:calc(100% - 4px);z-index:1}.rm-dns__half{align-items:center;border-radius:9999px;display:flex;flex:1;font-size:var(--font-14);justify-content:center;line-height:1;position:relative;transition:color .2s ease;z-index:2}.rm-dns__half[data-dns=day]{color:#f59e0b}.rm-dns__half[data-dns=night]{color:var(--text-muted,#8a90a8)}.rm-dns:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}body.night .rm-dns{background:#14141c;border-color:#2f2f40;box-shadow:inset 0 1px 2px rgba(0,0,0,.4)}body.night .rm-dns__thumb{background:var(--color-accent-soft);box-shadow:0 1px 4px rgba(180,133,255,.5);top:50%}body.night .rm-dns__half[data-dns=day]{color:#525670}body.night .rm-dns__half[data-dns=night]{color:var(--bg-surface)}.rm-dns--wide{flex-direction:row;height:32px;width:100%}.rm-dns--wide .rm-dns__thumb{height:calc(100% - 4px);left:2px;top:2px;transition:left .24s cubic-bezier(.2,0,0,1);width:calc(50% - 2px)}body.night .rm-dns--wide .rm-dns__thumb{left:50%;top:2px}.rm-dns--wide .rm-dns__half{font-size:var(--font-12);gap:var(--space-6)}.rm-dns__text{font-weight:600}@media (prefers-reduced-motion:reduce){.rm-dns__thumb{transition:none}}.rm-lang__current span,.rm-lang__item-flag{font-family:Twemoji Country Flags,Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif}.rm-lang{display:flex;flex:0 0 auto;position:relative}.rm-lang__current{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;box-sizing:border-box;cursor:pointer;display:flex;font-size:17px;gap:var(--space-8);height:32px;justify-content:center;line-height:1;margin:0;padding:0;transition:border-color .15s,background-color .15s;width:32px}.rm-lang__current:hover{background:var(--rm-shell-subtle)}.rm-lang__current:hover,.rm-lang__current[aria-expanded=true]{border-color:var(--rm-shell-accent)}.rm-lang__current:focus-visible{outline:2px solid var(--rm-shell-accent);outline-offset:2px}.rm-lang__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;bottom:0;box-shadow:0 8px 24px rgba(var(--shadow-rgb,30,41,59),.12);min-width:160px;padding:var(--space-4);position:absolute;right:calc(100% + 8px);z-index:300}.rm-lang__item{align-items:center;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-sizing:border-box;color:var(--rm-shell-text);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14);gap:var(--space-8);line-height:1.5;margin:0;padding:var(--space-4) var(--space-6);text-align:left;white-space:nowrap;width:100%}.rm-lang__item:hover{background:var(--rm-shell-subtle)}.rm-lang__item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-lang__item-flag{flex-shrink:0;font-size:17px;line-height:1}.rm-lang-enter-active,.rm-lang-leave-active{transition:opacity .14s ease,transform .14s ease}.rm-lang-enter-from,.rm-lang-leave-to{opacity:0;transform:translateX(4px)}@media (prefers-reduced-motion:reduce){.rm-lang-enter-active,.rm-lang-leave-active{transition:none}}.rm-hd{background:var(--rm-shell-surface);border-bottom:1px solid var(--rm-shell-border);display:flex;flex-direction:column;flex-shrink:0}.rm-hd__row{gap:var(--space-16);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-hd__crumbs,.rm-hd__row{align-items:center;display:flex}.rm-hd__crumbs{flex:1;font-size:var(--font-14);gap:var(--space-4);min-width:0}.rm-hd__crumb{align-items:center;color:var(--rm-shell-text-muted);display:inline-flex;gap:var(--space-6);text-decoration:none;transition:color .15s;white-space:nowrap}a.rm-hd__crumb:hover{color:var(--rm-shell-text)}.rm-hd__sep{color:var(--rm-shell-text-muted)}.rm-hd__picker{display:inline-flex;min-width:0;position:relative}.rm-hd__crumb--picker{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:4px;box-shadow:none;cursor:pointer;font:inherit;font-size:var(--font-14);margin:0;padding:var(--space-2) var(--space-4);transition:background-color .15s,color .15s}.rm-hd__crumb--picker:hover{background:var(--rm-shell-subtle)}.rm-hd__caret{display:block;flex-shrink:0;height:12px;opacity:.7;transition:transform .15s;width:12px}.rm-hd__caret--open{transform:rotate(180deg)}.rm-hd__menu{background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:8px;box-shadow:0 8px 24px rgba(30,41,59,.12);left:0;max-height:60vh;max-width:320px;min-width:200px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);z-index:300}.rm-hd__menu-item{align-items:center;border-radius:4px;color:var(--rm-shell-text);display:flex;font-size:var(--font-14);gap:var(--space-8);line-height:1.5;overflow:hidden;padding:var(--space-4) var(--space-6);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.rm-hd__menu-item:hover{background:var(--rm-shell-subtle)}.rm-hd__menu-group{color:var(--rm-shell-text-muted);display:block;font-size:var(--font-10);font-weight:500;letter-spacing:.025em;line-height:1.6;padding:var(--space-8) var(--space-6) var(--space-2);text-transform:uppercase}.rm-hd__menu-group:first-child{padding-top:var(--space-2)}.rm-hd__menu-item--active{background:color-mix(in srgb,var(--rm-shell-accent) 5%,transparent);font-weight:600}.rm-hd__menu-icon{align-items:center;background:var(--rm-shell-subtle);border-radius:4px;color:var(--rm-shell-text-dark);display:flex;flex-shrink:0;height:22px;justify-content:center;line-height:1;width:22px}.rm-hd__menu-icon i{font-size:var(--font-14);line-height:1}.rm-hd__menu-item--active .rm-hd__menu-icon{background:var(--rm-shell-accent);color:#fff}.rm-hd__crumb--last{color:var(--rm-shell-text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-hd__search{align-items:center;background:var(--rm-shell-surface);border:1px solid var(--rm-shell-border);border-radius:9999px;display:flex;flex-shrink:0;gap:var(--space-4);padding:var(--space-4) var(--space-12);width:300px}.rm-hd__search input{background:transparent;border:none;color:var(--rm-shell-text);flex:1;font-family:inherit;font-size:var(--font-14);line-height:20px;min-width:0;outline:none;padding:0}.rm-hd__search input::-moz-placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search input::placeholder{color:var(--rm-shell-text-muted)}.rm-hd__search i,.rm-hd__search svg{color:var(--rm-shell-text-muted);display:block;flex-shrink:0;font-size:var(--font-14);height:14px;line-height:20px;width:14px}.rm-hd__tabs{display:flex;gap:var(--space-16);padding:0 var(--space-16)}.rm-hd__tab{align-items:flex-start;-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:var(--rm-shell-text-dark);cursor:pointer;display:flex;flex-direction:column;font-family:inherit;font-size:var(--font-14);font-weight:700;line-height:1.6;margin:0;padding:var(--space-4) var(--space-4) var(--space-8);white-space:nowrap}.rm-hd__tab-label{align-items:center;display:inline-flex;gap:var(--space-6)}.rm-hd__tab-count{background:color-mix(in srgb,currentColor 12%,transparent);border-radius:var(--radius-pill);font-size:var(--font-12);font-weight:600;line-height:1.5;padding:0 var(--space-6)}.rm-hd__tab--active{border-bottom-color:var(--rm-shell-accent);color:var(--rm-shell-text)}@media (max-width:767px){.rm-sb--left{inset:0 auto 0 0;position:absolute;transform:translateX(-100%);transition:transform .2s;z-index:40}.rm-sb--left.rm-sb--open{transform:translateX(0)}.rm-sb--right{display:none}.rm-hd__search{flex:1;width:auto}}.rm-panel{background:var(--rm-shell-surface);border-left:1px solid var(--rm-shell-border);bottom:0;box-shadow:-8px 0 24px rgba(30,41,59,.08);display:flex;flex-direction:column;position:absolute;right:0;top:0;width:min(var(--rm-panel-w,640px),calc(100vw - 160px));z-index:401}.rm-panel__veil{backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);background:var(--rm-panel-veil,rgba(100,116,139,.12));cursor:default;inset:0;position:absolute;z-index:400}.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:opacity .22s ease}.rm-panel-veil-enter-from,.rm-panel-veil-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.rm-panel-veil-enter-active,.rm-panel-veil-leave-active{transition:none}}.rm-sb--right{position:relative;z-index:402}.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:transform .22s cubic-bezier(.4,0,.2,1)}.rm-panel-enter-from,.rm-panel-leave-to{transform:translateX(100%)}@media (prefers-reduced-motion:reduce){.rm-panel-enter-active,.rm-panel-leave-active,.rm-sb--right{transition:none}}.rm-panel__header{align-items:center;border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);height:var(--rm-bar-h);padding:0 var(--space-16)}.rm-panel__title{color:var(--rm-shell-text);flex:1;font-size:var(--font-14);font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rm-panel__close{-moz-appearance:none;appearance:none;-webkit-appearance:none;background:none;border:none;border-radius:0;box-shadow:none;color:var(--rm-shell-text-muted);cursor:pointer;display:block;flex-shrink:0;height:20px;margin:0;padding:0;transition:color .15s;width:20px}.rm-panel__close:hover{color:var(--rm-shell-text)}.rm-panel__close svg{display:block;height:100%;width:100%}.rm-panel__header:has(+.rm-panel__tabs){border-bottom:none}.rm-panel__tabs{border-bottom:1px solid var(--rm-shell-border);display:flex;flex-shrink:0;gap:var(--space-8);overflow-x:auto;padding:0 var(--space-16)}.rm-panel__tab{background:none;border:none;border-bottom:3px solid transparent;border-radius:8px 8px 0 0;color:var(--text-muted);cursor:pointer;font-family:inherit;font-size:var(--font-14);font-weight:500;line-height:1.6;margin-bottom:-1px;padding:var(--space-8) var(--space-16) var(--space-10);transition:background-color .3s,color .15s;white-space:nowrap}.rm-panel__tab:hover{color:var(--color-primary)}.rm-panel__tab.rm-panel__tab--disabled,.rm-panel__tab.rm-panel__tab--disabled:hover{color:var(--text-muted);cursor:default;opacity:.45}.rm-panel__tab--active{background:color-mix(in srgb,var(--color-primary) 6%,transparent);border-bottom-color:var(--color-primary);color:var(--color-primary);font-weight:600}.rm-panel__tab-count{background:color-mix(in srgb,currentColor 12%,transparent);border-radius:var(--radius-pill);display:inline-block;font-size:var(--font-12);font-weight:600;line-height:1.5;margin-left:var(--space-6);padding:0 var(--space-6)}.rm-panel__body{flex:1;min-height:0;overflow-y:auto;padding:var(--space-16)}@media (max-width:767px){.rm-panel{border-left:none;left:0;width:auto}}
@@ -14,6 +14,16 @@
14
14
  navigation devient un long defilement. Le style du libelle, lui, ne change
15
15
  pas. Un groupe sans `label` reste toujours ouvert, comme le premier groupe
16
16
  d'oip7.
17
+
18
+ Sous-pages : une entree peut porter `children`, sur un seul niveau. Une app
19
+ qui ouvre plusieurs objets du meme genre (une profession de l'annuaire HCP
20
+ et ses pages Recherche, Liste, Qualifications) mettait les pages de l'objet
21
+ ouvert dans un groupe a part, en haut, loin de l'entree cliquee : on ne
22
+ voyait plus a quoi elles se rapportaient. Elles se deplient desormais sous
23
+ leur entree, d'elles-memes quand la page courante en fait partie, et un
24
+ chevron les plie ou les deplie sans changer de page. Barre repliee, seules
25
+ les entrees de premier niveau restent : la sous-page courante allume
26
+ l'icone de son parent.
17
27
  -->
18
28
  <script setup lang="ts">
19
29
  import { computed, ref, watch } from 'vue'
@@ -30,6 +40,11 @@ export interface RmNavItem {
30
40
  badge?: string
31
41
  /** Force l'etat actif quand le chemin ne suffit pas a le deduire. */
32
42
  active?: boolean
43
+ /**
44
+ * Sous-pages, sur un seul niveau : elles se deplient sous l'entree. Cf.
45
+ * « Sous-pages » dans l'en-tete.
46
+ */
47
+ children?: RmNavItem[]
33
48
  }
34
49
 
35
50
  export interface RmNavSection {
@@ -102,14 +117,17 @@ const trim = (path: string) => (path.split('?')[0] || '').replace(/\/+$/, '') ||
102
117
  * prefixe, pour qu'une sous-page (« /prices/jo ») ouvre la section de son
103
118
  * entree.
104
119
  */
120
+ /** Les entrees d'une section et leurs sous-pages, a plat. */
121
+ const flat = (items: RmNavItem[]) => items.flatMap(i => [i, ...(i.children ?? [])])
122
+
105
123
  const sectionOf = (path?: string) => {
106
- const byActive = collapsibles.value.find(s => s.items.some(i => i.active))
124
+ const byActive = collapsibles.value.find(s => flat(s.items).some(i => i.active))
107
125
  if (byActive) return byActive.key
108
126
 
109
127
  const here = path ? trim(path) : null
110
128
  if (here) {
111
129
  const byPath = collapsibles.value.find(s =>
112
- s.items.some((i) => {
130
+ flat(s.items).some((i) => {
113
131
  if (!i.to) return false
114
132
  const to = trim(i.to)
115
133
  return here === to || here.startsWith(`${to}/`)
@@ -171,6 +189,36 @@ const isOpen = (s: RmNavSection) =>
171
189
  !isCollapsible(s) || props.collapsed || openSections.value.has(s.key)
172
190
  const isActive = (item: RmNavItem) => item.active ?? (!!item.to && item.to === props.activePath)
173
191
 
192
+ /** Une sous-page de l'entree est la page courante. */
193
+ const childActive = (item: RmNavItem) => !!item.children?.some(isActive)
194
+ /**
195
+ * Le fond actif de l'entree parente. Depliee, il passe a la sous-page : la
196
+ * premiere sous-page reprend souvent le lien du parent (« Medecins » et
197
+ * « Recherche » menent au meme ecran), et deux lignes surlignees ne disaient
198
+ * plus ou l'on est. Repliee, la barre ne montre plus les sous-pages : le
199
+ * parent porte alors l'etat de sa sous-page.
200
+ */
201
+ const parentActive = (item: RmNavItem) =>
202
+ props.collapsed ? isActive(item) || childActive(item) : isActive(item) && !childActive(item)
203
+
204
+ const itemKey = (item: RmNavItem) => item.to ?? item.href ?? item.label
205
+
206
+ /**
207
+ * Entrees aux sous-pages deployees. Celle de la page courante s'ouvre a
208
+ * l'arrivee, comme sa section ; le chevron plie et deplie les autres sans
209
+ * changer de page.
210
+ */
211
+ const openItems = ref<Set<string>>(new Set())
212
+ watch(() => props.sections.flatMap(s => s.items).filter(i => isActive(i) || childActive(i)).map(itemKey), (keys) => {
213
+ for (const key of keys) openItems.value.add(key)
214
+ }, { immediate: true })
215
+
216
+ function toggleItem(item: RmNavItem) {
217
+ const key = itemKey(item)
218
+ if (openItems.value.has(key)) openItems.value.delete(key)
219
+ else openItems.value.add(key)
220
+ }
221
+
174
222
  /**
175
223
  * Infobulle du rail replie.
176
224
  *
@@ -286,6 +334,66 @@ watch(() => props.collapsed, hideTip)
286
334
  <path d="M9 3L3 9M9 3H5M9 3V7" />
287
335
  </svg>
288
336
  </a>
337
+ <!-- Entree a sous-pages : le lien, son chevron, puis les sous-pages. -->
338
+ <div v-else-if="item.children?.length" class="rm-mi-parent">
339
+ <div class="rm-mi-row">
340
+ <NuxtLink
341
+ :to="item.to"
342
+ class="rm-mi"
343
+ :class="{ 'rm-mi--active': parentActive(item), 'rm-mi--within': !collapsed && childActive(item) }"
344
+ :title="collapsed ? undefined : item.label"
345
+ :aria-current="isActive(item) && !childActive(item) ? 'page' : undefined"
346
+ @click="item.to ? emit('navigate', item.to) : undefined"
347
+ @mouseenter="showTip($event, item)"
348
+ @mouseleave="hideTip"
349
+ @focus="showTip($event, item)"
350
+ @blur="hideTip"
351
+ >
352
+ <span class="rm-mi__icon"><i v-if="item.icon" :class="`ph ph-${item.icon}`" /></span>
353
+ <span class="rm-mi__label">{{ item.label }}</span>
354
+ <span v-if="item.badge" class="rm-mi__badge">{{ item.badge }}</span>
355
+ </NuxtLink>
356
+ <button
357
+ v-if="!collapsed"
358
+ class="rm-mi__toggle"
359
+ type="button"
360
+ :aria-expanded="openItems.has(itemKey(item))"
361
+ :aria-label="`${openItems.has(itemKey(item)) ? 'Masquer' : 'Afficher'} les pages de ${item.label}`"
362
+ @click="toggleItem(item)"
363
+ >
364
+ <svg class="rm-sb__chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
365
+ <path d="M4 2.5L7.5 6L4 9.5" />
366
+ </svg>
367
+ </button>
368
+ </div>
369
+ <div v-if="!collapsed && openItems.has(itemKey(item))" class="rm-sb__children" role="group" :aria-label="item.label">
370
+ <template v-for="child in item.children" :key="itemKey(child)">
371
+ <a
372
+ v-if="child.href"
373
+ :href="child.href"
374
+ target="_blank"
375
+ rel="noopener noreferrer"
376
+ class="rm-mi"
377
+ >
378
+ <span class="rm-mi__icon"><i v-if="child.icon" :class="`ph ph-${child.icon}`" /></span>
379
+ <span class="rm-mi__label">{{ child.label }}</span>
380
+ <span v-if="child.badge" class="rm-mi__badge">{{ child.badge }}</span>
381
+ </a>
382
+ <NuxtLink
383
+ v-else
384
+ :to="child.to"
385
+ class="rm-mi"
386
+ :class="{ 'rm-mi--active': isActive(child) }"
387
+ :aria-current="isActive(child) ? 'page' : undefined"
388
+ @click="child.to ? emit('navigate', child.to) : undefined"
389
+ >
390
+ <span class="rm-mi__icon"><i v-if="child.icon" :class="`ph ph-${child.icon}`" /></span>
391
+ <span class="rm-mi__label">{{ child.label }}</span>
392
+ <span v-if="child.badge" class="rm-mi__badge">{{ child.badge }}</span>
393
+ </NuxtLink>
394
+ </template>
395
+ </div>
396
+ </div>
289
397
  <NuxtLink
290
398
  v-else
291
399
  :to="item.to"
@@ -16,6 +16,7 @@
16
16
  import type { Component } from "vue";
17
17
  import type {
18
18
  OipExternalCrumb,
19
+ OipNavItem,
19
20
  OipNavSection,
20
21
  OipRailTile,
21
22
  } from "../types/oip";
@@ -132,10 +133,13 @@ const currentPath = computed(() => normalize(route.path));
132
133
  * Entree active : la plus longue dont le chemin prefixe celui de la page.
133
134
  * Un prefixe simple ferait briller "Prix" en meme temps que "Prix > JO".
134
135
  */
136
+ /** Les entrees d'une section et leurs sous-pages (`children`), a plat. */
137
+ const flatItems = (items: OipNavItem[]): OipNavItem[] => items.flatMap((item) => [item, ...(item.children ?? [])]);
138
+
135
139
  const activeTo = computed(() => {
136
140
  let best = "";
137
141
  for (const section of props.sections) {
138
- for (const item of section.items) {
142
+ for (const item of flatItems(section.items)) {
139
143
  if (!item.to) continue;
140
144
  const to = normalize(item.to);
141
145
  const hit = currentPath.value === to || currentPath.value.startsWith(to + "/");
@@ -152,13 +156,16 @@ const navSections = computed<OipNavSection[]>(() =>
152
156
  items: section.items.map((item) => ({
153
157
  ...item,
154
158
  active: !!item.to && normalize(item.to) === activeTo.value,
159
+ ...(item.children
160
+ ? { children: item.children.map((child) => ({ ...child, active: !!child.to && normalize(child.to) === activeTo.value })) }
161
+ : {}),
155
162
  })),
156
163
  })),
157
164
  );
158
165
 
159
166
  const activeItem = computed(() => {
160
167
  for (const section of props.sections) {
161
- for (const item of section.items) {
168
+ for (const item of flatItems(section.items)) {
162
169
  if (item.to && normalize(item.to) === activeTo.value) return item;
163
170
  }
164
171
  }
@@ -236,7 +243,7 @@ const partMenu = computed(() =>
236
243
  /** Le dernier maillon ouvre toutes les pages de la partie, sections confondues. */
237
244
  const pageMenu = computed(() =>
238
245
  props.sections.flatMap((section) =>
239
- section.items
246
+ flatItems(section.items)
240
247
  .filter((item) => item.to)
241
248
  .map((item) => ({
242
249
  label: item.label,
@@ -0,0 +1,354 @@
1
+ <!--
2
+ Ce a quoi un sujet se relie : les signes que l'article lui attribue, les
3
+ changements genetiques qu'il rapporte, et l'article lui-meme.
4
+
5
+ Reprend PatientsConnectionsGraph de Discovery (cytoscape + fcose). Un signe
6
+ explicitement ecarte, ou un gene teste puis ecarte, se dessine en pointille,
7
+ jamais en trait plein : un trait plein affirmerait ce que l'article nie.
8
+ -->
9
+ <template>
10
+ <div class="oip_list gap-8">
11
+ <div class="oip_row v-center space-between gap-16 wrap">
12
+ <div class="oip_row v-center gap-8">
13
+ <span class="graph_label">{{ fr ? "Disposition" : "Layout" }}</span>
14
+ <select v-model="layoutName" class="graph_select">
15
+ <option value="fcose">{{ fr ? "Force" : "Force" }}</option>
16
+ <option value="concentric">{{ fr ? "Concentrique" : "Concentric" }}</option>
17
+ <option value="circle">{{ fr ? "Cercle" : "Circle" }}</option>
18
+ <option value="grid">{{ fr ? "Grille" : "Grid" }}</option>
19
+ </select>
20
+ <button type="button" class="graph_fit" @click="fit">{{ fr ? "Recentrer" : "Fit" }}</button>
21
+ </div>
22
+ <div class="graph_legend">
23
+ <button
24
+ v-for="legend in legendEntries"
25
+ :key="legend.type"
26
+ type="button"
27
+ class="graph_leg"
28
+ :class="{ 'graph_leg--dim': hiddenTypes.has(legend.type) }"
29
+ :aria-pressed="!hiddenTypes.has(legend.type)"
30
+ :title="hiddenTypes.has(legend.type) ? `${fr ? 'Afficher' : 'Show'} ${legend.label}` : `${fr ? 'Masquer' : 'Hide'} ${legend.label}`"
31
+ @click="toggleType(legend.type)"
32
+ >
33
+ <span class="graph_dot" :style="{ background: colors[legend.type] }" />
34
+ {{ legend.label }}
35
+ </button>
36
+ </div>
37
+ </div>
38
+ <div ref="container" class="graph_container" />
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import type { PatientProfile } from "../../oip-patients/types";
44
+
45
+ type NodeType = "subject" | "paper" | "phenotype" | "gene" | "variant";
46
+
47
+ const props = withDefaults(defineProps<{ data: PatientProfile; lang?: "fr" | "en" }>(), { lang: "fr" });
48
+
49
+ const fr = computed(() => props.lang !== "en");
50
+
51
+ /**
52
+ * Lues dans les jetons plutot qu'ecrites ici : cytoscape veut une valeur
53
+ * concrete qu'il ne sait pas resoudre lui-meme, et la palette suit le theme.
54
+ */
55
+ function token(name: string, fallback: string): string {
56
+ if (typeof window === "undefined") return fallback;
57
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
58
+ }
59
+
60
+ const colors = ref<Record<NodeType, string>>({
61
+ subject: "#1dacfc",
62
+ paper: "#07816f",
63
+ phenotype: "#fda929",
64
+ gene: "#a17def",
65
+ variant: "#f55a8d",
66
+ });
67
+
68
+ function readColors() {
69
+ colors.value = {
70
+ subject: token("--chart-cat-1", colors.value.subject),
71
+ paper: token("--color-primary", colors.value.paper),
72
+ phenotype: token("--chart-cat-3", colors.value.phenotype),
73
+ gene: token("--chart-cat-4", colors.value.gene),
74
+ variant: token("--chart-cat-5", colors.value.variant),
75
+ };
76
+ }
77
+
78
+ const LEGEND_LABELS = computed<Record<NodeType, string>>(() => ({
79
+ subject: fr.value ? "Ce sujet" : "This subject",
80
+ paper: fr.value ? "Article" : "Paper",
81
+ phenotype: fr.value ? "Signe" : "Sign",
82
+ gene: fr.value ? "Gène" : "Gene",
83
+ variant: "Variant",
84
+ }));
85
+
86
+ const container = ref<HTMLElement | null>(null);
87
+ const layoutName = ref("fcose");
88
+ const hiddenTypes = ref<Set<NodeType>>(new Set());
89
+ let cy: any = null;
90
+ let observer: ResizeObserver | null = null;
91
+
92
+ function toggleType(type: NodeType) {
93
+ const next = new Set(hiddenTypes.value);
94
+ if (next.has(type)) next.delete(type);
95
+ else next.add(type);
96
+ hiddenTypes.value = next;
97
+ }
98
+
99
+ function fit() {
100
+ cy?.fit(undefined, 40);
101
+ }
102
+
103
+ const elements = computed(() => {
104
+ const record = props.data.record;
105
+ const subjectId = props.data.record_id;
106
+ const edgeLabel = (frText: string, enText: string) => (fr.value ? frText : enText);
107
+ const items: any[] = [{ data: { id: subjectId, label: props.data.label || subjectId, nodeType: "subject", size: 46 } }];
108
+
109
+ if (props.data.pmid) {
110
+ items.push({ data: { id: `pmid:${props.data.pmid}`, label: props.data.pmid, nodeType: "paper", size: 34 } });
111
+ items.push({ data: { source: subjectId, target: `pmid:${props.data.pmid}`, label: edgeLabel("décrit dans", "reported in"), dashed: false } });
112
+ }
113
+
114
+ for (const sign of record?.phenotypes ?? []) {
115
+ const id = sign.hpo_id || sign.label;
116
+ if (!id) continue;
117
+ items.push({ data: { id, label: sign.label || id, nodeType: "phenotype", size: 26 } });
118
+ items.push({
119
+ data: {
120
+ source: subjectId,
121
+ target: id,
122
+ label: sign.present === false ? edgeLabel("absent", "not present") : edgeLabel("présente", "has"),
123
+ dashed: sign.present === false,
124
+ },
125
+ });
126
+ }
127
+
128
+ for (const gene of record?.genotype ?? []) {
129
+ if (!gene.gene_hgnc) continue;
130
+ const rejected = gene.interpretation_status === "REJECTED";
131
+ items.push({ data: { id: `gene:${gene.gene_hgnc}`, label: gene.gene_hgnc, nodeType: "gene", size: 32 } });
132
+ items.push({ data: { source: subjectId, target: `gene:${gene.gene_hgnc}`, label: edgeLabel("gène", "gene"), dashed: rejected } });
133
+
134
+ const change = gene.cdna || gene.protein;
135
+ if (change) {
136
+ const id = `variant:${gene.gene_hgnc}:${change}`;
137
+ items.push({ data: { id, label: change, nodeType: "variant", size: 26 } });
138
+ items.push({ data: { source: `gene:${gene.gene_hgnc}`, target: id, label: gene.zygosity || edgeLabel("variant", "change"), dashed: rejected } });
139
+ }
140
+ }
141
+ return items;
142
+ });
143
+
144
+ /** Seuls les types presents, calcules avant le masquage : un type masque garde sa pastille. */
145
+ const legendEntries = computed(() => {
146
+ const present = new Set(elements.value.filter((item) => item.data.id).map((item) => item.data.nodeType as NodeType));
147
+ return (Object.keys(LEGEND_LABELS.value) as NodeType[])
148
+ .filter((type) => present.has(type))
149
+ .map((type) => ({ type, label: LEGEND_LABELS.value[type] }));
150
+ });
151
+
152
+ /**
153
+ * Masquer par type sans toucher a la disposition : les positions restent, le
154
+ * retour remet tout en place. Une arete part avec ses extremites.
155
+ */
156
+ function applyFilter() {
157
+ if (!cy) return;
158
+ cy.batch(() => {
159
+ cy.elements().show();
160
+ const hidden = cy.nodes().filter((node: any) => hiddenTypes.value.has(node.data("nodeType")));
161
+ hidden.connectedEdges().hide();
162
+ hidden.hide();
163
+ });
164
+ }
165
+
166
+ const style = () => {
167
+ const text = token("--text-primary", "#0f172a");
168
+ const muted = token("--text-muted", "#64748b");
169
+ const line = token("--border-strong", "#94a3b8");
170
+ const lineSoft = token("--border-default", "#cbd5e1");
171
+ return [
172
+ {
173
+ selector: "node",
174
+ style: {
175
+ label: "data(label)",
176
+ "text-valign": "center",
177
+ "text-halign": "center",
178
+ "font-size": "10px",
179
+ "font-family": "Inter, sans-serif",
180
+ "text-margin-y": -12,
181
+ width: "data(size)",
182
+ height: "data(size)",
183
+ color: text,
184
+ "font-weight": 600,
185
+ },
186
+ },
187
+ ...(Object.entries(colors.value) as [NodeType, string][]).map(([type, color]) => ({
188
+ selector: `node[nodeType="${type}"]`,
189
+ style: { "background-color": color },
190
+ })),
191
+ {
192
+ selector: "edge",
193
+ style: {
194
+ width: 1.5,
195
+ "line-color": line,
196
+ "curve-style": "bezier",
197
+ "font-size": "8px",
198
+ color: muted,
199
+ label: "data(label)",
200
+ "text-rotation": "autorotate",
201
+ },
202
+ },
203
+ { selector: "edge[?dashed]", style: { "line-style": "dashed", "line-color": lineSoft } },
204
+ ];
205
+ };
206
+
207
+ /**
208
+ * Sans arete, une disposition de force n'a rien a resoudre et empile les noeuds
209
+ * hors champ : la grille est le repli honnete.
210
+ */
211
+ function layoutOptions(hasEdges: boolean) {
212
+ const shared = { padding: 40, stop: () => fit() };
213
+ if (!hasEdges) return { ...shared, name: "grid", avoidOverlap: true };
214
+ if (layoutName.value === "concentric") {
215
+ return { ...shared, name: "concentric", concentric: (node: any) => node.degree(), levelWidth: () => 2, minNodeSpacing: 60 };
216
+ }
217
+ if (layoutName.value === "circle" || layoutName.value === "grid") {
218
+ return { ...shared, name: layoutName.value, spacingFactor: 1.5 };
219
+ }
220
+ return {
221
+ ...shared,
222
+ name: "fcose",
223
+ quality: "proof",
224
+ animate: true,
225
+ animationDuration: 500,
226
+ nodeDimensionsIncludeLabels: true,
227
+ nodeRepulsion: () => 20000,
228
+ idealEdgeLength: () => 150,
229
+ gravity: 0.3,
230
+ };
231
+ }
232
+
233
+ function relayout() {
234
+ if (!cy) return;
235
+ cy.layout(layoutOptions(cy.edges(":visible").length > 0)).run();
236
+ }
237
+
238
+ let fcoseRegistered = false;
239
+
240
+ async function draw() {
241
+ if (!container.value) return;
242
+ const cytoscape = (await import("cytoscape")).default;
243
+ if (!fcoseRegistered) {
244
+ cytoscape.use((await import("cytoscape-fcose")).default);
245
+ fcoseRegistered = true;
246
+ }
247
+ readColors();
248
+ cy?.destroy();
249
+
250
+ const els = elements.value;
251
+ cy = cytoscape({
252
+ container: container.value,
253
+ elements: els,
254
+ style: style() as any,
255
+ layout: layoutOptions(els.some((element) => !element.data.id)) as any,
256
+ minZoom: 0.2,
257
+ maxZoom: 3,
258
+ wheelSensitivity: 0.3,
259
+ });
260
+ applyFilter();
261
+ cy.one("layoutstop", fit);
262
+ }
263
+
264
+ onMounted(async () => {
265
+ await draw();
266
+ // Cytoscape mesure son canevas une fois : le rail et la barre laterale
267
+ // changent la largeur apres le montage.
268
+ if (container.value && typeof ResizeObserver !== "undefined") {
269
+ observer = new ResizeObserver(() => cy?.resize());
270
+ observer.observe(container.value);
271
+ }
272
+ });
273
+
274
+ watch(() => props.data.record_id, draw);
275
+ watch(locale, draw);
276
+ watch(layoutName, relayout);
277
+ watch(hiddenTypes, applyFilter);
278
+
279
+ onBeforeUnmount(() => {
280
+ observer?.disconnect();
281
+ cy?.destroy();
282
+ });
283
+ </script>
284
+
285
+ <style scoped>
286
+ .graph_container {
287
+ width: 100%;
288
+ max-width: 100%;
289
+ height: 440px;
290
+ overflow: hidden;
291
+ }
292
+
293
+ .graph_label {
294
+ font-size: 13px;
295
+ color: var(--text-muted);
296
+ }
297
+
298
+ .graph_select {
299
+ padding: 2px 6px;
300
+ font-size: 12px;
301
+ color: var(--text-primary);
302
+ background: transparent;
303
+ border: 1px solid var(--border-default);
304
+ border-radius: 6px;
305
+ }
306
+
307
+ .graph_fit {
308
+ padding: 0;
309
+ font-size: 13px;
310
+ color: var(--primary);
311
+ text-decoration: underline;
312
+ background: none;
313
+ border: 0;
314
+ cursor: pointer;
315
+ }
316
+
317
+ .graph_legend {
318
+ display: flex;
319
+ flex-wrap: wrap;
320
+ gap: 8px;
321
+ font-size: 12px;
322
+ }
323
+
324
+ .graph_leg {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 4px;
328
+ padding: 3px 8px;
329
+ font-size: 12px;
330
+ color: var(--text-primary);
331
+ background: none;
332
+ border: 0;
333
+ border-radius: 999px;
334
+ cursor: pointer;
335
+ transition:
336
+ background 0.15s,
337
+ opacity 0.15s;
338
+ }
339
+
340
+ .graph_leg:hover {
341
+ background: var(--bg-surface-2);
342
+ }
343
+
344
+ .graph_leg--dim {
345
+ opacity: 0.4;
346
+ }
347
+
348
+ .graph_dot {
349
+ display: inline-block;
350
+ width: 10px;
351
+ height: 10px;
352
+ border-radius: 50%;
353
+ }
354
+ </style>