.page-content { height: calc(100vh - 48px); /* Full height minus header */ margin-top: 48px; /* Push down below header */ width: 100%; position: relative; overflow-x: hidden; /* Prevent horizontal scrolling */ overflow-y: scroll; /* Enable vertical scrolling */ } .container { max-width: 1400px; margin: 0 auto; padding: 0 var(--space-2); position: relative; z-index: var(--z-base); } /* Sticky controls container */ .controls { position: sticky; top: -54px; z-index: calc(var(--z-header) - 1); background: var(--bg-color); padding: var(--space-1) 0; box-shadow: var(--shadow-xs); } /* Wrapper around the controls bar and breadcrumb nav. With the sticky-controls setting off it is transparent to layout (display: contents), preserving the original behavior (only the breadcrumb stays visible). When enabled, the whole wrapper sticks as one unit so the two bars can never drift apart. */ .sticky-topbar { display: contents; } body.sticky-controls .sticky-topbar { display: block; position: sticky; top: 0; z-index: calc(var(--z-header) - 1); background: var(--bg-color); box-shadow: var(--shadow-xs); } /* Responsive container for larger screens */ @media (min-width: 2150px) { .container { max-width: 1800px; } } @media (min-width: 3000px) { .container { max-width: 2400px; } } .controls-right { display: flex; align-items: center; gap: 8px; /* Push to the right of the row. Because it is also the flex item that is allowed to drop to a second row, an auto margin keeps it right-aligned on either row — no width: 100% / viewport breakpoint needed. */ margin-left: auto; } .actions { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); /* Wrap only when the controls genuinely cannot fit, instead of at a fixed viewport width. Viewport-based wrapping wasted space on high-DPI displays (e.g. a 2560px monitor at 200% scaling reports a ~1280px CSS viewport even when the window is maximized). */ flex-wrap: wrap; width: 100%; } .action-buttons { display: flex; align-items: center; gap: var(--space-2); /* Let the group shrink rather than overflow so .controls-right only wraps when it really has to. */ flex-wrap: wrap; flex-shrink: 1; min-width: 0; } /* Action button styling */ .control-group { position: relative; } .control-group button { /* Keeps the toolbar visually even without forcing the row to overflow (the old 100px floor pushed the total past the container on wide screens). */ min-width: 90px; display: flex; align-items: center; justify-content: center; gap: 4px; border-radius: var(--border-radius-xs); padding: 4px 10px; border: 1px solid var(--border-color); background: var(--card-bg); color: var(--text-color); font-size: 0.85em; transition: var(--transition-base); cursor: pointer; box-shadow: var(--shadow-xs); } .control-group button:hover, .control-group button:focus-visible { border-color: var(--lora-accent); background: var(--bg-color); transform: translateY(-1px); box-shadow: var(--shadow-lg); outline: none; } .control-group button:active { transform: translateY(0); box-shadow: var(--shadow-xs); } .control-group button i { opacity: 0.8; transition: opacity 0.2s ease; } .control-group button:hover i, .control-group button:focus-visible i { opacity: 1; } .control-group button:disabled { cursor: not-allowed; opacity: 0.6; pointer-events: none; } .control-group button.loading, .dropdown-toggle.loading { cursor: wait; opacity: 0.7; pointer-events: none; } /* Controls */ .control-group button.favorite-filter, .control-group button.update-filter { position: relative; overflow: hidden; } .control-group button.favorite-filter.active { background: var(--lora-accent); color: white; } .control-group button.favorite-filter i { margin-right: 4px; color: var(--favorite-color); } .control-group button.update-filter i { margin-right: 4px; color: var(--lora-accent); } .control-group button.update-filter.active { background: var(--lora-accent); color: white; } .control-group button.update-filter.active i { color: white; } .update-filter-group .dropdown-main.update-filter.active + .dropdown-toggle { background: var(--lora-accent); border-color: var(--lora-accent); color: white; } .update-filter-group .dropdown-main.update-filter.active + .dropdown-toggle i { color: inherit; } /* Active state for buttons that can be toggled */ .control-group button.active { background: var(--lora-accent); color: white; border-color: var(--lora-accent); } /* Recipes layout toggle (grid / masonry) — segmented control in the toolbar */ .layout-toggle-group { display: flex; gap: 0; } .layout-toggle-group .layout-toggle-btn { min-width: 36px; width: 36px; padding: 4px 0; border-radius: 0; } .layout-toggle-group .layout-toggle-btn:first-child { border-radius: var(--border-radius-xs) 0 0 var(--border-radius-xs); border-right: none; } .layout-toggle-group .layout-toggle-btn:last-child { border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0; } .layout-toggle-group .layout-toggle-btn:hover, .layout-toggle-group .layout-toggle-btn:focus-visible { transform: none; box-shadow: var(--shadow-xs); } /* Keyboard shortcut indicator styling */ .shortcut-key { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px; font-size: 10px; font-weight: 500; line-height: 1; text-transform: uppercase; border-radius: var(--border-radius-xs); background-color: var(--shortcut-bg); border: 1px solid var(--shortcut-border); color: var(--shortcut-text); opacity: 0.8; transition: var(--transition-base); } .control-group button:hover .shortcut-key { opacity: 1; background-color: var(--shortcut-bg-hover); border-color: var(--shortcut-border-hover); } /* Invert the keycap on active (accent-filled) buttons for contrast. Must come after the hover rule above so it wins on active+hover. */ .control-group button.active .shortcut-key, .control-group button.active:hover .shortcut-key { color: var(--lora-accent); background: rgba(255, 255, 255, 0.92); border-color: transparent; opacity: 1; } /* Ensure correct vertical alignment for text+shortcut */ .control-group button span { display: inline-flex; align-items: center; gap: 6px; } /* Select dropdown styling */ .control-group select { min-width: 100px; padding: 4px 26px 4px 10px; border-radius: var(--border-radius-xs); border: 1px solid var(--border-color); background-color: var(--card-bg); color: var(--text-color); font-size: 0.85em; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right 6px center; background-size: 14px; cursor: pointer; transition: var(--transition-base); box-shadow: var(--shadow-xs); } /* Style for optgroups */ .control-group select optgroup { font-weight: 600; font-style: normal; color: var(--text-color); background-color: var(--card-bg); } .control-group select option { padding: 4px 8px; background-color: var(--card-bg); color: var(--text-color); } /* Dark theme optgroup styling */ [data-theme="dark"] .control-group select optgroup { background-color: var(--card-bg); color: var(--text-color); } [data-theme="dark"] .control-group select option { background-color: var(--card-bg); color: var(--text-color); } .control-group select:hover { border-color: var(--lora-accent); background-color: var(--bg-color); transform: translateY(-1px); box-shadow: var(--shadow-lg); } .control-group select:focus { outline: none; border-color: var(--lora-accent); box-shadow: 0 0 0 2px oklch(var(--lora-accent) / 0.15); } /* Disabled sort dropdown — used when VLM custom filter is active */ .control-group select:disabled { opacity: 0.5; cursor: not-allowed; background-color: var(--bg-color); border-color: var(--border-color); box-shadow: none; transform: none; } .control-group select:disabled:hover { border-color: var(--border-color); background-color: var(--bg-color); transform: none; box-shadow: none; } /* === Sort dropdown — decoupled trigger width =========================== The native is kept in the DOM (visually hidden) so existing JS that reads/writes `.value` / `.disabled` and dynamically adds/removes