/* Varaverk plugin styles — inherits unRAID theme, adds plugin-specific layout */ #varaverk-wrap { padding: 10px; font-family: inherit; } /* Tab bar Scrolls itself once the tabs stop fitting. Without this the row simply overflowed and the whole page went with it, so reaching the last tab on a phone meant dragging the entire layout sideways — every card, not just the bar. The scrollbar is hidden the way the log panes hide theirs: a visible one costs a row of height on a bar that is 30px tall, and the content is obviously draggable. overscroll-behavior stops a flick at either end of the bar from chaining out and scrolling the page instead. */ #vv-tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 2px solid #444; align-items: flex-end; overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; scrollbar-width: none; } #vv-tabs::-webkit-scrollbar { display: none; } /* Neither squashed nor broken across lines — a flex item's default is to shrink, which turned "Partnership" into three characters and an ellipsis before the bar ever decided to scroll. */ .vv-tab { padding: 6px 16px; text-decoration: none; color: #aaa; border-radius: 4px 4px 0 0; flex-shrink: 0; white-space: nowrap; } .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; } /* One card surface for the whole plugin. #161616 on #2a2a2a was already what Settings, Watchdog, Arrs and Rsync drew — four pages agreeing under four different class names — while this shared one was still the older #1e1e1e on #444. Bringing it here is what makes them one look rather than two, and it is done on the appearance alone: flex and min-width stay, because the Monitor grid needs a 200px floor and the Arrs grid needs min-width:0, and merging those would change how both size their columns to fix a colour. */ .vv-card { flex: 1; min-width: 200px; background: #161616; border: 1px solid #2a2a2a; 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: the board ─────────────────────────────────────────────────────── Only the half of the layout that is the same at every width lives here. Which cards exist, how wide each one is, what order they sit in, how many columns the board has at this width and how tall a row may be are all generated by include/monitor_board.php and arrive as --vv-cols, --vv-sp and --vv-rowdiv. Do not put a column count or a breakpoint in this file; they are arithmetic over the card floor, and that arithmetic has one home. minmax(0, 1fr), not 1fr: bare 1fr means minmax(auto, 1fr), and that auto floor resolves to the larger of min-content and min-width — one card holding an unbreakable string would push its column past its share and drag the grid out of the container. The row axis carried this guard for years; the column axis never did. min-width: 0 on the cards for the same reason, overriding the 200px .vv-card floor the flex layouts on other pages need. On this board the ladder is what guarantees a card's width, so a card defending its own floor can only overflow the track the ladder just sized for it. */ #vv-monitor { display: grid; grid-template-columns: repeat(var(--vv-cols, 8), minmax(0, 1fr)); gap: 12px; width: 100%; box-sizing: border-box; } #vv-monitor > .vv-card { grid-column: span var(--vv-sp, 1); min-width: 0; } /* 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; } /* System card, held to the Network card's height. At eight columns these two share a row (system 1 + ups 1 + cpu 2 + memory 2 + network 2), so the grid already stretches them to the same height and nothing here is needed. Below eight the board re-cuts into rows of four: System lands in row 1 with UPS and CPU, Network in row 2 with Memory, and once rows are sized to content (vv_mon_rung_overflows) the two are free to differ. System is the taller — it carries a host block and a Varaverk block — so it is the one capped. 222px is Network measured rather than guessed: 2 border + 24 padding + 25 h3 + 61 header block + 110 canvas. The canvas is a fixed 110px, which is what makes the number worth writing down — most of Network's height cannot drift. What can is the IP list (LAN/EXT/TS), roughly 18px a row, so a host resolving fewer of the three leaves System slightly tall against it. Two ids on the selectors, to outrank #vv-monitor .vv-card from the auto-rows hatch above. That hatch lifts overflow off every card so content-sized rows can work; this card is the one place the clamps have to go back on, or a max-height with overflow:visible would draw straight through the card's own border. */ @media (max-width: 1383px), (max-height: 700px) { #vv-monitor #vv-system { max-height: 222px; overflow: hidden; } #vv-monitor #vv-system > div { overflow-y: auto; min-height: 0; } } /* 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. The divisor is --vv-rowdiv, not a literal 4. It used to be 4 because the board happened to be four rows at eight columns, so the two numbers were the same by accident and only one of them moved when the column count changed. monitor_board.php derives it as min(rows at this rung, VV_MON_ROWS_PER_SCREEN): the cap means "no card taller than this fraction of the screen", and a board short enough to fit divides by its own row count and fills the screen instead. */ /* ~1080p (viewport 701–1100px) */ @media (min-height: 701px) and (max-height: 1100px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 240px) / var(--vv-rowdiv, 4))); } } /* ~1440p (viewport 1101–1450px) — calibrated on 15" 1440p display */ @media (min-height: 1101px) and (max-height: 1450px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 335px) / var(--vv-rowdiv, 4))); } } /* ~4K (viewport > 1450px) */ @media (min-height: 1451px) { #vv-monitor { grid-auto-rows: minmax(0, calc((100vh - 500px) / var(--vv-rowdiv, 4))); } } /* Natural heights, page scrolls. Height is what makes a phone in landscape unusable, not width: this hatch was keyed to max-width:480 alone, so portrait (393px wide) got content-sized rows and worked, while landscape (851x393) missed it and inherited the four-row cap — 58px cards with overflow:hidden and scrollbars disabled. Nothing was visible and nothing said so. Height only now. The width half was a literal 695 that had to be kept in step with VV_MON_CARD_FLOOR by hand; monitor_board.php emits these same three rules for every rung whose row count exceeds VV_MON_ROWS_PER_SCREEN — see vv_mon_rung_overflows() — which covers that band and the four-column one above it from the arithmetic instead. What is left here is the case the ladder cannot see: a window wide enough for a rung that does fit a screen, on a screen too short to give those rows a usable height. */ @media (max-height: 700px) { #vv-monitor { grid-auto-rows: auto; } #vv-monitor .vv-card { overflow: visible; } #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, minmax(0, 1fr)); gap: 12px; margin-top: 12px; width: 100%; box-sizing: border-box; align-items: start; } /* Same reason as the board above: the ladder guarantees the width, so a card defending the 200px .vv-card floor can only overflow the track it was given. At the four-column rung's own minimum (696px) a column is 160px, and that floor would have pushed this row out of the page. */ #vv-monitor-ai > .vv-card { min-width: 0; } /* 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