/* 13-image-editor - part of the former static/style.css. * * The stylesheet is split by area, but the split is mechanical: rules were * moved verbatim and NOT regrouped. The numeric prefix is the load order, and * that order is what preserves the cascade. A rule sits in the file its * original position fell into, so an area's rules are not all in its file and * a file is not only that area's rules. * * Do not reorder the tags in static/index.html, and do not move a rule * between files: with 1,151 redeclarations and 1,886 !important in this * cascade, either can change which declaration wins. * * static/index.html and the PRECACHE list in static/sw.js must agree on this * order. tests/test_static_stylesheet_manifest.py and * tests/test_css_computed_style_snapshot.py fail if that drifts. */ .ge-draft-status[data-state="pending"], .ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } .ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } .ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } /* "ALPHA" badge — flags the editor as in-development. */ .ge-alpha-badge { flex-shrink: 0; font-size: 9px; font-weight: 700; letter-spacing: 0.1em; line-height: 1; padding: 3px 6px; margin-right: 4px; border-radius: 4px; color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); text-transform: uppercase; user-select: none; cursor: default; } @media (max-width: 768px) { /* Keep the toolbar tight on mobile — badge stays but shrinks. */ .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } } .ge-diffusion-status { font-size: 11px; cursor: pointer; padding: 2px 8px; border-radius: 4px; transition: all 0.15s; white-space: nowrap; } .ge-diffusion-status.online { color: var(--color-success, #4ade80); } .ge-diffusion-status.offline { color: var(--color-error, #ef4444); opacity: 0.7; } .ge-diffusion-status.offline:hover { opacity: 1; background: color-mix(in srgb, var(--color-error) 10%, transparent); } .ge-topbar-fill { display: inline-flex; align-items: center; gap: 4px; color: var(--accent, var(--red)); border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); } .ge-topbar-fill:hover { background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); border-color: var(--accent, var(--red)); } /* Mask-color swatch in the topbar — small label + circular swatch so the user can pick a contrasting overlay colour from anywhere. */ .ge-topbar-mask-color-wrap { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; } .ge-topbar-mask-color-label { font-size: 10px; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.06em; } .ge-topbar-mask-color { width: 18px; height: 18px; flex: 0 0 18px; } .ge-topbar-mask-color.cp-swatch-input { width: 18px; height: 18px; flex: 0 0 18px; border-radius: 50%; padding: 0; } .ge-topbar-sep { width: 1px; height: 16px; background: var(--border); margin: 0 4px; } /* Contextual tool options bar (between topbar and editor body) */ .ge-options-bar { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; flex-wrap: nowrap; row-gap: 4px; min-height: 40px; padding: 4px 10px; box-sizing: border-box; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--panel) 92%, var(--fg) 8%); overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; } .ge-options-tool { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 85%, transparent); } .ge-options-tool-icon { display: inline-flex; width: 18px; height: 18px; align-items: center; justify-content: center; } .ge-options-sep { width: 1px; height: 20px; background: var(--border); flex: 0 0 auto; } .ge-options-body { display: flex; align-items: center; gap: 14px; flex: 1 1 auto; min-width: 0; } /* Tool options moved out of the dock into the options bar (Photoshop): the real controls flow inline on the SAME single thin row, horizontally scrollable when they do not fit. Section headings/hints are dropped in this compact strip; the authoritative sliders mirror the contextual strip, so duplicated ranges are hidden. */ .ge-options-bar .ge-controls.ge-controls--in-options { flex: 0 1 auto; flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 14px; padding: 0; margin: 0; border: 0; min-width: 0; max-height: none; overflow: visible; } .ge-controls--in-options .ge-section-title, .ge-controls--in-options .ge-section-hint { display: none; } .ge-controls--in-options > section, .ge-controls--in-options > div { flex: 0 0 auto; } /* Lay out every visible tool section as one horizontal strip. Hidden sections carry an inline display:none and are left untouched; visible ones must override their inline display:block, hence !important. */ .ge-controls--in-options > div:not([style*="display:none"]):not([style*="display: none"]) { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; gap: 12px; } .ge-controls--in-options .ge-control-row { margin: 0; flex: 0 0 auto; display: flex; flex-direction: row; align-items: center; gap: 6px; } .ge-controls--in-options .ge-control-row label { white-space: nowrap; } /* The Text section is authored as a vertical panel. When it is relocated into the options bar its grids collapse to zero width, so the Font picker shrinks to ~14px and the Font/Size labels pile into "FoSize". Lay the text strip out as fixed-width field groups so the font picker keeps its room. */ .ge-controls--in-options .ge-text-section .ge-text-grid { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: flex-end; gap: 8px; flex: 0 0 auto; } .ge-controls--in-options .ge-text-section .ge-text-field { flex: 0 0 auto; min-width: 56px; align-items: stretch; } .ge-controls--in-options .ge-text-section .ge-text-field > span { white-space: nowrap; } .ge-controls--in-options .ge-text-section .ge-text-font-field { min-width: 116px; } .ge-controls--in-options .ge-text-section .ge-text-size-field { min-width: 56px; } .ge-controls--in-options .ge-text-section .ge-text-field > select, .ge-controls--in-options .ge-text-section .ge-text-field > input { width: 100%; } .ge-controls--in-options .ge-text-section textarea { flex: 0 0 auto; width: 200px; min-height: 0; height: 40px; resize: none; } .ge-options-bar:has(.ge-controls--in-options) .ge-options-body .ge-opt[data-opt="range"] { display: none; } /* Secondary brush rows (pen-pressure toggles + preset Save/delete) do not fit the thin strip; hide them behind the "..." overflow button. */ .ge-controls--in-options .ge-brush-pressure-row, .ge-controls--in-options .ge-brush-preset-row { display: none !important; } .ge-options-bar.ge-more-open { flex-wrap: wrap; } .ge-options-bar.ge-more-open .ge-controls--in-options .ge-brush-pressure-row, .ge-options-bar.ge-more-open .ge-controls--in-options .ge-brush-preset-row { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; gap: 10px; } /* The brush-preset picker stays visible inline whenever a painting tool is active, so switching brushes does not require discovering the "..." menu. */ .ge-controls--in-options .ge-has-brush-presets .ge-brush-preset-row { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; gap: 8px; } .ge-options-more { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; color: color-mix(in srgb, var(--fg) 65%, transparent); background: transparent; border: 1px solid var(--border); border-radius: 5px; cursor: pointer; } .ge-options-more:hover { color: var(--fg); border-color: color-mix(in srgb, var(--fg) 35%, transparent); } .ge-options-more[hidden] { display: none; } .ge-opt { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; } .ge-opt-label { display: flex; align-items: center; gap: 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: color-mix(in srgb, var(--fg) 55%, transparent); white-space: nowrap; } .ge-opt-value { color: color-mix(in srgb, var(--fg) 80%, transparent); font-variant-numeric: tabular-nums; } .ge-opt[data-opt="range"] input[type="range"] { width: 110px; } /* Checkbox options (e.g. Move tool Auto-Select) — restyle the native control so it matches the dark editor chrome instead of rendering as a light OS checkbox. */ .ge-opt.ge-auto-select-option { gap: 6px; cursor: pointer; } .ge-opt input[type="checkbox"] { appearance: none; -webkit-appearance: none; width: 14px; height: 14px; margin: 0; flex: 0 0 auto; box-sizing: border-box; border: 1px solid var(--border); border-radius: 3px; background: var(--bg); color: var(--fg); cursor: pointer; position: relative; } .ge-opt input[type="checkbox"]:checked { background: var(--accent, #4c8bf5); border-color: var(--accent, #4c8bf5); } .ge-opt input[type="checkbox"]:checked::after { content: ''; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); } .ge-opt input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent, #4c8bf5); outline-offset: 1px; } /* Dropdown options in the options bar (e.g. Auto-Select Target). */ .ge-opt[data-opt="select"] select { height: 24px; padding: 0 6px; border: 1px solid var(--border); border-radius: 5px; background: var(--bg); color: var(--fg); font: inherit; font-size: 11px; cursor: pointer; } .ge-opt[data-opt="select"] select:focus-visible { outline: 2px solid var(--accent, #4c8bf5); outline-offset: 1px; } .ge-opt-modes { display: inline-flex; gap: 2px; } .ge-opt-mode { min-width: 26px; height: 24px; padding: 0 6px; font-size: 11px; border: 1px solid var(--border); background: transparent; color: var(--fg); border-radius: 5px; cursor: pointer; } .ge-opt-mode.active { background: var(--accent, #4c8bf5); border-color: transparent; color: #fff; } .ge-opt-empty { font-size: 11px; opacity: 0.5; } .ge-options-empty .ge-options-sep { display: none; } @media (max-width: 820px) { .ge-options-bar { display: none; } } /* Document tab strip (Photoshop-style) — names the open document and offers a close affordance. Sits between the options bar and the body. */ .ge-doc-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; min-height: 30px; padding: 4px 8px 0; box-sizing: border-box; background: var(--panel); border-bottom: 1px solid var(--border); overflow: hidden; } .ge-doc-tabs::-webkit-scrollbar { display: none; } .ge-doc-tab { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; max-width: 240px; padding: 5px 6px 5px 12px; font-size: 11px; color: color-mix(in srgb, var(--fg) 70%, transparent); background: color-mix(in srgb, var(--panel) 82%, var(--fg) 6%); border: 1px solid var(--border); border-bottom: none; border-radius: 6px 6px 0 0; } .ge-doc-tab.is-active { background: var(--bg); color: var(--fg); border-bottom: 1px solid var(--bg); margin-bottom: -1px; } .ge-doc-tab-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 190px; } .ge-doc-tab-close { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; border: none; border-radius: 4px; background: transparent; color: inherit; font-size: 13px; line-height: 1; cursor: pointer; opacity: 0.6; } .ge-doc-tab-close:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 14%, transparent); } /* Title-bar actions: Import + Save and the window controls (_ □ ×). */ .ge-doc-tabs-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-left: auto; } /* Import/Save in the title bar render as ONE compact row (glyph + label), matching the File/Edit/… topbar button height — not the 2-line stack. */ .ge-doc-tabs-actions .ge-stacked-btn { flex-direction: row; height: 26px; gap: 4px; padding: 0 9px !important; } .ge-doc-tabs-actions .ge-stacked-btn .ge-stacked-glyph { display: inline-flex; } .ge-doc-tabs-actions .ge-stacked-btn .ge-stacked-label { top: 0; font-size: 10px; opacity: 0.8; } /* Save shows a SINGLE label (the live "Saved"/"Unsaved" status), not "SAVE Saved". */ .ge-doc-tabs-actions #ge-save-menu-btn { flex-wrap: nowrap !important; column-gap: 5px; } .ge-doc-tabs-actions #ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 auto; } .ge-doc-tabs-actions #ge-save-menu-btn .ge-save-action-label { display: none !important; } /* Import + Save now live in the topbar row (their old title bar is gone): one compact row each, matching the File/Edit/… button height. */ .ge-topbar-right .ge-stacked-btn { flex-direction: row; height: 26px; gap: 4px; padding: 0 9px !important; } .ge-topbar-right .ge-stacked-btn .ge-stacked-glyph { display: inline-flex; } .ge-topbar-right .ge-stacked-btn .ge-stacked-label { top: 0; font-size: 10px; opacity: 0.85; } .ge-topbar-right #ge-save-menu-btn { flex-wrap: nowrap !important; column-gap: 5px; } .ge-topbar-right #ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 auto; } .ge-topbar-right #ge-save-menu-btn .ge-save-action-label { display: none !important; } .ge-win-controls { display: flex; align-items: center; gap: 1px; margin-left: 4px; } .ge-win-btn.ge-btn, .ge-win-controls #ge-maximize-toggle { width: 24px; height: 22px; padding: 0; border: none; border-radius: 4px; background: transparent; box-shadow: none; } .ge-win-btn { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 22px; padding: 0; border: none; border-radius: 4px; background: transparent; color: var(--fg); font-size: 14px; line-height: 1; cursor: pointer; opacity: 0.75; } .ge-win-btn:hover, .ge-win-controls #ge-maximize-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 14%, transparent); } .ge-win-close:hover { background: #e5484d; color: #fff; } .ge-win-controls #ge-maximize-toggle svg { top: 0 !important; } /* The editor maximize now lives in the gallery modal header, beside the window's minimize/close buttons (it adopts .modal-minimize-btn so it matches them exactly). Keep its icon optically centred rather than the minimize glyph's 2px drop. */ #gallery-modal .modal-header .ge-header-max-btn svg { top: 0 !important; transform: none; } @media (max-width: 820px) { .ge-doc-tabs { min-height: 26px; padding: 3px 6px 0; } .ge-doc-tab-label { max-width: 140px; } } /* Editor body (toolbar + canvas + panel) */ .ge-editor-body { display: flex; flex: 1; min-height: 0; overflow: hidden; position: relative; } .ge-editor-footer { display: flex; align-items: center; justify-content: flex-start; gap: 10px; min-height: 34px; padding: 3px 10px; box-sizing: border-box; flex: 0 0 auto; border-top: 1px solid var(--border); background: var(--panel); } .ge-editor-footer .ge-canvas-size { min-width: 0; padding-left: 10px; font-size: 11px; white-space: nowrap; font-variant-numeric: tabular-nums; color: color-mix(in srgb, var(--fg) 65%, transparent); } .ge-status-tool { min-width: 0; padding-left: 10px; border-left: 1px solid var(--border); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: color-mix(in srgb, var(--fg) 55%, transparent); } .ge-status-model { min-width: 0; max-width: 280px; padding-left: 10px; border-left: 1px solid var(--border); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: color-mix(in srgb, var(--fg) 55%, transparent); } .ge-footer-history { display: flex; align-items: center; gap: 4px; margin-right: 8px; } .ge-footer-history > .ge-btn { min-width: 28px; min-height: 26px; } .ge-footer-zoom { display: flex; align-items: center; gap: 5px; margin-left: auto; white-space: nowrap; } .ge-footer-zoom > .ge-btn { min-width: 28px; min-height: 26px; } .ge-footer-zoom .ge-zoom-stack { flex-direction: row; gap: 5px; min-width: 56px; } .ge-footer-zoom .ge-zoom-label { top: 0; left: 0; font-size: 11px; letter-spacing: 0; opacity: 0.8; } .ge-footer-zoom .ge-zoom-glyph { left: 0; } .ge-footer-zoom .ge-stacked-btn { min-width: 48px; } .gallery-editor-container { flex: 1; min-height: 0; display: none; } .gallery-editor-container input[type="number"] { -moz-appearance: textfield; appearance: textfield; } .gallery-editor-container input[type="number"]::-webkit-inner-spin-button, .gallery-editor-container input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; appearance: none; margin: 0; } .ge-toolbar { display: flex; flex-direction: column; gap: 2px; /* Asymmetric horizontal padding shifts the whole column 4 px left — buttons, hover backgrounds, active highlight pills, and section separators all move together. */ padding: 6px 6px 6px 0; background: var(--panel); border-right: 1px solid var(--border); width: 50px; flex-shrink: 0; min-height: 0; overflow-x: hidden; overflow-y: auto; scrollbar-width: none; } .ge-toolbar::-webkit-scrollbar { display: none; } .ge-tool-sep { font-size: 8px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-muted); text-align: center; padding: 8px 0 4px; border-top: 1px solid var(--border); margin-top: 4px; opacity: 0.6; flex-shrink: 0; } /* Foreground / background color swatches pinned to the bottom of the tool rail (Photoshop-style), with a swap button. */ .ge-tool-swatches { position: sticky; bottom: 0; margin-top: auto; padding: 10px 0 8px; display: flex; flex-direction: column; align-items: center; gap: 2px; background: var(--panel); } .ge-swatch { display: block; width: 26px; height: 26px; border: 1px solid var(--border); border-radius: 999px; overflow: hidden; cursor: pointer; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); background: #fff; } .ge-swatch input[type="color"] { -webkit-appearance: none; appearance: none; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: pointer; } /* After attachColorPicker swaps the rail swatch to type=text + .cp-swatch-input, show just the colour (hide the hex text) and keep it round. */ .ge-swatch input.cp-swatch-input { width: 100%; height: 100%; padding: 0; border: 0; border-radius: 999px; color: transparent; caret-color: transparent; text-shadow: none; font-size: 0; outline: none; cursor: pointer; background: var(--panel); } .ge-swatch input.cp-swatch-input::selection { background: transparent; } .ge-swatch input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } .ge-swatch input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 3px; } .ge-swatch-fg { z-index: 2; margin-left: 8px; margin-top: -10px; } .ge-swatch-bg { margin-right: 8px; } .ge-swatch-swap { width: 18px; height: 15px; margin-top: 2px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: color-mix(in srgb, var(--fg) 55%, transparent); background: transparent; border: 0; cursor: pointer; } .ge-swatch-swap:hover { color: var(--fg); } /* The left-rail fg/bg swatches are ROUND (Photoshop-style). Overrides the generic `.ge-swatch-grid .ge-swatch` sizing rule further down the sheet. */ .ge-tool-swatches .ge-swatch { width: 26px; height: 26px; aspect-ratio: auto; border-radius: 999px; } /* All tool buttons share a fixed height so the column reads as a clean grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of wrapping to two lines and making one button taller than the others. */ .ge-tool-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; padding: 0 2px; height: 34px; flex-shrink: 0; border: none; background: none; color: var(--fg); opacity: 0.6; cursor: pointer; border-radius: 6px; transition: background 0.15s, opacity 0.15s; } .ge-tool-btn { position: relative; } /* Hover background is now a pseudo so we can shift it 2 px left independently from the button's own bounds (which contain the icon and label at their already-shifted positions). */ .ge-tool-btn:hover { opacity: 0.85; } .ge-tool-btn.active { opacity: 1; color: var(--red); background: none; /* Padding stays the same as inactive buttons so the icon stays horizontally aligned with its neighbors. The taller highlight is drawn by ::before so it can extend up/down without pushing the button or shifting the icon. */ } /* Tool button contents stay above the active highlight pseudo. Also shifted an additional 2 px left so the icon + label sit further inside the highlight pill (which still anchors to the button bounds). */ .ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } .ge-tool-icon { font-size: 15px; line-height: 1; } .ge-tool-icon svg { width: 15px; height: 15px; } .ge-tool-label { font-size: 9px; line-height: 1.2; } /* Corner triangle on tool slots that hide a family of tools (Photoshop mark). Clicking it opens the flyout; the face itself still selects. */ .ge-tool-flyout-tri { position: absolute !important; right: 3px !important; bottom: 3px !important; left: auto !important; width: 0; height: 0; border-left: 5px solid transparent; border-bottom: 5px solid currentColor; opacity: 0.5; z-index: 3; cursor: pointer; } .ge-tool-flyout-tri:hover { opacity: 0.9; } .ge-tool-flyout-label { font-size: 12px; white-space: nowrap; } /* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- left of any tool button marked `ai: true`. Same color as the icon itself (inherits foreground) so it reads as part of the tool, not a flag. */ .ge-tool-ai { position: absolute !important; top: 1px; z-index: 2; color: inherit; opacity: 0.7; pointer-events: none; font-size: 11px; line-height: 1; font-weight: 700; left: 3px !important; } .ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } .ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } /* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) so they read as AI-backed at a glance — same glyph as .ge-tool-ai in the toolbar, just sitting inline next to the label. */ .ge-btn-ai-mark { display: inline-block; font-size: 11px; line-height: 1; font-weight: 700; opacity: 0.75; margin-right: 1px; } /* Per-tool clear-selection badge — shows a tiny X in the top-right of the Lasso / Wand button when each holds a selection. Click clears the selection without switching tools (handler stops propagation). */ .ge-tool-clear { position: absolute !important; top: 3px; right: 3px; width: 14px; height: 14px; display: none; align-items: center; justify-content: center; border-radius: 50%; color: #fff; background: var(--red); cursor: pointer; opacity: 0.95; z-index: 2; box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; left: auto !important; } .ge-tool-clear:hover { opacity: 1; transform: scale(1.15); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); } /* Flyout slots — related tools collapse behind a single face button; the caret (or right-click / long-press) opens the family. The flyout is detached from the toolbar's scroll clip via position:fixed, with coords set in toolbar.js on open. */ .ge-tool-slot { position: relative; display: flex; flex-direction: column; align-items: center; flex-shrink: 0; } .ge-tool-slot > .ge-tool-btn { width: 100%; } .ge-tool-caret { position: absolute; right: 3px; bottom: 3px; width: 12px; height: 12px; padding: 0; display: none; align-items: center; justify-content: center; border: none; border-radius: 3px; background: color-mix(in srgb, var(--fg) 14%, transparent); color: var(--fg); opacity: 0.7; cursor: pointer; z-index: 2; } .ge-tool-slot:hover > .ge-tool-caret, .ge-tool-slot.open > .ge-tool-caret { display: inline-flex; } .ge-tool-caret:hover { opacity: 1; } .ge-tool-flyout { position: fixed; display: none; flex-direction: column; gap: 1px; padding: 4px; min-width: 132px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); z-index: 200; } .ge-tool-slot.open > .ge-tool-flyout { display: flex; } .ge-tool-flyout .ge-tool-btn { flex-direction: row; justify-content: flex-start; gap: 8px; height: 34px; width: 100%; padding: 0 8px; opacity: 0.85; border-radius: 5px; } .ge-tool-flyout .ge-tool-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } .ge-tool-flyout .ge-tool-btn.active { opacity: 1; } .ge-tool-flyout .ge-tool-label { font-size: 12px; white-space: nowrap; } .ge-tool-flyout .ge-tool-ai { position: static !important; left: auto !important; } .ge-tool-flyout .ge-tool-btn .ge-tool-clear { display: none !important; } .ge-tool-flyout-sep { height: 1px; margin: 3px 2px; background: var(--border); opacity: 0.7; } .ge-tool-flyout-heading { font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.55; padding: 2px 8px 1px; } .ge-tool-flyout-preset { opacity: 0.78; } .ge-tool-flyout-preset .ge-tool-flyout-label { font-size: 11px; } /* Aspect-ratio placeholder shown while a draft is loading. */ .ge-canvas-placeholder { background: color-mix(in srgb, var(--fg) 8%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); border-radius: 4px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1; } /* Canvas area (center) */ .ge-canvas-area { flex: 1; display: flex; align-items: center; justify-content: center; overflow: auto; background: var(--bg); position: relative; min-width: 0; /* Stop touch interactions inside the editor from dragging the whole page (or the gallery modal). Touches on the canvas are owned by the editor's drawing logic — no native pan/zoom. */ overscroll-behavior: contain; } .ge-ruler { position: absolute; z-index: 16; touch-action: none; user-select: none; } .ge-ruler-horizontal { top: 0; left: 18px; right: 0; width: calc(100% - 18px); height: 18px; cursor: row-resize; } .ge-ruler-vertical { top: 18px; bottom: 0; left: 0; width: 18px; height: calc(100% - 18px); cursor: col-resize; } .ge-ruler-corner { position: absolute; top: 0; left: 0; z-index: 17; width: 18px; height: 18px; background: var(--panel); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); box-sizing: border-box; } .ge-wand-loading { position: absolute; inset: 0; z-index: 25; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--bg) 30%, transparent); pointer-events: none; } .ge-ai-command { position: absolute; left: 50%; bottom: calc(14px + var(--ge-ai-lift, 0px)); z-index: 18; width: min(430px, calc(100% - 28px)); transform: translateX(-50%); padding: 7px; box-sizing: border-box; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); border-radius: 8px; background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); backdrop-filter: blur(8px); transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; } /* While the floating Transform box is open it owns the canvas area. The bottom-docked Quick Edit bar would otherwise sit over the bottom-centre transform handle and intercept the touch aimed at it. */ #gallery-editor-container:has(.ge-transform-popup) .ge-ai-command { display: none; } .ge-ai-command-toggle { width: 100%; height: 28px; display: none; align-items: center; justify-content: center; gap: 5px; border: 0; border-radius: 6px; background: transparent; color: var(--accent, var(--red)); font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; } .ge-ai-command-toggle-caret { opacity: 0.7; flex-shrink: 0; } .ge-ai-command-head { display: flex; align-items: center; min-height: 24px; padding: 0 2px 5px; color: var(--fg); } .ge-ai-command-title { margin-left: 5px; font-size: 11px; font-weight: 650; } .ge-ai-command-collapsed { bottom: -16px; width: auto; min-width: 102px; padding: 2px 9px; opacity: 0.32; border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); } .ge-canvas-area:hover .ge-ai-command-collapsed, .ge-ai-command-collapsed:hover, .ge-ai-command-collapsed:focus-within { bottom: 10px; opacity: 0.94; border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); } .ge-ai-command-model { flex: 1 1 auto; min-width: 0; height: 26px; box-sizing: border-box; padding: 0 6px; border: 1px solid var(--border); border-radius: 7px; background: color-mix(in srgb, var(--bg) 76%, #000 24%); color: var(--fg); font: inherit; font-size: 11px; } .ge-ai-command-model:focus { outline: none; border-color: var(--accent, var(--red)); } /* AI settings dialog (image-edit + agent model pickers, double-check toggle) */ .ge-ai-settings { position: absolute; inset: 0; z-index: 261; display: flex; align-items: center; justify-content: center; padding: 20px; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(2px); } .ge-ai-settings[hidden] { display: none; } .ge-ai-settings-card { width: min(420px, 100%); max-height: 100%; overflow: auto; display: flex; flex-direction: column; gap: 12px; padding: 18px; border: 1px solid var(--border); border-radius: 12px; background: var(--panel, #232323); color: var(--fg); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5); } .ge-ai-settings-head { display: flex; align-items: center; justify-content: space-between; } .ge-ai-settings-head h3 { margin: 0; font-size: 15px; } .ge-ai-settings-close { width: 26px; height: 26px; border: none; border-radius: 7px; background: transparent; color: var(--fg-muted); cursor: pointer; font-size: 16px; line-height: 1; } .ge-ai-settings-close:hover { background: rgba(255, 255, 255, 0.1); color: var(--fg); } .ge-ai-settings-body { display: flex; flex-direction: column; gap: 6px; } .ge-ai-settings-label { margin-top: 4px; color: var(--fg-muted); font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; } .ge-ai-settings-body .ge-ai-command-model { flex: 0 0 auto; width: 100%; height: 30px; } .ge-ai-settings-note { margin: 2px 0 0; color: var(--fg-muted); font-size: 11px; line-height: 1.4; } .ge-ai-settings-opt { display: flex; align-items: center; gap: 6px; margin-top: 6px; color: var(--fg-muted); font-size: 12px; cursor: pointer; user-select: none; } .ge-ai-settings-opt input { margin: 0; accent-color: var(--accent, var(--red)); } .ge-ai-settings-strength { display: flex; align-items: center; gap: 8px; } .ge-ai-settings-strength input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--accent, var(--red)); } .ge-ai-settings-strength-val { flex: 0 0 auto; min-width: 30px; text-align: right; font-size: 11px; color: var(--fg-muted); } .ge-ai-settings-foot { display: flex; justify-content: flex-end; } .ge-ai-settings-done { height: 30px; padding: 0 14px; border: none; border-radius: 8px; background: var(--accent, var(--red)); color: #fff; font: inherit; font-size: 12px; cursor: pointer; } .ge-ai-command-form { display: flex; align-items: center; gap: 6px; } .ge-ai-command-input { flex: 1 1 auto; min-width: 0; height: 32px; box-sizing: border-box; padding: 0 9px; border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--bg) 76%, #000 24%); color: var(--fg); font: inherit; font-size: 12px; } .ge-ai-command-input:focus { outline: none; border-color: var(--accent, var(--red)); } .ge-ai-command-clear { position: relative; top: -2px; width: 26px; height: 32px; flex: 0 0 26px; display: inline-flex; align-items: center; justify-content: center; margin-left: -38px; border: 0; border-radius: 6px; background: transparent; color: var(--fg-muted); cursor: pointer; padding: 0; z-index: 1; } .ge-ai-command-clear:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 10%, transparent); } .ge-ai-command-run { position: relative; top: -2px; background: var(--send-btn-bg, var(--red)); color: #fff; border: none; border-radius: 8px; min-width: 32px; width: 32px; height: 32px !important; padding: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; transition: background 0.25s, opacity 0.12s; overflow: hidden; } .ge-ai-command-run:hover { background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); } .ge-ai-command-busy .ge-ai-command-run { opacity: 0.58; cursor: wait; } .ge-ai-command-busy .ge-ai-command-run svg { display: none; } .ge-ai-command-close { width: 28px; height: 32px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 8px; background: transparent; color: var(--fg); opacity: 0.52; cursor: pointer; padding: 0; } .ge-ai-command-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 10%, transparent); } .ge-ai-command-suggestions { position: absolute; left: 7px; right: 7px; bottom: calc(100% - 2px); z-index: 2; display: flex; flex-direction: column; gap: 2px; max-height: 236px; overflow: auto; padding: 5px; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); border-radius: 8px; background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); } .ge-ai-command-suggestion { width: 100%; display: grid; grid-template-columns: 22px 38px minmax(82px, auto) minmax(0, 1fr); align-items: center; gap: 8px; min-height: 28px; padding: 4px 7px; border: 0; border-radius: 6px; background: transparent; color: var(--fg); text-align: left; cursor: pointer; font: inherit; } .ge-ai-command-suggestion:hover, .ge-ai-command-suggestion.active { background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); } .ge-ai-command-suggestion-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; color: var(--fg-muted); } .ge-ai-command-suggestion-icon svg { width: 15px; height: 15px; display: block; } .ge-ai-command-suggestion.active .ge-ai-command-suggestion-icon, .ge-ai-command-suggestion:hover .ge-ai-command-suggestion-icon { color: var(--fg); } .ge-ai-command-suggestion-kind { min-width: 0; overflow: hidden; color: var(--accent, var(--red)); font-size: 8px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; } .ge-ai-command-suggestion-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 650; } .ge-ai-command-suggestion-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg-muted); font-size: 10px; } .ge-ai-command-status { margin-top: 4px; font-size: 10px; line-height: 1.3; color: var(--fg-muted); } .ge-ai-command-status[data-kind="running"] { color: var(--accent, var(--red)); } .ge-ai-command-status[data-kind="done"] { color: var(--green, #4caf50); } .ge-ai-command-status[data-kind="error"] { color: var(--red, #e5484d); } .ge-ai-command-progress { position: relative; height: 14px; margin-top: 5px; border-radius: 7px; background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); overflow: hidden; } .ge-ai-command-progress[hidden] { display: none; } .ge-ai-command-progress-fill { position: absolute; inset: 0 auto 0 0; width: 0%; border-radius: 7px; background: linear-gradient(90deg, var(--accent, var(--red)), color-mix(in srgb, var(--accent, var(--red)) 55%, #fff)); transition: width 0.4s ease; } .ge-ai-command-progress-label { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 600; letter-spacing: 0.2px; color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); pointer-events: none; } /* Stacked Quick Edit jobs — transient jelly-style notifications: each prompt shows a row while queued/running, fades out when finished, and multiple rows stack under each other. No persistent history to dismiss. */ .ge-ai-command-stack { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 168px; overflow-y: auto; } .ge-ai-job { display: flex; align-items: center; gap: 6px; padding: 5px 6px; border-radius: 7px; border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); background: color-mix(in srgb, var(--fg) 4%, transparent); font-size: 10px; line-height: 1.25; animation: ge-ai-job-in 0.2s ease-out; } .ge-ai-job-leaving { opacity: 0; transform: translateY(-4px); transition: opacity 0.32s ease, transform 0.32s ease; } @keyframes ge-ai-job-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .ge-ai-job-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 13px; color: var(--fg-muted); } .ge-ai-job-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; } .ge-ai-job-prompt { color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ge-ai-job-detail { color: var(--fg-muted); font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ge-ai-job-detail[hidden] { display: none; } .ge-ai-job-cancel { flex: 0 0 auto; padding: 2px 6px; border-radius: 5px; border: 1px solid color-mix(in srgb, var(--red, #e5484d) 40%, transparent); background: transparent; color: var(--red, #e5484d); font-size: 9px; line-height: 1.4; cursor: pointer; } .ge-ai-job-cancel:hover { background: color-mix(in srgb, var(--red, #e5484d) 16%, transparent); } .ge-ai-job-cancel[hidden] { display: none; } .ge-ai-job-running { border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); } .ge-ai-job-running .ge-ai-job-detail { color: var(--accent, var(--red)); } .ge-ai-job-running .ge-ai-job-icon { color: var(--accent, var(--red)); } .ge-ai-job-done .ge-ai-job-icon { color: var(--green, #4caf50); } .ge-ai-job-error .ge-ai-job-icon { color: var(--red, #e5484d); } .ge-ai-job-error .ge-ai-job-detail { color: var(--red, #e5484d); } .ge-ai-job-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } /* The running row uses the app's whirlpool spinner (canvas), not a CSS ring. */ .ge-ai-job-icon .ai-spinner-whirlpool { display: inline-flex; } /* "fx" glyph for the new-adjustment-layer button next to the "+" new-layer button. */ .ge-add-fx-glyph { font-size: 10px; font-style: italic; font-weight: 700; letter-spacing: -0.4px; line-height: 1; } /* Loading overlay shown while a large image is decoding — centered whirlpool + "Loading" caption over a translucent dim. */ .ge-loading-overlay { position: absolute; inset: 0; z-index: 20; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--bg) 70%, transparent); pointer-events: none; } /* Full-editor cover (project load): above the toolbar/panel and near-opaque so the top toolbar/old content doesn't peek through while loading. */ .ge-loading-overlay-full { z-index: 200; background: color-mix(in srgb, var(--bg) 92%, transparent); pointer-events: auto; } .ge-loading-overlay .ge-loading-inner { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 10px; background: var(--panel); border: 1px solid var(--border); box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); } .ge-loading-overlay .ge-loading-text { font-size: 12px; opacity: 0.8; } /* Shortcuts cheatsheet (toggled by `?` or the keyboard icon in the top bar). */ #ge-shortcuts-overlay { position: absolute; inset: 0; z-index: 50; background: color-mix(in srgb, var(--bg) 70%, transparent); display: flex; align-items: center; justify-content: center; font-size: 12px; } #ge-shortcuts-overlay[hidden] { display: none; } .ge-shortcuts-card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 18px 12px; box-shadow: 0 8px 30px color-mix(in srgb, var(--fg) 25%, transparent); width: min(720px, 92vw); max-height: 86vh; overflow-y: auto; color: var(--fg); } .ge-shortcuts-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; font-weight: 600; font-size: 13px; } .ge-shortcuts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 24px; } @media (min-width: 720px) { .ge-shortcuts-grid { grid-template-columns: repeat(4, 1fr); } } .ge-shortcuts-col h5 { margin: 0 0 6px; font-size: 11px; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; } .ge-shortcuts-foot { margin-top: 12px; font-size: 11px; opacity: 0.55; text-align: center; } .ge-main-canvas { /* Cursor is managed entirely by JS so each tool can show its own icon (move arrow, circle overlay, etc.) without CSS fighting it. */ image-rendering: pixelated; box-shadow: 0 2px 12px rgba(0,0,0,0.3); background: repeating-conic-gradient(#808080 0% 25%, #a0a0a0 0% 50%) 50% / 16px 16px; } /* AI-working indicator: while a Quick Edit / inpaint request is in flight a fireflies glow drifts over only the region being edited (the selection bounds, or the whole canvas when nothing is selected) — the same drifting, pulsing look as the "yoyo" theme's ascii-fireflies background, scoped so it never covers the workspace around the image. */ .ge-canvas-area.ge-canvas-ai-busy::after { content: ""; position: absolute; left: var(--ge-ai-busy-x, 0px); top: var(--ge-ai-busy-y, 0px); width: var(--ge-ai-busy-w, 100%); height: var(--ge-ai-busy-h, 100%); border-radius: 3px; z-index: 11; pointer-events: none; /* slight dark scrim under the ASCII skeleton so the glyphs read clearly */ background: rgba(6, 14, 9, 0.38); } .ge-canvas-area .ge-ai-ascii-fireflies { position: absolute; left: var(--ge-ai-busy-x, 0px); top: var(--ge-ai-busy-y, 0px); width: var(--ge-ai-busy-w, 100%); height: var(--ge-ai-busy-h, 100%); margin: 0; padding: 0; overflow: hidden; border-radius: 3px; z-index: 12; pointer-events: none; white-space: pre; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 11px; line-height: 1; letter-spacing: 0; color: #b8e6c1; text-shadow: 0 0 6px rgba(120, 220, 150, 0.55); background: transparent !important; opacity: 0; transition: opacity 0.25s ease; } .ge-canvas-area.ge-canvas-ai-busy .ge-ai-ascii-fireflies { opacity: 1; } .ge-canvas-area .ge-ai-ascii-fireflies::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(100deg, transparent 35%, rgba(184, 230, 193, 0.20) 50%, transparent 65%); background-size: 220% 100%; background-repeat: no-repeat; animation: ge-ai-canvas-sheen 2.2s linear infinite; mix-blend-mode: screen; } @media (prefers-reduced-motion: reduce) { .ge-canvas-area .ge-ai-ascii-fireflies { display: none; } } @keyframes ge-ai-canvas-sheen { 0% { background-position: 130% 0; } 100% { background-position: -30% 0; } } /* Transform-overlay canvas — sits over the main canvas with extra margin so resize / rotation handles can render OUTSIDE the image bounds. Pointer events disabled so it doesn't intercept clicks. */ .ge-transform-overlay { position: absolute; image-rendering: pixelated; pointer-events: none; z-index: 5; } .ge-selection-overlay { position: absolute; image-rendering: pixelated; pointer-events: none; z-index: 4; } .ge-quick-mask-bar { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 18; display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 4px 5px 4px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); box-shadow: 0 4px 16px rgba(0,0,0,0.24); color: var(--fg); font-size: 11px; white-space: nowrap; } .ge-quick-mask-label { font-weight: 600; } .ge-quick-mask-tools { display: flex; } .ge-quick-mask-tool, .ge-quick-mask-done { min-height: 24px; padding: 2px 8px; border: 1px solid var(--border); background: var(--bg); color: inherit; font: inherit; cursor: pointer; } .ge-quick-mask-tool:first-child { border-radius: 4px 0 0 4px; } .ge-quick-mask-tool:last-child { border-radius: 0 4px 4px 0; border-left: 0; } .ge-quick-mask-tool.active { background: var(--accent); color: #fff; border-color: var(--accent); } .ge-quick-mask-done { border-radius: 4px; } .ge-marquee-constraint-row { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 5px 7px; font-size: 10px; } .ge-marquee-constraint-row > select { min-width: 0; height: 26px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; } .ge-marquee-dimensions { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 26px; gap: 5px; align-items: center; } .ge-marquee-dimensions label { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 4px; } .ge-marquee-dimensions input { width: 100%; min-width: 0; height: 26px; padding: 2px 5px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; } .ge-marquee-dimensions .ge-icon-btn { width: 26px; height: 26px; } /* Right panel */ .ge-right-panel { width: 340px; flex-shrink: 0; display: flex; flex-direction: column; border-left: 1px solid var(--border); background: var(--panel); /* The whole panel scrolls so every tool control is reachable; the layers-actions row uses position:sticky to stay pinned at the bottom of the viewport while scrolling. */ overflow-y: auto; overflow-x: hidden; } /* Controls */ .ge-controls { flex: 0 0 auto; padding: 10px 10px 6px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; } .ge-layer-geometry-section { display: flex; flex-direction: column; gap: 6px; } #ge-layer-geometry-name { margin-left: auto; min-width: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-muted); font-size: 9px; font-weight: 400; text-transform: none; } .ge-layer-geometry-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; } .ge-layer-geometry-grid label { display: grid; grid-template-columns: 12px minmax(0, 1fr); align-items: center; gap: 2px; min-width: 0; color: var(--color-muted); font-size: 9px; } .ge-layer-geometry-grid input { width: 100%; height: 26px; box-sizing: border-box; min-width: 0; padding: 3px 4px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; text-align: right; appearance: textfield; } .ge-layer-geometry-grid input:focus { outline: none; border-color: var(--red); } .ge-layer-geometry-grid input[readonly] { opacity: 0.58; cursor: default; } @media (max-width: 700px) { .ge-layer-geometry-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } } /* Brush controls (Color + Size) — give the two rows breathing room between them, since neither sits inside a separator-bordered section. */ #ge-brush-controls { display: flex; flex-direction: column; gap: 2px; } /* The dropdown shells set `display:flex` above, which otherwise beats the UA `[hidden]{display:none}` rule and leaves every menu open on load. */ .ge-file-menu[hidden], .ge-edit-menu[hidden], .ge-layer-menu[hidden], .ge-type-menu[hidden], .ge-window-menu[hidden], .ge-help-menu[hidden], .ge-resize-menu[hidden], .ge-edge-menu[hidden], .ge-view-menu[hidden], .ge-image-menu[hidden], .ge-selection-menu[hidden], .ge-filter-menu[hidden], .ge-3d-menu[hidden], .ge-plugins-menu[hidden], .ge-save-menu[hidden] { display: none; } .ge-brush-pressure-row { justify-content: space-between; gap: 6px; } .ge-pressure-option { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; opacity: 0.7; } .ge-pressure-option .toggle-switch { width: 24px; height: 14px; } .ge-pressure-option .toggle-slider::before { width: 10px; height: 10px; } .ge-pressure-option .toggle-switch input:checked + .toggle-slider::before { transform: translateX(10px); } .ge-brush-preset-row select { flex: 1 1 auto; min-width: 0; } .ge-brush-preset-row .ge-btn { flex: 0 0 auto; } .ge-text-section, .ge-shape-section { display: flex; flex-direction: column; gap: 8px; min-width: 0; } .ge-shape-gradient-extra-stops { display: flex; flex-direction: column; gap: 6px; } .ge-gradient-extra-stop { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: 6px; align-items: end; } .ge-gradient-stop-remove { min-width: 28px; height: 28px; padding: 0 6px; } .ge-text-section textarea, .ge-text-section select, .ge-text-section input[type="number"], .ge-shape-section input[type="number"] { box-sizing: border-box; min-width: 0; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; } .ge-text-section textarea { width: 100%; min-height: 58px; resize: vertical; padding: 6px 7px; line-height: 1.35; } .ge-text-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(56px, 0.42fr) auto; gap: 6px; align-items: end; } .ge-text-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; font-size: 9px; color: var(--color-muted); } .ge-text-field select, .ge-text-field input { width: 100%; height: 28px; padding: 3px 6px; } .ge-text-format-row { gap: 4px; } .ge-text-toggle { width: auto; min-width: 24px; flex: 1 1 24px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--border); border-radius: 4px; background: transparent; color: var(--fg); cursor: pointer; } .ge-text-toggle:hover { background: color-mix(in srgb, var(--fg) 7%, transparent); } .ge-text-toggle.active { color: var(--red); border-color: color-mix(in srgb, var(--red) 55%, var(--border)); background: color-mix(in srgb, var(--red) 12%, transparent); } .ge-text-format-sep { width: 1px; height: 20px; background: var(--border); margin: 0 2px; } .ge-text-opentype-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } .ge-text-inline-check { flex-direction: row; align-items: center; gap: 4px; } .ge-text-inline-check > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-text-inline-check input[type="checkbox"] { flex: 0 0 auto; width: auto; height: auto; min-width: 0; margin: 0; } .ge-shape-types { display: flex; gap: 4px; } .ge-shape-sides-field { max-width: 90px; } .ge-direct-text-editor { position: fixed; z-index: 10020; box-sizing: border-box; padding: 4px; overflow: hidden; resize: both; border: 1px dashed var(--red); outline: 1px solid color-mix(in srgb, var(--red) 24%, transparent); border-radius: 2px; background: color-mix(in srgb, var(--bg) 8%, transparent); caret-color: var(--red); transform-origin: top left; box-shadow: none; } /* Floating Transform popup — horizontal layout, draggable from anywhere on the popup (matches the FX / adjust popups). Defaults to the right side of the editor (over the layers panel). */ /* Match the .ge-adj-popup layout convention: icon + title + [_][×] header bar, then the body. Drag from the header (same as FX popups). */ .ge-transform-popup { position: absolute; z-index: 10020; display: flex; flex-direction: column; gap: 4px; padding: 0 0 8px; /* Never let the single-row popup grow wider than the editor: its min-content width otherwise pushes it left across the tool column. Capping + wrapping keeps the tool buttons clickable. */ max-width: calc(100% - 90px); min-width: 0; background: color-mix(in srgb, var(--panel) 98%, var(--bg)); border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); backdrop-filter: blur(6px); user-select: none; } .ge-transform-popup-head { /* Inherits .ge-adj-head styling — flex row, icon + title + buttons, bottom border, grab cursor — with the title nudged down 2px for visual balance and explicit horizontal padding so the icon doesn't hug the left edge. */ display: flex !important; align-items: center; gap: 6px; padding: 8px 12px; padding-top: 10px; touch-action: none; cursor: grab; } .ge-transform-popup-head:active { cursor: grabbing; } .ge-transform-popup-head .ge-head-btns { margin-left: auto !important; } .ge-transform-popup-head .ge-adj-title { position: relative; top: 2px; } /* Icon nudged 4px lower than the title baseline so it reads as the row's anchor rather than floating above it. */ .ge-transform-popup-head .ge-adj-icon { position: relative; top: 4px; } /* Mobile-only: shift the head content UP to match the rest of the editor's mobile popup styling. */ @media (max-width: 820px) { .ge-transform-popup-head .ge-adj-title { top: -2px; } .ge-transform-popup-head .ge-adj-icon { top: 0; } } /* Make sure the head-buttons cluster sits hard-right with a small gap, matching the FX-popup convention. */ .ge-transform-popup-head .ge-head-btns { margin-left: auto; } .ge-transform-min-hint { display: inline-flex; align-items: center; opacity: 0.45; margin-right: -2px; line-height: 1; color: var(--fg-muted); } /* Small "Merge" text label next to each merge / flatten button so the icons read at a glance instead of needing the title-tooltip. */ .ge-merge-label { font-size: 10px; margin-left: 4px; opacity: 0.75; } .ge-transform-popup-body { display: flex; align-items: center; flex-wrap: wrap; row-gap: 6px; gap: 6px; padding: 4px 12px; min-width: 0; } .ge-transform-popup-hint { padding: 0 12px; } .ge-transform-field { display: inline-flex; align-items: center; gap: 4px; position: relative; } .ge-transform-field label { font-size: 11px; font-weight: 600; opacity: 0.65; min-width: 12px; text-align: right; } /* Hide the native browser spin-buttons entirely — we render our own themed ▲/▼ via .ge-transform-spin (see below). */ .ge-transform-popup-input { width: 76px; height: 24px; padding: 0 18px 0 6px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; font-variant-numeric: tabular-nums; text-align: right; cursor: text; -moz-appearance: textfield; box-sizing: border-box; } /* Rotation field reserves extra room on the right for both the ° suffix AND the custom spinner so they don't sit on top of each other. The suffix renders inside that reserved zone. */ .ge-transform-popup-input-rot { width: 76px; padding-right: 30px; } .ge-transform-popup-input:focus { outline: none; border-color: var(--red); } /* Custom themed spinner — two slim chevron buttons stacked to the right of the input. Tight stack so the pair reads as one control instead of two floating arrows. */ .ge-transform-spin { position: absolute; right: 3px; top: 50%; /* Nudged up 4px relative to the input's vertical center. */ transform: translateY(calc(-50% - 4px)); display: flex; flex-direction: column; align-items: stretch; gap: 0; height: 18px; } .ge-transform-spin button { flex: 1 1 0; width: 12px; min-height: 0; padding: 0; background: transparent; color: var(--fg-muted); border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); font-size: 7px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background 0.12s, color 0.12s, border-color 0.12s; } .ge-transform-spin button:first-child { border-radius: 3px 3px 0 0; border-bottom: 0; } .ge-transform-spin button:last-child { border-radius: 0 0 3px 3px; } .ge-transform-spin button:hover { background: color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); color: var(--fg); border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); } .ge-transform-spin button:active { background: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); } /* Position the ° suffix to the LEFT of the spinner so they don't overlap. The rotation input's larger padding leaves room. */ .ge-transform-popup-suffix { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); font-size: 10px; opacity: 0.55; pointer-events: none; } .ge-transform-aspect-btn { background: none; border: 1px solid var(--border); border-radius: 4px; color: var(--fg-muted); cursor: pointer; /* 2px smaller than the W/H input height so it reads as a chip-style toggle rather than another form field. Pulled up 2px so it tucks between W and H rather than sitting on their baseline. */ height: 22px; width: 22px; padding: 0; position: relative; top: -3px; display: inline-flex; align-items: center; justify-content: center; opacity: 0.5; transition: opacity 0.12s, color 0.12s, border-color 0.12s; } .ge-transform-aspect-btn:hover { opacity: 1; } .ge-transform-aspect-btn.active { opacity: 1; color: var(--accent, var(--red)); border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); } /* Quick-action cluster (flip H, flip V, rotate 90°) — sits between the rotation input and the Apply button. Each button matches the input height and uses the editor's accent palette on hover. */ .ge-transform-quick { display: inline-flex; align-items: center; gap: 2px; padding: 0 4px; /* Pulled up 2px so the flip/rotate icons sit slightly higher than the W/H/rotation inputs, matching the visual weight of the head row. */ position: relative; top: -2px; border-left: 1px solid color-mix(in srgb, var(--border) 70%, transparent); } .ge-transform-quick-btn { width: 24px; height: 24px; padding: 0; background: none; color: var(--fg-muted); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; opacity: 0.7; transition: opacity 0.12s, color 0.12s, border-color 0.12s, background 0.12s; } .ge-transform-quick-btn:hover { opacity: 1; color: var(--fg); border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); } .ge-transform-quick-btn:active { background: color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } .ge-transform-popup-hint { font-size: 10px; opacity: 0.5; margin: 0; line-height: 1.3; } .ge-transform-preserve-field { display: inline-flex; align-items: center; gap: 5px; padding: 0 12px; font-size: 10px; color: var(--fg-muted); cursor: pointer; } .ge-transform-preserve-field input { accent-color: var(--accent, var(--red)); margin: 0; } /* Floating Inpaint prompt — pops next to the user's last brush stroke so they can type a description and Generate without diverting to the side panel. */ .ge-inpaint-popup { position: fixed; z-index: 280; display: inline-flex; align-items: center; gap: 6px; padding: 6px 8px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent); } .ge-inpaint-popup-input { width: 240px; padding: 6px 8px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 12px; position: relative; top: 0; } .ge-inpaint-popup-input:focus { outline: none; border-color: var(--red); } .ge-inpaint-popup-run { padding: 6px 12px; font-size: 12px; font-weight: 600; position: relative; top: 1px; } .ge-inpaint-popup-close { background: none; border: none; color: var(--fg-muted); font-size: 16px; line-height: 1; padding: 2px 4px; margin-left: 2px; cursor: pointer; position: relative; top: 0; opacity: 0.7; transition: opacity 0.15s, color 0.15s; } .ge-inpaint-popup-close:hover { opacity: 1; color: var(--fg); } /* Loading state: the popup's panel chrome (bg/border/shadow) gets out of the way so only the whirlpool floats over the canvas. */ .ge-inpaint-popup.ge-inpaint-popup-loading { background: transparent; border-color: transparent; box-shadow: none; padding: 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center; } .ge-controls input[type="range"] { width: 100%; box-sizing: border-box; margin: 0; display: block; } .ge-control-row { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--fg); } .ge-control-row label { flex-shrink: 0; opacity: 0.7; min-width: 36px; } .ge-color-picker { width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; padding: 0; cursor: pointer; background: none; overflow: hidden; } /* attachColorPicker swaps the native for a styled text input with the same `.ge-color-picker` class plus `.cp-swatch-input`. Force the 24×24 circular swatch back on it so it doesn't render as a wide text box stretched across the row. */ .ge-color-picker.cp-swatch-input { width: 24px; height: 24px; flex: 0 0 24px; border: 1px solid var(--border); border-radius: 50%; padding: 0; cursor: pointer; -webkit-appearance: none; appearance: none; /* Hide the (read-only) text caret + selection so the element reads as a swatch, not a text field. */ color: transparent; text-shadow: none; caret-color: transparent; font-size: 0; user-select: none; } .ge-color-picker.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } .ge-size-slider { flex: 1 1 auto; min-width: 0; height: 8px; accent-color: var(--red); -webkit-appearance: none; appearance: none; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; margin: 0; } .ge-size-label { font-size: 10px; opacity: 0.5; min-width: 28px; text-align: right; } /* Eraser controls: tighter rows + a small brush preview on the left of each label so the user can see what each slider affects. The three previews share the same dot but each leans on a different rendering trick (alpha / scatter / blur) to mirror its slider's effect. */ .ge-eraser-row { display: flex !important; align-items: center !important; gap: 8px; padding: 2px 0; /* Lets the trailing value span pin to the row's right edge so it stays visible when the slider expands left over the label. */ position: relative; } /* Inline value chip next to the label text. (Previously absolutely- positioned over the slider track to ride the expand animation, which caused overlap now that the dynamic-slider behavior is off for these.) */ .ge-eraser-row label > span[id$="-label"] { margin-left: 4px; padding: 0 4px; border-radius: 3px; cursor: text; font-size: 10px; opacity: 0.7; font-variant-numeric: tabular-nums; } /* When the value chip is pulled out of its label and placed AFTER the slider (see DOM transform in galleryEditor.js init), it sits on the row's right edge and naturally shortens the slider's flex space. */ .ge-eraser-row > span[id$="-label"].ge-slider-value { flex: 0 0 auto; margin: 0 0 0 6px; padding: 0 4px; font-size: 10px; opacity: 0.7; font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; cursor: text; border-radius: 3px; white-space: nowrap; } /* Per-tool model selector row (lives at the top of each AI tool's section in the side panel). Mirrors the inpaint Model row layout. */ .ge-tool-model-row { display: flex !important; align-items: center; gap: 6px; margin-bottom: 6px; min-width: 0; } .ge-tool-model-row > label { font-size: 11px; opacity: 0.6; flex: 0 0 auto; } .ge-tool-model { flex: 1 1 0; min-width: 0; font-size: 10px; padding: 2px 4px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; } /* Section label inside a dropdown — small uppercase header. */ .dropdown-section-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; font-weight: 600; padding: 6px 8px 2px; } .dropdown-section-divider { height: 1px; background: var(--border); margin: 4px 0; opacity: 0.6; } /* Inline numeric editor that replaces the value chip on click. */ .ge-slider-edit { position: absolute; z-index: 11; font: inherit; font-size: 10px; font-variant-numeric: tabular-nums; padding: 1px 4px; border: 1px solid var(--red); border-radius: 3px; background: var(--bg); color: var(--fg); outline: none; text-align: center; } /* Merge dropdown — position is set by JS each time it opens (relative to the button's bounding rect) because the parent .ge-layers has overflow:hidden which would otherwise clip the popup. */ .ge-merge-menu { position: fixed; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 3px; box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent); z-index: 50; display: flex; flex-direction: column; min-width: 140px; } .ge-merge-menu .dropdown-item-compact { background: none; border: none; width: 100%; text-align: left; padding: 5px 8px; font-size: 11px; gap: 8px; font: inherit; font-size: 11px; } /* Thin separator inside a tool section. */ .ge-section-divider { border: 0; border-top: 1px solid var(--border); margin: 10px -2px; opacity: 0.6; } /* Small explanatory paragraph under AI tool section labels. */ .ge-section-hint { font-size: 10.5px; line-height: 1.45; opacity: 0.55; margin: 0 0 8px; } /* Clone-tool hint swaps between "Alt-click" (desktop) and "Double-tap" (mobile) so the on-screen instruction matches the actual gesture. */ .ge-clone-hint-mobile { display: none; } @media (max-width: 820px) { .ge-clone-hint-desktop { display: none; } .ge-clone-hint-mobile { display: inline; } } /* Section title + adjacent "?" affordance — collapses the long explanatory paragraph into a hoverable tooltip so the side panel stays compact. */ .ge-section-title-with-help { display: inline-flex; align-items: center; gap: 2px; } /* Drop the source-whitespace text node between the title text and the ? span so the chip hugs the title with no extra gap. */ .ge-section-title-with-help .ge-section-help { margin-left: 0; } .ge-section-help { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border-radius: 50%; background: color-mix(in srgb, var(--fg) 12%, transparent); color: var(--fg-muted); font-size: 9px; font-weight: 700; cursor: help; user-select: none; transition: background 0.12s, color 0.12s; } .ge-section-help:hover, .ge-section-help:focus-visible { background: color-mix(in srgb, var(--fg) 24%, transparent); color: var(--fg); outline: none; } /* Lightweight section title — used inside the inpaint panel to group the mask actions (Eye / Invert / Clear) under a "Selection" header. */ .ge-section-title { margin: 10px 0 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; font-weight: 600; } /* Selection-row buttons stay compact so eye + invert + clear fit on one line beside each other. */ .ge-inpaint-mask-row { display: flex !important; gap: 4px; align-items: center; } .ge-inpaint-mask-row .ge-btn { flex: 0 0 auto; } .ge-inpaint-mask-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; font-weight: 600; margin-right: 4px; } /* Fixed label column so the three sliders start at the same x and have the same length when unused. Without this, "Flow" (4 chars) leaves more room than "Opacity" (7 chars). */ .ge-eraser-row label { font-size: 10px; opacity: 0.55; flex: 0 0 78px; white-space: nowrap; display: inline-flex; justify-content: space-between; gap: 4px; } /* Fixed-size slider track for all side-panel sliders — no dynamic width/height expansion on drag. Only the thumb grows on interaction (see ::-webkit-slider-thumb / ::-moz-range-thumb below). */ .ge-eraser-row input[type="range"] { flex: 1 1 auto; min-width: 0; height: 8px; accent-color: var(--red); -webkit-appearance: none; appearance: none; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; margin: 0; position: relative; z-index: 2; } .ge-eraser-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 9px; height: 9px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; transition: width 0.12s ease 0.5s, height 0.12s ease 0.5s; } .ge-eraser-row input[type="range"]:hover::-webkit-slider-thumb, .ge-eraser-row input[type="range"].is-using::-webkit-slider-thumb, .ge-eraser-row input[type="range"]:active::-webkit-slider-thumb { width: 16px; height: 16px; transition: width 0.12s ease 0s, height 0.12s ease 0s; } .ge-eraser-row input[type="range"]::-moz-range-thumb { width: 9px; height: 9px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; transition: width 0.12s ease 0.5s, height 0.12s ease 0.5s; } .ge-eraser-row input[type="range"]:hover::-moz-range-thumb, .ge-eraser-row input[type="range"].is-using::-moz-range-thumb, .ge-eraser-row input[type="range"]:active::-moz-range-thumb { width: 16px; height: 16px; transition: width 0.12s ease 0s, height 0.12s ease 0s; } /* Help "?" icon next to a section title. Subtle by default, hover brightens. Tooltip via native `title` attribute. */ .ge-section-help { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; margin-left: 4px; font-size: 9px; font-weight: 600; border: 1px solid var(--border); border-radius: 50%; color: var(--fg-muted); background: none; cursor: help; vertical-align: middle; position: relative; top: -1px; opacity: 0.7; transition: opacity 0.15s, color 0.15s, border-color 0.15s; } .ge-section-help:hover { opacity: 1; color: var(--fg); border-color: var(--fg-muted); } /* Layer-FX popup — floating window bound to one layer. */ .ge-fx-popup { position: fixed; z-index: 300; width: 320px; max-height: 80vh; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 12px 32px rgba(0,0,0,0.45); display: flex; flex-direction: column; font-size: 11px; color: var(--fg); } .ge-fx-popup-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; border-bottom: 1px solid var(--border); font-weight: 600; } .ge-fx-popup-title { font-size: 12px; } .ge-fx-popup-close { background: none; border: none; color: var(--fg-muted); font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px; } .ge-fx-popup-close:hover { color: var(--fg); } .ge-fx-popup-body { padding: 8px 10px; overflow-y: auto; } .ge-fx-popup-foot { display: flex; gap: 6px; padding: 6px 10px; border-top: 1px solid var(--border); } .ge-fx-group-title { font-size: 10px; font-weight: 600; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.5px; margin: 8px 0 4px; } .ge-fx-group-title:first-child { margin-top: 0; } .ge-fx-cb-tone { font-size: 10px; opacity: 0.55; font-style: italic; margin: 4px 0 2px; } .ge-fx-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; } .ge-fx-row-label { font-size: 10px; opacity: 0.7; flex: 0 0 110px; white-space: nowrap; } .ge-fx-row input[type="range"] { flex: 1 1 auto; min-width: 0; height: 6px; accent-color: var(--red); -webkit-appearance: none; appearance: none; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; } .ge-fx-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 11px; height: 11px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; } .ge-fx-row input[type="range"]::-moz-range-thumb { width: 11px; height: 11px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; } .ge-fx-row-value { font-size: 10px; opacity: 0.7; font-variant-numeric: tabular-nums; flex: 0 0 40px; text-align: right; } /* Layer-row FX button — tinted when the layer has any non-identity FX. */ .ge-layer-fx-btn { opacity: 0.55; } .ge-layer-fx-btn:hover { opacity: 0.9; } .ge-layer-fx-btn.active { opacity: 1; color: var(--accent, var(--red)); } /* Add-mask button on each layer row. Subtle by default; lights up red when a lasso/wand selection is live so the user instantly sees that clicking it will bake that selection into a mask on this layer. */ .ge-layer-mask-btn { opacity: 0.45; } .ge-layer-mask-btn:hover { opacity: 0.9; } .ge-layer-mask-btn.from-selection { opacity: 1; color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } /* Shared frosted-glass style for FX menu + adjustment popups. */ .ge-frosted { background: color-mix(in srgb, var(--panel) 70%, transparent); backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%); border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); box-shadow: 0 12px 40px rgba(0,0,0,0.55); } /* FX dropdown menu from the layer-row fx icon. */ .ge-fx-menu { position: fixed; z-index: 305; min-width: 200px; padding: 4px; border-radius: 8px; display: flex; flex-direction: column; gap: 2px; } .ge-fx-menu-item { background: none; border: none; text-align: left; padding: 7px 10px; font-size: 12px; color: var(--fg); border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 8px; } .ge-fx-menu-icon { display: inline-flex; align-items: center; color: var(--fg-muted); flex-shrink: 0; } .ge-fx-menu-item:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); } .ge-fx-menu-item:hover .ge-fx-menu-icon { color: var(--fg); } .ge-add-layer-menu { position: fixed; z-index: 10004; width: 218px; padding: 5px; border-radius: 7px; display: flex; flex-direction: column; gap: 1px; } .ge-add-layer-menu button { height: 30px; padding: 0 9px; border: 0; border-radius: 4px; background: transparent; color: var(--fg); display: flex; align-items: center; gap: 8px; text-align: left; font: inherit; font-size: 11px; cursor: pointer; } .ge-add-layer-menu button:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } .ge-add-layer-symbol { width: 16px; display: inline-flex; justify-content: center; color: var(--accent, var(--red)); font-size: 13px; } .ge-add-layer-divider { height: 1px; margin: 4px 5px; background: color-mix(in srgb, var(--fg) 10%, transparent); } /* Per-type adjustment popup. */ .ge-adj-popup { position: fixed; z-index: 305; width: 320px; border-radius: 10px; display: flex; flex-direction: column; font-size: 11px; color: var(--fg); } .ge-adj-popup .ge-adj-head { display: flex; align-items: center; gap: 6px; padding: 8px 12px; border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); cursor: grab; user-select: none; } .ge-adj-popup .ge-head-btns { margin-left: auto; } .ge-adj-icon { display: inline-flex; align-items: center; color: var(--fg-muted); flex-shrink: 0; } .ge-adj-title { font-weight: 600; font-size: 12px; flex: 1 1 auto; } .ge-adj-min, .ge-adj-close, .ge-history-close { background: none; border: none; color: var(--fg-muted); font-size: 16px; line-height: 1; cursor: pointer; padding: 0 4px; flex-shrink: 0; } .ge-history-close:hover { color: var(--fg); } /* Mobile-only: shift head icons/buttons up and enlarge the minimise glyph so it reads as a window-minimise affordance, not a centered minus. Desktop keeps the original tight head styling. */ @media (max-width: 820px) { .ge-adj-icon { position: relative; top: -4px; } .ge-adj-title { position: relative; top: -2px; } .ge-adj-min, .ge-adj-close, .ge-history-close { position: relative; top: -4px; } .ge-adj-min, .ge-adj-popup .ge-adj-min, .ge-history-head .ge-adj-min, .ge-transform-popup-head .ge-adj-min { font-size: 22px !important; font-weight: 600 !important; padding: 0 8px !important; line-height: 0.6 !important; position: relative; top: 4px; } .ge-adj-head .ge-transform-aspect-btn { position: relative; top: -4px; } } .ge-adj-min:hover, .ge-adj-close:hover { color: var(--fg); } /* Floating dock at bottom-right for minimised FX popups. */ #ge-fx-dock { position: fixed; left: 50%; transform: translateX(-50%); bottom: 12px; z-index: 304; display: flex; flex-direction: row; gap: 6px; align-items: center; pointer-events: none; max-width: calc(100vw - 24px); flex-wrap: wrap; justify-content: center; } .ge-fx-dock-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 6px; font-size: 11px; color: var(--fg); cursor: pointer; pointer-events: auto; max-width: 220px; white-space: nowrap; overflow: hidden; } .ge-fx-dock-chip:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .ge-fx-dock-icon { display: inline-flex; align-items: center; } .ge-fx-dock-close { margin-left: 2px; opacity: 0.55; padding: 0 2px; font-size: 14px; line-height: 1; } .ge-fx-dock-close:hover { opacity: 1; color: var(--red); } /* Sub-layer row icon in the layer panel. */ .ge-adj-sub-name { display: inline-flex !important; align-items: center; gap: 4px; } .ge-adj-sub-icon { display: inline-flex; align-items: center; color: var(--accent, var(--red)); flex-shrink: 0; } .ge-adj-sub-icon svg { width: 11px; height: 11px; } /* History panel — labeled timeline of edits. */ #ge-history-panel { position: fixed; z-index: 10004; width: 240px; max-height: 60vh; border-radius: 8px; display: flex; flex-direction: column; color: var(--fg); font-size: 11px; } .ge-history-head { display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); cursor: grab; user-select: none; } .ge-history-title { font-weight: 600; font-size: 12px; flex: 1 1 auto; } .ge-history-close { background: none; border: none; color: var(--fg-muted); font-size: 16px; line-height: 1; cursor: pointer; padding: 0 4px; flex-shrink: 0; } .ge-history-close:hover { color: var(--fg); } .ge-history-list { overflow-y: auto; padding: 4px 0; } .ge-history-row { display: flex; align-items: center; gap: 8px; width: 100%; padding: 5px 10px; background: none; border: none; text-align: left; cursor: pointer; color: var(--fg); font-size: 11px; } .ge-history-row:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .ge-history-row.current { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } .ge-history-row.current .ge-history-row-dot { background: var(--accent, var(--red)); } .ge-history-row.future { opacity: 0.45; } .ge-history-row-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-muted); flex-shrink: 0; } .ge-history-row-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-history-row-time { font-size: 9px; opacity: 0.55; flex-shrink: 0; } /* Named snapshots (Photoshop-style restore points) — camera button + rows. */ .ge-history-snapshots { border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); } .ge-history-snap-head { display: flex; align-items: center; gap: 6px; padding: 7px 10px 4px; } .ge-history-snap-title { flex: 1 1 auto; font-weight: 600; font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; opacity: 0.7; } .ge-history-snap-new { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; background: none; border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); border-radius: 5px; color: var(--fg-muted); cursor: pointer; flex-shrink: 0; } .ge-history-snap-new:hover { color: var(--fg); border-color: color-mix(in srgb, var(--fg) 35%, transparent); } .ge-history-snap-new svg { width: 13px; height: 13px; display: block; flex: 0 0 auto; } .ge-history-snap-rows { padding: 0 0 4px; } .ge-history-snap-empty { padding: 2px 10px 6px; font-size: 10px; opacity: 0.45; } .ge-history-snap-row { display: flex; align-items: center; gap: 6px; padding: 0 6px 0 0; } .ge-history-snap-restore { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; padding: 5px 4px 5px 10px; background: none; border: none; text-align: left; cursor: pointer; color: var(--fg); font-size: 11px; } .ge-history-snap-restore:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .ge-history-snap-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent, var(--red)); flex-shrink: 0; } .ge-history-snap-del { background: none; border: none; color: var(--fg-muted); font-size: 14px; line-height: 1; cursor: pointer; padding: 0 4px; flex-shrink: 0; } .ge-history-snap-del:hover { color: var(--accent, var(--red)); } /* Docked History panel: the panel body *is* the timeline (Photoshop-style), so it fills the dock and scrolls its own list. */ .ge-history-body { padding: 0; display: flex; flex-direction: column; min-height: 0; } .ge-history-body .ge-history-list { flex: 1 1 auto; min-height: 90px; } .ge-history-popout { margin: 6px 10px; align-self: flex-start; font-size: 10px; } /* (The docked History panel dropped its "Open in window" button — the timeline is already shown inline; the floating window stays on the topbar clock.) */ /* Tight button group in popup heads — min + close sit immediately next to each other with no inherited flex gap, pushed to the right edge. */ .ge-head-btns { display: inline-flex; align-items: center; gap: 0; margin-left: auto; flex-shrink: 0; } .ge-head-btns > button { width: 26px; height: 26px; padding: 0 !important; margin: 0; font-size: 18px; display: inline-flex; align-items: center; justify-content: center; border-radius: 6px; } .ge-head-btns > button:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); } /* Detail-header heart toggle — sits next to the icon Edit button. */ .gallery-detail-fav-header { opacity: 0.7; transition: opacity 0.15s, color 0.15s; } .gallery-detail-fav-header:hover { opacity: 1; } .gallery-detail-fav-header.active { color: var(--red); opacity: 1; } /* Canvas loading overlay — covers the canvas area while a blocking op (rotation, big resize, etc.) runs. */ .ge-canvas-loading { position: absolute; inset: 0; z-index: 50; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; border-radius: 8px; pointer-events: auto; } .ge-canvas-loading-spinner { width: 32px; height: 32px; border: 3px solid color-mix(in srgb, var(--fg) 25%, transparent); border-top-color: var(--accent, var(--red)); border-radius: 50%; animation: ge-canvas-spin 0.8s linear infinite; } .ge-canvas-loading-msg { font-size: 12px; opacity: 0.85; } /* Missing-dependency notice in tool sections (Bg Remove etc.). */ .ge-dep-notice { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); border-radius: 6px; margin: 4px 0 8px; font-size: 11px; } .ge-dep-notice-text { flex: 1 1 auto; line-height: 1.4; } .ge-dep-notice-text strong { display: block; margin-bottom: 2px; } .ge-dep-notice-text code { font-family: 'Fira Code', monospace; font-size: 10px; padding: 1px 4px; border-radius: 3px; background: color-mix(in srgb, var(--fg) 10%, transparent); } .ge-dep-notice .ge-btn { flex-shrink: 0; } /* Brief highlight on a Cookbook deps row arrived-at via deep-link. */ .cookbook-pkg-flash { animation: cookbook-pkg-flash 1.4s ease-out; } @keyframes cookbook-pkg-flash { 0% { background: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); } 100% { background: transparent; } } /* Flash highlight when an anchor-link ([Name](#task-/#skill-/#research-)) opens a panel and focuses a specific item. */ @keyframes anchor-item-flash { 0% { background: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); } 60% { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } 100% { background: transparent; } } .task-card-flash, .skill-row-flash, .research-card-flash { animation: anchor-item-flash 2s ease-out; border-radius: 6px; } .memory-item-focus { animation: anchor-item-flash 2.2s ease-out; outline: 1px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); outline-offset: 2px; border-radius: 6px; } /* Preview dot icons for the Blur sub-menu — visual cue for each blur type (Gaussian = soft round, Zoom = radial streaks). */ .ge-blur-icon { width: 14px; height: 14px; border-radius: 50%; display: inline-block; flex-shrink: 0; background: var(--fg); } .ge-blur-icon.ge-blur-gaussian { background: radial-gradient(circle, var(--fg) 0%, var(--fg) 25%, transparent 75%); filter: blur(1.5px); } .ge-blur-icon.ge-blur-zoom { background: radial-gradient(circle, var(--fg) 0%, var(--fg) 20%, transparent 60%); box-shadow: 0 -5px 0 -3px var(--fg), 0 5px 0 -3px var(--fg), -5px 0 0 -3px var(--fg), 5px 0 0 -3px var(--fg), -4px -4px 0 -3px var(--fg), 4px -4px 0 -3px var(--fg), -4px 4px 0 -3px var(--fg), 4px 4px 0 -3px var(--fg); } /* Inline-swatch color picker — small square that fits next to a title. */ .ge-inline-swatch { width: 16px !important; height: 16px !important; padding: 0 !important; border: 1px solid var(--border) !important; border-radius: 3px !important; cursor: pointer; flex-shrink: 0; -webkit-appearance: none; appearance: none; background: none; } /* Drop overlay over the gallery editor while a file drag is in flight. */ .ge-drop-overlay { position: absolute; inset: 0; z-index: 200; pointer-events: none; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border: 3px dashed color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); border-radius: 8px; } .ge-drop-overlay-msg { padding: 12px 20px; font-size: 14px; font-weight: 600; color: var(--fg); background: color-mix(in srgb, var(--panel) 70%, transparent); backdrop-filter: blur(10px); border-radius: 8px; } /* Small buttons that show an SVG icon + text label side-by-side. */ .ge-btn-iconlabel { display: inline-flex !important; align-items: center; gap: 4px; white-space: nowrap; } .ge-btn-iconlabel svg { flex-shrink: 0; opacity: 0.85; } .ge-btn-iconlabel:hover svg { opacity: 1; } .ge-adj-body { padding: 8px 12px; max-height: 60vh; overflow-y: auto; overflow-x: hidden; } .ge-adj-preset-field { display: grid; grid-template-columns: 46px 1fr; align-items: center; gap: 8px; margin: 8px 12px 0; color: color-mix(in srgb, var(--fg) 65%, transparent); font-size: 10px; } .ge-adj-preset-select { min-width: 0; width: 100%; padding: 5px 7px; border: 1px solid var(--border); border-radius: 6px; background: color-mix(in srgb, var(--panel) 90%, transparent); color: var(--fg); font: inherit; } .ge-adj-preset-select:focus { outline: none; border-color: var(--accent, var(--red)); } .ge-adj-popup { box-sizing: border-box; overflow: hidden; } .ge-adj-foot { display: flex; gap: 6px; justify-content: flex-end; padding: 8px 12px; border-top: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); } .ge-adj-foot-spacer { flex: 1 1 auto; } .ge-adj-compare-btn.is-active { color: var(--accent, var(--red)); border-color: color-mix(in srgb, var(--accent, var(--red)) 65%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); } .ge-adj-channel-field { display: grid; grid-template-columns: 64px 1fr; align-items: center; gap: 8px; margin: 0 0 8px; color: color-mix(in srgb, var(--fg) 65%, transparent); font-size: 10px; } .ge-adj-channel-select, .ge-adj-selective-range { width: 100%; min-width: 0; height: 27px; padding: 0 25px 0 7px; border: 1px solid var(--border); border-radius: 4px; background: var(--panel); color: var(--fg); font: inherit; } .ge-gradient-map-preview { height: 24px; margin-bottom: 9px; border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); border-radius: 4px; background: linear-gradient(90deg, var(--ge-gradient-start), var(--ge-gradient-end)); } .ge-gradient-color-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 8px; } .ge-gradient-color-row label { display: grid; grid-template-columns: 1fr 30px; align-items: center; gap: 6px; font-size: 10px; opacity: .75; } .ge-gradient-color-row input[type="color"] { width: 30px; height: 24px; padding: 1px; border: 1px solid var(--border); border-radius: 4px; background: transparent; } .ge-adj-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 28px; font-size: 10px; color: color-mix(in srgb, var(--fg) 70%, transparent); } .ge-adj-toggle-row input { accent-color: var(--red); } .ge-curves-wrap { width: 100%; aspect-ratio: 14 / 9; border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); background: #0b0d10; overflow: hidden; } .ge-curves-canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; } .ge-adj-help { margin: 7px 0 0; font-size: 9px; line-height: 1.35; opacity: .5; } .ge-adjustment-layer-item .ge-layer-adjustment-marker { color: var(--accent, var(--red)); } .ge-adjustment-layer-item .ge-layer-adjustment-marker svg { width: 12px; height: 12px; } /* Mobile-only: oversize the foot Cancel/Apply buttons to match the Transform popup. Desktop keeps the original ge-btn-sm sizing. */ @media (max-width: 820px) { .ge-adj-foot .ge-adj-cancel-btn, .ge-adj-foot .ge-adj-apply-btn { padding: 8px 16px !important; font-size: 13px !important; min-width: 80px; text-align: center; } } .ge-adj-row { display: flex; align-items: center; gap: 3px; padding: 0; margin: 0; min-width: 0; line-height: 1; } .ge-adj-row > label { flex: 0 0 96px; font-size: 10px; opacity: 0.7; } .ge-adj-row input[type="range"] { flex: 1 1 auto; min-width: 0; height: 6px; accent-color: var(--red); -webkit-appearance: none; appearance: none; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; } /* Mobile-only: fixed-size sliders + taller rows so the thumb growing on active doesn't push the row height around. */ @media (max-width: 820px) { .ge-adj-row { min-height: 28px; } /* Let the slider consume the space left after the label and value chip. A fixed 160px track overflows the 320px phone sheet once the popup's horizontal padding and controls are included. */ .ge-adj-row > label { flex: 0 0 clamp(72px, 26vw, 96px); min-width: 0; } .ge-adj-row input[type="range"] { flex: 1 1 auto; width: auto; min-width: 56px; max-width: none; } } @media (max-width: 360px) { /* Give Gradient Map's color labels and swatches a usable tap width on narrow phones; the popup remains scrollable if the extra row is needed. */ .ge-gradient-color-row { grid-template-columns: 1fr; gap: 6px; } } .ge-adj-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--red); cursor: pointer; border: none; transition: width 0.12s ease, height 0.12s ease; } .ge-adj-row input[type="range"]:hover::-webkit-slider-thumb, .ge-adj-row input[type="range"]:active::-webkit-slider-thumb { width: 18px; height: 18px; } .ge-adj-row input[type="range"]::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; transition: width 0.12s ease, height 0.12s ease; } .ge-adj-row input[type="range"]:hover::-moz-range-thumb, .ge-adj-row input[type="range"]:active::-moz-range-thumb { width: 18px; height: 18px; } /* Value chip — fixed-width column, text aligned LEFT so the chip's left edge always sits immediately after the slider regardless of value length. Without a fixed width Hue's "180 °" made its slider shorter than Saturation's "100" since slider was flex-1. */ .ge-adj-value { flex: 0 0 34px; margin-left: 0; padding: 0 2px; font-size: 10px; opacity: 0.7; text-align: left; font-variant-numeric: tabular-nums; white-space: nowrap; cursor: text; } .ge-adj-value:hover { opacity: 1; } .ge-adj-cb-tone { font-size: 10px; opacity: 0.55; font-style: italic; margin: 2px 0 0; line-height: 1; } .ge-adj-cb-row { padding: 0; gap: 3px; } .ge-adj-cb-tone-select { width: 100%; padding: 6px 8px; font-size: 12px; border-radius: 6px; border: 1px solid var(--border); background: color-mix(in srgb, var(--panel) 90%, transparent); color: var(--fg); } .ge-adj-cb-tone-select:focus { outline: none; border-color: var(--red); } .ge-adj-hist-details[open] > summary::before { content: '▾ '; } .ge-adj-hist-details .ge-adj-hist-wrap { margin-top: 4px; } .ge-adj-histogram { display: block; width: 100%; height: 80px; border-radius: 4px; background: rgba(0,0,0,0.25); } .ge-adj-hist-wrap { position: relative; margin-bottom: 14px; } .ge-adj-clip-status { margin-top: 3px; color: var(--fg-muted); font-size: 10px; line-height: 1.25; font-variant-numeric: tabular-nums; } .ge-adj-hist-handles { position: absolute; left: 0; right: 0; bottom: -10px; height: 12px; pointer-events: none; } .ge-adj-hist-handle { position: absolute; width: 12px; height: 12px; cursor: ew-resize; pointer-events: auto; border-style: solid; border-width: 0 6px 10px 6px; border-color: transparent transparent var(--fg-muted) transparent; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.4)); } .ge-adj-hist-handle:hover { border-bottom-color: var(--fg); } .ge-adj-hist-handle.hist-h-black { border-bottom-color: #111; } .ge-adj-hist-handle.hist-h-white { border-bottom-color: #fff; } .ge-adj-hist-handle.hist-h-gamma { border-bottom-color: var(--accent, var(--red)); } /* Per-slider revert button. */ .ge-adj-revert { background: none; border: none; color: var(--fg-muted); cursor: pointer; padding: 2px 4px; margin-left: 2px; opacity: 0.55; display: inline-flex; align-items: center; justify-content: center; transition: opacity 0.15s, color 0.15s; } .ge-adj-revert:hover { opacity: 1; color: var(--fg); } /* Develop panel polish: full-contrast value readouts, grouped heading labels, and revert arrows that stay hidden until hover or a non-default value. */ .ge-adj-develop .ge-adj-value { flex: 0 0 52px; color: var(--fg); font-size: 12px; opacity: 1; } .ge-adj-group-label { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; opacity: .55; margin: 10px 0 4px; } .ge-adj-develop .ge-adj-group-label:first-child { margin-top: 2px; } .ge-adj-develop .ge-adj-revert { min-width: 24px; min-height: 24px; margin-left: 6px; padding: 0; opacity: 0; pointer-events: none; } .ge-adj-develop .ge-adj-row:hover .ge-adj-revert, .ge-adj-develop .ge-adj-row.is-modified .ge-adj-revert { opacity: 1; pointer-events: auto; } /* Adjustment sub-layer row in the layer panel — indented under parent. */ .ge-adj-sub-item { padding-left: 18px; opacity: 0.85; border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); margin-left: 6px; font-size: 10.5px; } .ge-adj-sub-item .ge-layer-name { opacity: 0.85; font-style: italic; } /* Mask sub-layer rows. Same indent as adj sub-layers but the highlight bar uses fg-muted instead of accent — these aren't FX effects, just selection-state. When activated (the click target for paint / inpaint / generate), border + name go full strength. */ .ge-mask-sub-item { border-left-color: color-mix(in srgb, var(--fg) 30%, transparent); } .ge-mask-sub-item.active { border-left-color: var(--red); opacity: 1; } .ge-clone-source-status { display: flex; align-items: center; gap: 6px; min-height: 24px; margin: 2px 0 5px; padding: 0 6px; color: var(--fg); opacity: 0.68; font-size: 10px; } .ge-clone-source-status span { flex: 1; min-width: 0; } .ge-clone-source-status .ge-icon-btn { margin-left: auto; } .ge-mask-inline-thumb { width: 34px; height: 26px; flex: 0 0 34px; opacity: 0.78; } .ge-eraser-preview { width: 14px; height: 14px; border-radius: 50%; background: var(--fg); flex-shrink: 0; display: inline-block; position: relative; } .ge-actions { display: flex; gap: 4px; flex-wrap: wrap; } .ge-btn { padding: 4px 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font-size: 11px; cursor: pointer; transition: background 0.15s; } .ge-btn:hover { background: color-mix(in srgb, var(--fg) 10%, var(--bg)); } .ge-btn.active { background: color-mix(in srgb, var(--accent, var(--red)) 16%, var(--bg)); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .ge-btn-sm { padding: 3px 6px; font-size: 10px; } /* Busy state on AI tool buttons — whirlpool + verb text side by side. */ .ge-btn-processing { display: inline-flex; align-items: center; justify-content: center; gap: 6px; } .ge-btn-busy-label { font-size: inherit; line-height: 1; } /* Mask-vis eye button is icon-only — strip the inherited .ge-btn background / border so it reads as a plain affordance, matching the layer-row eye toggles. */ .ge-mask-vis-btn { background: none !important; border: none !important; padding: 2px 4px !important; display: inline-flex; align-items: center; justify-content: center; opacity: 0.5; transition: opacity 0.12s; } .ge-mask-vis-btn:hover { opacity: 0.85; background: none !important; } .ge-mask-vis-btn.visible { opacity: 0.9; } /* Paint/Erase segmented toggle in the Inpaint Brush section. */ .ge-inpaint-mode-btn { font-size: 11px; padding: 4px 8px; opacity: 0.6; border: 1px solid var(--border); } .ge-inpaint-mode-btn:hover { opacity: 0.85; } .ge-inpaint-mode-btn.active { opacity: 1; background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); font-weight: 600; } /* Wand New / + Add / − Subtract segmented toggle. */ .ge-wand-mode-btn { flex: 1 1 0; font-size: 11px; padding: 4px 6px; opacity: 0.6; border: 1px solid var(--border); } .ge-wand-mode-btn:hover { opacity: 0.85; } .ge-wand-mode-btn.active { opacity: 1; background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); font-weight: 600; } .ge-wand-live-btn { flex: 0 0 auto; opacity: 0.6; min-width: 46px; height: 22px; padding: 0 10px !important; border-radius: 999px !important; font-size: 10px; line-height: 1; background: color-mix(in srgb, var(--fg) 5%, transparent); transition: opacity 0.15s, color 0.15s, border-color 0.15s, background 0.15s; } .ge-wand-live-btn:hover { opacity: 0.85; } .ge-wand-live-btn.active { opacity: 1; background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); font-weight: 600; } /* Top-bar buttons (undo/redo, zoom +/-, Fit, 1:1, Resize, Edge, Import, Download, Save) — visually their glyphs sit too low compared to the surrounding labels, so shift contents up 2 px via asymmetric padding. */ /* Visually the buttons sit lower than the surrounding text labels on the topbar. Keep the box padding symmetric but lift the whole box 2 px so it centers against the adjacent label baselines. */ .ge-topbar .ge-btn, .ge-topbar .ge-btn-sm, .ge-topbar select, .ge-topbar input { position: relative; top: -2px; } /* The tiny "Gen" / "Inpaint" inline labels now look too high relative to the shifted-up controls — nudge them down 2 px to re-align. */ .ge-topbar span[style*="font-size:9px"] { position: relative; top: 2px; } /* The Gen and Inpaint model selects — must visually sit on the same line as the small "Gen" / "Inpaint" labels (which are at top:2px). Use !important so this beats the generic `.ge-topbar select` rule above no matter what element-vs-class specificity quirks. */ .ge-topbar .ge-ai-model { position: relative !important; top: 1px !important; } /* The ◢ glyph baseline reads lower than the surrounding text — shift it up 1 px so it visually centers with the "Edge" label. */ .ge-edge-glyph { position: relative; top: -1px; } /* Flex-line-break helper used in the mobile transform popup. Hidden by default so desktop keeps its single-row layout. */ .ge-row-break { display: none; } /* Stacked button — glyph on top, small uppercase label below. Currently used for Undo/Redo so users get an obvious "UNDO" / "REDO" affordance label without losing the compact icon. */ .ge-stacked-btn { display: inline-flex !important; flex-direction: column; align-items: center; justify-content: center; gap: 1px; padding: 2px 8px !important; line-height: 1; } .ge-stacked-btn .ge-stacked-glyph svg { display: block; } .ge-stacked-btn .ge-stacked-caret { margin-left: 2px; font-size: 7px; line-height: 1; opacity: 0.55; } .ge-stacked-btn .ge-stacked-label { font-size: 8px; letter-spacing: 0.06em; opacity: 0.65; font-weight: 600; position: relative; top: 2px; } /* Zoom % indicator — magnifier glyph on top, percentage label below. Matches the Undo/Redo stacked-button layout so the topbar reads as one consistent row of stacked icons. */ .ge-zoom-stack { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; line-height: 1; padding: 0 4px; } .ge-zoom-stack .ge-zoom-glyph { display: inline-flex; align-items: center; justify-content: center; opacity: 0.7; /* Glyph nudged 1 px right (was 2 px left). */ position: relative; left: -1px; } .ge-zoom-stack .ge-zoom-glyph svg { width: 16px; height: 16px; } .ge-zoom-stack .ge-zoom-label { font-size: 8px; letter-spacing: 0.06em; opacity: 0.65; font-weight: 600; position: relative; top: 4px; left: 1px; } /* + Add sits 1 px below the title baseline to match the surrounding header buttons after the recent header restructure. */ .ge-layers-header #ge-add-layer { position: relative; top: 1px; } .ge-layer-blend-row { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; align-items: center; gap: 6px; padding: 4px 7px; border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent); font-size: 10px; color: color-mix(in srgb, var(--fg) 62%, transparent); } .ge-layer-opacity-head { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; } .ge-layer-opacity-head input { width: 48px; height: 25px; padding: 0 4px; border-radius: 4px; font-size: 10px; text-align: right; } .ge-layer-blend-row select { width: 100%; min-width: 0; height: 25px; padding: 0 24px 0 7px; border-radius: 4px; font-size: 10px; } .ge-layer-selection-bar { min-height: 30px; display: flex; align-items: center; gap: 5px; padding: 3px 10px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg-secondary)); color: var(--fg); font-size: 10px; } .ge-layer-selection-bar > span { flex: 1; opacity: 0.72; } .ge-layer-selection-bar .ge-icon-btn { width: 24px; height: 24px; min-width: 24px; border: 0; background: transparent; color: inherit; opacity: 0.72; } .ge-layer-selection-bar .ge-icon-btn.danger:hover { color: var(--red); } /* Layer-row buttons in the right panel — the unicode glyphs (◉, ⤢) have irregular baseline metrics so the box reads as too low; shift up 2 px. */ .ge-layer-vis, .ge-layer-btn { position: relative; top: -2px; } /* Delete (×) button glyph baseline reads even lower than the visibility icon — push it up an extra 2 px so it visually centers. */ .ge-layer-btn.danger { top: -4px; } /* Save / Save as new / Download dropdown anchored to the primary Save button in the topbar. */ .ge-save-wrap { position: relative; display: inline-block; flex-shrink: 0; } .ge-save-menu { position: fixed; min-width: 160px; padding: 4px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); z-index: 10005; display: flex; flex-direction: column; gap: 2px; } .ge-save-state-icon { display: none; } .ge-save-label { white-space: nowrap; min-width: 0; } /* Save button: glyph on its own row, then the SAVE action label and the live draft-status chip share the second row. */ #ge-save-menu-btn { flex-direction: row !important; flex-wrap: wrap; column-gap: 4px; } #ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 100%; } #ge-save-menu-btn .ge-save-action-label { top: 0; } #ge-save-menu-btn .ge-save-label { top: 0; } .ge-save-state-saved { color: var(--fg); } .ge-save-menu .dropdown-item-compact { width: 100%; background: none; border: none; text-align: left; font: inherit; } /* Resize and Edge popups in the topbar — same anchored-dropdown pattern as the Save menu. Resize lists preset canvas sizes; Edge is a tiny form. Image + Filter menus reuse the same chrome. */ .ge-resize-wrap, .ge-edge-wrap, .ge-view-wrap, .ge-image-wrap, .ge-layer-wrap, .ge-type-wrap, .ge-selection-wrap, .ge-filter-wrap, .ge-3d-wrap, .ge-plugins-wrap, .ge-window-wrap, .ge-file-wrap, .ge-edit-wrap, .ge-help-wrap { position: relative; display: inline-block; } .ge-resize-menu, .ge-edge-menu, .ge-view-menu, .ge-image-menu, .ge-file-menu, .ge-edit-menu, .ge-layer-menu, .ge-type-menu, .ge-selection-menu, .ge-filter-menu, .ge-3d-menu, .ge-plugins-menu, .ge-window-menu, .ge-help-menu { position: absolute; top: calc(100% + 2px); right: 0; padding: 4px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); z-index: 12; display: flex; flex-direction: column; gap: 2px; } /* Mobile: escape the topbar's stacking context (z 5) so the dropdown lands above the bottom-sheet panels (z 50 / 60). Switch to fixed and pin under the topbar. Anchor to the LEFT of the viewport: the editor's `.gallery-modal-content` ancestor carries a (identity) transform, so a fixed dropdown's containing block is that ancestor rather than the viewport. A `right` anchor resolved against the narrow topbar wrap and pushed these ~224px menus off-screen (x ≈ -160 at 390px); a left anchor keeps them fully on-screen. */ @media (max-width: 820px) { .ge-resize-menu, .ge-edge-menu, .ge-view-menu, .ge-image-menu, .ge-file-menu, .ge-edit-menu, .ge-layer-menu, .ge-type-menu, .ge-selection-menu, .ge-filter-menu, .ge-3d-menu, .ge-plugins-menu, .ge-window-menu, .ge-help-menu { position: fixed; top: 44px; left: 8px; right: auto; z-index: 500; max-width: calc(100vw - 16px); } } .ge-resize-menu { min-width: 200px; } .ge-edge-menu { min-width: 220px; padding: 10px; gap: 6px; } .ge-view-menu { min-width: 174px; } .ge-image-menu { min-width: 180px; } .ge-file-menu { min-width: 208px; } .ge-edit-menu { min-width: 180px; } .ge-layer-menu { min-width: 200px; } .ge-type-menu { min-width: 180px; } .ge-selection-menu { min-width: 224px; } .ge-filter-menu { min-width: 180px; } .ge-3d-menu { min-width: 220px; } .ge-plugins-menu { min-width: 200px; } .ge-window-menu { min-width: 200px; } .ge-help-menu { min-width: 200px; } .ge-resize-menu .dropdown-item-compact, .ge-view-menu .dropdown-item-compact, .ge-image-menu .dropdown-item-compact, .ge-file-menu .dropdown-item-compact, .ge-edit-menu .dropdown-item-compact, .ge-layer-menu .dropdown-item-compact, .ge-type-menu .dropdown-item-compact, .ge-selection-menu .dropdown-item-compact, .ge-filter-menu .dropdown-item-compact, .ge-3d-menu .dropdown-item-compact, .ge-plugins-menu .dropdown-item-compact, .ge-window-menu .dropdown-item-compact, .ge-help-menu .dropdown-item-compact { width: 100%; background: none; border: none; text-align: left; font: inherit; } .ge-view-toggle .ge-view-check { opacity: 0; } .ge-view-toggle.active .ge-view-check { opacity: 1; color: var(--red); } .ge-window-toggle .ge-window-check { opacity: 0; } .ge-window-toggle.active .ge-window-check { opacity: 1; color: var(--red); } .ge-view-grid-size { display: grid; grid-template-columns: 1fr 52px auto; align-items: center; gap: 5px; padding: 4px 14px 4px 8px; font-size: 10px; color: var(--color-muted); } .ge-view-grid-size input { width: 52px; height: 24px; box-sizing: border-box; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; text-align: right; } /* Sub-menu label inside the topbar dropdowns — small uppercase header so "Blur" / "Transform" reads as a category, not an action. Shared by every editor dropdown (`.ge-menu-label`) and the legacy filter class. */ .ge-filter-submenu-label, .ge-menu-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.72; font-weight: 700; color: var(--color-muted); padding: 8px 8px 3px; border-top: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); pointer-events: none; } /* The first header in a menu sits flush to the menu's own padding so the dropdown does not show an empty band at the top (Photoshop menus start with an item or a tight separator, never dead space). */ .ge-filter-submenu-label:first-child, .ge-menu-label:first-child, .dropdown-section-label:first-child { padding-top: 2px; } /* Disabled dropdown items (e.g. Fill when there's no selection) read as muted + non-interactive. */ .ge-image-menu .dropdown-item-compact:disabled, .ge-layer-menu .dropdown-item-compact:disabled, .ge-type-menu .dropdown-item-compact:disabled, .ge-selection-menu .dropdown-item-compact:disabled, .ge-filter-menu .dropdown-item-compact:disabled, .ge-3d-menu .dropdown-item-compact:disabled, .ge-plugins-menu .dropdown-item-compact:disabled, .ge-file-menu .dropdown-item-compact:disabled, .ge-edit-menu .dropdown-item-compact:disabled, .ge-view-menu .dropdown-item-compact:disabled, .ge-window-menu .dropdown-item-compact:disabled, .ge-help-menu .dropdown-item-compact:disabled { opacity: 0.4; cursor: not-allowed; } /* Photoshop menu feel — dark chrome, hover highlight, right-aligned shortcut hints, submenu chevrons, keyboard-focus ring. */ .ge-file-menu .dropdown-item-compact, .ge-edit-menu .dropdown-item-compact, .ge-image-menu .dropdown-item-compact, .ge-layer-menu .dropdown-item-compact, .ge-type-menu .dropdown-item-compact, .ge-selection-menu .dropdown-item-compact, .ge-filter-menu .dropdown-item-compact, .ge-view-menu .dropdown-item-compact, .ge-window-menu .dropdown-item-compact, .ge-help-menu .dropdown-item-compact, .ge-3d-menu .dropdown-item-compact, .ge-plugins-menu .dropdown-item-compact { position: relative; justify-content: flex-start; } .ge-file-menu .dropdown-item-compact:hover:not(:disabled), .ge-edit-menu .dropdown-item-compact:hover:not(:disabled), .ge-image-menu .dropdown-item-compact:hover:not(:disabled), .ge-layer-menu .dropdown-item-compact:hover:not(:disabled), .ge-type-menu .dropdown-item-compact:hover:not(:disabled), .ge-selection-menu .dropdown-item-compact:hover:not(:disabled), .ge-filter-menu .dropdown-item-compact:hover:not(:disabled), .ge-view-menu .dropdown-item-compact:hover:not(:disabled), .ge-window-menu .dropdown-item-compact:hover:not(:disabled), .ge-help-menu .dropdown-item-compact:hover:not(:disabled), .ge-3d-menu .dropdown-item-compact:hover:not(:disabled), .ge-plugins-menu .dropdown-item-compact:hover:not(:disabled) { background: var(--accent, var(--red)); color: var(--panel); } .ge-file-menu .dropdown-item-compact:focus-visible, .ge-edit-menu .dropdown-item-compact:focus-visible, .ge-image-menu .dropdown-item-compact:focus-visible, .ge-layer-menu .dropdown-item-compact:focus-visible, .ge-type-menu .dropdown-item-compact:focus-visible, .ge-selection-menu .dropdown-item-compact:focus-visible, .ge-filter-menu .dropdown-item-compact:focus-visible, .ge-view-menu .dropdown-item-compact:focus-visible, .ge-window-menu .dropdown-item-compact:focus-visible, .ge-help-menu .dropdown-item-compact:focus-visible, .ge-3d-menu .dropdown-item-compact:focus-visible, .ge-plugins-menu .dropdown-item-compact:focus-visible { outline: none; background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent); color: var(--fg); } .ge-file-menu .dropdown-shortcut, .ge-edit-menu .dropdown-shortcut, .ge-image-menu .dropdown-shortcut, .ge-layer-menu .dropdown-shortcut, .ge-selection-menu .dropdown-shortcut, .ge-filter-menu .dropdown-shortcut, .ge-view-menu .dropdown-shortcut, .ge-window-menu .dropdown-shortcut, .ge-help-menu .dropdown-shortcut, .ge-save-menu .dropdown-shortcut { margin-left: auto; font-size: 9.5px; opacity: 0.5; font-variant-numeric: tabular-nums; white-space: nowrap; } /* Menu items that open a submenu render a chevron on the right edge. */ .ge-topbar .dropdown-item-compact.ge-has-submenu::after { content: '›'; margin-left: auto; font-size: 13px; line-height: 1; opacity: 0.6; } /* --- Photoshop polish pass ------------------------------------------------- 1. Flat text menu bar: title-case labels, no icon/box chrome, tight gaps. 2. Compact dropdown rows + real separators instead of faint headers. 3. Pin the right-hand action group so SAVE can never be clipped; the menu row scrolls internally when the editor gets narrow. */ .ge-topbar { justify-content: flex-start; } .ge-topbar-left { flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; } .ge-topbar-left::-webkit-scrollbar { display: none; } .ge-topbar-right { flex: 0 0 auto; margin-left: auto; } .ge-topbar-left .ge-stacked-btn { flex-direction: row !important; gap: 0 !important; padding: 5px 7px !important; background: transparent; border: 0; box-shadow: none; border-radius: 4px; color: var(--fg); } .ge-topbar-left .ge-stacked-btn .ge-stacked-glyph, .ge-topbar-left .ge-stacked-btn .ge-stacked-caret { display: none; } .ge-topbar-left .ge-stacked-btn .ge-stacked-label { position: static; top: 0; font-size: 12px; letter-spacing: 0; opacity: 1; font-weight: 400; text-transform: none; color: inherit; } .ge-topbar-left .ge-stacked-btn:hover { background: var(--accent, var(--red)); color: var(--panel); } /* Keep the menu bar Photoshop-short. The stacked buttons render as flat text labels here, so they only need a tight row height. */ .ge-topbar { padding: 2px 10px; } .ge-topbar-left .ge-stacked-btn { height: 26px; min-height: 0; padding: 0 8px !important; } /* When the menu bar is tight, drop the right group's text first (icon-only Import/Save) so all twelve menus stay visible instead of Window/Help scrolling out of view. Toggled from JS by measuring overflow. */ .ge-topbar.ge-topbar-compact #ge-import-topbar { padding: 5px 7px !important; } .ge-topbar.ge-topbar-compact #ge-import-topbar .ge-stacked-label, .ge-topbar.ge-topbar-compact #ge-save-menu-btn .ge-save-action-label { display: none !important; } .ge-topbar.ge-topbar-compact #ge-save-menu-btn { padding: 5px 8px !important; } /* Non-essential right-group chrome hid so the full Photoshop menu bar (through Help) always fits without scrolling out of view. */ .ge-topbar .ge-alpha-badge, .ge-topbar #ge-shortcuts-btn { display: none; } /* Compact dropdown rows (Photoshop menus are ~24px tall, 12-13px text). */ .ge-topbar .dropdown .dropdown-item-compact { min-height: 0; padding: 4px 10px; font-size: 12.5px; line-height: 1.35; gap: 8px; } .ge-topbar .dropdown .dropdown-icon { width: 13px; height: 13px; } .ge-topbar .dropdown .dropdown-shortcut { font-size: 11px; } /* Section headers become real separators (Photoshop has no section labels); the first one is dropped so a menu never starts with dead space. */ .ge-topbar .dropdown .ge-menu-label, .ge-topbar .dropdown .ge-filter-submenu-label, .ge-topbar .dropdown .dropdown-section-label { font-size: 0; line-height: 0; letter-spacing: 0; opacity: 1; height: 0; padding: 0; margin: 5px 10px; border-top: 1px solid var(--border); overflow: hidden; pointer-events: none; } .ge-topbar .dropdown > .ge-menu-label:first-child, .ge-topbar .dropdown > .ge-filter-submenu-label:first-child, .ge-topbar .dropdown > .dropdown-section-label:first-child { display: none; } .ge-selection-save-row { display: grid; grid-template-columns: minmax(0, 1fr) 28px; gap: 5px; padding: 3px 4px; } .ge-selection-save-row input { min-width: 0; height: 27px; padding: 3px 7px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font: inherit; } .ge-saved-selection-list { display: flex; flex-direction: column; gap: 1px; } .ge-saved-selection-row { display: grid; grid-template-columns: minmax(0, 1fr) 26px; align-items: center; } .ge-saved-selection-load, .ge-saved-selection-delete { min-width: 0; height: 26px; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; } .ge-saved-selection-load { padding: 3px 8px; overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; } .ge-saved-selection-delete { opacity: 0.45; font-size: 15px; } .ge-saved-selection-load:hover, .ge-saved-selection-delete:hover { background: var(--hover); opacity: 1; } .ge-saved-selection-empty { padding: 4px 8px 6px; font-size: 10px; opacity: 0.45; } /* Filter live-preview modal — translucent backdrop over the editor with a frosted parameter panel anchored top-right. The active layer re-renders live as the user drags the sliders. */ .ge-filter-overlay { position: absolute; inset: 0; z-index: 14; display: flex; align-items: flex-start; justify-content: flex-end; padding: 60px 20px 20px; background: rgba(0, 0, 0, 0.18); pointer-events: auto; } .ge-filter-modal { width: 240px; background: color-mix(in srgb, var(--panel) 95%, transparent); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); backdrop-filter: blur(10px); padding: 12px; display: flex; flex-direction: column; gap: 8px; } .ge-filter-modal-head { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.7; } .ge-filter-row { display: flex; flex-direction: column; gap: 2px; } .ge-filter-row label { font-size: 10px; opacity: 0.7; display: flex; justify-content: space-between; align-items: center; } .ge-filter-row-value { font-variant-numeric: tabular-nums; font-weight: 600; opacity: 0.85; } .ge-filter-row input[type="range"] { width: 100%; } .ge-filter-row input[type="color"] { width: 100%; height: 24px; padding: 1px; border: 1px solid var(--border, #333); border-radius: 4px; background: transparent; cursor: pointer; } .ge-filter-modal-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 4px; } /* Controlled image export. */ .ge-export-overlay { position: fixed; inset: 0; z-index: 10020; display: grid; place-items: center; padding: 16px; background: rgba(0, 0, 0, 0.56); } .ge-export-dialog { width: min(720px, calc(100vw - 32px)); max-height: calc(100vh - 32px); overflow: auto; background: var(--panel); color: var(--fg); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 18px 54px rgba(0, 0, 0, 0.48); } .ge-export-head, .ge-export-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; } .ge-export-head { border-bottom: 1px solid var(--border); } .ge-export-head h2 { margin: 0; font-size: 13px; letter-spacing: 0; } .ge-export-close { border: 0; background: transparent; color: var(--fg); width: 28px; height: 28px; padding: 0; font-size: 20px; line-height: 1; cursor: pointer; opacity: 0.6; } .ge-export-close:hover { opacity: 1; } .ge-export-body { display: grid; grid-template-columns: minmax(260px, 1.25fr) minmax(220px, 0.75fr); gap: 14px; padding: 14px; } .ge-export-preview-wrap { min-height: 300px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--border); border-radius: 6px; background-color: #22262d; background-image: linear-gradient(45deg, rgba(255,255,255,0.06) 25%, transparent 25%), linear-gradient(-45deg, rgba(255,255,255,0.06) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.06) 75%), linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.06) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0; } .ge-export-preview { display: block; max-width: calc(100% - 24px); max-height: 420px; box-shadow: 0 4px 18px rgba(0,0,0,0.38); } .ge-export-fields { display: flex; flex-direction: column; gap: 10px; } .ge-export-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: 10px; opacity: 0.78; } .ge-export-field > span { display: flex; justify-content: space-between; align-items: center; } .ge-export-field input[type="text"], .ge-export-field input[type="number"] { width: 100%; box-sizing: border-box; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; padding: 7px 8px; font: inherit; font-size: 11px; } .ge-export-field input:focus { outline: none; border-color: var(--red); } .ge-export-format { display: grid; grid-template-columns: repeat(3, 1fr); border: 1px solid var(--border); border-radius: 5px; overflow: hidden; } .ge-export-format button { min-height: 30px; border: 0; border-right: 1px solid var(--border); background: var(--bg); color: var(--fg); font: inherit; font-size: 10px; cursor: pointer; } .ge-export-format button:last-child { border-right: 0; } .ge-export-format button.active { background: color-mix(in srgb, var(--red) 18%, var(--bg)); color: var(--red); } .ge-export-dimensions { display: grid; grid-template-columns: minmax(0, 1fr) 28px minmax(0, 1fr); gap: 5px; align-items: end; } .ge-export-link { width: 28px; height: 29px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); opacity: 0.45; cursor: pointer; } .ge-export-link.active { opacity: 1; color: var(--red); border-color: var(--red); } .ge-export-quality input { width: 100%; accent-color: var(--red); } .ge-export-surface-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; border-top: 1px solid var(--border); padding-top: 10px; } .ge-export-toggle, .ge-export-matte { display: flex; align-items: center; gap: 6px; font-size: 10px; } .ge-export-matte.disabled { opacity: 0.35; pointer-events: none; } .ge-export-footer { border-top: 1px solid var(--border); font-size: 10px; } @media (max-width: 700px) { .ge-export-overlay { padding: 8px; place-items: end center; } .ge-export-dialog { width: 100%; max-height: calc(100dvh - 16px); } .ge-export-body { grid-template-columns: 1fr; padding: 10px; } .ge-export-preview-wrap { min-height: 190px; max-height: 260px; } .ge-export-preview { max-height: 230px; } } .ge-menu-divider { height: 1px; background: var(--border); margin: 4px 0; } .ge-edge-form { display: flex; flex-direction: column; gap: 8px; } .ge-edge-label { font-size: 11px; opacity: 0.65; } .ge-edge-input { padding: 6px 8px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 12px; } .ge-edge-input:focus { outline: none; border-color: var(--red); } .ge-edge-actions { display: flex; gap: 6px; } .ge-edge-actions .ge-btn { flex: 1; } .ge-edge-hint { margin: 0; font-size: 10px; opacity: 0.5; line-height: 1.4; } /* Styled "New canvas" prompt — replaces the native browser prompt(). */ .ge-canvas-prompt { max-width: 430px; width: 90vw; } .ge-canvas-prompt .modal-header h4 { margin: 0; font-size: 14px; } .ge-canvas-prompt-row { display: flex; align-items: flex-end; gap: 10px; justify-content: center; margin: 6px 0 2px; } .ge-canvas-prompt-field { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } .ge-canvas-prompt-field > span { font-size: 11px; opacity: 0.65; } .ge-canvas-prompt-field input { width: 100%; padding: 8px 10px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; font: inherit; font-size: 14px; } .ge-canvas-prompt-field input:focus { outline: none; border-color: var(--red); } .ge-canvas-prompt-x { font-size: 18px; opacity: 0.4; align-self: center; padding-bottom: 8px; position: relative; top: 8px; } .ge-canvas-prompt-hint { margin: 0 auto 0 0; font-size: 11px; opacity: 0.5; line-height: 1.35; text-align: left; } .ge-canvas-prompt-section-label { display: flex; align-items: center; gap: 5px; margin: 2px 0 7px; font-size: 10px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; opacity: .62; } .ge-canvas-units-field { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 8px 0 2px; font-size: 11px; opacity: .75; } .ge-canvas-control-label { display: inline-flex; align-items: center; gap: 5px; min-width: 0; } .ge-canvas-prompt-icon, .ge-canvas-preset-icon { flex: 0 0 auto; opacity: .68; } .ge-canvas-units-field select, .ge-canvas-interpolation-field select { min-width: 112px; padding: 5px 7px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; font: inherit; } .ge-canvas-anchor-label { margin: 14px 0 6px; font-size: 10px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; opacity: .62; text-align: center; } .ge-canvas-anchor-grid { display: grid; grid-template-columns: repeat(3, 22px); gap: 5px; justify-content: center; } .ge-canvas-anchor { width: 22px; height: 22px; padding: 0; border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); border-radius: 4px; background: color-mix(in srgb, var(--fg) 6%, transparent); cursor: pointer; position: relative; } .ge-canvas-anchor:hover, .ge-canvas-anchor.active { border-color: var(--red); background: color-mix(in srgb, var(--red) 12%, transparent); } .ge-canvas-resize-options { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-top: 12px; } .ge-canvas-lock-option { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; opacity: .72; cursor: pointer; } .ge-canvas-interpolation-field { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; opacity: .72; } .ge-canvas-interpolation-field select { min-width: 92px; padding: 4px 6px; border: 1px solid var(--border); border-radius: 5px; background: var(--bg); color: var(--fg); font: inherit; } .ge-canvas-preset-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; flex: 0 0 auto; } .ge-canvas-preset { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; flex: 0 0 auto; min-height: 62px; min-width: 0; padding: 10px 9px; line-height: 1.25; border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); color: var(--fg); font: inherit; text-align: center; cursor: pointer; box-sizing: border-box; } .ge-canvas-preset:hover, .ge-canvas-preset.active { border-color: var(--red); background: color-mix(in srgb, var(--red) 12%, transparent); } .ge-canvas-preset span, .ge-canvas-preset strong { display: block; width: auto; max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-canvas-preset span { font-size: 11px; font-weight: 600; } .ge-canvas-preset strong { font-size: 10px; font-weight: 500; opacity: .8; } .ge-canvas-preview { display: flex; align-items: center; justify-content: center; margin: 2px 0 8px; padding: 6px; border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); } .ge-canvas-preview img { max-width: 100%; max-height: 120px; object-fit: contain; display: block; } .ge-canvas-preview[hidden], .ge-canvas-resolution-field[hidden] { display: none; } .ge-canvas-resolution-field { display: inline-flex; align-items: center; gap: 6px; } .ge-canvas-resolution-field input[type="number"] { width: 84px; } .ge-canvas-resolution-unit { font-size: 11px; opacity: .6; } .ge-canvas-prompt-footer { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); margin-top: 8px; } .ge-btn-primary { background: var(--red); color: #fff; border-color: var(--red); font-weight: 600; } .ge-btn-primary:hover { background: color-mix(in srgb, var(--red) 85%, #000); } .ge-zoom-label { font-size: 10px; opacity: 0.5; } /* Nudge whirlpool spinners 1px down so they sit on the text baseline when placed inside action buttons (Generate, Harmonize, etc). */ .ge-btn .ai-spinner-whirlpool, .ge-btn .spinner-whirlpool, button .ai-spinner-whirlpool, button .spinner-whirlpool { position: relative; top: 1px; } /* Layers panel */ .ge-layers { /* Natural height now that the whole right panel scrolls — flex:1 would collapse the list inside a scrolling parent. */ flex: 0 0 auto; display: flex; flex-direction: column; } /* Vertical drag handle that sits on the LEFT edge of the right panel. Drag horizontally to widen / narrow the entire side panel. Slim by default; brighter on hover so the user notices it's interactive. */ .ge-panel-resize { position: absolute; left: -3px; top: 0; bottom: 0; width: 6px; cursor: ew-resize; background: transparent; z-index: 5; transition: background 0.12s; } .ge-panel-resize:hover, .ge-panel-resize:active { background: color-mix(in srgb, var(--red) 30%, transparent); } /* The right panel needs `position: relative` so the handle (absolute) anchors to it instead of the viewport. */ .ge-right-panel { position: relative; } /* Docked right panel stack (desktop) — Photoshop-style: several panel groups are visible at once, stacked vertically. Each group shows one tab's panel, collapses to its tab strip, and resizes via the divider below it. The panel wrappers only re-parent existing elements, so all ids/classes/listeners keep working; inactive panels are `hidden`. */ .ge-right-panel--dock { overflow: hidden; } .ge-dock { display: flex; flex-direction: column; flex: 0 1 auto; min-height: 0; max-height: 68%; overflow-y: auto; overflow-x: hidden; } .ge-right-panel--dock > .ge-layers { flex: 1 1 auto; min-height: 0; } .ge-dock-group { display: flex; flex-direction: column; flex: 0 0 auto; min-height: 0; overflow: hidden; } .ge-dock-group-head { display: flex; align-items: stretch; flex: 0 0 auto; gap: 2px; padding: 0 4px 0 6px; background: color-mix(in srgb, var(--panel) 88%, var(--fg) 12%); border-bottom: 1px solid var(--border); } .ge-dock-tabs { display: flex; flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; gap: 1px; } .ge-dock-tabs::-webkit-scrollbar { display: none; } .ge-dock-tab { flex: 0 0 auto; padding: 5px 14px 5px 8px; font: inherit; font-size: 9px; font-weight: 600; letter-spacing: 0.01em; text-transform: uppercase; white-space: nowrap; color: color-mix(in srgb, var(--fg) 72%, transparent); background: transparent; border: 0; border-bottom: 2px solid transparent; cursor: pointer; } .ge-dock-tab:hover { color: var(--fg); } .ge-dock-tab.active { color: var(--fg); border-bottom-color: var(--red); } .ge-dock-tab:focus-visible { outline: 2px solid var(--red); outline-offset: -2px; } /* A tab + its tear-off ("undock") control. The undock button appears on hover / focus and floats that single panel into its own window. It sits inside the tab button (top-right corner), not beside it. */ .ge-dock-tab-item { display: inline-flex; align-items: stretch; flex: 0 0 auto; position: relative; } .ge-dock-tab-pop { position: absolute; top: 1px; right: 1px; display: none; align-items: center; justify-content: center; width: 13px; height: 13px; padding: 0; border: 0; border-radius: 3px; background: color-mix(in srgb, var(--panel) 88%, transparent); color: color-mix(in srgb, var(--fg) 55%, transparent); cursor: pointer; } .ge-dock-tab-item:hover .ge-dock-tab-pop, .ge-dock-tab:focus-visible ~ .ge-dock-tab-pop { display: inline-flex; } .ge-dock-tab-pop:hover { color: var(--fg); } .ge-dock-collapse { flex: 0 0 auto; width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: color-mix(in srgb, var(--fg) 72%, transparent); background: transparent; border: 0; cursor: pointer; transition: transform 0.12s; } .ge-dock-collapse:hover { color: var(--fg); } .ge-dock-detach { flex: 0 0 auto; width: 26px; height: 26px; padding: 0; margin-left: 2px; border-left: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); display: inline-flex; align-items: center; justify-content: center; color: color-mix(in srgb, var(--fg) 70%, transparent); background: transparent; border-top: 0; border-right: 0; border-bottom: 0; cursor: pointer; } .ge-dock-detach:hover { color: var(--fg); } /* Collapsed groups shrink to their tab header only (never clip it). */ .ge-dock-group.is-collapsed { flex: 0 0 auto !important; height: auto !important; } .ge-dock-tabs-more { flex: 0 0 auto; width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: color-mix(in srgb, var(--fg) 72%, transparent); background: transparent; border: 0; cursor: pointer; } .ge-dock-tabs-more:hover { color: var(--fg); } .ge-dock-collapse svg, .ge-dock-detach svg, .ge-dock-tabs-more svg { width: 16px; height: 16px; flex: 0 0 auto; } .ge-dock-tabs-menu { position: fixed; z-index: 10060; min-width: 140px; padding: 4px; display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5); } .ge-dock-tabs-menu-item { text-align: left; padding: 5px 8px; font-size: 11.5px; color: var(--fg); background: transparent; border: 0; border-radius: 4px; cursor: pointer; } .ge-dock-tabs-menu-item:hover { background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent); } .ge-dock-group.is-floating .ge-dock-detach { display: none; } /* Detached panel group — floats above the editor in its own draggable window with a Dock control to return it to the stack. */ .ge-float-window { position: fixed; z-index: 10050; width: 260px; max-width: calc(100vw - 16px); background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5); overflow: hidden; } .ge-float-head { display: flex; align-items: center; gap: 6px; padding: 5px 6px 5px 10px; background: color-mix(in srgb, var(--panel) 88%, var(--fg) 12%); border-bottom: 1px solid var(--border); cursor: grab; touch-action: none; } .ge-float-title { flex: 1 1 auto; min-width: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-float-btns { display: inline-flex; align-items: center; gap: 2px; } .ge-float-dock { width: 28px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: color-mix(in srgb, var(--fg) 70%, transparent); background: transparent; border: 0; cursor: pointer; } .ge-float-dock svg { width: 16px; height: 16px; flex: 0 0 auto; } .ge-float-dock:hover { color: var(--fg); } .ge-float-body { max-height: 60vh; overflow-y: auto; } .ge-float-body > .ge-dock-group { flex: 0 0 auto !important; height: auto !important; } .ge-dock-group.is-collapsed .ge-dock-collapse { transform: rotate(-90deg); } .ge-dock-group.is-collapsed .ge-dock-panels { display: none; } .ge-dock-panels { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; /* A visible thin scrollbar makes it obvious when a group's content (e.g. the swatch grid) continues below the fold instead of looking like it was clipped mid-row. */ scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--fg) 30%, transparent) transparent; } .ge-dock-panels::-webkit-scrollbar { width: 8px; } .ge-dock-panels::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--fg) 28%, transparent); border-radius: 4px; } .ge-dock-panels::-webkit-scrollbar-track { background: transparent; } .ge-dock-panel { display: flex; flex-direction: column; flex: 0 0 auto; min-width: 0; } .ge-dock-panel[hidden] { display: none; } /* View > Panels visibility: hide a panel's body + its tab-strip item, a whole group (and its divider) once every member is hidden, and the pinned Layers / Tool Options strips. Tagged by panel-visibility.js. */ .ge-dock-panel[data-panel-hidden="1"], .ge-dock-tab-item[data-panel-hidden="1"], .ge-dock-group[data-panel-hidden="1"], .ge-dock-divider[data-panel-hidden="1"], .ge-layers[data-panel-hidden="1"], .ge-controls[data-panel-hidden="1"], .ge-dock[data-panel-hidden="1"], .ge-right-panel[data-panel-hidden="1"] { display: none !important; } /* When every dock group is hidden the rail must shrink to its remaining pinned content instead of stretching to full height and leaving a blank column. */ .ge-right-panel[data-panel-dock-hidden="1"] { align-self: flex-start; height: auto; } .ge-right-panel[data-panel-dock-hidden="1"] > .ge-layers { flex: 0 0 auto; max-height: 100%; } .ge-dock-select option[data-panel-hidden="1"] { display: none; } /* Drag divider between two stacked panel groups. */ .ge-dock-divider { flex: 0 0 auto; height: 5px; margin: -1px 0; background: transparent; cursor: ns-resize; transition: background 0.12s; } .ge-dock-divider:hover, .ge-dock-divider:active { background: color-mix(in srgb, var(--red) 35%, transparent); } /* Compact fallback (narrow desktop columns) — a single selector that swaps one panel at a time. */ .ge-dock--compact .ge-dock-tabbar { display: flex; flex: 0 0 auto; padding: 7px 8px; border-bottom: 1px solid var(--border); background: var(--panel); } .ge-dock-select { flex: 1 1 auto; min-width: 0; width: 100%; padding: 5px 8px; font: inherit; font-size: 11px; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: var(--fg); background-color: var(--bg, var(--panel)); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; } .ge-dock-select:focus-visible { outline: 2px solid var(--red); outline-offset: 1px; } .ge-dock--compact .ge-dock-panels { flex: 1 1 auto; max-height: none; } /* Floating value bubble that pops above the slider thumb while dragging. Solves the "I can't see the number" problem when the slider's opaque plate covers the row's right-edge value chip. */ .ge-slider-bubble { /* Fixed positioning so the bubble escapes any overflow:hidden/auto on the slider's ancestor chain (the layers list clips, and used to swallow the bubble entirely). JS sets viewport-relative left/top; the translate centers and lifts the bubble above the slider thumb. */ position: fixed; transform: translate(-50%, -100%); z-index: 10000; padding: 5px 10px; background: var(--red); color: #fff; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; border-radius: 6px; box-shadow: 0 3px 10px color-mix(in srgb, var(--red) 40%, transparent); pointer-events: none; white-space: nowrap; opacity: 0; transition: opacity 0.1s; } .ge-slider-bubble.visible { opacity: 1; } /* Floating thumbnail shown when hovering a layer row. */ .ge-layer-thumb { position: fixed; z-index: 60; padding: 4px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 18px color-mix(in srgb, var(--fg) 30%, transparent); pointer-events: none; display: none; } .ge-layers-header { display: flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.85; border-bottom: 1px solid var(--border); position: relative; } .ge-layers-title { flex: 1; } .ge-layers-header #ge-add-layer { flex: 0 0 auto; white-space: nowrap; } .ge-layer-header-tools { display: flex; flex: 0 1 auto; flex-wrap: wrap; align-items: center; gap: 4px; } /* Panel action icons live in a compact row at the BOTTOM of the panel (Photoshop-style); #ge-layer-tools stays an empty hook for shared selected-layer actions. */ .ge-layers-footer { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 4px 8px; border-top: 1px solid var(--border); background: color-mix(in srgb, var(--bg-secondary, var(--panel)) 40%, var(--panel)); } .ge-layers-grab { display: none; } /* mobile only */ .ge-layers-merge-wrap { position: relative; display: inline-flex; } .ge-layers-merge-wrap #ge-layers-merge-btn { position: relative; top: 0; } .ge-layers-merge-menu { /* Fixed positioning so the menu escapes the layers panel's overflow clipping (which made it look transparent). JS sets exact coords via getBoundingClientRect on the trigger button at open time. */ position: fixed; z-index: 200; min-width: 160px; padding: 4px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); display: flex; flex-direction: column; gap: 2px; pointer-events: auto; } .ge-layers-merge-menu .dropdown-item-compact { width: 100%; background: none; border: none; text-align: left; font: inherit; } .ge-layers-actions { display: flex; gap: 4px; padding: 6px 8px; border-top: 1px solid var(--border); flex-wrap: wrap; /* Pin the action row to the bottom of the right panel's scroll viewport. Without this, when the tool-controls section is tall enough to overflow the panel, the merge buttons get pushed below the fold and require scrolling to reach. */ position: sticky; bottom: 0; background: var(--panel); z-index: 4; /* Centered cluster instead of stretching the buttons to fill the panel width. */ justify-content: center; } /* Merge / flatten buttons sit content-sized so the cluster reads as a centered group of three, not three stretched bars across the panel. */ .ge-layers-actions .ge-btn { flex: 0 0 auto; min-width: 0; padding: 4px 8px; font-size: 10px; display: inline-flex; align-items: center; justify-content: center; gap: 4px; white-space: nowrap; } .ge-layers-actions .ge-icon-btn { /* Icon-only buttons stay narrow — no risk of label overflow. */ flex: 0 0 auto; padding: 4px 8px; } .ge-layers-actions .ge-btn svg { flex-shrink: 0; margin-right: 0 !important; } .ge-layers-list { /* Cap the list so a huge layer stack scrolls within its own box rather than pushing the whole panel arbitrarily tall. */ max-height: 320px; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 0 6px; } .ge-layer-item { display: flex; align-items: center; gap: 4px; padding: 3px 8px; cursor: pointer; border-left: 2px solid transparent; transition: background 0.1s; font-size: 11px; color: var(--fg); } .ge-layer-item:focus-visible, .ge-layer-group-row:focus-visible { outline: 1px solid var(--accent, var(--red)); outline-offset: -1px; } @media (min-width: 701px) { .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) { display: grid; grid-template-columns: 12px 22px 28px minmax(0, 1fr) 24px; row-gap: 2px; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-drag { grid-column: 1; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-vis { grid-column: 2; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-inline-thumb { grid-column: 3; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name-input { grid-column: 4; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-lock-btn { grid-column: 5; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row { grid-column: 1 / -1; grid-row: 2; width: 100%; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-controls { grid-column: 1 / -1; grid-row: 3; justify-content: flex-end; min-width: 0; } /* Photoshop-style: an idle layer row is ONE compact line. The per-row opacity slider and action strip stay in the DOM (so they remain testable) but only surface on the active or hovered row. */ .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-controls { display: none; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-opacity-row, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-controls, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item):hover > .ge-layer-opacity-row, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item):hover > .ge-layer-controls { display: flex; } } .ge-layer-inline-thumb { display: block; width: 28px; height: 21px; flex: 0 0 28px; object-fit: contain; border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); border-radius: 3px; background: #303030; image-rendering: auto; } /* Row-hover tint removed — kept the layer row's background stable so it doesn't shift visually as the user reaches for the opacity slider. */ .ge-layer-item.active { background: color-mix(in srgb, var(--accent, var(--red)) 52%, var(--bg-secondary, var(--bg))); border-left-color: var(--accent, var(--red)); box-shadow: inset 3px 0 0 var(--accent, var(--red)), inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); } .ge-layer-item.active > .ge-layer-name, .ge-layer-group-row.active > .ge-layer-group-name { font-weight: 700; } /* "Active parent" — the layer is selected, but a mask sub-layer below it is the actual paint target. Soft hint so the user can see which parent owns the active mask without competing with the mask's strong red highlight. */ .ge-layer-item.active-parent { background: color-mix(in srgb, var(--fg) 4%, transparent); border-left-color: color-mix(in srgb, var(--fg) 30%, transparent); } .ge-layer-item.selected:not(.active):not(.active-parent) { background: color-mix(in srgb, var(--accent, var(--red)) 22%, var(--bg)); box-shadow: inset 2px 0 0 color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); } .ge-layer-group-row { min-height: 36px; display: grid; grid-template-columns: 12px 18px 24px 34px minmax(0, 1fr) 24px; grid-template-rows: auto auto auto; align-items: center; gap: 3px; padding: 3px 7px 3px calc(7px + var(--group-depth, 0) * 14px); border-bottom: 1px solid color-mix(in srgb, var(--border) 72%, transparent); background: color-mix(in srgb, var(--bg-secondary) 78%, var(--bg)); color: var(--fg); width: 100%; box-sizing: border-box; } .ge-layer-group-row .ge-layer-group-drag { grid-column: 1; grid-row: 1; } .ge-layer-group-row .ge-layer-group-toggle { grid-column: 2; grid-row: 1; } .ge-layer-group-row .ge-layer-vis { grid-column: 3; grid-row: 1; } .ge-layer-group-row .ge-group-inline-thumb { grid-column: 4; grid-row: 1; } .ge-layer-group-row .ge-layer-group-name { grid-column: 5; grid-row: 1; } .ge-layer-group-row .ge-layer-lock-btn { grid-column: 6; grid-row: 1; } .ge-layer-group-row .ge-layer-opacity-row { grid-column: 1 / -1; grid-row: 2; display: flex; width: 100%; min-width: 0; } .ge-layer-group-row .ge-layer-opacity-row .ge-layer-opacity { flex: 1 1 auto; width: 100%; min-width: 0; } .ge-layer-opacity-row { display: flex; align-items: center; gap: 6px; min-width: 0; } .ge-layer-opacity-row .ge-layer-opacity { flex: 1 1 auto; width: auto; } .ge-layer-group-row .ge-layer-controls { grid-column: 1 / -1; grid-row: 3; justify-content: flex-end; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row { grid-column: 1 / -1; grid-row: 2; width: 100%; } .ge-layer-group-drag { touch-action: none; } .ge-layer-group-row.group-dragging { opacity: 0.62; outline: 1px solid color-mix(in srgb, var(--accent, var(--red)) 65%, transparent); } .ge-group-drop-line { position: absolute; z-index: 20; left: 6px; right: 6px; height: 2px; pointer-events: none; background: var(--accent, var(--red)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--bg) 65%, transparent); } .ge-layer-group-row.active { background: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--bg-secondary)); box-shadow: inset 2px 0 0 var(--accent, var(--red)), inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); } .ge-layer-group-toggle { width: 18px; height: 22px; padding: 0; border: 0; background: transparent; color: inherit; opacity: 0.62; } .ge-layer-group-toggle svg { transition: transform 120ms ease; } .ge-layer-group-toggle.expanded svg { transform: rotate(90deg); } .ge-layer-group-name { min-width: 0; display: flex; align-items: center; gap: 5px; overflow: hidden; font-size: 11px; font-weight: 600; } .ge-layer-group-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-group-mask-sub-item { padding-left: calc(20px + var(--group-mask-depth, 1) * 14px); } .ge-layer-vis { border: none; background: none; color: var(--fg); cursor: pointer; padding: 2px 4px; display: inline-flex; align-items: center; justify-content: center; opacity: 0.4; transition: opacity 0.12s; } .ge-layer-vis:hover { opacity: 0.8; } .ge-layer-vis.visible { opacity: 0.9; } .ge-layer-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .ge-layer-clipped-marker { display: inline-flex; align-items: center; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.82; } .ge-layer-name-input { flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--fg); font-size: 11px; padding: 1px 4px; border-radius: 3px; min-width: 0; } /* All sliders inside the image editor share the same visual language as the layer opacity slider — rounded pill, dim track, red thumb. Track AND thumb grow when interacting for finer tweaking. */ .gallery-editor-container input[type="range"] { -webkit-appearance: none; appearance: none; height: 4px; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; accent-color: var(--red); cursor: pointer; transition: height 0.15s ease; } .gallery-editor-container input[type="range"]:hover, .gallery-editor-container input[type="range"]:focus, .gallery-editor-container input[type="range"]:active { height: 10px; } .gallery-editor-container input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; transition: width 0.12s ease, height 0.12s ease; } .gallery-editor-container input[type="range"]::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: var(--red); border: none; cursor: pointer; transition: width 0.12s ease, height 0.12s ease; } .gallery-editor-container input[type="range"]:hover::-webkit-slider-thumb, .gallery-editor-container input[type="range"]:focus::-webkit-slider-thumb, .gallery-editor-container input[type="range"]:active::-webkit-slider-thumb { width: 18px; height: 18px; } .gallery-editor-container input[type="range"]:hover::-moz-range-thumb, .gallery-editor-container input[type="range"]:focus::-moz-range-thumb, .gallery-editor-container input[type="range"]:active::-moz-range-thumb { width: 18px; height: 18px; } /* Layer-row opacity slider — fixed track, only thumb grows on interaction. Takes a bit of row space at rest, but no jumpy expand animation. */ .ge-layer-opacity { width: 70px; height: 6px; accent-color: var(--red); flex-shrink: 0; -webkit-appearance: none; appearance: none; background: color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 999px; position: relative; top: 0; } .ge-layer-opacity:hover::-webkit-slider-thumb, .ge-layer-opacity:active::-webkit-slider-thumb, .ge-layer-opacity.dragging::-webkit-slider-thumb { width: 18px; height: 18px; } .ge-layer-opacity:hover::-moz-range-thumb, .ge-layer-opacity:active::-moz-range-thumb, .ge-layer-opacity.dragging::-moz-range-thumb { width: 18px; height: 18px; } .ge-layer-controls { display: flex; gap: 2px; flex-shrink: 0; } .ge-layer-controls .ge-mask-inspect-btn.active { color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } .ge-mask-properties { position: relative; flex: 0 0 22px; } .ge-mask-properties > summary:hover, .ge-mask-properties[open] > summary { opacity: 0.8; } .ge-mask-properties[open] > summary { color: var(--accent, var(--red)); opacity: 0.95; } .ge-mask-properties-panel { position: fixed; box-sizing: border-box; margin: 0; bottom: auto; overflow: auto; color: var(--fg); z-index: 9999; right: auto; top: 0; width: 190px; padding: 8px; display: grid; gap: 7px; border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); border-radius: 5px; background: var(--bg-secondary, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.28); } .ge-mask-properties-panel:not(:popover-open) { display: none; } .ge-mask-property-row { display: grid; grid-template-columns: 52px minmax(0, 1fr) 35px; align-items: center; gap: 6px; font-size: 10px; } .ge-mask-property-row input { min-width: 0; width: 100%; } .ge-mask-apply-btn { width: 100%; min-height: 26px; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); border-radius: 4px; background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); color: var(--fg); cursor: pointer; font-size: 10px; } .ge-mask-apply-btn:hover { background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); } .ge-layer-tools { display: flex; align-items: center; justify-content: flex-end; gap: 2px; min-height: 34px; padding: 3px 7px; border-top: 1px solid color-mix(in srgb, var(--border) 80%, transparent); background: color-mix(in srgb, var(--bg-secondary) 72%, var(--bg)); } .ge-layer-tools .ge-layer-controls { display: flex; flex-wrap: wrap; justify-content: flex-end; width: 100%; } .ge-eyedropper-live { display: flex; align-items: center; gap: 7px; min-height: 28px; padding: 3px 6px; border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); border-radius: 5px; background: color-mix(in srgb, var(--fg) 4%, transparent); } .ge-eyedropper-loupe { width: 42px; height: 42px; flex: 0 0 42px; border-radius: 4px; image-rendering: pixelated; background: var(--bg); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 22%, transparent); } .ge-eyedropper-live-swatch { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 4px; background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 22%, transparent); } .ge-eyedropper-live-values { display: grid; gap: 1px; min-width: 0; font-size: 10px; line-height: 1.25; } .ge-eyedropper-live-values > span { opacity: .62; font-variant-numeric: tabular-nums; } .ge-eyedropper-live-swatch.empty { background: repeating-linear-gradient(135deg, transparent 0 4px, color-mix(in srgb, var(--fg) 18%, transparent) 4px 6px); } .ge-eyedropper-live code { font-size: 11px; font-variant-numeric: tabular-nums; opacity: .75; } /* In the contextual options bar keep the eyedropper readout on ONE compact row so the bar height matches the other tools and the centred canvas does not jump between tools (which shifted the eyedropper sample point). The loupe canvas keeps its 84x84 backing store; only its display size shrinks, so the live pixel readout still works. */ .ge-controls--in-options .ge-eyedropper-live { min-height: 0; height: 26px; padding: 0 6px; gap: 6px; } .ge-controls--in-options .ge-eyedropper-loupe { width: 22px; height: 22px; flex: 0 0 22px; } .ge-controls--in-options .ge-eyedropper-live-swatch { width: 14px; height: 14px; flex: 0 0 14px; } .ge-controls--in-options .ge-eyedropper-live-values { display: flex; flex-direction: row; align-items: center; gap: 8px; } /* Left-edge drag handle on each layer row. */ .ge-layer-drag { display: flex; align-items: center; justify-content: center; width: 12px; margin-right: 2px; opacity: 0.4; cursor: grab; touch-action: none; user-select: none; flex-shrink: 0; } .ge-layer-drag:hover { opacity: 0.85; } /* While dragSort lifts the item via absolute positioning, the placeholder slot shows where it'll land — give it a subtle red outline so users see the target clearly. */ .ge-layers-list .drag-placeholder { background: color-mix(in srgb, var(--red) 12%, transparent); border: 1px dashed color-mix(in srgb, var(--red) 50%, transparent); border-radius: 4px; box-sizing: border-box; } .ge-layer-item.dragging { background: var(--panel); box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent); border-radius: 4px; } .ge-layer-btn { border: none; background: none; color: var(--fg); cursor: pointer; font-size: 12px; padding: 0 2px; opacity: 0.4; transition: opacity 0.1s; } .ge-layer-btn:hover { opacity: 0.8; } .ge-layer-btn.active { color: var(--accent, var(--red)); opacity: 0.95; } .ge-layer-btn:disabled { cursor: default; opacity: 0.16; } .ge-layer-style-btn { font-style: italic; font-weight: 700; font-size: 13px; } .ge-layer-style-menu { animation: none; position: fixed; inset: auto; margin: 0; padding: 4px; min-width: 170px; max-width: calc(100vw - 12px); max-height: calc(100dvh - 12px); overflow-y: auto; box-sizing: border-box; background: var(--panel, var(--bg)); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 8px 24px #0005; } .ge-layer-style-menu:not(:popover-open) { display: none; } .ge-layer-style-menu:popover-open { display: block; } .ge-layer-style-menu > button { width: 100%; border: 0; background: none; font: inherit; text-align: left; } .ge-layer-btn.danger:hover { color: var(--red); opacity: 1; } .ge-layer-lock-menu { position: fixed; z-index: 10004; width: 176px; padding: 4px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); } .ge-layer-lock-option { width: 100%; height: 30px; display: grid; grid-template-columns: 20px minmax(0, 1fr); align-items: center; gap: 6px; padding: 0 8px; border: 0; border-radius: 4px; background: transparent; color: var(--fg); font: inherit; font-size: 11px; text-align: left; cursor: pointer; } .ge-layer-lock-option:hover, .ge-layer-lock-option:focus-visible { outline: none; background: color-mix(in srgb, var(--fg) 8%, transparent); } .ge-layer-align-menu { width: 218px; } .ge-layer-lock-check { width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid color-mix(in srgb, var(--fg) 28%, transparent); border-radius: 3px; color: var(--accent, var(--red)); font-size: 11px; line-height: 1; } @media (max-width: 700px) { .ge-layer-lock-menu { width: auto; } .ge-layer-lock-option { height: 44px; font-size: 13px; } } /* Crop apply button */ .ge-crop-apply { position: absolute; z-index: 10; display: inline-flex; align-items: center; gap: 4px; padding: 4px 6px; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .ge-crop-apply input[type="number"] { width: 56px; padding: 3px 4px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; -moz-appearance: textfield; } .ge-crop-x { font-size: 11px; opacity: 0.5; } .ge-crop-apply-btn { padding: 4px 10px; background: var(--red); color: #fff; border: none; border-radius: 4px; font-size: 11px; font-weight: 600; cursor: pointer; position: relative; top: -2px; } .ge-crop-apply-btn:hover { background: color-mix(in srgb, var(--red) 85%, #000); } /* Crop aspect preset buttons (options bar) */ .ge-crop-preset-row { display: flex; gap: 4px; } .ge-crop-preset.active { background: var(--accent, #4c8bf5); border-color: transparent; color: #fff; } /* Text input floating */ .ge-text-input { background: var(--bg); border: 1px solid var(--red); color: var(--fg); padding: 4px 8px; font-size: 14px; border-radius: 4px; min-width: 120px; } .ge-brush-cursor { position: fixed; pointer-events: none; z-index: 2147483647; border: 1.5px solid #fff; border-radius: 50%; box-shadow: 0 0 0 1px rgba(0,0,0,0.72); display: none; box-sizing: border-box; } /* Pixel brush: a hard square footprint instead of a feathered circle. */ .ge-brush-cursor.ge-brush-cursor--pixel { border-radius: 0; } .ge-inpaint-section { padding: 8px 0; border-top: 1px solid var(--border); margin-top: 4px; } .ge-inpaint-popover-head { display: none; } @media (min-width: 821px) { .ge-controls.ge-inpaint-popover-host { padding: 0; border-bottom: 0; gap: 0; } .ge-inpaint-section.ge-inpaint-popover { position: fixed; z-index: 10004; width: 330px; max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px; margin: 0; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 12px 36px rgba(0, 0, 0, 0.42); } .ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: -10px -10px 8px; padding: 8px 10px; border-bottom: 1px solid var(--border); cursor: grab; user-select: none; } .ge-inpaint-section.ge-inpaint-popover > .ge-section-title-with-help:not(.ge-inpaint-popover-title) { display: none; } .ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-title { margin: 0; flex: 1 1 auto; } .ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-close { flex: 0 0 auto; position: relative; top: -8px; border: none; background: none; color: var(--fg-muted); font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px; border-radius: 4px; transition: color 0.14s ease, background-color 0.14s ease; } .ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-close:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 10%, transparent); } } .ge-inpaint-model-row { display: flex; align-items: center; gap: 8px; } .ge-inpaint-model-row label { flex: 0 0 auto; font-size: 10px; opacity: 0.65; } .ge-inpaint-model-row #ge-ai-inpaint { flex: 1 1 auto; min-width: 0; font-size: 10px; padding: 4px 6px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; } .ge-inpaint-prompt { width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font-size: 12px; font-family: inherit; margin-top: 4px; box-sizing: border-box; } .ge-inpaint-prompt:focus { border-color: var(--red); outline: none; } /* Responsive: stack on narrow screens */ @media (max-width: 700px) { /* The editor body holds the sidebar | canvas | layers panel; on mobile we stack them as toolbar (top) → canvas (middle) → layers (bottom). Without this the body stays a flex row and the canvas ends up zero-width because the sidebar takes the full width. */ .ge-editor-body { flex-direction: column; } /* Brush/Size-style rows: stack the label above its slider so the slider gets a full row instead of getting smashed against the panel's right edge. Doesn't touch .ge-eraser-row which has its own value-chip-on-the-right layout. */ .ge-control-row:not(.ge-eraser-row):has(input[type="range"]) { flex-direction: column; align-items: stretch; gap: 2px; } .ge-control-row:not(.ge-eraser-row):has(input[type="range"]) > input[type="range"] { width: 100%; } /* More breathing room around lasso/wand sliders on mobile so they don't hug the right edge of the controls panel. */ #ge-lasso-section .ge-eraser-row, #ge-wand-section .ge-eraser-row { padding-right: 12px; margin-top: 6px; } /* Lasso action buttons (Clear / Copy / To Mask / Bg Remove): pin to the right edge of the row instead of left-aligned. */ #ge-lasso-section .ge-actions, #ge-wand-section .ge-actions { justify-content: flex-end; } /* Universal thumb-grow on active for every slider on mobile. */ .ge-controls input[type="range"]:active::-webkit-slider-thumb, .ge-controls input[type="range"].is-using::-webkit-slider-thumb { width: 22px; height: 22px; } .ge-controls input[type="range"]:active::-moz-range-thumb, .ge-controls input[type="range"].is-using::-moz-range-thumb { width: 22px; height: 22px; } .ge-controls input[type="range"]::-webkit-slider-thumb, .ge-controls input[type="range"]::-moz-range-thumb { transition: width 0.12s ease, height 0.12s ease; } /* Keep the right-hand actions (Import / Save / maximize) pinned and always visible on phones: the menu row scrolls internally instead of dragging the whole topbar sideways, which otherwise pushes Save off-screen. Uses a child selector to beat the base `.ge-topbar-left{flex:0 0 auto}`. */ .ge-topbar > .ge-topbar-left { flex: 1 1 0; min-width: 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; } .ge-topbar > .ge-topbar-left::-webkit-scrollbar { display: none; } .ge-topbar > .ge-topbar-right { flex: 0 0 auto; margin-left: 0; } .ge-toolbar { flex-direction: row; width: 100%; height: auto; padding: 4px 0; border-right: none; border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden; flex-shrink: 0; gap: 0; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .ge-toolbar::-webkit-scrollbar { display: none; } /* Audit #5: the menu row and tool rail scroll sideways with hidden scrollbars, so they read as static. Fade the overflowing edge (and drop the fade on the side with nothing more to scroll to) so it's obvious there are off-screen menus/tools. attachScrollAffordance() toggles the classes. */ .ge-topbar-left.ge-scroll-fade, .ge-toolbar.ge-scroll-fade { -webkit-mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 calc(100% - 42px), transparent 100%); mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 calc(100% - 42px), transparent 100%); } .ge-topbar-left.ge-scroll-fade.is-at-left, .ge-toolbar.ge-scroll-fade.is-at-left { -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 42px), transparent 100%); mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 42px), transparent 100%); } .ge-topbar-left.ge-scroll-fade.is-at-right, .ge-toolbar.ge-scroll-fade.is-at-right { -webkit-mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 100%); mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 100%); } .ge-topbar-left.ge-scroll-fade.is-at-left.is-at-right, .ge-toolbar.ge-scroll-fade.is-at-left.is-at-right { -webkit-mask-image: none; mask-image: none; } /* The horizontal mobile rail keeps a fixed short height; the fg/bg swatch stack would stretch it and push tools under the layer sheet, so it is hidden here (color stays reachable in the options bar). */ .ge-tool-swatches { display: none; } /* Section labels in the toolbar were vertical breaks — turn them into thin vertical dividers when the column flows horizontally. */ .ge-tool-sep { border-top: none; border-left: 1px solid var(--border); margin: 0 4px; padding: 0; width: 1px; height: 24px; text-indent: -9999px; overflow: hidden; flex-shrink: 0; } .ge-tool-btn { flex-shrink: 0; width: 48px; } /* Horizontal rail: slots become inline columns and the flyout caret is always visible so touch users can open tool families without a hover. */ .ge-tool-slot { flex-direction: row; flex-shrink: 0; } .ge-tool-slot > .ge-tool-btn { width: 48px; } .ge-tool-caret { display: inline-flex; } /* Canvas is the main thing — give it as much vertical space as the editor body can spare. min-height keeps it visible even when the right panel is at its max. */ .ge-canvas-area { flex: 1 1 auto; min-height: 40vh; } /* Right panel = layers list. Docked as a bottom sheet with a peek state: collapsed shows the header strip (and the active layer row peeks through), expanded reveals all layers + actions. The user swipes up to expand, down to collapse — the active layer is never fully hidden. */ .ge-right-panel { position: fixed; left: 0; right: 0; bottom: 0; width: 100%; max-height: 70vh; height: 70vh; background: var(--panel); border-left: none; border-top: 1px solid var(--border); border-radius: 12px 12px 0 0; box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35); z-index: 50; flex-shrink: 0; /* Peek height grows with the layer count up to a cap. JS sets the `--peek-height` custom property from _renderLayerPanel(); default fallback is enough for the header + one row. */ transform: translateY(calc(100% - var(--peek-height, 110px))); transition: transform 0.22s ease-out; } .ge-right-panel.expanded { transform: translateY(0); } /* Minimized: panel slides almost fully off-screen — only a thin handle strip remains visible so the user can swipe back up. */ .ge-right-panel.minimized { transform: translateY(calc(100% - 24px)); } .ge-layers-grab { display: block !important; position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 40px; height: 4px; background: color-mix(in srgb, var(--fg) 30%, transparent); border-radius: 2px; } .ge-layers-header { padding-top: 16px; position: relative; } /* Peek state: bound the whole layers section to the peek height so its bottom always meets the viewport edge and the list scrolls inside it. Previously only the list was capped (peek - 52px) while the section itself grew past the peek, so the add/fx/delete footer hung ~89px below the phone viewport with no way to reach it. */ .ge-right-panel:not(.expanded) .ge-layers { max-height: var(--peek-height, 110px); overflow: hidden; } .ge-right-panel:not(.expanded) .ge-layers-list { flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; -webkit-overflow-scrolling: touch; } /* Mobile layer-row buttons — bump hit targets up so fingers can reliably tap visibility / fx / delete. */ .ge-layer-vis, .ge-layer-btn, .ge-layer-fx-btn { width: 44px !important; min-width: 44px !important; height: 44px !important; min-height: 44px !important; font-size: 18px !important; display: inline-flex; align-items: center; justify-content: center; } .ge-layer-btn svg, .ge-layer-fx-btn svg { width: 18px; height: 18px; } /* The desktop actions sit at 0.4 opacity, which reads as "nearly invisible" against the pink selected-row overlay on a phone. Lift the resting opacity so the fx / mask / duplicate icons stay legible. */ .ge-layer-btn, .ge-layer-fx-btn { opacity: 0.78; } .ge-layer-btn:hover, .ge-layer-fx-btn:hover { opacity: 1; } .ge-layers-list { overflow-x: clip; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) { display: grid; grid-template-columns: 12px 44px 34px minmax(0, 1fr) 44px; grid-template-rows: 44px auto; align-items: center; row-gap: 3px; } .ge-layers-list > .ge-layer-group-row { grid-template-columns: 12px 44px 44px 34px minmax(0, 1fr) 44px; grid-template-rows: 44px auto; align-items: center; row-gap: 3px; } .ge-layer-group-row .ge-layer-group-drag { grid-column: 1; grid-row: 1; } .ge-layer-group-row .ge-layer-group-toggle { grid-column: 2; grid-row: 1; } .ge-layer-group-row .ge-layer-vis { grid-column: 3; grid-row: 1; } .ge-layer-group-row .ge-group-inline-thumb { grid-column: 4; grid-row: 1; } .ge-layer-group-row .ge-layer-group-name { grid-column: 5; grid-row: 1; } .ge-layer-group-row .ge-layer-lock-btn { grid-column: 6; grid-row: 1; } .ge-layer-group-row .ge-layer-opacity-row { grid-column: 1 / -1; grid-row: 2; margin-left: 52px; width: auto; } .ge-layer-group-row .ge-layer-controls { grid-column: 1 / -1; grid-row: 3; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-drag { grid-column: 1; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-vis { grid-column: 2; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-inline-thumb { grid-column: 3; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name, .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name-input { grid-column: 4; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-lock-btn { grid-column: 5; grid-row: 1; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row { grid-column: 1 / -1; grid-row: 2; display: flex; width: auto; min-width: 0; margin-left: 52px; } /* Keep the active row's action strip on ONE line inside its row so it can never wrap over the next layer; scroll it sideways instead. (documents-gallery-editor.css sets flex-wrap:wrap on the .active row, so this needs the extra .ge-right-panel scope to win.) */ .ge-right-panel .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-controls, .ge-right-panel .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active-parent > .ge-layer-controls, .ge-right-panel .ge-layers-list > .ge-layer-group-row.active > .ge-layer-controls { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; } .ge-mask-sub-item { display: grid; grid-template-columns: 40px 34px minmax(80px, 1fr) auto; grid-template-rows: 40px; align-items: center; padding-left: 8px; } .ge-mask-sub-item > .ge-layer-vis { grid-column: 1; grid-row: 1; } .ge-mask-sub-item > .ge-mask-inline-thumb { grid-column: 2; grid-row: 1; } .ge-mask-sub-item > .ge-layer-name { grid-column: 3; grid-row: 1; display: flex !important; min-width: 0; } .ge-mask-sub-item > .ge-layer-controls { grid-column: 4; grid-row: 1; min-width: 0; justify-content: flex-start; } .ge-mask-sub-item > .ge-layer-controls .ge-layer-btn { width: 30px !important; min-width: 30px !important; height: 30px !important; font-size: 14px !important; } /* Layer opacity slider stays at its expanded size at all times on mobile — there's room for it and the resize-on-hover felt jittery when scrolling. */ .ge-layer-opacity, .ge-layer-opacity:hover, .ge-layer-opacity:active, .ge-layer-opacity.dragging { width: 160px !important; height: 10px !important; } /* Transform / adjust popups dock at the TOP of the viewport on mobile so they overlay the topbar instead of eating canvas space. Fixed positioning escapes any transformed ancestor stacking. */ .ge-transform-popup, .ge-adj-popup, .ge-fx-popup { position: fixed !important; left: 8px !important; right: 8px !important; top: 32px !important; bottom: auto !important; max-width: calc(100vw - 16px) !important; width: auto !important; min-width: 0 !important; min-height: 240px !important; max-height: calc(100vh - 40px); overflow-y: auto; z-index: 10003 !important; } .ge-fx-menu { position: fixed !important; left: 8px !important; right: 8px !important; top: auto !important; bottom: calc(env(safe-area-inset-bottom, 0px) + 8px) !important; width: auto !important; min-width: 0 !important; max-width: calc(100vw - 16px) !important; z-index: 10002 !important; } /* Hide both the minimise and × close buttons in the header on mobile — a clearly labelled Cancel button next to Apply in the body is more discoverable than tiny header icons. */ .ge-transform-popup #ge-transform-min, .ge-transform-popup #ge-transform-cancel { display: none !important; } /* Wrap the transform body into precision rows on mobile: Row 1: X + Y Row 2: W + H Row 3: ↻ rotate + quick actions + Apply/Cancel */ .ge-transform-popup-body { flex-wrap: wrap !important; align-items: center; row-gap: 8px; column-gap: 6px; } .ge-transform-popup-body #ge-transform-rot { width: 3.5ch !important; min-width: 0 !important; } /* 3-row layout, enforced by explicit `.ge-row-break` elements in the DOM (the only reliable way to force a flex-wrap line break). Row 1: W input + H input Row 2: aspect lock + ↻ rotate input Row 3: flip-h, flip-v, rot-90 + Cancel + Apply */ .ge-transform-popup-body .ge-row-break { display: block; flex-basis: 100%; height: 0; width: 100%; } /* Flip buttons slightly bigger so they're easier to tap. */ .ge-transform-quick-btn { padding: 8px 10px !important; min-width: 40px; min-height: 40px; } .ge-transform-quick-btn svg { width: 18px; height: 18px; } /* Cancel + Apply share the right side of row 2, same size, paired. */ .ge-transform-popup-body #ge-transform-cancel-btn { margin-left: auto; } .ge-transform-popup-body #ge-transform-cancel-btn, .ge-transform-popup-body #ge-transform-apply { padding: 8px 16px !important; font-size: 13px !important; min-width: 80px; text-align: center; } /* Stepper-style number control on mobile: [ − ] [ input ] [ + ] Stack reverses so the − sits visually on the LEFT of the input, + on the RIGHT. Big finger-sized squares, big +/- glyphs, both buttons support tap-and-hold auto-repeat (JS) for fast scrubbing. */ .ge-transform-field { display: inline-flex !important; align-items: stretch; gap: 0; } .ge-transform-field > label { display: inline-flex; align-items: center; padding-right: 6px; font-weight: 600; } .ge-transform-field:not(:has(#ge-transform-rot)) > label { position: relative; top: -1px; } .ge-transform-spin { display: inline-flex !important; flex-direction: row !important; /* Use grid-order to swap positions of [−][+]: the − should sit BEFORE the input, the + AFTER it. Achieve this by absolute order — first child styled differently, second styled differently, then we move the whole .ge-transform-spin's first button visually before the input via reverse-flex. */ order: 0; width: auto !important; } .ge-transform-field { flex-direction: row; } /* Re-flow: label + [-] + input + [+] We split the .ge-transform-spin's two buttons across the field's flex line using order: order:0 for −, order:2 for input, order:3 for +. The spin itself gets `display: contents` so its children can be reordered independently against the input. */ .ge-transform-spin { display: contents !important; } .ge-transform-field > input.ge-transform-popup-input { order: 2; text-align: center; } .ge-transform-spin button[data-spin="down"] { order: 1; } .ge-transform-spin button[data-spin="up"] { order: 3; } /* The buttons themselves — chunky, square, identical in size. Reset the base `:first-child` rule (which strips the bottom border for the old stacked layout) so both buttons render with the same visual box in the new row layout. */ .ge-transform-spin button { box-sizing: border-box !important; width: 36px !important; height: 36px !important; min-width: 36px; min-height: 36px; padding: 0 0 0 0 !important; font-size: 18px !important; font-weight: 700; line-height: 1 !important; border: 1px solid var(--border) !important; border-radius: 6px !important; flex-shrink: 0; display: inline-flex !important; align-items: center; justify-content: center; position: relative; top: -4px; } .ge-transform-spin button:first-child, .ge-transform-spin button:last-child { border-radius: 6px !important; border-bottom: 1px solid var(--border) !important; } /* Graphic-editor adjustment buttons use text glyphs that sit visually low — lift them to match the +/- button alignment. */ .ge-adj-close, .ge-adj-min { padding-top: 0 !important; padding-bottom: 8px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; } .ge-transform-spin button[data-spin="down"] { margin-right: 4px; } .ge-transform-spin button[data-spin="up"] { margin-left: 4px; } /* The input between them — generous tap area and clear typography. */ .ge-transform-field > input.ge-transform-popup-input { height: 36px !important; padding: 0 6px !important; font-size: 14px !important; min-width: 56px; position: relative; top: 2px; } /* Rotate input — compact so it fits on row 2 alongside the flip quick buttons and the Cancel + Apply pair. */ .ge-transform-field > #ge-transform-rot.ge-transform-popup-input { min-width: 0; width: 64px !important; } /* Suffix (°) sits right after the rotate input but before the + button. */ .ge-transform-popup-suffix { order: 3; align-self: center; padding: 0 2px; } /* Slider thumb grows only while actively dragged — keeps the resting row compact, thumb pops larger when the user grabs it. */ .gallery-editor-container input[type="range"]:active::-webkit-slider-thumb, .ge-layer-opacity:active::-webkit-slider-thumb, .ge-layer-opacity.dragging::-webkit-slider-thumb { width: 22px !important; height: 22px !important; } .gallery-editor-container input[type="range"]:active::-moz-range-thumb, .ge-layer-opacity:active::-moz-range-thumb, .ge-layer-opacity.dragging::-moz-range-thumb { width: 22px !important; height: 22px !important; } /* Slider value bubble on mobile — bigger text/padding for readability. Position is JS-driven via `position: fixed` from the base rule, so don't override top/bottom here — `bottom: 100%` with fixed positioning was pushing the bubble off-screen. */ .ge-slider-bubble { font-size: 14px !important; padding: 5px 10px !important; } /* Editor topbar — scrolls horizontally on narrow viewports. Buttons fill more vertical+horizontal space so each is a comfortable finger-tap target instead of feeling cramped. */ .ge-topbar { overflow-x: auto; overflow-y: hidden; flex-wrap: nowrap; justify-content: flex-start; gap: 6px; padding: 3px 8px; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .ge-topbar::-webkit-scrollbar { display: none; } .ge-topbar-left, .ge-topbar-right { flex-shrink: 0; gap: 6px; } .ge-alpha-badge { display: none; } #ge-undo { position: sticky !important; left: 8px; background: var(--panel); z-index: 8; box-shadow: 8px 0 10px -10px color-mix(in srgb, var(--fg) 55%, transparent); } .ge-topbar .ge-btn, .ge-topbar .ge-btn-sm { padding: 4px 9px !important; font-size: 12px !important; min-height: 28px; line-height: 1; top: 0 !important; } .ge-topbar .ge-btn svg, .ge-topbar .ge-btn-sm svg { width: 16px; height: 16px; } .ge-topbar select, .ge-topbar input, .ge-topbar .ge-ai-model { padding: 6px 8px !important; font-size: 12px !important; min-height: 32px; top: 0 !important; } .ge-topbar .ge-canvas-size { font-size: 12px !important; padding: 0 6px; line-height: 34px; } #ge-history-panel { left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important; width: auto !important; max-height: min(60dvh, 420px); border-radius: 12px 12px 0 0 !important; z-index: 10001 !important; box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35); } #ge-history-panel .ge-history-head { cursor: default; } #ge-shortcuts-btn { display: none !important; } /* Topbar popups that need real interaction (Resize sliders, Edge form, Save with sub-items) become bottom-sheet slide-ups on mobile so there's room to interact. Image and Filter are tight single-column dropdowns — they stay anchored to their button. */ .ge-resize-menu, .ge-edge-menu, .ge-save-menu { position: fixed !important; left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important; min-width: 0 !important; max-width: 100vw !important; width: auto; padding: 18px 16px 24px !important; background: var(--panel); border: 1px solid var(--border); border-radius: 12px 12px 0 0 !important; box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35); z-index: 10005; animation: ge-controls-slide-up 0.2s ease-out; } /* Image / Filter stay as anchored dropdowns; just cap width so they don't run off-screen on narrow viewports. */ .ge-image-menu, .ge-selection-menu, .ge-filter-menu { max-width: calc(100vw - 16px); } .ge-resize-menu::before, .ge-edge-menu::before, .ge-save-menu::before { content: ''; position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 40px; height: 4px; background: color-mix(in srgb, var(--fg) 30%, transparent); border-radius: 2px; } /* The Edge form needs a bit of breathing room when it spans the viewport; the W/H inputs in the crop apply pop should stay compact. */ .ge-edge-menu { padding: 12px; } .ge-canvas-prompt { width: calc(100vw - 16px) !important; max-width: none !important; } .ge-canvas-prompt-footer { flex-wrap: wrap; } .ge-canvas-prompt-footer .ge-canvas-prompt-hint { flex: 1 0 100%; margin-bottom: 2px; } /* Inpaint floating prompt — keep it inside the canvas area on mobile so it doesn't run off the right edge. */ .ge-inpaint-popup { max-width: calc(100vw - 16px); } .ge-inpaint-popup-input { width: 160px; } /* Tool-specific controls (brush size/color, eraser opacity/flow, lasso feather, inpaint prompt panel, etc.) lift out of the right panel and dock as a slide-up bottom sheet so they have actual room to breathe on a phone. A short grab-handle visually separates it from the canvas. */ .ge-controls { position: fixed !important; left: 0; right: 0; bottom: 0; z-index: 60; border-bottom: none; border-top: 1px solid var(--border); background: var(--panel); border-radius: 12px 12px 0 0; box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35); max-height: 60vh; overflow-y: auto; padding: 18px 16px 24px; gap: 12px; /* Slide-up entrance animation when the controls become visible. */ animation: ge-controls-slide-up 0.18s ease-out; } .ge-controls::before { content: ''; position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 40px; height: 4px; background: color-mix(in srgb, var(--fg) 30%, transparent); border-radius: 2px; } @keyframes ge-controls-slide-up { from { transform: translateY(100%); } to { transform: translateY(0); } } /* User swiped the sheet down — keep tool active but hide its controls. Re-tapping the same tool button toggles back. */ .ge-controls.dismissed { transform: translateY(100%); pointer-events: none; /* Smooth slide-out matching the slide-in duration. */ animation: none; transition: transform 0.18s ease-in; } /* The layers right-panel (z 50) and the controls sheet (z 60) both dock to the bottom on phones, covering the footer's history group and making Undo/Redo unreachable by touch. Lift the history controls above both sheets so a touch stroke can still be undone. */ .ge-footer-history { position: relative; z-index: 65; } /* Once moved into the layer-sheet header the pill no longer needs the overlay z-index/opacity lift — keep it crisp and flush right. */ .ge-layers-header .ge-footer-history { z-index: auto; opacity: 1; margin-left: auto; } } /* ── Group Chat ────────────────────────────────────── */ .msg-group .role { font-weight: 600; } #group-toggle-btn.active, .overflow-menu-item#overflow-group-btn.active { color: var(--red); background: color-mix(in srgb, var(--red) 12%, transparent); } /* Group model picker — match app theme */ #group-model-picker .modal-content { background: var(--bg); color: var(--fg); border: 1px solid var(--border); } #group-model-picker .modal-header { border-bottom: 1px solid var(--border); } #group-model-picker .memory-item { border-radius: 6px; transition: background 0.1s; } #group-model-picker .memory-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } /* Select and Mask workspace */ .ge-sam-overlay { position: fixed; inset: 0; z-index: 10050; display: flex; align-items: center; justify-content: center; background: rgba(3, 5, 8, 0.72); } .ge-sam-panel { width: min(560px, 94vw); max-height: 92vh; overflow: auto; display: flex; flex-direction: column; background: var(--panel, #14181f); color: var(--fg, #e8ecf1); border: 1px solid var(--border, #2a313c); border-radius: 10px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55); } .ge-sam-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border, #2a313c); } .ge-sam-title { font-weight: 600; font-size: 13px; } .ge-sam-output-label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--color-muted, #97a1ad); } .ge-sam-body { display: flex; gap: 14px; padding: 14px; flex-wrap: wrap; } .ge-sam-preview { background: #11151b; border: 1px solid var(--border, #2a313c); border-radius: 6px; cursor: crosshair; touch-action: none; } .ge-sam-controls { flex: 1 1 200px; min-width: 200px; display: flex; flex-direction: column; gap: 8px; font-size: 11px; } .ge-sam-controls label { display: flex; flex-direction: column; gap: 2px; } .ge-sam-brush-row { display: flex; gap: 6px; } .ge-sam-brush-add.active, .ge-sam-brush-subtract.active { border-color: var(--accent, #4c8bf5); color: var(--accent, #4c8bf5); } .ge-sam-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border, #2a313c); } /* Color & analysis dock panels (Phase 7) */ .ge-panel { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; color: var(--fg, #e6e9ef); } .ge-panel-title { font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--color-muted, #8b93a1); } .ge-panel-empty { font-size: 12px; opacity: .6; padding: 6px 0; } .ge-swatch-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(20px, 1fr)); gap: 4px; } .ge-swatch { width: 100%; aspect-ratio: 1 / 1; border: 1px solid var(--border, #2a313c); border-radius: 4px; cursor: pointer; padding: 0; } .ge-swatch.active { outline: 2px solid var(--accent, #4c8bf5); outline-offset: 1px; } .ge-current-swatch { height: 26px; border-radius: 5px; border: 1px solid var(--border, #2a313c); } /* SWATCHES panel hosts the shared color picker's trigger swatch (colorPicker.js) and, next to it, harmony Suggestions + Recent colours. */ .ge-swatch-body { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; } .ge-swatch-body .ge-color-input.cp-swatch-input { width: 44px; height: 44px; flex: 0 0 auto; min-width: 0; border-radius: 8px; border: 1px solid var(--border, #2a313c); cursor: pointer; background: none; } .ge-swatch-body .ge-color-input.cp-swatch-input::selection { background: transparent; } .ge-swatch-col { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-width: 0; } .ge-swatch-row-label { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.78; } .ge-swatch-row { display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 4px; scrollbar-width: none; } .ge-swatch-row::-webkit-scrollbar { display: none; } .ge-mini-swatch { width: 16px; height: 16px; padding: 0; flex: 0 0 auto; border: 1px solid color-mix(in srgb, var(--fg) 22%, transparent); border-radius: 4px; cursor: pointer; } .ge-mini-swatch:hover { transform: scale(1.12); } .ge-mini-empty { font-size: 10px; opacity: 0.45; } /* Always-visible SV + hue picker inside the SWATCHES panel (panels.js). Reuses the shared .cp-sl/.cp-hue classes; this container just lays them out inline instead of inside the floating .cp-popover, so the panel reads as a picker without a click. */ .ge-swatch-body { flex-wrap: wrap; } .ge-inline-picker { display: flex; flex-direction: column; gap: 4px; flex: 1 1 100%; width: 100%; margin-top: 6px; } .ge-inline-picker .cp-sl { height: 80px; } .ge-inline-picker .cp-hue { margin-top: 4px; } .ge-hex-row { display: flex; align-items: center; gap: 4px; flex: 1 1 100%; width: 100%; margin-top: 6px; background: color-mix(in srgb, var(--fg) 6%, transparent); border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; } .ge-hex-hash { color: color-mix(in srgb, var(--fg) 65%, transparent); font-size: 12px; font-weight: 600; } .ge-hex-input { flex: 1 1 auto; min-width: 0; background: transparent; border: 0; outline: 0; color: var(--fg); font: inherit; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; padding: 0; } .ge-gradient-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 6px; } .ge-gradient-swatch { height: 24px; border-radius: 5px; border: 1px solid var(--border, #2a313c); cursor: pointer; padding: 0; } .ge-channel-list { display: flex; flex-direction: column; gap: 2px; max-height: 220px; overflow-y: auto; } .ge-channel-row { display: flex; align-items: center; gap: 6px; padding: 4px 6px; border-radius: 5px; cursor: pointer; font-size: 12px; } .ge-channel-row.active { background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent); } .ge-channel-eye { width: 20px; text-align: center; opacity: .8; } .ge-channel-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-histogram-canvas, .ge-navigator-canvas { width: 100%; background: #0b0d10; border: 1px solid var(--border, #2a313c); border-radius: 5px; } .ge-navigator-canvas { cursor: grab; touch-action: none; } .ge-navigator-canvas:active { cursor: grabbing; } .ge-info-rows { display: flex; flex-direction: column; gap: 4px; } .ge-info-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; } .ge-info-label { color: var(--color-muted, #8b93a1); } .ge-info-value { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; } .ge-info-swatch { width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--border, #2a313c); display: inline-block; } /* Command palette (Phase 11) */ .ge-command-palette { position: fixed; inset: 0; z-index: 10100; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; background: rgba(6, 8, 11, 0.45); } .ge-command-palette[hidden] { display: none; } .ge-command-panel { width: min(520px, 92vw); max-height: 60vh; display: flex; flex-direction: column; background: var(--panel, #161a21); border: 1px solid var(--border, #2a313c); border-radius: 10px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); overflow: hidden; } .ge-command-input { width: 100%; box-sizing: border-box; padding: 12px 14px; border: 0; border-bottom: 1px solid var(--border, #2a313c); background: transparent; color: var(--fg, #e6e9ef); font-size: 14px; outline: none; } .ge-command-list { overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px; } .ge-command-item { text-align: left; padding: 8px 10px; border: 0; border-radius: 6px; background: transparent; color: var(--fg, #e6e9ef); font-size: 13px; cursor: pointer; } .ge-command-item.active, .ge-command-item:hover { background: var(--accent, #4c8bf5); color: #fff; } .ge-command-empty { margin: 0; padding: 12px; color: var(--color-muted, #8b93a1); font-size: 13px; text-align: center; } /* Extra studio dock panels (Properties / Character / Paths / Patterns / Adjustments / History) */ .ge-panel-body { display: flex; flex-direction: column; gap: 8px; padding: 8px 10px; } .ge-text-align-row { display: flex; gap: 4px; } .ge-path-list { gap: 4px; } .ge-adjustment-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px; padding: 4px 6px; } .ge-adjustment-grid .ge-btn { width: 100%; text-align: left; } /* Actions panel (Phase 11) */ .ge-actions-name-row, .ge-actions-add-row, .ge-actions-controls, .ge-actions-batch-row { display: flex; gap: 6px; } .ge-actions-batch-select { flex: 1 1 auto; min-width: 0; } .ge-actions-name { flex: 1 1 auto; min-width: 0; } .ge-actions-command { flex: 1 1 auto; min-width: 0; } .ge-actions-list { display: flex; flex-direction: column; gap: 4px; max-height: 240px; overflow-y: auto; } .ge-actions-step, .ge-actions-saved-row { display: flex; align-items: center; gap: 6px; } .ge-actions-step-index { width: 18px; color: var(--color-muted, #8b93a1); font-variant-numeric: tabular-nums; } .ge-actions-step-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ge-action-remove { border: none; background: none; color: var(--color-muted, #8b93a1); cursor: pointer; } .ge-actions-controls .ge-btn.active { border-color: var(--accent, #4c8bf5); color: var(--accent, #4c8bf5); } /* Studio dock panels (PhotoCraft parity: Properties/Character/Adjustments/Paths/Patterns/History) */ .ge-panel-body { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; } .ge-text-align-row { display: flex; gap: 4px; } .ge-adjustment-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; padding: 4px 6px; } .ge-adjustment-grid .ge-btn { width: 100%; justify-content: flex-start; font-size: 11px; } .ge-path-list { gap: 4px; } /* ── Full-viewport editor (Photoshop-style) ────────────────────────────── The editor opens filling the whole viewport instead of the small gallery sheet. `#ge-maximize-toggle` flips to a remembered windowed mode by adding `.ge-editor-windowed` to #gallery-editor-container. */ .gallery-modal-content:has(#gallery-editor-container:not(.ge-editor-windowed)) { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; margin: 0; border-radius: 0; } #gallery-modal:has(#gallery-editor-container:not(.ge-editor-windowed)) { padding: 0; align-items: stretch; /* Escape the desktop "chat area" narrowing (left = icon-rail + sidebar) so the editor really fills the whole viewport, over the nav. */ left: 0; width: 100vw; } #ge-maximize-toggle.is-windowed { color: var(--fg); } /* Multi-document editor tabs live in the gallery tab bar. The container is display:contents so each extra tab is a direct flex child of .gallery-tabs, matching the primary #gallery-editor-tab. */ .gallery-editor-tabs-extra { display: contents; } .gallery-tab.gallery-tab-new { min-width: 26px; justify-content: center; font-size: 16px; font-weight: 600; opacity: 0.75; } .gallery-tab.gallery-tab-new:hover { opacity: 1; } /* Draggable dialog grab bars (Image Size / canvas-size prompt). */ .ge-draggable-handle { cursor: move; user-select: none; touch-action: none; } .ge-draggable-handle:active { cursor: grabbing; } /* ============================================================================ * Editor control kit * ---------------------------------------------------------------------------- * One shared, theme-token style for the interactive controls scattered across * the editor (options bar, adjustment dialogs, Image/Canvas Size, transform * popup, layers, brush settings, filters). control-kit.js adds the .ge-ctl* * classes; these rules make every such control look like the rest of Odysseus * (matches .cookbook-slider / .admin-slider / .toggle-switch). Older, more * specific rules (transform popup, eraser/fx rows) still win on purpose. * ==========================================================================*/ /* -- Range sliders --------------------------------------------------------*/ .ge-ctl-range { -webkit-appearance: none; appearance: none; max-width: 100%; height: 14px; margin: 0; padding: 0; background: transparent; cursor: pointer; vertical-align: middle; --ge-pct: 50%; --ge-accent: var(--accent, var(--red)); } .ge-ctl-range::-webkit-slider-runnable-track { height: 6px; border-radius: 4px; background: linear-gradient( to right, var(--ge-accent) 0 var(--ge-pct), color-mix(in srgb, var(--fg) 14%, transparent) var(--ge-pct) 100% ); } .ge-ctl-range::-moz-range-track { height: 6px; border-radius: 4px; background: color-mix(in srgb, var(--fg) 14%, transparent); } .ge-ctl-range::-moz-range-progress { height: 6px; border-radius: 4px; background: var(--ge-accent); } .ge-ctl-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; margin-top: -4px; border-radius: 50%; background: var(--fg); border: 2px solid var(--panel); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35); cursor: pointer; transition: transform 0.1s, box-shadow 0.1s; } .ge-ctl-range::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: var(--fg); border: 2px solid var(--panel); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35); cursor: pointer; } .ge-ctl-range:hover::-webkit-slider-thumb { transform: scale(1.15); box-shadow: 0 0 0 4px color-mix(in srgb, var(--ge-accent) 22%, transparent); } .ge-ctl-range:active::-webkit-slider-thumb { transform: scale(1.25); box-shadow: 0 0 0 6px color-mix(in srgb, var(--ge-accent) 28%, transparent); } .ge-ctl-range:hover::-moz-range-thumb { box-shadow: 0 0 0 4px color-mix(in srgb, var(--ge-accent) 22%, transparent); } .ge-ctl-range:focus-visible { outline: none; } .ge-ctl-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--panel), 0 0 0 5px color-mix(in srgb, var(--ge-accent) 55%, transparent); } .ge-ctl-range:disabled { opacity: 0.45; cursor: default; } .ge-ctl-range:disabled::-webkit-slider-thumb { transform: none; box-shadow: none; } /* -- Number fields (no native spinners) -----------------------------------*/ .ge-num-field { position: relative; display: inline-flex; align-items: center; } .ge-ctl-num { width: auto; max-width: 100%; height: 26px; padding: 0 6px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; font-variant-numeric: tabular-nums; text-align: right; -moz-appearance: textfield; appearance: textfield; box-sizing: border-box; } .ge-ctl-num::-webkit-outer-spin-button, .ge-ctl-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .ge-ctl-num:focus { outline: none; border-color: var(--accent, var(--red)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } .ge-ctl-num:disabled, .ge-ctl-num[readonly] { opacity: 0.5; cursor: default; } /* Small stacked/horizontal stepper next to a number field */ .ge-stepper { display: inline-flex; flex-direction: column; margin-left: 3px; border-radius: 4px; overflow: hidden; border: 1px solid var(--border); } .ge-stepper button { width: 16px; height: 12px; padding: 0; border: 0; background: color-mix(in srgb, var(--fg) 8%, transparent); color: var(--fg); font-size: 9px; line-height: 1; cursor: pointer; } .ge-stepper button:hover { background: var(--accent, var(--red)); color: var(--panel); } .ge-stepper button:active { background: color-mix(in srgb, var(--accent, var(--red)) 75%, #000); color: var(--panel); } /* -- Selects --------------------------------------------------------------*/ .ge-ctl-select { height: 26px; max-width: 100%; padding: 0 22px 0 8px; background-color: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; appearance: none; -webkit-appearance: none; background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); background-position: calc(100% - 12px) 11px, calc(100% - 8px) 11px; background-size: 4px 4px, 4px 4px; background-repeat: no-repeat; cursor: pointer; } .ge-ctl-select:hover { border-color: color-mix(in srgb, var(--fg) 45%, var(--border)); } .ge-ctl-select:focus { outline: none; border-color: var(--accent, var(--red)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } .ge-ctl-select option { background: var(--select-option-bg, var(--panel)); color: var(--select-option-fg, var(--fg)); } /* -- Checkboxes -----------------------------------------------------------*/ input.ge-ctl-check { appearance: none; -webkit-appearance: none; width: 14px; height: 14px; margin: 0; border: 1px solid var(--border); border-radius: 3px; background: var(--bg); cursor: pointer; position: relative; vertical-align: middle; flex: 0 0 auto; } input.ge-ctl-check:hover { border-color: color-mix(in srgb, var(--fg) 45%, var(--border)); } input.ge-ctl-check:checked { background: var(--accent, var(--red)); border-color: var(--accent, var(--red)); } input.ge-ctl-check:checked::after { content: ''; position: absolute; left: 4px; top: 1px; width: 3px; height: 7px; border: solid var(--panel); border-width: 0 2px 2px 0; transform: rotate(45deg); } input.ge-ctl-check:focus-visible { outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); } /* -- Small icon/text buttons ----------------------------------------------*/ .ge-ctl-btn { height: 24px; min-width: 24px; padding: 0 8px; display: inline-flex; align-items: center; justify-content: center; gap: 4px; background: color-mix(in srgb, var(--fg) 6%, transparent); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; cursor: pointer; } .ge-ctl-btn:hover { background: color-mix(in srgb, var(--fg) 14%, transparent); } .ge-ctl-btn:active { background: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); } .ge-ctl-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); } .ge-ctl-btn:disabled { opacity: 0.45; cursor: default; } /* Scrubby numeric label — drag horizontally to change the value */ .ge-scrub { cursor: ew-resize; user-select: none; touch-action: none; } .ge-scrub::after { content: '\2194'; font-size: 8px; opacity: 0.4; margin-left: 3px; } .ge-scrub.ge-scrubbing { color: var(--accent, var(--red)); } /* On touch/mobile the topbar menus are icon/touch driven — hide keyboard shortcut hints. */ @media (max-width: 820px) { .ge-topbar .dropdown .dropdown-shortcut { display: none; } } /* Editor landing ("Image Editor" chooser + Saved projects) — keep it scrollable at EVERY width, not just <=620px. The base rule centres the content vertically (justify-content: center) with no overflow; once the saved-project list grows taller than the sheet (worst in list view), flex centring pushes the launch controls AND the grid/list toggle ABOVE the sheet, while #gallery-editor-container's overflow: hidden makes that top region unreachable — the user could not scroll back and got stuck. Using auto margins to centre collapses them to 0 on overflow, so the top stays reachable while short content still looks vertically centred. */ .gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing { justify-content: flex-start; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; } .gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing::before, .gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing::after { content: ''; flex: 0 0 auto; margin: auto; } /* Tool-aware canvas right-click menu (canvas-context-menu.js). */ .ge-canvas-menu { position: fixed; z-index: 100000; min-width: 208px; padding: 5px; background: #2b2b2b; border: 1px solid #454545; border-radius: 6px; box-shadow: 0 10px 32px rgba(0, 0, 0, 0.55); font-size: 13px; color: #e6e6e6; user-select: none; } .ge-canvas-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 10px; border: 0; border-radius: 4px; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; } .ge-canvas-menu-item:hover:not([disabled]), .ge-canvas-menu-item:focus-visible:not([disabled]) { background: #3d6ea5; color: #fff; outline: none; } .ge-canvas-menu-item[disabled] { color: #6d6d6d; cursor: default; } .ge-canvas-menu-lead { width: 12px; flex: 0 0 12px; text-align: center; color: #9ecbff; } .ge-canvas-menu-label { flex: 1 1 auto; white-space: nowrap; } .ge-canvas-menu-hint { flex: 0 0 auto; margin-left: 20px; color: #9a9a9a; font-size: 11px; } .ge-canvas-menu-item:hover .ge-canvas-menu-hint { color: #d8e6f5; } .ge-canvas-menu-caret { flex: 0 0 auto; margin-left: 16px; color: #b8b8b8; } .ge-canvas-menu-sep { height: 1px; margin: 4px 6px; background: #454545; } .ge-canvas-menu-sub { min-width: 172px; max-height: 60vh; overflow-y: auto; } /* Editor setup checklist (preflight) — shown over the canvas on open. */ .ge-preflight { position: absolute; inset: 0; z-index: 260; display: flex; align-items: center; justify-content: center; padding: 16px; background: rgba(8, 10, 14, 0.55); backdrop-filter: blur(2px); } .ge-preflight[hidden] { display: none; } .ge-preflight-card { width: min(560px, 100%); max-height: 100%; overflow: auto; background: #23262c; color: #e8eaed; border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 12px; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5); padding: 16px 18px; } .ge-preflight-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } .ge-preflight-head h3 { margin: 0 0 3px; font-size: 15px; } .ge-preflight-head p { margin: 0; font-size: 12px; color: #a9b0ba; } .ge-preflight-close { background: transparent; border: 0; color: #a9b0ba; font-size: 20px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px; } .ge-preflight-close:hover { background: rgba(255, 255, 255, 0.08); color: #fff; } .ge-preflight-rows { display: flex; flex-direction: column; gap: 8px; margin: 14px 0; } .ge-preflight-row { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 9px; background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.06); } .ge-preflight-row.is-done { border-color: rgba(90, 200, 130, 0.35); } .ge-preflight-row.is-todo { border-color: rgba(230, 120, 110, 0.4); } .ge-preflight-badge { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 1px; } .ge-preflight-row.is-done .ge-preflight-badge { background: rgba(70, 180, 110, 0.2); color: #5fd08a; } .ge-preflight-row.is-todo .ge-preflight-badge { background: rgba(220, 90, 80, 0.18); color: #ff8a7d; } .ge-preflight-row.is-optional { border-color: rgba(210, 180, 90, 0.35); } .ge-preflight-row.is-optional .ge-preflight-badge { background: rgba(210, 180, 90, 0.16); color: #e0c266; } .ge-preflight-opt { margin-left: 6px; padding: 1px 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); color: var(--fg-muted); font-size: 9px; letter-spacing: 0.05em; text-transform: uppercase; vertical-align: middle; } .ge-preflight-body { flex: 1 1 auto; min-width: 0; } .ge-preflight-title { font-size: 13px; font-weight: 600; } .ge-preflight-hint { font-size: 11.5px; color: #a9b0ba; margin-top: 2px; } .ge-preflight-detail { font-size: 11px; color: #7f8792; margin-top: 3px; font-style: italic; } .ge-preflight-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; } .ge-preflight-act { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.14); color: #dfe3e8; font-size: 11px; padding: 4px 9px; border-radius: 7px; cursor: pointer; white-space: nowrap; } .ge-preflight-act:hover { background: rgba(255, 255, 255, 0.16); } .ge-preflight-manual { display: flex; align-items: center; cursor: pointer; } .ge-preflight-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid rgba(255, 255, 255, 0.08); padding-top: 12px; } .ge-preflight-remember { font-size: 11.5px; color: #a9b0ba; display: flex; align-items: center; gap: 6px; cursor: pointer; } .ge-preflight-foot-actions { display: flex; align-items: center; gap: 10px; } .ge-preflight-summary { font-size: 11.5px; color: #8fd6a8; } .ge-preflight-start { background: #3d6ea5; border: 0; color: #fff; font-size: 12.5px; font-weight: 600; padding: 7px 16px; border-radius: 8px; cursor: pointer; } .ge-preflight-start:hover { background: #4a80bd; } .ge-setup-btn { background: transparent; border: 1px solid rgba(255, 255, 255, 0.16); color: #c8cdd4; font-size: 11px; padding: 2px 8px; border-radius: 6px; cursor: pointer; margin-left: 8px; } .ge-setup-btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; } @media (max-width: 700px) { .ge-preflight-card { padding: 13px 14px; } } /* ── Quick Edit — premium look ────────────────────────────────────────── */ @property --ge-qe-angle { syntax: ''; initial-value: 0deg; inherits: false; } .ge-ai-command { border: 1px solid transparent; border-radius: 15px; padding: 9px; background: linear-gradient( color-mix(in srgb, var(--panel, #232323) 95%, transparent), color-mix(in srgb, var(--panel, #232323) 90%, transparent) ) padding-box, conic-gradient(from 215deg, color-mix(in srgb, var(--accent, var(--red)) 78%, transparent), color-mix(in srgb, #7aa2ff 52%, transparent) 34%, color-mix(in srgb, var(--accent, var(--red)) 18%, transparent) 58%, color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) ) border-box; box-shadow: 0 16px 44px color-mix(in srgb, #000 46%, transparent), 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 10%, transparent), inset 0 1px 0 color-mix(in srgb, #fff 9%, transparent); backdrop-filter: blur(16px) saturate(1.25); } .ge-ai-command::before { content: ""; position: absolute; inset: -1px; z-index: 0; border-radius: inherit; padding: 1px; background: conic-gradient(from var(--ge-qe-angle), transparent 0 60%, color-mix(in srgb, var(--accent, var(--red)) 88%, transparent) 78%, color-mix(in srgb, #d6e3ff 96%, transparent) 84%, transparent 92%); -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0.8; pointer-events: none; animation: ge-qe-arc 7s linear infinite; } @keyframes ge-qe-arc { to { --ge-qe-angle: 360deg; } } .ge-ai-command-head { padding-bottom: 8px; } .ge-ai-command-title { letter-spacing: 0.03em; background: linear-gradient(90deg, color-mix(in srgb, var(--fg) 92%, #fff), color-mix(in srgb, var(--accent, var(--red)) 70%, var(--fg))); -webkit-background-clip: text; background-clip: text; color: transparent; } .ge-ai-command .ge-btn-ai-mark { background: linear-gradient(135deg, color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 22%), #9ec1ff); -webkit-background-clip: text; background-clip: text; color: transparent; animation: ge-qe-spark 3.6s ease-in-out infinite; } @keyframes ge-qe-spark { 0%, 100% { opacity: 0.82; transform: scale(1) rotate(0); } 50% { opacity: 1; transform: scale(1.14) rotate(18deg); } } .ge-ai-command-input { height: 36px; border-radius: 10px; border-color: color-mix(in srgb, var(--accent, var(--red)) 22%, var(--border)); background: linear-gradient(color-mix(in srgb, #fff 5%, transparent), transparent 55%), color-mix(in srgb, var(--bg) 72%, #000 28%); box-shadow: inset 0 1px 2px color-mix(in srgb, #000 42%, transparent); transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; } .ge-ai-command-input:focus { border-color: color-mix(in srgb, var(--accent, var(--red)) 82%, transparent); box-shadow: inset 0 1px 2px color-mix(in srgb, #000 42%, transparent), 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); } .ge-ai-command-clear { height: 36px; } .ge-ai-command-run { height: 36px !important; min-width: 36px; width: 36px; border-radius: 10px; background: linear-gradient(135deg, color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 14%), color-mix(in srgb, var(--accent, var(--red)) 62%, #6f8dff)); box-shadow: 0 6px 18px color-mix(in srgb, var(--accent, var(--red)) 46%, transparent), inset 0 1px 0 color-mix(in srgb, #fff 32%, transparent); transition: transform 0.12s ease, box-shadow 0.16s ease, filter 0.16s ease, background 0.16s ease; } .ge-ai-command-run:hover { background: linear-gradient(135deg, color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 20%), color-mix(in srgb, var(--accent, var(--red)) 60%, #7a95ff)); filter: brightness(1.06); transform: translateY(-1px); box-shadow: 0 9px 24px color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); } .ge-ai-command-run:active { transform: translateY(0); filter: brightness(0.98); } .ge-ai-command-suggestions { border-radius: 12px; border-color: color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); background: color-mix(in srgb, var(--panel, #232323) 97%, transparent); box-shadow: 0 16px 36px color-mix(in srgb, #000 42%, transparent); } .ge-ai-command-suggestion { border-radius: 8px; transition: background 0.12s ease, transform 0.12s ease; } .ge-ai-command-suggestion:hover, .ge-ai-command-suggestion.active { background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); transform: translateX(1px); } .ge-ai-command-status { font-weight: 600; } .ge-ai-command-collapsed { border-radius: 999px; padding: 3px 13px; } .ge-ai-command-collapsed:hover, .ge-ai-command-collapsed:focus-within { box-shadow: 0 10px 26px color-mix(in srgb, #000 30%, transparent), 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); } @media (prefers-reduced-motion: reduce) { .ge-ai-command::before, .ge-ai-command .ge-btn-ai-mark { animation: none; } } /* Quick Edit routing-mode selector (Auto / Quick / Agent / Image): uses the standard ctl-select look, pushed to the right of the header row. */ .ge-ai-command .ge-ai-command-mode { margin-left: auto; margin-right: 6px; max-width: 104px; } /* Mobile footer fix (audit 2026-10-11 #1): the footer overflowed horizontally (scrollWidth 532 vs clientWidth 378) so the zoom controls sat off-screen. On phones drop the wide text readouts and tighten the controls so history + zoom fit. */ @media (max-width: 700px) { .ge-editor-footer { gap: 6px; padding: 3px 6px; overflow: hidden; min-width: 0; } .ge-editor-footer .ge-status-tool, .ge-editor-footer .ge-status-model, .ge-editor-footer .ge-canvas-size { display: none; } .ge-footer-history { margin-right: 2px; } .ge-footer-zoom { gap: 3px; min-width: 0; } .ge-footer-zoom > .ge-btn, .ge-footer-history > .ge-btn { min-width: 26px; } /* Float the footer above the peek/expanded layers sheet so its zoom/setup controls stay tappable (position/left/right/bottom set in JS). */ .ge-editor-footer.ge-footer-floating { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28); } } /* EVAL: an editor toast fired while a floating panel is open (Adjust/Develop, FX, Transform, filter modal, History) is moved to the bottom-centre by ui.js so it never covers the panel's controls. */ body.ge-editor-open .toast.toast-editor-bottom { top: auto; bottom: 84px; left: 12px; right: 12px; max-width: none; min-width: 0; } /* Audit #7 / EVAL: on a phone, a toast at the viewport top overlaps the editor's menu row AND the top-docked Transform/Adjust/FX popups (Develop, Filters). While the editor is open, anchor shared toasts at the bottom, centred just above the floating footer, so they never cover the controls. The horizontal slide animation is preserved (the toast stays full-width). */ @media (max-width: 700px) { body.ge-editor-open .toast { top: auto; bottom: 84px; left: 12px; right: 12px; max-width: none; min-width: 0; } } /* Mobile touch targets (audit 2026-10-11 #3): keep the primary editor controls at least 44x44 CSS px for a comfortable tap. Scoped to the open editor on phones — desktop is untouched. */ @media (max-width: 700px) { .ge-toolbar .ge-tool-btn { min-width: 44px; min-height: 44px; height: 44px; } /* Tap targets: only the primary in-sheet controls (layers footer, editor footer, panels overlay) get the full 44px box. Applying this to every editor button inflated the top menu row and clipped its labels, so the topbar/menu buttons keep their natural size. */ .ge-layers-footer .ge-btn, .ge-editor-footer .ge-btn, .ge-mobile-panels .ge-btn { min-height: 44px; min-width: 44px; } body.ge-editor-open .ge-ai-command-toggle, body.ge-editor-open .ge-setup-btn { min-height: 44px; } body.ge-editor-open .ge-layer-opacity, body.ge-editor-open .ge-ctl-select, body.ge-editor-open .ge-ctl-num { min-height: 44px; } /* Horizontal-scroll fix (audit 2026-10-11 #4): the menu row and tool rail stay single-row (wrapping them made the chrome 2-3x taller), but their sideways scroll is contained so it never drags the page or overshoots the fade. */ .ge-topbar-left, .ge-toolbar, .ge-dock-tabs { overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; } } /* Colour panels: chips are round and the redundant inner title is dropped (the dock tab strip already names each panel). Adjustments get SVG icons. */ .ge-mini-swatch, .ge-swatch-grid .ge-swatch, .ge-swatch-body .ge-color-input.cp-swatch-input { border-radius: 50%; } .ge-gradient-swatch { border-radius: 999px; } #ge-panel-swatches > .ge-panel-title, #ge-panel-gradients > .ge-panel-title, #ge-panel-patterns > .ge-panel-title, #ge-panel-adjustments > .ge-panel-title, #ge-panel-properties > .ge-panel-title, #ge-panel-histogram > .ge-panel-title { display: none; } .ge-adjustment-grid .ge-adj-btn { display: flex; align-items: center; gap: 5px; min-height: 0; padding: 3px 6px; } .ge-adj-icon { display: inline-flex; flex: 0 0 auto; } .ge-adj-icon svg { width: 14px; height: 14px; display: block; } .ge-adj-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Mobile panel access (≤ 700 px). The desktop dock is bypassed on phones, so the studio panels are mounted in a compact overlay opened from a "Panels" button in the layers header (see right-panel.js). */ @media (max-width: 700px) { .ge-layers-panels-btn { order: 1; margin-left: 8px; min-height: 44px; min-width: 44px; padding: 0 12px; border-radius: 8px; } .ge-mobile-panels { position: fixed; left: 0; right: 0; bottom: 0; z-index: 70; display: flex; flex-direction: column; max-height: 62vh; background: var(--panel, #1b1f27); border-top: 1px solid var(--border, #2a313c); border-radius: 12px 12px 0 0; box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.35); overflow: hidden; } .ge-mobile-panels[hidden] { display: none; } .ge-mobile-panels-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--border, #2a313c); } .ge-mobile-panels-title { font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; } .ge-mobile-panels-close { min-width: 44px; min-height: 44px; } .ge-mobile-panels .ge-dock-tabbar { padding: 8px 12px 4px; } .ge-mobile-panels .ge-dock-select { width: 100%; min-height: 44px; } .ge-mobile-panels .ge-dock-panels { overflow-y: auto; padding: 4px 12px 16px; } } /* Mobile layer rows: only the ACTIVE layer shows its opacity slider, and the slider stays a thin fixed-width track instead of stretching edge-to-edge over the thumbnail and the fx/action strip. The panel's Blend/Opacity row is the primary touch control for opacity. */ @media (max-width: 700px) { .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row { display: none; } .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-opacity-row { display: flex; } body.ge-editor-open .ge-layer-item .ge-layer-opacity { min-height: 0 !important; height: 18px !important; flex: 0 0 auto !important; width: 140px !important; } } /* Mobile layers footer: keep the add / fx / merge / Fill tools on ONE row (they were wrapping and orphaning "Fill" on its own line). */ @media (max-width: 700px) { .ge-layers-footer { flex-wrap: nowrap; gap: 4px; overflow-x: auto; -webkit-overflow-scrolling: touch; } .ge-layers-footer .ge-layer-header-tools, .ge-layers-footer .ge-layer-tools { flex-wrap: nowrap; gap: 3px; } /* Spread the tool row across the full sheet width instead of leaving a big empty right gutter (the empty #ge-layer-tools hook used to hold the right edge via space-between). */ .ge-layers-footer .ge-layer-header-tools { flex: 1 1 auto; justify-content: space-between; } .ge-layers-footer .ge-layer-tools:empty { display: none; } .ge-layers-footer .ge-btn { min-width: 40px !important; flex: 0 0 auto; } .ge-layers-footer #ge-layer-fill { min-width: 52px !important; width: auto !important; padding: 0 10px !important; } } /* Tool-rail "more tools" chevron. The rail is wrapped (see galleryEditor.js) so an overlay chevron can sit on its right edge on phones, outside the scrolling element (and thus outside its mask fade). */ .ge-toolbar-wrap { display: contents; } .ge-tool-more { display: none; } @media (max-width: 700px) { .ge-toolbar-wrap { display: flex; flex: 0 0 auto; width: 100%; position: relative; align-items: stretch; } .ge-toolbar-wrap > .ge-toolbar { flex: 1 1 auto; min-width: 0; } .ge-tool-more { display: none; position: absolute; top: 0; bottom: 0; right: 0; width: 22px; align-items: center; justify-content: center; padding: 0; border: none; background: transparent; color: var(--fg, #e6e9ef); font-size: 20px; line-height: 1; cursor: pointer; z-index: 2; } .ge-tool-more.is-visible { display: flex; } /* Keep the floating editor footer from stacking on the Quick Edit card. The card owns the bottom of the canvas while it is open, so the footer (zoom / Setup / AI settings) hides until it closes. */ body:has(.ge-ai-command:not(.ge-ai-command-collapsed)) .ge-editor-footer.ge-footer-floating { display: none; } } /* On phones the Quick Edit card sits between the tool rail and the peek layers sheet, so the suggestion popup cannot grow upward without being clipped by the rail. Show the suggestions as a compact horizontal chip row inside the card instead. */ @media (max-width: 700px) { .ge-ai-command:not(.ge-ai-command-collapsed) { display: flex; flex-direction: column; } .ge-ai-command-head { order: 0; } .ge-ai-command-suggestions { position: static; order: 1; left: auto; right: auto; bottom: auto; z-index: auto; flex-direction: row; flex-wrap: nowrap; gap: 6px; max-height: none; padding: 0 0 6px; border: 0; border-radius: 0; background: transparent; box-shadow: none; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .ge-ai-command-suggestions::-webkit-scrollbar { display: none; } .ge-ai-command-form { order: 2; } .ge-ai-command-status { order: 3; } .ge-ai-command-stack { order: 4; } .ge-ai-command-progress { order: 5; } .ge-ai-command-suggestion { width: auto; flex: 0 0 auto; grid-template-columns: none; gap: 0; min-height: 30px; padding: 5px 11px; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); border-radius: 16px; background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); } .ge-ai-command-suggestion-icon, .ge-ai-command-suggestion-kind, .ge-ai-command-suggestion-hint { display: none; } .ge-ai-command-suggestion-main { white-space: nowrap; font-size: 11px; font-weight: 550; } } /* While the editor is open on a phone the app-shell chat selector sits right above the editor chrome; lift it above the 70% default so it stays legible. */ @media (max-width: 700px) { body.ge-editor-open .chat-meta-overlay { display: none !important; } body.ge-editor-open .tour-hint { display: none !important; } body.ge-editor-open { font-family: var(--font-family, 'Fira Code', monospace); } } /* Phone menu bar: tighten the Photoshop menus and keep the Save control icon-only so the twelve menus + Import + Save are not crowded. */ @media (max-width: 700px) { .ge-topbar-left .ge-stacked-btn { font-size: 11px; padding: 0 6px !important; } .ge-topbar.ge-topbar-compact #ge-draft-status { display: none; } } /* Phone editor: dock the zoom/Setup footer flush to the Layers sheet. The footer spans the full width with square corners so canvas bottom == footer top == sheet top, with no rounded-card seam or dead space between them, and the sheet's grab handle (which would read as a stray strip) is hidden. */ @media (max-width: 700px) { .ge-editor-footer.ge-footer-floating { border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0; } body.ge-footer-docked .ge-right-panel .ge-layers-grab { display: none !important; } } /* Long dropdown menus (Filter especially) can grow past the viewport bottom; cap them to the space below the topbar and let them scroll. */ .ge-image-menu, .ge-layer-menu, .ge-filter-menu, .ge-edit-menu, .ge-selection-menu, .ge-type-menu, .ge-3d-menu, .ge-view-menu, .ge-window-menu, .ge-plugins-menu, .ge-help-menu { max-height: min(560px, 70vh) !important; overflow-y: auto !important; overscroll-behavior: contain; scrollbar-width: auto !important; scrollbar-color: auto !important; -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background-color: var(--red); border-radius: 4px; } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */ .ge-menu-thumb { position: absolute; top: 0; right: 2px; width: 3px; min-height: 18px; border-radius: 2px; background: var(--red); pointer-events: none; display: none; z-index: 3; } /* Busy veil shown while a heavy pixel filter commits on a large document. */ .ge-busy-veil { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.35); z-index: 60; pointer-events: none; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--fg); font: inherit; font-size: 12px; } .ge-busy-spin { width: 14px; height: 14px; border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent); border-top-color: var(--red); border-radius: 50%; animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Flyout submenu panels: compact rows + a viewport-bounded height so long lists (the 19 Image > Adjustments entries) all fit or scroll. */ .ge-menu-parent + .ge-submenu .dropdown-item-compact { min-height: 0 !important; height: auto !important; padding: 3px 10px !important; line-height: 1.2 !important; font-size: 12px !important; } .ge-submenu { max-height: calc(100vh - 180px) !important; overflow-y: auto !important; scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image > Adjustments entries) fit without clipping, and let the panel scroll. */ .ge-submenu .dropdown-item-compact { padding-top: 2px; padding-bottom: 2px; } .ge-submenu { scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Release the parent menu's overflow clip while a flyout is open so the panel is not cut off at the menu edge. */ .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The onboarding "Pro tip" tour hint floats over the editor menus; hide it while the editor is open so it never occludes a dropdown or flyout. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Long dropdown menus (Filter especially) can grow past the viewport bottom; cap them to the space below the topbar and let them scroll. */ .ge-image-menu, .ge-layer-menu, .ge-filter-menu, .ge-edit-menu, .ge-selection-menu, .ge-type-menu, .ge-3d-menu, .ge-view-menu, .ge-window-menu, .ge-plugins-menu, .ge-help-menu { max-height: min(560px, 70vh) !important; overflow-y: auto !important; overscroll-behavior: contain; scrollbar-width: auto !important; scrollbar-color: auto !important; -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background-color: var(--red); border-radius: 4px; } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */ .ge-menu-thumb { position: absolute; top: 0; right: 2px; width: 3px; min-height: 18px; border-radius: 2px; background: var(--red); pointer-events: none; display: none; z-index: 3; } /* Busy veil shown while a heavy pixel filter commits on a large document. */ .ge-busy-veil { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.35); z-index: 60; pointer-events: none; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--fg); font: inherit; font-size: 12px; } .ge-busy-spin { width: 14px; height: 14px; border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent); border-top-color: var(--red); border-radius: 50%; animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Flyout submenu panels: compact rows + a viewport-bounded height so long lists (the 19 Image > Adjustments entries) all fit or scroll. */ .ge-menu-parent + .ge-submenu .dropdown-item-compact { min-height: 0 !important; height: auto !important; padding: 3px 10px !important; line-height: 1.2 !important; font-size: 12px !important; } .ge-submenu { max-height: calc(100vh - 180px) !important; overflow-y: auto !important; scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image > Adjustments entries) fit without clipping, and let the panel scroll. */ .ge-submenu .dropdown-item-compact { padding-top: 2px; padding-bottom: 2px; } .ge-submenu { scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Release the parent menu's overflow clip while a flyout is open so the panel is not cut off at the menu edge. */ .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The onboarding "Pro tip" tour hint floats over the editor menus; hide it while the editor is open so it never occludes a dropdown or flyout. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Phone editor: while the Quick Edit card is open it owns the bottom of the canvas. Force the layers sheet back to its peek height and hide the floating zoom/Setup footer so neither the sheet nor the footer covers the card. */ @media (max-width: 700px) { body.ge-qe-open .ge-editor-footer.ge-footer-floating { display: none !important; } body.ge-qe-open .ge-right-panel:not(.minimized) { transform: translateY(calc(100% - var(--peek-height, 110px))) !important; } } /* Long dropdown menus (Filter especially) can grow past the viewport bottom; cap them to the space below the topbar and let them scroll. */ .ge-image-menu, .ge-layer-menu, .ge-filter-menu, .ge-edit-menu, .ge-selection-menu, .ge-type-menu, .ge-3d-menu, .ge-view-menu, .ge-window-menu, .ge-plugins-menu, .ge-help-menu { max-height: min(560px, 70vh) !important; overflow-y: auto !important; overscroll-behavior: contain; scrollbar-width: auto !important; scrollbar-color: auto !important; -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent); } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background-color: var(--red); border-radius: 4px; } .ge-image-menu::-webkit-scrollbar, .ge-layer-menu::-webkit-scrollbar, .ge-filter-menu::-webkit-scrollbar, .ge-edit-menu::-webkit-scrollbar, .ge-selection-menu::-webkit-scrollbar, .ge-type-menu::-webkit-scrollbar, .ge-3d-menu::-webkit-scrollbar, .ge-view-menu::-webkit-scrollbar, .ge-window-menu::-webkit-scrollbar, .ge-plugins-menu::-webkit-scrollbar, .ge-help-menu::-webkit-scrollbar { width: 8px; } .ge-image-menu::-webkit-scrollbar-track, .ge-layer-menu::-webkit-scrollbar-track, .ge-filter-menu::-webkit-scrollbar-track, .ge-edit-menu::-webkit-scrollbar-track, .ge-selection-menu::-webkit-scrollbar-track, .ge-type-menu::-webkit-scrollbar-track, .ge-3d-menu::-webkit-scrollbar-track, .ge-view-menu::-webkit-scrollbar-track, .ge-window-menu::-webkit-scrollbar-track, .ge-plugins-menu::-webkit-scrollbar-track, .ge-help-menu::-webkit-scrollbar-track { background: var(--panel); } .ge-image-menu::-webkit-scrollbar-thumb, .ge-layer-menu::-webkit-scrollbar-thumb, .ge-filter-menu::-webkit-scrollbar-thumb, .ge-edit-menu::-webkit-scrollbar-thumb, .ge-selection-menu::-webkit-scrollbar-thumb, .ge-type-menu::-webkit-scrollbar-thumb, .ge-3d-menu::-webkit-scrollbar-thumb, .ge-view-menu::-webkit-scrollbar-thumb, .ge-window-menu::-webkit-scrollbar-thumb, .ge-plugins-menu::-webkit-scrollbar-thumb, .ge-help-menu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */ .ge-menu-thumb { position: absolute; top: 0; right: 2px; width: 3px; min-height: 18px; border-radius: 2px; background: var(--red); pointer-events: none; display: none; z-index: 3; } /* Busy veil shown while a heavy pixel filter commits on a large document. */ .ge-busy-veil { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.35); z-index: 60; pointer-events: none; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 8px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--fg); font: inherit; font-size: 12px; } .ge-busy-spin { width: 14px; height: 14px; border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent); border-top-color: var(--red); border-radius: 50%; animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Flyout submenu panels: compact rows + a viewport-bounded height so long lists (the 19 Image > Adjustments entries) all fit or scroll. */ .ge-menu-parent + .ge-submenu .dropdown-item-compact { min-height: 0 !important; height: auto !important; padding: 3px 10px !important; line-height: 1.2 !important; font-size: 12px !important; } .ge-submenu { max-height: calc(100vh - 180px) !important; overflow-y: auto !important; scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image > Adjustments entries) fit without clipping, and let the panel scroll. */ .ge-submenu .dropdown-item-compact { padding-top: 2px; padding-bottom: 2px; } .ge-submenu { scrollbar-width: auto; scrollbar-color: auto; } .ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; } .ge-submenu::-webkit-scrollbar-track { background: var(--panel); } .ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); } /* Release the parent menu's overflow clip while a flyout is open so the panel is not cut off at the menu edge. */ .ge-filter-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent.open) { overflow: visible !important; } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The global "Pro tip" hint card can overlap the editor menus; hide it while the editor is open. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* The onboarding "Pro tip" tour hint floats over the editor menus; hide it while the editor is open so it never occludes a dropdown or flyout. */ body.ge-editor-open .tour-hint { display: none !important; } /* Busy veil shown while a heavy filter renders on a large document. */ .ge-busy-veil { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(8, 10, 14, 0.45); z-index: 40; } .ge-busy-veil.is-on { display: flex; } .ge-busy-card { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } .ge-busy-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent); border-top-color: var(--red); animation: ge-busy-spin 0.8s linear infinite; } @keyframes ge-busy-spin { to { transform: rotate(360deg); } } /* Photoshop-style nested submenus (Filter groups + Image > Adjustments). A parent row shows a chevron and reveals a flyout panel on hover/click. Children stay inside the same menu subtree so the delegated [data-filter-action] / [data-image-action] handlers still fire. */ .dropdown-item-compact.ge-menu-parent { position: relative; justify-content: space-between; } .ge-submenu-caret { margin-left: auto; color: var(--color-muted); } .ge-submenu { display: none; position: absolute; left: calc(100% - 2px); top: 0; min-width: 190px; max-height: min(70vh, 560px); overflow-y: auto; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); z-index: 20; padding: 4px; } .ge-menu-parent.open + .ge-submenu, .ge-menu-parent:hover + .ge-submenu, .ge-menu-parent:focus-within + .ge-submenu, .ge-submenu:hover { display: block; } /* The dropdowns clip overflow to draw the custom scroll thumb; release the clip while a flyout is open/hovered so it can extend past the menu edge. */ .ge-filter-menu:has(.ge-menu-parent.open), .ge-filter-menu:has(.ge-menu-parent:hover), .ge-filter-menu:has(.ge-submenu:hover), .ge-image-menu:has(.ge-menu-parent.open), .ge-image-menu:has(.ge-menu-parent:hover), .ge-image-menu:has(.ge-submenu:hover), .ge-selection-menu:has(.ge-menu-parent.open), .ge-selection-menu:has(.ge-menu-parent:hover), .ge-selection-menu:has(.ge-submenu:hover) { overflow: visible !important; -webkit-mask-image: none; mask-image: none; } /* The Window menu's Workspace flyout needs the same clip release. */ .ge-window-menu:has(.ge-menu-parent.open), .ge-window-menu:has(.ge-menu-parent:hover), .ge-window-menu:has(.ge-submenu:hover) { overflow: visible !important; -webkit-mask-image: none; mask-image: none; } /* Perspective Crop overlay (tools/perspective-crop.js). */ .ge-pcrop-overlay { position: fixed; inset: 0; z-index: 10050; pointer-events: none; } .ge-pcrop-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; } .ge-pcrop-svg polygon { fill: color-mix(in srgb, var(--red) 12%, transparent); stroke: var(--red); stroke-width: 1.5; vector-effect: non-scaling-stroke; } .ge-pcrop-handle { position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%; background: #fff; border: 2px solid var(--red); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5); cursor: grab; pointer-events: auto; touch-action: none; } .ge-pcrop-handle:active { cursor: grabbing; } .ge-pcrop-bar { position: absolute; display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 8px; background: var(--panel, #222); border: 1px solid rgba(255, 255, 255, 0.12); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45); pointer-events: auto; transform: translateY(-100%); } .ge-pcrop-bar .ge-pcrop-title { font-size: 12px; font-weight: 600; color: var(--fg); } .ge-pcrop-bar .ge-pcrop-size { font-size: 11px; opacity: 0.75; font-variant-numeric: tabular-nums; } .ge-pcrop-spacer { flex: 1 1 auto; min-width: 12px; } /* Mobile (390) usability: several editor controls sat well below the 44px touch-target guideline, so they were easy to miss with a thumb. Give the primary top-menu buttons, the Layers-header action row, the layer-opacity slider and the Quick Edit field/run button a comfortable hit area on phones. Sizing only — no layout/order change. */ @media (max-width: 700px) { .ge-topbar .ge-btn-sm, .ge-topbar .ge-stacked-btn { min-height: 34px; } #ge-undo, #ge-redo, #ge-history-btn, #ge-compare-btn { min-width: 38px; min-height: 38px; display: inline-flex; align-items: center; justify-content: center; } .ge-layer-opacity { height: 30px; } #ge-ai-command-input { min-height: 40px; } #ge-ai-command-run { min-width: 42px; min-height: 42px; } }