/* 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%); position: relative; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; } /* A wand "More" dropdown must escape the bar's scroll clip while open. */ .ge-options-bar:has(#ge-wand-more-menu:not([hidden])) { overflow: visible; } .ge-options-bar:has(#ge-brush-more-menu:not([hidden])) { overflow: visible; } .ge-options-bar:has(#ge-lasso-more-menu:not([hidden])) { overflow: visible; } .ge-options-bar:has(#ge-sam-more-menu:not([hidden])) { overflow: visible; } .ge-options-bar:has(#ge-quickselect-more-menu:not([hidden])) { overflow: visible; } .ge-options-bar:has(#ge-objectselect-more-menu:not([hidden])) { overflow: visible; } .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; /* Shrink to content instead of growing: an empty mirror body otherwise expands and leaves a large blank gap before the relocated tool controls, pushing them away from the left edge of the strip. */ flex: 0 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 { /* Grow to fill the strip so the selection action row can be pinned to the right edge of the screen. */ flex: 1 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; } /* The "rembg not installed" notice would otherwise be a two-line block and double the strip height; keep the install action, drop the essay. */ .ge-controls--in-options #ge-rembg-dep-missing { padding: 0; border: 0; background: none; gap: 8px; } .ge-controls--in-options #ge-rembg-dep-missing .ge-dep-notice-text { 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; /* Let the active tool section span the strip so its action row can be pushed to the right edge. */ flex: 1 1 auto; } /* The Text tool's option set is too wide for the thin strip: let the bar grow and wrap onto extra rows instead of clipping the trailing fields. */ .ge-options-bar.ge-options-tall { flex-wrap: wrap; align-items: center; overflow-y: visible; row-gap: 4px; padding-top: 6px; padding-bottom: 6px; } /* The active tool's name already sits in the rail; on the tall (Text) strip its label would claim a whole line and shove the controls down. */ .ge-options-bar.ge-options-tall .ge-options-tool { display: none; } .ge-options-bar.ge-options-tall .ge-controls--in-options { flex: 1 1 auto; flex-wrap: wrap !important; row-gap: 4px; } .ge-options-bar.ge-options-tall .ge-controls--in-options > div:not([style*="display:none"]):not([style*="display: none"]) { flex-wrap: wrap !important; row-gap: 4px; } /* The Text section's own field groups must wrap onto their own rows too, otherwise the trailing format/Warp fields run past the right edge. */ .ge-options-bar.ge-options-tall .ge-controls--in-options .ge-text-section { display: flex !important; flex-wrap: wrap !important; align-items: flex-end; gap: 8px; width: 100%; } .ge-options-bar.ge-options-tall .ge-controls--in-options .ge-text-section > * { flex: 0 0 auto; } .ge-options-bar.ge-options-tall .ge-controls--in-options .ge-text-section > textarea { flex: 1 1 240px; min-width: 240px; } /* The brush sliders (Opacity/Flow/Softness) are a touch tight against the strip width once the Color/Size/preset controls are inline; trim the gutters so the whole brush strip fits on one line. */ .ge-controls--in-options #ge-brush-section { gap: 6px; } /* The brush controls (Color/Size/preset/caret) hug their content; only the trailing slider section grows, so the Opacity/Flow/Softness group sits right after the caret instead of leaving a wide gap. */ .ge-controls--in-options #ge-brush-controls { flex: 0 0 auto; } .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 selection action row (visibility, clear ×, Invert, Erase, Copy Layer, To Mask, Quick Mask) rides a touch high in the thin strip; nudge it up 2px to sit on the same optical line as the Style/Free row before it. */ .ge-controls--in-options .ge-control-row.ge-actions { position: relative; top: -2px; margin-left: auto; } /* The action buttons themselves ride ~2px below the mode/Style rows in the thin strip; lift them so the whole row reads on one line (wand + marquee). */ .ge-controls--in-options .ge-control-row.ge-actions .ge-btn { position: relative; top: -2px; } /* Pen selection: the action row belongs on the left edge (not pushed right by the shared auto margin), and its ✓ should sit on the same optical line as the ✕ in Cancel. */ #ge-pen-section .ge-control-row.ge-actions { margin-left: 0; } #ge-pen-commit svg { position: relative; top: 2px; } /* Nudge the Clear ✕ glyph down 1px, and light it up (accent) while a selection is active so it is obvious there is something to clear. */ #ge-marquee-clear svg, #ge-wand-clear svg, #ge-lasso-clear svg, #ge-sam-clear svg, #ge-quickselect-clear svg, #ge-objectselect-clear svg { position: relative; top: 1px; } #ge-marquee-clear.has-selection, #ge-wand-clear.has-selection, #ge-lasso-clear.has-selection, #ge-sam-clear.has-selection, #ge-quickselect-clear.has-selection, #ge-objectselect-clear.has-selection { color: var(--accent, var(--red)); border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); } /* The Clone "clear sampled source" ✕ matches the wand/marquee ✕: the SVG cross sits 1px lower, and the button lights up (accent) while a source is set. */ #ge-clone-source-clear svg { position: relative; top: -1px; } #ge-clone-source-clear.has-selection svg { top: 1px; } #ge-clone-source-clear.has-selection { color: var(--accent, var(--red)); border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); } /* Every clear ✕ is the same square target, wherever it lives: the wand/ marquee/lasso/sam/quickselect/objectselect ones sit in the options strip while the Clone one sits in the right panel, but they must read (and hit) identically instead of inheriting each container's line-height. */ #ge-marquee-clear, #ge-wand-clear, #ge-lasso-clear, #ge-sam-clear, #ge-quickselect-clear, #ge-objectselect-clear, #ge-clone-source-clear { width: 26px; height: 26px; min-width: 26px; min-height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; line-height: 1; } /* The marquee Style row lives in the nowrap options strip. When "Fixed ratio/size" reveals the W/H inputs, their default intrinsic width inflates the grid's max-content and the 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; } /* The px readout IS the whole label on the brush size row, so let the label hug the number instead of sitting in the shared 36px label column (same principle as the Dodge/Burn "Int" label): otherwise the value floats well to the left of its own slider and it is unclear which bar it controls. */ .ge-brush-size-row label { min-width: 0; } /* 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; } /* Some eraser-style rows are hidden with an inline display:none (e.g. the smudge Strength row when the tool is not Smudge; it is shown/hidden by JS). The `display:flex !important` above would otherwise override that inline hide, so explicitly honour an inline none. */ .ge-eraser-row[style*="display:none"], .ge-eraser-row[style*="display: none"] { display: none !important; } /* Dodge/Burn's Intensity label is abbreviated, so let the column shrink to the word's own width: the text then sits right against its slider instead of leaving a dead gap that made the pairing hard to read. */ #ge-dodgeburn-intensity-row label { flex: 0 0 auto; min-width: 20px; } /* Nudge the Intensity dot and its label 10px to the right so the pair reads as one unit instead of hugging the row's left edge. */ #ge-dodgeburn-intensity-row > .ge-eraser-preview { margin-left: 10px; } /* 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; } /* Wand strip: pull the tolerance value chip a touch closer to its slider. */ #ge-wand-section .ge-slider-value { position: relative; left: -6px; } /* Keep the tolerance value chip tinted at all times (not just on hover) so it clearly reads as belonging to the slider rather than floating far away. */ #ge-wand-section #ge-wand-tol-label.ge-slider-value { background: color-mix(in srgb, var(--fg) 10%, transparent); opacity: 1; } /* 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; } /* The chat shell sizes every text input to 32px tall; the inline chip editor must stay chip-sized instead, otherwise its text sits ~4px low. Higher specificity than `input[type="text"]` so it wins. */ .ge-eraser-row input.ge-slider-edit, .ge-adj-row input.ge-slider-edit { height: 16px; line-height: 14px; padding: 0 4px; box-sizing: border-box; } /* 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: 14px; height: 14px; 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; text-align: right; padding-right: 2px; } .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; position: relative; top: 0; } .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; } /* Wand sliders: the shared 78px label column reserved room for the value chip that now lives after the slider, so "Tolerance"/"Feather" sat in a wide box with a gap before the track. Let the label hug its text. */ #ge-wand-section .ge-eraser-row label { flex: 0 0 auto; } /* Quick Select's brush-size row uses the same .ge-eraser-row pattern as the wand Tolerance row, so its slider picks up the shared row styling (8px track) instead of the bare .ge-ctl-range look, and its label hugs its text. */ #ge-quickselect-section .ge-eraser-row label { flex: 0 0 auto; } /* The wand strip is too wide (mode + tolerance + feather + edge stroke + actions). Tuck the post-selection refine sliders and the tip into a "More" dropdown so the strip fits without horizontal scrolling. */ .ge-wand-more-wrap { position: relative; display: inline-flex; } .ge-wand-more-btn { position: relative; top: -4px; left: -8px; padding: 0 4px; background: transparent; border-color: transparent; } .ge-wand-more-btn:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); border-color: transparent; } .ge-wand-more-btn svg { display: block; transition: transform 0.12s ease; } .ge-wand-more-btn.active, .ge-wand-more-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .ge-wand-more-btn[aria-expanded="true"] svg { transform: rotate(180deg); } .ge-wand-more-menu { position: absolute; top: calc(100% + 4px); left: 0; z-index: 60; display: flex; flex-direction: column; gap: 8px; min-width: 260px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); } .ge-wand-more-menu[hidden] { display: none; } .ge-wand-more-menu .ge-eraser-row { margin: 0; } .ge-wand-more-menu .ge-wand-tip { margin: 0; font-size: 9px; opacity: 0.45; } /* Brush "v" caret: same pattern as the wand caret — drops the less-used stroke controls into a small popover so the strip stays one tidy line. */ .ge-brush-more-wrap { position: relative; display: inline-flex; } .ge-brush-more-btn { position: relative; top: -2px; padding: 0 4px; background: transparent; border-color: transparent; } .ge-brush-more-btn:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); border-color: transparent; } .ge-brush-more-btn svg { display: block; transition: transform 0.12s ease; } .ge-brush-more-btn.active, .ge-brush-more-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .ge-brush-more-btn[aria-expanded="true"] svg { transform: rotate(180deg); } .ge-brush-more-menu { position: absolute; top: calc(100% + 4px); right: 0; z-index: 60; display: flex; flex-direction: column; gap: 10px; min-width: 280px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); } .ge-brush-more-menu[hidden] { display: none; } /* Text/Shape overflow menu: the strip keeps only the primary fields and the rarely-used rows live in this popover, so switching tools no longer makes the options bar (and the canvas below it) jump by ~80px. */ .ge-options-bar:has(.ge-opt-more:not([hidden])) { overflow: visible; } .ge-controls--in-options .ge-text-section, .ge-controls--in-options .ge-shape-section { position: relative; } .ge-opt-more { position: absolute; top: calc(100% + 4px); right: 0; z-index: 60; display: flex; flex-direction: column; gap: 10px; width: 320px; max-height: min(70vh, 440px); overflow-y: auto; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); } .ge-opt-more[hidden] { display: none; } #ge-text-more .ge-text-grid, #ge-shape-more .ge-text-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr)); gap: 8px; align-items: end; width: 100%; } #ge-text-more .ge-text-field, #ge-shape-more .ge-text-field { min-width: 0; align-items: stretch; } #ge-shape-more .ge-control-row { flex-wrap: wrap; } .ge-more-toggle[aria-expanded="true"] { border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } /* Blend sits at the dropdown's right edge so it lines up with the sliders. */ .ge-brush-more-menu #ge-brush-blend { margin-left: auto; } /* The caret lives right of the size slider, inside the size row. */ .ge-brush-size-row .ge-brush-more-wrap { margin-left: 4px; } .ge-brush-more-menu .ge-eraser-row, .ge-brush-more-menu .ge-control-row { margin: 0; } .ge-brush-more-menu .ge-section-title { margin: 0; } .ge-controls--in-options .ge-brush-more-menu .ge-brush-pressure-row { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; gap: 10px; } /* Lasso "v" caret: same pattern as the wand/brush caret — the secondary lasso controls (feather, edge stroke, magnetic, width, contrast) drop into a small popover so the lasso strip matches the other select tools. */ .ge-lasso-more-wrap { position: relative; display: inline-flex; } .ge-lasso-more-btn { position: relative; top: -2px; padding: 0 4px; background: transparent; border-color: transparent; } .ge-lasso-more-btn:hover { background: color-mix(in srgb, var(--fg) 12%, transparent); border-color: transparent; } .ge-lasso-more-btn svg { display: block; transition: transform 0.12s ease; } .ge-lasso-more-btn.active, .ge-lasso-more-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .ge-lasso-more-btn[aria-expanded="true"] svg { transform: rotate(180deg); } .ge-lasso-more-menu { position: absolute; top: calc(100% + 4px); left: 0; z-index: 60; display: flex; flex-direction: column; gap: 8px; min-width: 260px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); } .ge-lasso-more-menu[hidden] { display: none; } .ge-lasso-more-menu .ge-eraser-row, .ge-lasso-more-menu .ge-control-row { margin: 0; } .ge-lasso-more-menu .ge-lasso-tip { margin: 0; font-size: 9px; opacity: 0.45; } /* 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: 56%; overflow-y: auto; overflow-x: hidden; } .ge-right-panel--dock > .ge-layers { flex: 1 1 auto; min-height: 240px; } .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: 10px; 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; } /* Unified control-icon size across the right dock + Layers panel so the sidebar reads as one set (was a mix of 12/13/14px). */ .ge-layer-btn svg, .ge-layer-fx-btn svg, .ge-layer-vis svg, .ge-layer-lock-btn svg, .ge-layer-mask-btn svg, .ge-layer-clip-btn svg, .ge-layer-group-toggle svg { width: 14px; height: 14px; } .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-tabs-menu-sep { height: 1px; margin: 4px 2px; background: var(--border); } .ge-dock-tabs-menu-close { color: var(--red, #e5484d); } .ge-dock-tabs-menu-close:hover { background: color-mix(in srgb, var(--red, #e5484d) 20%, 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.is-effect-dragging { opacity: 0.45; } .ge-layer-item.is-effect-drop-target, .ge-layer-group-row.is-effect-drop-target { background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); outline: 1px solid var(--accent, var(--red)); outline-offset: -1px; } .ge-effect-drag-ghost { position: fixed; z-index: 10030; padding: 4px 9px; border-radius: 6px; border: 1px solid var(--border); background: var(--panel); color: var(--fg); font-size: 10px; white-space: nowrap; pointer-events: none; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45); } .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; } /* Floating eyedropper loupe — a circle that mirrors the panel's live pixel preview right next to the pointer while the eyedropper is pressed, so you can see the exact pixel being sampled. */ .ge-eyedropper-cursor-loupe { position: fixed; z-index: 2600; width: 108px; height: 108px; padding: 3px; border-radius: 50%; background: color-mix(in srgb, var(--panel) 92%, transparent); box-shadow: 0 6px 20px color-mix(in srgb, #000 35%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 26%, transparent); pointer-events: none; display: none; opacity: 0; transform: scale(0.9); transition: opacity .12s ease, transform .12s ease; } .ge-eyedropper-cursor-loupe.is-visible { display: block; opacity: 1; transform: scale(1); } .ge-eyedropper-cursor-loupe canvas { display: block; width: 100%; height: 100%; border-radius: 50%; image-rendering: pixelated; background: var(--bg); } /* 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 select.ge-crop-size-preset { max-width: 96px; margin-right: 8px; padding: 3px 4px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 11px; } .ge-crop-close { position: absolute; top: -13px; right: -8px; width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: 1px solid var(--border); border-radius: 50%; background: var(--panel); color: var(--fg); font-size: 12px; line-height: 1; cursor: pointer; } .ge-crop-close-x { display: block; line-height: 1; transform: translateY(-2px); } .ge-crop-close:hover { background: var(--red); color: #fff; border-color: transparent; } .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); } /* Full crop-rect outline in screen space. The canvas-space crop overlay is clipped to the image, so this frame shows the part of the rect that reaches beyond the canvas (which expands the image on Apply). */ .ge-crop-frame { position: absolute; pointer-events: none; z-index: 6; border: 1px dashed #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55); box-sizing: border-box; } /* Crop aspect preset buttons (options bar) */ .ge-crop-preset-row { display: flex; gap: 4px; position: relative; top: -4px; } .ge-crop-preset.active { background: var(--accent, #4c8bf5); border-color: transparent; color: #fff; } .ge-crop-custom { display: inline-flex; align-items: center; } .ge-crop-custom input { width: 58px; height: 24px; padding: 0 6px; border: 1px solid var(--border); border-radius: 5px; background: var(--bg); color: var(--fg); font-size: 12px; text-align: center; } .ge-crop-custom input::placeholder { color: var(--fg-muted, var(--fg)); opacity: 0.6; } .ge-crop-custom input:focus { outline: none; border-color: var(--accent, #4c8bf5); } /* 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); /* The strip's `.ge-controls--in-options > div` rule turns every visible section into a horizontal nowrap flex row; the popover must stack its rows vertically instead, or the prompt/model/buttons land hundreds of pixels off the panel. */ display: flex !important; flex-direction: column !important; flex-wrap: nowrap !important; align-items: stretch; gap: 8px; overflow-x: hidden; 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-prompt { flex: 0 0 auto; width: 100%; } .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: 0; border-right: none; border-bottom: 1px solid var(--border); overflow: visible; flex-shrink: 0; gap: 0; } .ge-toolbar-scroll { flex-direction: row; align-items: center; gap: 0; width: 100%; padding: 4px 0; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .ge-toolbar-scroll::-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-scroll.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-scroll.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-scroll.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-scroll.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(var(--safe-bottom) + 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-hex-copy { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; padding: 0; background: transparent; border: 0; border-radius: 4px; color: color-mix(in srgb, var(--fg) 55%, transparent); cursor: pointer; } .ge-hex-copy:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 12%, transparent); } .ge-hex-copy.copied { color: var(--accent, var(--red)); } .ge-hex-fav.active { color: var(--accent, var(--red)); } .ge-hex-fav.active svg { fill: var(--accent, var(--red)); } .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: 2px; padding: 3px 4px; } .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: -5px; 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: 10px; 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: var(--portal-z, 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 / Img Gen / Img Edit): 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; } /* Edit-input scope (All layers / This layer) beside the routing mode. */ .ge-ai-command .ge-ai-command-scope { margin-right: 6px; max-width: 104px; } .ge-ai-command .ge-ai-command-scope[hidden] { display: none; } /* 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); } } /* Phones: the floating zoom/Setup footer collides with the peek/expanded layers sheet and the Quick Edit card, so remove it entirely on phones. Undo/Redo/History already live in the layers-sheet header, and pinch-zoom replaces the zoom buttons. */ @media (max-width: 700px) { .ge-editor-footer { display: none !important; } /* The Quick Edit card lifts over the layers sheet / keyboard; keep it above every other editor asset (sheet headers, tool rail, popups) while lifted. */ .ge-ai-command { z-index: 90; } } /* 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; } /* The one-time Free Transform hint always lands in the same place: centred under the canvas. Without this it alternated between the top-right corner and the full-width bottom banner depending on what else was open. */ body.ge-editor-open .toast.toast-transform-hint { top: auto; bottom: 84px; left: 0; right: 0; margin-left: auto; margin-right: auto; width: max-content; min-width: 0; max-width: min(520px, calc(100vw - 32px)); } /* 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-scroll, .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; margin: 0; padding: 2px 7px; line-height: 1.2; } .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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } /* 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; } .ge-submenu-sep { height: 1px; margin: 4px 2px; background: var(--border); } /* 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; } } /* Holding Alt with a paint tool borrows the eyedropper: show the sampling cursor and hide the brush ring so the borrow is obvious. */ body.ge-alt-eyedropper .ge-canvas-area, body.ge-alt-eyedropper .ge-main-canvas { cursor: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%3E%3Cg%20stroke%3D%22black%22%20stroke-width%3D%224%22%20opacity%3D%22.85%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m2%2022%201-1h3l9-9%22%2F%3E%3Cpath%20d%3D%22M3%2021v-3l9-9%22%2F%3E%3Cpath%20d%3D%22m15%206%203.4-3.4a2.1%202.1%200%201%201%203%203L18%209l.9.9a1%201%200%200%201%200%201.4l-1.6%201.6a1%201%200%200%201-1.4%200L12%208.4a1%201%200%200%201%200-1.4l1.6-1.6a1%201%200%200%201%201.4%200l.9.9Z%22%2F%3E%3C%2Fg%3E%3Cg%20stroke%3D%22white%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m2%2022%201-1h3l9-9%22%2F%3E%3Cpath%20d%3D%22M3%2021v-3l9-9%22%2F%3E%3Cpath%20d%3D%22m15%206%203.4-3.4a2.1%202.1%200%201%201%203%203L18%209l.9.9a1%201%200%200%201%200%201.4l-1.6%201.6a1%201%200%200%201-1.4%200L12%208.4a1%201%200%200%201%200-1.4l1.6-1.6a1%201%200%200%201%201.4%200l.9.9Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") 2 22, crosshair !important; } body.ge-alt-eyedropper .ge-brush-cursor { display: none !important; } .ge-liquify-hint { color: var(--fg-muted, #8a8f98); font-size: 11px; line-height: 1.35; }