/* Varaverk plugin styles — inherits unRAID theme, adds plugin-specific layout */ #varaverk-wrap { padding: 10px; font-family: inherit; } /* Tab bar */ #vv-tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 2px solid #444; align-items: flex-end; } .vv-tab { padding: 6px 16px; text-decoration: none; color: #aaa; border-radius: 4px 4px 0 0; } .vv-tab:hover { color: #fff; background: #333; } .vv-tab.active { color: #fff; background: #555; border-bottom: 2px solid #fff; } /* Expand toggle button */ #vv-expand-btn { background: none; border: none; cursor: pointer; color: #333; font-size: 15px; padding: 2px 8px 4px; line-height: 1; border-radius: 3px; transition: color .15s; margin-left: 6px; flex-shrink: 0; } #vv-expand-btn:hover { color: #888; } #vv-expand-btn.active { color: #aaa; } /* Fullscreen mode — hide Unraid chrome, reclaim the space */ body.vv-fullscreen #header { display: none !important; } body.vv-fullscreen #menu { display: none !important; } body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5rem !important; } /* Cards / layout */ .vv-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; } .vv-card { flex: 1; min-width: 200px; background: #1e1e1e; border: 1px solid #444; border-radius: 6px; padding: 12px; } .vv-wide { flex: 100%; } .vv-card h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; color: #888; letter-spacing: 0.05em; white-space: normal; overflow: hidden; min-width: 0; display: flex; align-items: center; justify-content: space-between; } /* Cog icon — links to related Unraid page. Hidden until card hovered. */ .vv-card-cog { color: #2a2a2a; font-size: 13px; line-height: 1; text-decoration: none; padding: 1px 3px; border-radius: 3px; flex-shrink: 0; transition: color 0.15s, background 0.15s; font-style: normal; font-weight: normal; text-transform: none; letter-spacing: 0; cursor: pointer; } .vv-card:hover .vv-card-cog { color: #4a4a4a; } .vv-card-cog:hover { color: #aaa !important; background: #333; } /* Card header icon wrapper */ .vv-ico { display:inline-flex; align-items:center; opacity:0.38; flex-shrink:0; } /* Status accent — left border colour for state-driven cards */ .vv-accent-ok { border-left-color: #2a5a2a !important; } .vv-accent-warn { border-left-color: #5a4000 !important; } .vv-accent-err { border-left-color: #5a1e1e !important; } #vv-monitor .vv-card { transition: border-left-color 0.5s; } /* Status banner — coloured strip at top of card body */ .vv-banner { border-radius: 4px; padding: 5px 10px; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 600; } .vv-banner-ok { background: #061306; border: 1px solid #1a401a; color: #4caf50; } .vv-banner-warn { background: #130e00; border: 1px solid #3d2e00; color: #ff9800; } .vv-banner-err { background: #140404; border: 1px solid #3d1010; color: #f44336; } .vv-banner-off { background: #0d0d0d; border: 1px solid #252525; color: #555; } /* System card — no h3, no top padding waste */ #vv-system { padding-top: 14px; } /* System action buttons */ .vv-sys-btn { background: #2a2a2a; border: 1px solid #e65100; color: #ff9800; border-radius: 3px; padding: 3px 0; font-size: 6px; cursor: pointer; line-height: 1; width: 50px; min-width: 0; text-align: center; } .vv-sys-btn:hover { background: #3a2000; color: #ffb74d; border-color: #ff9800; } /* Fallback state badge */ .vv-state-badge { font-size: 20px; font-weight: bold; padding: 4px 0; margin-bottom: 2px; } .vv-state-normal { color: #4caf50; } .vv-state-failover { color: #f44336; } .vv-state-no_internet { color: #ff9800; } .vv-state-dark { color: #9e9e9e; } .vv-state-unknown { color: #666; } /* Docker table */ #vv-docker-table { width: 100%; border-collapse: collapse; font-size: 13px; } #vv-docker-table th { text-align: left; padding: 4px 8px; color: #888; border-bottom: 1px solid #444; } #vv-docker-table td { padding: 4px 8px; border-bottom: 1px solid #2a2a2a; } .vv-status-up { color: #4caf50; } .vv-status-down { color: #f44336; } /* Scheduler */ .vv-hint { color: #888; font-size: 13px; margin-bottom: 16px; } .vv-sched-card { margin-bottom: 10px; } .vv-script { background: #161616; border: 1px solid #333; border-radius: 4px; padding: 6px 10px; margin: 4px 0; margin-left: 20px; } .vv-job-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; transition: background 0.1s, box-shadow 0.1s; } /* Orchestrator top row — clickable, show pointer + hover feedback */ .vv-orch-row { cursor: pointer; border-radius: 4px; } .vv-orch-row:hover { background: rgba(255,255,255,0.04); } .vv-job-label { flex: 1; font-size: 16px; font-weight: bold; color: #6fcf97; min-width: 80px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .vv-job-actions { display: flex; align-items: center; gap: 8px; padding-left: 44px; margin-top: 6px; } .vv-job-desc { font-size: 14px; color: #777; margin: 3px 0 2px 0; padding-left: 44px; box-sizing: border-box; width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; } .vv-cron { flex: 0 0 80px; width: 80px; background: #111; border: 1px solid #444; color: #ddd; padding: 4px 6px; border-radius: 4px; font-family: monospace; font-size: 11px; } .vv-event-badge { flex: 0 0 auto; padding: 3px 8px; border-radius: 4px; font-size: 12px; background: #1a3a1a; border: 1px solid #2e6b2e; color: #6fcf6f; white-space: nowrap; font-weight: 500; } .vv-flag-badge { flex: 0 0 auto; padding: 2px 6px; border-radius: 3px; font-size: 11px; background: #2e2200; border: 1px solid #6b4e00; color: #d4a017; white-space: nowrap; font-family: monospace; } .vv-log-label { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #888; cursor: pointer; white-space: nowrap; flex-shrink: 0; } .vv-log-label input { cursor: pointer; accent-color: #4caf50; } .vv-log-label:has(input:checked) { color: #4caf50; } .vv-children { padding-top: 8px; border-top: 1px solid #333; margin-top: 8px; } .vv-advanced-toggle { background: none; border: 1px solid #555; color: #aaa; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; } .vv-advanced-toggle:hover { border-color: #888; color: #fff; } .vv-btn-sm { padding: 3px 10px; background: #2a2a2a; border: 1px solid #555; color: #ccc; border-radius: 4px; cursor: pointer; font-size: 12px; white-space: nowrap; } .vv-btn-sm:hover { border-color: #888; color: #fff; } .vv-btn-sm.active { border-color: #4caf50; color: #4caf50; } /* Save checkmark */ .vv-save-check { color: #4caf50; font-size: 13px; width: 14px; flex-shrink: 0; opacity: 0; text-align: center; } @keyframes vv-check-fade { 0%,60% { opacity: 1; } 100% { opacity: 0; } } .vv-save-check.vv-check-show { animation: vv-check-fade 2s forwards; } /* Running dot */ .vv-job-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; } .vv-dot-running { background: #4caf50; animation: vv-pulse-dot 1s ease-in-out infinite; } #vv-log-dot { animation: vv-pulse-dot 1s ease-in-out infinite; } @keyframes vv-pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } } /* Selected job row — editor-style left accent + blue tint */ .vv-row-selected { background: rgba(100,149,237,0.1); border-radius: 4px; outline: 1px solid rgba(100,149,237,0.35); box-shadow: inset 3px 0 0 #6495ed; } /* Two-panel layout */ #vv-sched-layout { display: flex; gap: 16px; align-items: stretch; } #vv-sched-left { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-self: flex-start; } #vv-sched-cards { flex: 1; } #vv-sched-right { display: none; flex: 1 1 0; min-width: 0; flex-direction: column; overflow: hidden; align-self: flex-start; } /* Flex items default to min-height:auto, which resolves to their content size — so a view with a long log in it refuses to shrink. The assistant below it is flex-shrink:0 and this panel is a fixed height with overflow:hidden, so whatever will not fit is not scrolled, it is clipped off the bottom: the assistant's own composer, which is the last thing in the panel. vvFitRight() sizes these explicitly and this is what lets that size actually take effect. */ #vv-sched-right > * { min-height: 0; } #vv-sched-right.vv-panel-visible { display: flex; } .vv-log-card { flex: 1; display: flex; flex-direction: column; padding-bottom: 0; } .vv-log-right-pre { max-height: none; overflow-y: auto; } /* Scheduler stacked layout (narrow viewport) */ @media (max-width: 900px) { #vv-sched-layout { flex-direction: column; align-items: stretch; } #vv-sched-left { flex: none; width: 100%; } #vv-sched-right { flex-direction: column; width: 100%; overflow: visible; } .vv-log-right-pre { min-height: 520px; max-height: 680px; } /* Toolbar: stack title row above buttons row, let buttons wrap */ .vv-log-toolbar { flex-direction: column; align-items: flex-start; gap: 6px; } .vv-log-toolbar > div { flex-wrap: wrap; gap: 6px !important; } /* Array-event rows: badge already shows the event, cron input is redundant */ .vv-job-row:has(.vv-event-badge) .vv-cron { display: none; } } /* Plugin settings row (Advanced mode) */ .vv-nb-settings { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 5px 10px; background: #101010; border-bottom: 1px solid #1a1a1a; } /* ── Monitor: locked row height + scrollable cards ────────────────────────── */ /* Cards on the monitor grid are flex columns — h3 pins, body scrolls */ #vv-monitor .vv-card { display: flex; flex-direction: column; overflow: hidden; } #vv-monitor .vv-card h3 { flex-shrink: 0; } #vv-monitor .vv-card > div { flex: 1; min-height: 0; overflow-y: auto; -ms-overflow-style: none; scrollbar-width: none; } #vv-monitor .vv-card > div::-webkit-scrollbar { display: none; } /* Hide scrollbars on any nested scrollable div inside monitor cards */ #vv-monitor .vv-card div::-webkit-scrollbar { display: none; } /* Dynamic row heights capped per screen tier — rows size to content, never exceed the cap. minmax(0, Xpx): track is content-driven but capped; align-items:stretch makes all cards in a row fill the track, so short cards (Pools, Watchdog) match tall ones (Array). Breakpoints are viewport height (after browser chrome), not screen height. */ /* ~720p (viewport ≤ 700px) */ @media (min-width: 481px) and (max-height: 700px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 160px) / 4)); } } /* ~1080p (viewport 701–1100px) */ @media (min-width: 481px) and (min-height: 701px) and (max-height: 1100px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 240px) / 4)); } } /* ~1440p (viewport 1101–1450px) — calibrated on 15" 1440p display */ @media (min-width: 481px) and (min-height: 1101px) and (max-height: 1450px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 335px) / 4)); } } /* ~4K (viewport > 1450px) */ @media (min-width: 481px) and (min-height: 1451px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 500px) / 4)); } } /* Mobile: natural heights, let page scroll */ @media (max-width: 480px) { #vv-monitor { grid-auto-rows: auto !important; } #vv-monitor .vv-card { overflow: visible !important; } #vv-monitor .vv-card > div { overflow-y: visible; min-height: auto; } } /* ── Monitor: AI row ────────────────────────────────────────────────────────── Same eight columns and the same gap as #vv-monitor, and margin-top matches that grid's row gap, so the two containers read as one board. What differs is height, which is the whole reason it is a separate grid: no grid-auto-rows cap and no overflow:hidden on the cards, so the Assistant's expand control gives the transcript real room instead of growing it inside a box the row had already sized. align-items:start, not the stretch the grid above uses, so a card opts in to matching the Assistant rather than being dragged to its height. All three in this row opt in below; the default stays start so a card added later has to say so. */ #vv-monitor-ai { display: grid; grid-template-columns: repeat(8, 1fr); gap: 12px; margin-top: 12px; width: 100%; box-sizing: border-box; align-items: start; } /* Explicit placement, two rows. The left column carries AI over Tokens; Conversations and the Assistant span both rows, so the Assistant sets the height and the two stacked cards divide what it sets. This has to be written as row+column rather than the spans that were inline on the markup: a span says how wide, never which row, and auto-placement would drop Tokens into the first gap it found rather than under AI. */ /* AI fills its own row rather than sitting at the top of it. The Assistant spans both rows, and grid hands a spanning item's surplus height to the tracks it spans — so at Large, row 1 grows well past what the AI card's few lines need and the card left a gap above Tokens. Stretching is a no-op at the smaller sizes, where there is no surplus to take. */ #vv-ai-stats-card { grid-column: 1 / span 1; grid-row: 1; align-self: stretch; } #vv-ai-tokens-card { grid-column: 1 / span 1; grid-row: 2; } #vv-ai-chats-card { grid-column: 2 / span 2; grid-row: 1 / span 2; } #vv-ai-assistant-card { grid-column: 4 / span 5; grid-row: 1 / span 2; } /* Conversations and Tokens take their height from the Assistant rather than setting their own. align-self overrides the row's align-items:start for these two, so they fill the track the Assistant defines — level at rest, and level again after expanding, since the track grows with it. They need their own scroll to do that, which Conversations did not need inside #vv-monitor: there it inherited a cap from `.vv-card > div { flex:1; overflow-y:auto }`. Out here both are content-driven, so forty saved chats — or a ledger with both hosts and a footer — would make the card the tallest thing in its column and set the height it is supposed to be following. The h3 pins and the body takes what is left, the same shape the monitor grid above uses. align-self alone does not finish the job, which is why vvAiRowSync() in monitor.php sets a max-height on both. A stretched item's own content still feeds grid track sizing — the track is sized from content first and stretched afterwards — so a card long enough grows the row it was meant to fit inside, and stretching to that row then changes nothing. The cap is measured from the Assistant, which is the one card here whose height is its own business. */ #vv-monitor-ai #vv-ai-chats-card, #vv-monitor-ai #vv-ai-tokens-card { align-self: stretch; display: flex; flex-direction: column; overflow: hidden; } #vv-monitor-ai #vv-ai-chats-card h3, #vv-monitor-ai #vv-ai-tokens-card h3 { flex-shrink: 0; } /* height:0 with flex:1 is the part that makes these follow rather than lead. flex-basis is already 0, so it changes nothing about the layout; what it changes is the height the body reports upward. Without it the card can still hand the auto-sized grid track a max-content height of its whole contents, and the track it was meant to fit inside grows to fit it instead. It grows back to fill the track through flex, then scrolls. */ #vv-monitor-ai #vv-ai-chats-card > div, #vv-monitor-ai #vv-ai-tokens-card > div { flex: 1; height: 0; min-height: 0; overflow-y: auto; -ms-overflow-style: none; scrollbar-width: none; } #vv-monitor-ai #vv-ai-chats-card > div::-webkit-scrollbar, #vv-monitor-ai #vv-ai-tokens-card > div::-webkit-scrollbar { display: none; } /* ── AI token ledger — shared by the AI tab and the Monitor card ────────────── Moved out of pages/ai.php so both surfaces can draw the same rows. A tab's inline