mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 03:42:20 +02:00
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9142 lines
265 KiB
CSS
9142 lines
265 KiB
CSS
/* 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 <link> tags in static/index.html, and do not move a rule
|
||
* between files: with 1,151 redeclarations and 1,886 !important in this
|
||
* cascade, either can change which declaration wins.
|
||
*
|
||
* static/index.html and the PRECACHE list in static/sw.js must agree on this
|
||
* order. tests/test_static_stylesheet_manifest.py and
|
||
* tests/test_css_computed_style_snapshot.py fail if that drifts.
|
||
*/
|
||
.ge-draft-status[data-state="pending"],
|
||
.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); }
|
||
.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); }
|
||
.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); }
|
||
/* "ALPHA" badge — flags the editor as in-development. */
|
||
.ge-alpha-badge {
|
||
flex-shrink: 0;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.1em;
|
||
line-height: 1;
|
||
padding: 3px 6px;
|
||
margin-right: 4px;
|
||
border-radius: 4px;
|
||
color: var(--accent, var(--red));
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent);
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent);
|
||
text-transform: uppercase;
|
||
user-select: none;
|
||
cursor: default;
|
||
}
|
||
@media (max-width: 768px) {
|
||
/* Keep the toolbar tight on mobile — badge stays but shrinks. */
|
||
.ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; }
|
||
}
|
||
.ge-diffusion-status {
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
transition: all 0.15s;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-diffusion-status.online {
|
||
color: var(--color-success, #4ade80);
|
||
}
|
||
.ge-diffusion-status.offline {
|
||
color: var(--color-error, #ef4444);
|
||
opacity: 0.7;
|
||
}
|
||
.ge-diffusion-status.offline:hover {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--color-error) 10%, transparent);
|
||
}
|
||
.ge-topbar-fill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
color: var(--accent, var(--red));
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border));
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
|
||
}
|
||
.ge-topbar-fill:hover {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent);
|
||
border-color: var(--accent, var(--red));
|
||
}
|
||
/* Mask-color swatch in the topbar — small label + circular swatch so
|
||
the user can pick a contrasting overlay colour from anywhere. */
|
||
.ge-topbar-mask-color-wrap {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-topbar-mask-color-label {
|
||
font-size: 10px;
|
||
opacity: 0.6;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
.ge-topbar-mask-color {
|
||
width: 18px;
|
||
height: 18px;
|
||
flex: 0 0 18px;
|
||
}
|
||
.ge-topbar-mask-color.cp-swatch-input {
|
||
width: 18px;
|
||
height: 18px;
|
||
flex: 0 0 18px;
|
||
border-radius: 50%;
|
||
padding: 0;
|
||
}
|
||
.ge-topbar-sep {
|
||
width: 1px;
|
||
height: 16px;
|
||
background: var(--border);
|
||
margin: 0 4px;
|
||
}
|
||
|
||
/* Contextual tool options bar (between topbar and editor body) */
|
||
.ge-options-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 0 0 auto;
|
||
flex-wrap: nowrap;
|
||
row-gap: 4px;
|
||
min-height: 40px;
|
||
padding: 4px 10px;
|
||
box-sizing: border-box;
|
||
border-bottom: 1px solid var(--border);
|
||
background: color-mix(in srgb, var(--panel) 92%, var(--fg) 8%);
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
scrollbar-width: thin;
|
||
}
|
||
.ge-options-tool {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 0 0 auto;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: color-mix(in srgb, var(--fg) 85%, transparent);
|
||
}
|
||
.ge-options-tool-icon {
|
||
display: inline-flex;
|
||
width: 18px;
|
||
height: 18px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.ge-options-sep {
|
||
width: 1px;
|
||
height: 20px;
|
||
background: var(--border);
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-options-body {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
/* Tool options moved out of the dock into the options bar (Photoshop):
|
||
the real controls flow inline on the SAME single thin row, horizontally
|
||
scrollable when they do not fit. Section headings/hints are dropped in
|
||
this compact strip; the authoritative sliders mirror the contextual
|
||
strip, so duplicated ranges are hidden. */
|
||
.ge-options-bar .ge-controls.ge-controls--in-options {
|
||
flex: 0 1 auto;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
gap: 14px;
|
||
padding: 0;
|
||
margin: 0;
|
||
border: 0;
|
||
min-width: 0;
|
||
max-height: none;
|
||
overflow: visible;
|
||
}
|
||
.ge-controls--in-options .ge-section-title,
|
||
.ge-controls--in-options .ge-section-hint { display: none; }
|
||
.ge-controls--in-options > section,
|
||
.ge-controls--in-options > div { flex: 0 0 auto; }
|
||
/* Lay out every visible tool section as one horizontal strip. Hidden
|
||
sections carry an inline display:none and are left untouched; visible
|
||
ones must override their inline display:block, hence !important. */
|
||
.ge-controls--in-options > div:not([style*="display:none"]):not([style*="display: none"]) {
|
||
display: flex !important;
|
||
flex-direction: row !important;
|
||
flex-wrap: nowrap !important;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.ge-controls--in-options .ge-control-row {
|
||
margin: 0;
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.ge-controls--in-options .ge-control-row label { white-space: nowrap; }
|
||
/* The Text section is authored as a vertical panel. When it is relocated into
|
||
the options bar its grids collapse to zero width, so the Font picker shrinks
|
||
to ~14px and the Font/Size labels pile into "FoSize". Lay the text strip out
|
||
as fixed-width field groups so the font picker keeps its room. */
|
||
.ge-controls--in-options .ge-text-section .ge-text-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: flex-end;
|
||
gap: 8px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-controls--in-options .ge-text-section .ge-text-field {
|
||
flex: 0 0 auto;
|
||
min-width: 56px;
|
||
align-items: stretch;
|
||
}
|
||
.ge-controls--in-options .ge-text-section .ge-text-field > span { white-space: nowrap; }
|
||
.ge-controls--in-options .ge-text-section .ge-text-font-field { min-width: 116px; }
|
||
.ge-controls--in-options .ge-text-section .ge-text-size-field { min-width: 56px; }
|
||
.ge-controls--in-options .ge-text-section .ge-text-field > select,
|
||
.ge-controls--in-options .ge-text-section .ge-text-field > input { width: 100%; }
|
||
.ge-controls--in-options .ge-text-section textarea {
|
||
flex: 0 0 auto;
|
||
width: 200px;
|
||
min-height: 0;
|
||
height: 40px;
|
||
resize: none;
|
||
}
|
||
.ge-options-bar:has(.ge-controls--in-options) .ge-options-body .ge-opt[data-opt="range"] { display: none; }
|
||
/* Secondary brush rows (pen-pressure toggles + preset Save/delete) do not
|
||
fit the thin strip; hide them behind the "..." overflow button. */
|
||
.ge-controls--in-options .ge-brush-pressure-row,
|
||
.ge-controls--in-options .ge-brush-preset-row { display: none !important; }
|
||
.ge-options-bar.ge-more-open { flex-wrap: wrap; }
|
||
.ge-options-bar.ge-more-open .ge-controls--in-options .ge-brush-pressure-row,
|
||
.ge-options-bar.ge-more-open .ge-controls--in-options .ge-brush-preset-row {
|
||
display: flex !important;
|
||
flex-direction: row !important;
|
||
flex-wrap: nowrap !important;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
/* The brush-preset picker stays visible inline whenever a painting tool is
|
||
active, so switching brushes does not require discovering the "..." menu. */
|
||
.ge-controls--in-options .ge-has-brush-presets .ge-brush-preset-row {
|
||
display: flex !important;
|
||
flex-direction: row !important;
|
||
flex-wrap: nowrap !important;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.ge-options-more {
|
||
flex: 0 0 auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
padding: 0;
|
||
color: color-mix(in srgb, var(--fg) 65%, transparent);
|
||
background: transparent;
|
||
border: 1px solid var(--border);
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-options-more:hover { color: var(--fg); border-color: color-mix(in srgb, var(--fg) 35%, transparent); }
|
||
.ge-options-more[hidden] { display: none; }
|
||
.ge-opt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-opt-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 10px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
color: color-mix(in srgb, var(--fg) 55%, transparent);
|
||
white-space: nowrap;
|
||
}
|
||
.ge-opt-value {
|
||
color: color-mix(in srgb, var(--fg) 80%, transparent);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.ge-opt[data-opt="range"] input[type="range"] { width: 110px; }
|
||
/* Checkbox options (e.g. Move tool Auto-Select) — restyle the native
|
||
control so it matches the dark editor chrome instead of rendering as
|
||
a light OS checkbox. */
|
||
.ge-opt.ge-auto-select-option { gap: 6px; cursor: pointer; }
|
||
.ge-opt input[type="checkbox"] {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
width: 14px;
|
||
height: 14px;
|
||
margin: 0;
|
||
flex: 0 0 auto;
|
||
box-sizing: border-box;
|
||
border: 1px solid var(--border);
|
||
border-radius: 3px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.ge-opt input[type="checkbox"]:checked {
|
||
background: var(--accent, #4c8bf5);
|
||
border-color: var(--accent, #4c8bf5);
|
||
}
|
||
.ge-opt input[type="checkbox"]:checked::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 4px;
|
||
top: 1px;
|
||
width: 4px;
|
||
height: 8px;
|
||
border: solid #fff;
|
||
border-width: 0 2px 2px 0;
|
||
transform: rotate(45deg);
|
||
}
|
||
.ge-opt input[type="checkbox"]:focus-visible {
|
||
outline: 2px solid var(--accent, #4c8bf5);
|
||
outline-offset: 1px;
|
||
}
|
||
/* Dropdown options in the options bar (e.g. Auto-Select Target). */
|
||
.ge-opt[data-opt="select"] select {
|
||
height: 24px;
|
||
padding: 0 6px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 5px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-opt[data-opt="select"] select:focus-visible {
|
||
outline: 2px solid var(--accent, #4c8bf5);
|
||
outline-offset: 1px;
|
||
}
|
||
.ge-opt-modes { display: inline-flex; gap: 2px; }
|
||
.ge-opt-mode {
|
||
min-width: 26px;
|
||
height: 24px;
|
||
padding: 0 6px;
|
||
font-size: 11px;
|
||
border: 1px solid var(--border);
|
||
background: transparent;
|
||
color: var(--fg);
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-opt-mode.active {
|
||
background: var(--accent, #4c8bf5);
|
||
border-color: transparent;
|
||
color: #fff;
|
||
}
|
||
.ge-opt-empty { font-size: 11px; opacity: 0.5; }
|
||
.ge-options-empty .ge-options-sep { display: none; }
|
||
@media (max-width: 820px) {
|
||
.ge-options-bar { display: none; }
|
||
}
|
||
|
||
/* Document tab strip (Photoshop-style) — names the open document and
|
||
offers a close affordance. Sits between the options bar and the body. */
|
||
.ge-doc-tabs {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
flex: 0 0 auto;
|
||
min-height: 30px;
|
||
padding: 4px 8px 0;
|
||
box-sizing: border-box;
|
||
background: var(--panel);
|
||
border-bottom: 1px solid var(--border);
|
||
overflow: hidden;
|
||
}
|
||
.ge-doc-tabs::-webkit-scrollbar { display: none; }
|
||
.ge-doc-tab {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 0 0 auto;
|
||
max-width: 240px;
|
||
padding: 5px 6px 5px 12px;
|
||
font-size: 11px;
|
||
color: color-mix(in srgb, var(--fg) 70%, transparent);
|
||
background: color-mix(in srgb, var(--panel) 82%, var(--fg) 6%);
|
||
border: 1px solid var(--border);
|
||
border-bottom: none;
|
||
border-radius: 6px 6px 0 0;
|
||
}
|
||
.ge-doc-tab.is-active {
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border-bottom: 1px solid var(--bg);
|
||
margin-bottom: -1px;
|
||
}
|
||
.ge-doc-tab-label {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
max-width: 190px;
|
||
}
|
||
.ge-doc-tab-close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: inherit;
|
||
font-size: 13px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
opacity: 0.6;
|
||
}
|
||
.ge-doc-tab-close:hover {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--fg) 14%, transparent);
|
||
}
|
||
/* Title-bar actions: Import + Save and the window controls (_ □ ×). */
|
||
.ge-doc-tabs-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 0 0 auto;
|
||
margin-left: auto;
|
||
}
|
||
/* Import/Save in the title bar render as ONE compact row (glyph + label),
|
||
matching the File/Edit/… topbar button height — not the 2-line stack. */
|
||
.ge-doc-tabs-actions .ge-stacked-btn {
|
||
flex-direction: row;
|
||
height: 26px;
|
||
gap: 4px;
|
||
padding: 0 9px !important;
|
||
}
|
||
.ge-doc-tabs-actions .ge-stacked-btn .ge-stacked-glyph { display: inline-flex; }
|
||
.ge-doc-tabs-actions .ge-stacked-btn .ge-stacked-label { top: 0; font-size: 10px; opacity: 0.8; }
|
||
/* Save shows a SINGLE label (the live "Saved"/"Unsaved" status), not "SAVE Saved". */
|
||
.ge-doc-tabs-actions #ge-save-menu-btn { flex-wrap: nowrap !important; column-gap: 5px; }
|
||
.ge-doc-tabs-actions #ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 auto; }
|
||
.ge-doc-tabs-actions #ge-save-menu-btn .ge-save-action-label { display: none !important; }
|
||
/* Import + Save now live in the topbar row (their old title bar is gone):
|
||
one compact row each, matching the File/Edit/… button height. */
|
||
.ge-topbar-right .ge-stacked-btn {
|
||
flex-direction: row;
|
||
height: 26px;
|
||
gap: 4px;
|
||
padding: 0 9px !important;
|
||
}
|
||
.ge-topbar-right .ge-stacked-btn .ge-stacked-glyph { display: inline-flex; }
|
||
.ge-topbar-right .ge-stacked-btn .ge-stacked-label { top: 0; font-size: 10px; opacity: 0.85; }
|
||
.ge-topbar-right #ge-save-menu-btn { flex-wrap: nowrap !important; column-gap: 5px; }
|
||
.ge-topbar-right #ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 auto; }
|
||
.ge-topbar-right #ge-save-menu-btn .ge-save-action-label { display: none !important; }
|
||
.ge-win-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1px;
|
||
margin-left: 4px;
|
||
}
|
||
.ge-win-btn.ge-btn,
|
||
.ge-win-controls #ge-maximize-toggle {
|
||
width: 24px;
|
||
height: 22px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
.ge-win-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 24px;
|
||
height: 22px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
opacity: 0.75;
|
||
}
|
||
.ge-win-btn:hover,
|
||
.ge-win-controls #ge-maximize-toggle:hover {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--fg) 14%, transparent);
|
||
}
|
||
.ge-win-close:hover {
|
||
background: #e5484d;
|
||
color: #fff;
|
||
}
|
||
.ge-win-controls #ge-maximize-toggle svg { top: 0 !important; }
|
||
/* The editor maximize now lives in the gallery modal header, beside the
|
||
window's minimize/close buttons (it adopts .modal-minimize-btn so it matches
|
||
them exactly). Keep its icon optically centred rather than the minimize
|
||
glyph's 2px drop. */
|
||
#gallery-modal .modal-header .ge-header-max-btn svg { top: 0 !important; transform: none; }
|
||
@media (max-width: 820px) {
|
||
.ge-doc-tabs { min-height: 26px; padding: 3px 6px 0; }
|
||
.ge-doc-tab-label { max-width: 140px; }
|
||
}
|
||
|
||
/* Editor body (toolbar + canvas + panel) */
|
||
.ge-editor-body {
|
||
display: flex;
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
.ge-editor-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 10px;
|
||
min-height: 34px;
|
||
padding: 3px 10px;
|
||
box-sizing: border-box;
|
||
flex: 0 0 auto;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--panel);
|
||
}
|
||
.ge-editor-footer .ge-canvas-size {
|
||
min-width: 0;
|
||
padding-left: 10px;
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
font-variant-numeric: tabular-nums;
|
||
color: color-mix(in srgb, var(--fg) 65%, transparent);
|
||
}
|
||
.ge-status-tool {
|
||
min-width: 0;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border);
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: color-mix(in srgb, var(--fg) 55%, transparent);
|
||
}
|
||
.ge-status-model {
|
||
min-width: 0;
|
||
max-width: 280px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border);
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: color-mix(in srgb, var(--fg) 55%, transparent);
|
||
}
|
||
.ge-footer-history {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
margin-right: 8px;
|
||
}
|
||
.ge-footer-history > .ge-btn { min-width: 28px; min-height: 26px; }
|
||
.ge-footer-zoom {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
margin-left: auto;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-footer-zoom > .ge-btn { min-width: 28px; min-height: 26px; }
|
||
.ge-footer-zoom .ge-zoom-stack { flex-direction: row; gap: 5px; min-width: 56px; }
|
||
.ge-footer-zoom .ge-zoom-label { top: 0; left: 0; font-size: 11px; letter-spacing: 0; opacity: 0.8; }
|
||
.ge-footer-zoom .ge-zoom-glyph { left: 0; }
|
||
.ge-footer-zoom .ge-stacked-btn { min-width: 48px; }
|
||
.gallery-editor-container {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: none;
|
||
}
|
||
.gallery-editor-container input[type="number"] {
|
||
-moz-appearance: textfield;
|
||
appearance: textfield;
|
||
}
|
||
.gallery-editor-container input[type="number"]::-webkit-inner-spin-button,
|
||
.gallery-editor-container input[type="number"]::-webkit-outer-spin-button {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
margin: 0;
|
||
}
|
||
|
||
.ge-toolbar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
/* Asymmetric horizontal padding shifts the whole column 4 px left —
|
||
buttons, hover backgrounds, active highlight pills, and section
|
||
separators all move together. */
|
||
padding: 6px 6px 6px 0;
|
||
background: var(--panel);
|
||
border-right: 1px solid var(--border);
|
||
width: 50px;
|
||
flex-shrink: 0;
|
||
min-height: 0;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-toolbar::-webkit-scrollbar { display: none; }
|
||
.ge-tool-sep {
|
||
font-size: 8px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
color: var(--color-muted);
|
||
text-align: center;
|
||
padding: 8px 0 4px;
|
||
border-top: 1px solid var(--border);
|
||
margin-top: 4px;
|
||
opacity: 0.6;
|
||
flex-shrink: 0;
|
||
}
|
||
/* Foreground / background color swatches pinned to the bottom of the
|
||
tool rail (Photoshop-style), with a swap button. */
|
||
.ge-tool-swatches {
|
||
position: sticky;
|
||
bottom: 0;
|
||
margin-top: auto;
|
||
padding: 10px 0 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2px;
|
||
background: var(--panel);
|
||
}
|
||
.ge-swatch {
|
||
display: block;
|
||
width: 26px;
|
||
height: 26px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
|
||
background: #fff;
|
||
}
|
||
.ge-swatch input[type="color"] {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 0;
|
||
border: 0;
|
||
background: none;
|
||
cursor: pointer;
|
||
}
|
||
/* After attachColorPicker swaps the rail swatch to type=text + .cp-swatch-input,
|
||
show just the colour (hide the hex text) and keep it round. */
|
||
.ge-swatch input.cp-swatch-input {
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 0;
|
||
border: 0;
|
||
border-radius: 999px;
|
||
color: transparent;
|
||
caret-color: transparent;
|
||
text-shadow: none;
|
||
font-size: 0;
|
||
outline: none;
|
||
cursor: pointer;
|
||
background: var(--panel);
|
||
}
|
||
.ge-swatch input.cp-swatch-input::selection { background: transparent; }
|
||
.ge-swatch input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
|
||
.ge-swatch input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 3px; }
|
||
.ge-swatch-fg {
|
||
z-index: 2;
|
||
margin-left: 8px;
|
||
margin-top: -10px;
|
||
}
|
||
.ge-swatch-bg { margin-right: 8px; }
|
||
.ge-swatch-swap {
|
||
width: 18px;
|
||
height: 15px;
|
||
margin-top: 2px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--fg) 55%, transparent);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
}
|
||
.ge-swatch-swap:hover { color: var(--fg); }
|
||
/* The left-rail fg/bg swatches are ROUND (Photoshop-style). Overrides the
|
||
generic `.ge-swatch-grid .ge-swatch` sizing rule further down the sheet. */
|
||
.ge-tool-swatches .ge-swatch {
|
||
width: 26px;
|
||
height: 26px;
|
||
aspect-ratio: auto;
|
||
border-radius: 999px;
|
||
}
|
||
/* All tool buttons share a fixed height so the column reads as a clean
|
||
grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of
|
||
wrapping to two lines and making one button taller than the others. */
|
||
.ge-tool-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
padding: 0 2px;
|
||
height: 34px;
|
||
flex-shrink: 0;
|
||
border: none;
|
||
background: none;
|
||
color: var(--fg);
|
||
opacity: 0.6;
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
transition: background 0.15s, opacity 0.15s;
|
||
}
|
||
.ge-tool-btn { position: relative; }
|
||
/* Hover background is now a pseudo so we can shift it 2 px left
|
||
independently from the button's own bounds (which contain the icon
|
||
and label at their already-shifted positions). */
|
||
.ge-tool-btn:hover { opacity: 0.85; }
|
||
.ge-tool-btn.active {
|
||
opacity: 1;
|
||
color: var(--red);
|
||
background: none;
|
||
/* Padding stays the same as inactive buttons so the icon stays
|
||
horizontally aligned with its neighbors. The taller highlight is
|
||
drawn by ::before so it can extend up/down without pushing the
|
||
button or shifting the icon. */
|
||
}
|
||
/* Tool button contents stay above the active highlight pseudo. Also
|
||
shifted an additional 2 px left so the icon + label sit further inside
|
||
the highlight pill (which still anchors to the button bounds). */
|
||
.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; }
|
||
.ge-tool-icon { font-size: 15px; line-height: 1; }
|
||
.ge-tool-icon svg { width: 15px; height: 15px; }
|
||
.ge-tool-label { font-size: 9px; line-height: 1.2; }
|
||
/* Corner triangle on tool slots that hide a family of tools (Photoshop
|
||
mark). Clicking it opens the flyout; the face itself still selects. */
|
||
.ge-tool-flyout-tri {
|
||
position: absolute !important;
|
||
right: 3px !important;
|
||
bottom: 3px !important;
|
||
left: auto !important;
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 5px solid transparent;
|
||
border-bottom: 5px solid currentColor;
|
||
opacity: 0.5;
|
||
z-index: 3;
|
||
cursor: pointer;
|
||
}
|
||
.ge-tool-flyout-tri:hover { opacity: 0.9; }
|
||
.ge-tool-flyout-label { font-size: 12px; white-space: nowrap; }
|
||
/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top-
|
||
left of any tool button marked `ai: true`. Same color as the icon
|
||
itself (inherits foreground) so it reads as part of the tool, not
|
||
a flag. */
|
||
.ge-tool-ai {
|
||
position: absolute !important;
|
||
top: 1px;
|
||
z-index: 2;
|
||
color: inherit;
|
||
opacity: 0.7;
|
||
pointer-events: none;
|
||
font-size: 11px;
|
||
line-height: 1;
|
||
font-weight: 700;
|
||
left: 3px !important;
|
||
}
|
||
.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; }
|
||
.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; }
|
||
/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.)
|
||
so they read as AI-backed at a glance — same glyph as .ge-tool-ai
|
||
in the toolbar, just sitting inline next to the label. */
|
||
.ge-btn-ai-mark {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
line-height: 1;
|
||
font-weight: 700;
|
||
opacity: 0.75;
|
||
margin-right: 1px;
|
||
}
|
||
|
||
/* Per-tool clear-selection badge — shows a tiny X in the top-right of
|
||
the Lasso / Wand button when each holds a selection. Click clears
|
||
the selection without switching tools (handler stops propagation). */
|
||
.ge-tool-clear {
|
||
position: absolute !important;
|
||
top: 3px;
|
||
right: 3px;
|
||
width: 14px;
|
||
height: 14px;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
color: #fff;
|
||
background: var(--red);
|
||
cursor: pointer;
|
||
opacity: 0.95;
|
||
z-index: 2;
|
||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent),
|
||
0 1px 4px color-mix(in srgb, var(--red) 50%, transparent);
|
||
transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s;
|
||
left: auto !important;
|
||
}
|
||
.ge-tool-clear:hover {
|
||
opacity: 1;
|
||
transform: scale(1.15);
|
||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent),
|
||
0 2px 6px color-mix(in srgb, var(--red) 60%, transparent);
|
||
}
|
||
|
||
/* Flyout slots — related tools collapse behind a single face button; the
|
||
caret (or right-click / long-press) opens the family. The flyout is
|
||
detached from the toolbar's scroll clip via position:fixed, with coords
|
||
set in toolbar.js on open. */
|
||
.ge-tool-slot {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-tool-slot > .ge-tool-btn { width: 100%; }
|
||
.ge-tool-caret {
|
||
position: absolute;
|
||
right: 3px;
|
||
bottom: 3px;
|
||
width: 12px;
|
||
height: 12px;
|
||
padding: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
border-radius: 3px;
|
||
background: color-mix(in srgb, var(--fg) 14%, transparent);
|
||
color: var(--fg);
|
||
opacity: 0.7;
|
||
cursor: pointer;
|
||
z-index: 2;
|
||
}
|
||
.ge-tool-slot:hover > .ge-tool-caret,
|
||
.ge-tool-slot.open > .ge-tool-caret { display: inline-flex; }
|
||
.ge-tool-caret:hover { opacity: 1; }
|
||
.ge-tool-flyout {
|
||
position: fixed;
|
||
display: none;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
padding: 4px;
|
||
min-width: 132px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||
z-index: 200;
|
||
}
|
||
.ge-tool-slot.open > .ge-tool-flyout { display: flex; }
|
||
.ge-tool-flyout .ge-tool-btn {
|
||
flex-direction: row;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
height: 34px;
|
||
width: 100%;
|
||
padding: 0 8px;
|
||
opacity: 0.85;
|
||
border-radius: 5px;
|
||
}
|
||
.ge-tool-flyout .ge-tool-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); }
|
||
.ge-tool-flyout .ge-tool-btn.active { opacity: 1; }
|
||
.ge-tool-flyout .ge-tool-label { font-size: 12px; white-space: nowrap; }
|
||
.ge-tool-flyout .ge-tool-ai { position: static !important; left: auto !important; }
|
||
.ge-tool-flyout .ge-tool-btn .ge-tool-clear { display: none !important; }
|
||
.ge-tool-flyout-sep { height: 1px; margin: 3px 2px; background: var(--border); opacity: 0.7; }
|
||
.ge-tool-flyout-heading {
|
||
font-size: 9px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.55;
|
||
padding: 2px 8px 1px;
|
||
}
|
||
.ge-tool-flyout-preset { opacity: 0.78; }
|
||
.ge-tool-flyout-preset .ge-tool-flyout-label { font-size: 11px; }
|
||
|
||
/* Aspect-ratio placeholder shown while a draft is loading. */
|
||
.ge-canvas-placeholder {
|
||
background: color-mix(in srgb, var(--fg) 8%, transparent);
|
||
border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent);
|
||
border-radius: 4px;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 1;
|
||
}
|
||
/* Canvas area (center) */
|
||
.ge-canvas-area {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: auto;
|
||
background: var(--bg);
|
||
position: relative;
|
||
min-width: 0;
|
||
/* Stop touch interactions inside the editor from dragging the whole
|
||
page (or the gallery modal). Touches on the canvas are owned by the
|
||
editor's drawing logic — no native pan/zoom. */
|
||
overscroll-behavior: contain;
|
||
}
|
||
.ge-ruler {
|
||
position: absolute;
|
||
z-index: 16;
|
||
touch-action: none;
|
||
user-select: none;
|
||
}
|
||
.ge-ruler-horizontal {
|
||
top: 0;
|
||
left: 18px;
|
||
right: 0;
|
||
width: calc(100% - 18px);
|
||
height: 18px;
|
||
cursor: row-resize;
|
||
}
|
||
.ge-ruler-vertical {
|
||
top: 18px;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 18px;
|
||
height: calc(100% - 18px);
|
||
cursor: col-resize;
|
||
}
|
||
.ge-ruler-corner {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
z-index: 17;
|
||
width: 18px;
|
||
height: 18px;
|
||
background: var(--panel);
|
||
border-right: 1px solid var(--border);
|
||
border-bottom: 1px solid var(--border);
|
||
box-sizing: border-box;
|
||
}
|
||
.ge-wand-loading {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 25;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: color-mix(in srgb, var(--bg) 30%, transparent);
|
||
pointer-events: none;
|
||
}
|
||
.ge-ai-command {
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: calc(14px + var(--ge-ai-lift, 0px));
|
||
z-index: 18;
|
||
width: min(430px, calc(100% - 28px));
|
||
transform: translateX(-50%);
|
||
padding: 7px;
|
||
box-sizing: border-box;
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border));
|
||
border-radius: 8px;
|
||
background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent);
|
||
box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent);
|
||
backdrop-filter: blur(8px);
|
||
transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
|
||
}
|
||
/* While the floating Transform box is open it owns the canvas area. The
|
||
bottom-docked Quick Edit bar would otherwise sit over the bottom-centre
|
||
transform handle and intercept the touch aimed at it. */
|
||
#gallery-editor-container:has(.ge-transform-popup) .ge-ai-command {
|
||
display: none;
|
||
}
|
||
.ge-ai-command-toggle {
|
||
width: 100%;
|
||
height: 28px;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: var(--accent, var(--red));
|
||
font: inherit;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.ge-ai-command-toggle-caret {
|
||
opacity: 0.7;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-ai-command-head {
|
||
display: flex;
|
||
align-items: center;
|
||
min-height: 24px;
|
||
padding: 0 2px 5px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-ai-command-title {
|
||
margin-left: 5px;
|
||
font-size: 11px;
|
||
font-weight: 650;
|
||
}
|
||
.ge-ai-command-collapsed {
|
||
bottom: -16px;
|
||
width: auto;
|
||
min-width: 102px;
|
||
padding: 2px 9px;
|
||
opacity: 0.32;
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border));
|
||
box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent);
|
||
}
|
||
.ge-canvas-area:hover .ge-ai-command-collapsed,
|
||
.ge-ai-command-collapsed:hover,
|
||
.ge-ai-command-collapsed:focus-within {
|
||
bottom: 10px;
|
||
opacity: 0.94;
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border));
|
||
box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent);
|
||
}
|
||
.ge-ai-command-model {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
height: 26px;
|
||
box-sizing: border-box;
|
||
padding: 0 6px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 7px;
|
||
background: color-mix(in srgb, var(--bg) 76%, #000 24%);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 11px;
|
||
}
|
||
.ge-ai-command-model:focus {
|
||
outline: none;
|
||
border-color: var(--accent, var(--red));
|
||
}
|
||
/* AI settings dialog (image-edit + agent model pickers, double-check toggle) */
|
||
.ge-ai-settings {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 261;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.ge-ai-settings[hidden] { display: none; }
|
||
.ge-ai-settings-card {
|
||
width: min(420px, 100%);
|
||
max-height: 100%;
|
||
overflow: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
padding: 18px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: var(--panel, #232323);
|
||
color: var(--fg);
|
||
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
|
||
}
|
||
.ge-ai-settings-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.ge-ai-settings-head h3 { margin: 0; font-size: 15px; }
|
||
.ge-ai-settings-close {
|
||
width: 26px;
|
||
height: 26px;
|
||
border: none;
|
||
border-radius: 7px;
|
||
background: transparent;
|
||
color: var(--fg-muted);
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
}
|
||
.ge-ai-settings-close:hover { background: rgba(255, 255, 255, 0.1); color: var(--fg); }
|
||
.ge-ai-settings-body { display: flex; flex-direction: column; gap: 6px; }
|
||
.ge-ai-settings-label {
|
||
margin-top: 4px;
|
||
color: var(--fg-muted);
|
||
font-size: 10px;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
}
|
||
.ge-ai-settings-body .ge-ai-command-model { flex: 0 0 auto; width: 100%; height: 30px; }
|
||
.ge-ai-settings-note { margin: 2px 0 0; color: var(--fg-muted); font-size: 11px; line-height: 1.4; }
|
||
.ge-ai-settings-opt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-top: 6px;
|
||
color: var(--fg-muted);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.ge-ai-settings-opt input { margin: 0; accent-color: var(--accent, var(--red)); }
|
||
.ge-ai-settings-strength { display: flex; align-items: center; gap: 8px; }
|
||
.ge-ai-settings-strength input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--accent, var(--red)); }
|
||
.ge-ai-settings-strength-val { flex: 0 0 auto; min-width: 30px; text-align: right; font-size: 11px; color: var(--fg-muted); }
|
||
.ge-ai-settings-foot { display: flex; justify-content: flex-end; }
|
||
.ge-ai-settings-done {
|
||
height: 30px;
|
||
padding: 0 14px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: var(--accent, var(--red));
|
||
color: #fff;
|
||
font: inherit;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-ai-command-form {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.ge-ai-command-input {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
height: 32px;
|
||
box-sizing: border-box;
|
||
padding: 0 9px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: color-mix(in srgb, var(--bg) 76%, #000 24%);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 12px;
|
||
}
|
||
.ge-ai-command-input:focus {
|
||
outline: none;
|
||
border-color: var(--accent, var(--red));
|
||
}
|
||
.ge-ai-command-clear {
|
||
position: relative;
|
||
top: -2px;
|
||
width: 26px;
|
||
height: 32px;
|
||
flex: 0 0 26px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: -38px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: var(--fg-muted);
|
||
cursor: pointer;
|
||
padding: 0;
|
||
z-index: 1;
|
||
}
|
||
.ge-ai-command-clear:hover {
|
||
color: var(--fg);
|
||
background: color-mix(in srgb, var(--fg) 10%, transparent);
|
||
}
|
||
.ge-ai-command-run {
|
||
position: relative;
|
||
top: -2px;
|
||
background: var(--send-btn-bg, var(--red));
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
min-width: 32px;
|
||
width: 32px;
|
||
height: 32px !important;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 0 0 auto;
|
||
transition: background 0.25s, opacity 0.12s;
|
||
overflow: hidden;
|
||
}
|
||
.ge-ai-command-run:hover {
|
||
background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white));
|
||
}
|
||
.ge-ai-command-busy .ge-ai-command-run {
|
||
opacity: 0.58;
|
||
cursor: wait;
|
||
}
|
||
.ge-ai-command-busy .ge-ai-command-run svg {
|
||
display: none;
|
||
}
|
||
.ge-ai-command-close {
|
||
width: 28px;
|
||
height: 32px;
|
||
flex: 0 0 auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
opacity: 0.52;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
.ge-ai-command-close:hover {
|
||
opacity: 0.9;
|
||
background: color-mix(in srgb, var(--fg) 10%, transparent);
|
||
}
|
||
.ge-ai-command-suggestions {
|
||
position: absolute;
|
||
left: 7px;
|
||
right: 7px;
|
||
bottom: calc(100% - 2px);
|
||
z-index: 2;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
max-height: 236px;
|
||
overflow: auto;
|
||
padding: 5px;
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border));
|
||
border-radius: 8px;
|
||
background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%);
|
||
box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent);
|
||
}
|
||
.ge-ai-command-suggestion {
|
||
width: 100%;
|
||
display: grid;
|
||
grid-template-columns: 22px 38px minmax(82px, auto) minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 28px;
|
||
padding: 4px 7px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font: inherit;
|
||
}
|
||
.ge-ai-command-suggestion:hover,
|
||
.ge-ai-command-suggestion.active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent);
|
||
}
|
||
.ge-ai-command-suggestion-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 20px;
|
||
height: 20px;
|
||
color: var(--fg-muted);
|
||
}
|
||
.ge-ai-command-suggestion-icon svg {
|
||
width: 15px;
|
||
height: 15px;
|
||
display: block;
|
||
}
|
||
.ge-ai-command-suggestion.active .ge-ai-command-suggestion-icon,
|
||
.ge-ai-command-suggestion:hover .ge-ai-command-suggestion-icon {
|
||
color: var(--fg);
|
||
}
|
||
.ge-ai-command-suggestion-kind {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
color: var(--accent, var(--red));
|
||
font-size: 8px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
}
|
||
.ge-ai-command-suggestion-main {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: 11px;
|
||
font-weight: 650;
|
||
}
|
||
.ge-ai-command-suggestion-hint {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--fg-muted);
|
||
font-size: 10px;
|
||
}
|
||
.ge-ai-command-status {
|
||
margin-top: 4px;
|
||
font-size: 10px;
|
||
line-height: 1.3;
|
||
color: var(--fg-muted);
|
||
}
|
||
.ge-ai-command-status[data-kind="running"] {
|
||
color: var(--accent, var(--red));
|
||
}
|
||
.ge-ai-command-status[data-kind="done"] {
|
||
color: var(--green, #4caf50);
|
||
}
|
||
.ge-ai-command-status[data-kind="error"] {
|
||
color: var(--red, #e5484d);
|
||
}
|
||
.ge-ai-command-progress {
|
||
position: relative;
|
||
height: 14px;
|
||
margin-top: 5px;
|
||
border-radius: 7px;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
|
||
overflow: hidden;
|
||
}
|
||
.ge-ai-command-progress[hidden] { display: none; }
|
||
.ge-ai-command-progress-fill {
|
||
position: absolute;
|
||
inset: 0 auto 0 0;
|
||
width: 0%;
|
||
border-radius: 7px;
|
||
background: linear-gradient(90deg, var(--accent, var(--red)), color-mix(in srgb, var(--accent, var(--red)) 55%, #fff));
|
||
transition: width 0.4s ease;
|
||
}
|
||
.ge-ai-command-progress-label {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.2px;
|
||
color: #fff;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
|
||
pointer-events: none;
|
||
}
|
||
/* Stacked Quick Edit jobs — transient jelly-style notifications: each prompt
|
||
shows a row while queued/running, fades out when finished, and multiple rows
|
||
stack under each other. No persistent history to dismiss. */
|
||
.ge-ai-command-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
margin-top: 6px;
|
||
max-height: 168px;
|
||
overflow-y: auto;
|
||
}
|
||
.ge-ai-job {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 6px;
|
||
border-radius: 7px;
|
||
border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent);
|
||
background: color-mix(in srgb, var(--fg) 4%, transparent);
|
||
font-size: 10px;
|
||
line-height: 1.25;
|
||
animation: ge-ai-job-in 0.2s ease-out;
|
||
}
|
||
.ge-ai-job-leaving {
|
||
opacity: 0;
|
||
transform: translateY(-4px);
|
||
transition: opacity 0.32s ease, transform 0.32s ease;
|
||
}
|
||
@keyframes ge-ai-job-in {
|
||
from { opacity: 0; transform: translateY(4px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
.ge-ai-job-icon {
|
||
flex: 0 0 auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 13px;
|
||
color: var(--fg-muted);
|
||
}
|
||
.ge-ai-job-body {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
}
|
||
.ge-ai-job-prompt {
|
||
color: var(--fg);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.ge-ai-job-detail {
|
||
color: var(--fg-muted);
|
||
font-size: 9px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.ge-ai-job-detail[hidden] { display: none; }
|
||
.ge-ai-job-cancel {
|
||
flex: 0 0 auto;
|
||
padding: 2px 6px;
|
||
border-radius: 5px;
|
||
border: 1px solid color-mix(in srgb, var(--red, #e5484d) 40%, transparent);
|
||
background: transparent;
|
||
color: var(--red, #e5484d);
|
||
font-size: 9px;
|
||
line-height: 1.4;
|
||
cursor: pointer;
|
||
}
|
||
.ge-ai-job-cancel:hover {
|
||
background: color-mix(in srgb, var(--red, #e5484d) 16%, transparent);
|
||
}
|
||
.ge-ai-job-cancel[hidden] { display: none; }
|
||
.ge-ai-job-running { border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); }
|
||
.ge-ai-job-running .ge-ai-job-detail { color: var(--accent, var(--red)); }
|
||
.ge-ai-job-running .ge-ai-job-icon { color: var(--accent, var(--red)); }
|
||
.ge-ai-job-done .ge-ai-job-icon { color: var(--green, #4caf50); }
|
||
.ge-ai-job-error .ge-ai-job-icon { color: var(--red, #e5484d); }
|
||
.ge-ai-job-error .ge-ai-job-detail { color: var(--red, #e5484d); }
|
||
.ge-ai-job-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: currentColor;
|
||
}
|
||
/* The running row uses the app's whirlpool spinner (canvas), not a CSS ring. */
|
||
.ge-ai-job-icon .ai-spinner-whirlpool { display: inline-flex; }
|
||
/* "fx" glyph for the new-adjustment-layer button next to the "+" new-layer button. */
|
||
.ge-add-fx-glyph {
|
||
font-size: 10px;
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
letter-spacing: -0.4px;
|
||
line-height: 1;
|
||
}
|
||
/* Loading overlay shown while a large image is decoding — centered
|
||
whirlpool + "Loading" caption over a translucent dim. */
|
||
.ge-loading-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 20;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: color-mix(in srgb, var(--bg) 70%, transparent);
|
||
pointer-events: none;
|
||
}
|
||
/* Full-editor cover (project load): above the toolbar/panel and near-opaque so
|
||
the top toolbar/old content doesn't peek through while loading. */
|
||
.ge-loading-overlay-full {
|
||
z-index: 200;
|
||
background: color-mix(in srgb, var(--bg) 92%, transparent);
|
||
pointer-events: auto;
|
||
}
|
||
.ge-loading-overlay .ge-loading-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 10px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent);
|
||
}
|
||
.ge-loading-overlay .ge-loading-text {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* Shortcuts cheatsheet (toggled by `?` or the keyboard icon in the top bar). */
|
||
#ge-shortcuts-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 50;
|
||
background: color-mix(in srgb, var(--bg) 70%, transparent);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
}
|
||
#ge-shortcuts-overlay[hidden] { display: none; }
|
||
.ge-shortcuts-card {
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 14px 18px 12px;
|
||
box-shadow: 0 8px 30px color-mix(in srgb, var(--fg) 25%, transparent);
|
||
width: min(720px, 92vw);
|
||
max-height: 86vh;
|
||
overflow-y: auto;
|
||
color: var(--fg);
|
||
}
|
||
.ge-shortcuts-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
.ge-shortcuts-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 14px 24px;
|
||
}
|
||
@media (min-width: 720px) {
|
||
.ge-shortcuts-grid { grid-template-columns: repeat(4, 1fr); }
|
||
}
|
||
.ge-shortcuts-col h5 {
|
||
margin: 0 0 6px;
|
||
font-size: 11px;
|
||
opacity: 0.6;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
font-weight: 600;
|
||
}
|
||
.ge-shortcuts-foot {
|
||
margin-top: 12px;
|
||
font-size: 11px;
|
||
opacity: 0.55;
|
||
text-align: center;
|
||
}
|
||
.ge-main-canvas {
|
||
/* Cursor is managed entirely by JS so each tool can show its own
|
||
icon (move arrow, circle overlay, etc.) without CSS fighting it. */
|
||
image-rendering: pixelated;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
|
||
background: repeating-conic-gradient(#808080 0% 25%, #a0a0a0 0% 50%) 50% / 16px 16px;
|
||
}
|
||
/* AI-working indicator: while a Quick Edit / inpaint request is in flight a
|
||
fireflies glow drifts over only the region being edited (the selection
|
||
bounds, or the whole canvas when nothing is selected) — the same drifting,
|
||
pulsing look as the "yoyo" theme's ascii-fireflies background, scoped so it
|
||
never covers the workspace around the image. */
|
||
.ge-canvas-area.ge-canvas-ai-busy::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: var(--ge-ai-busy-x, 0px);
|
||
top: var(--ge-ai-busy-y, 0px);
|
||
width: var(--ge-ai-busy-w, 100%);
|
||
height: var(--ge-ai-busy-h, 100%);
|
||
border-radius: 3px;
|
||
z-index: 11;
|
||
pointer-events: none;
|
||
/* slight dark scrim under the ASCII skeleton so the glyphs read clearly */
|
||
background: rgba(6, 14, 9, 0.38);
|
||
}
|
||
.ge-canvas-area .ge-ai-ascii-fireflies {
|
||
position: absolute;
|
||
left: var(--ge-ai-busy-x, 0px);
|
||
top: var(--ge-ai-busy-y, 0px);
|
||
width: var(--ge-ai-busy-w, 100%);
|
||
height: var(--ge-ai-busy-h, 100%);
|
||
margin: 0;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
border-radius: 3px;
|
||
z-index: 12;
|
||
pointer-events: none;
|
||
white-space: pre;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
font-size: 11px;
|
||
line-height: 1;
|
||
letter-spacing: 0;
|
||
color: #b8e6c1;
|
||
text-shadow: 0 0 6px rgba(120, 220, 150, 0.55);
|
||
background: transparent !important;
|
||
opacity: 0;
|
||
transition: opacity 0.25s ease;
|
||
}
|
||
.ge-canvas-area.ge-canvas-ai-busy .ge-ai-ascii-fireflies { opacity: 1; }
|
||
.ge-canvas-area .ge-ai-ascii-fireflies::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
background: linear-gradient(100deg, transparent 35%, rgba(184, 230, 193, 0.20) 50%, transparent 65%);
|
||
background-size: 220% 100%;
|
||
background-repeat: no-repeat;
|
||
animation: ge-ai-canvas-sheen 2.2s linear infinite;
|
||
mix-blend-mode: screen;
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.ge-canvas-area .ge-ai-ascii-fireflies { display: none; }
|
||
}
|
||
@keyframes ge-ai-canvas-sheen {
|
||
0% { background-position: 130% 0; }
|
||
100% { background-position: -30% 0; }
|
||
}
|
||
|
||
/* Transform-overlay canvas — sits over the main canvas with extra
|
||
margin so resize / rotation handles can render OUTSIDE the image
|
||
bounds. Pointer events disabled so it doesn't intercept clicks. */
|
||
.ge-transform-overlay {
|
||
position: absolute;
|
||
image-rendering: pixelated;
|
||
pointer-events: none;
|
||
z-index: 5;
|
||
}
|
||
.ge-selection-overlay {
|
||
position: absolute;
|
||
image-rendering: pixelated;
|
||
pointer-events: none;
|
||
z-index: 4;
|
||
}
|
||
.ge-quick-mask-bar {
|
||
position: absolute;
|
||
top: 10px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 18;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 32px;
|
||
padding: 4px 5px 4px 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--panel);
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.24);
|
||
color: var(--fg);
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-quick-mask-label { font-weight: 600; }
|
||
.ge-quick-mask-tools { display: flex; }
|
||
.ge-quick-mask-tool,
|
||
.ge-quick-mask-done {
|
||
min-height: 24px;
|
||
padding: 2px 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg);
|
||
color: inherit;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
}
|
||
.ge-quick-mask-tool:first-child { border-radius: 4px 0 0 4px; }
|
||
.ge-quick-mask-tool:last-child { border-radius: 0 4px 4px 0; border-left: 0; }
|
||
.ge-quick-mask-tool.active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||
.ge-quick-mask-done { border-radius: 4px; }
|
||
.ge-marquee-constraint-row {
|
||
display: grid;
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 5px 7px;
|
||
font-size: 10px;
|
||
}
|
||
.ge-marquee-constraint-row > select {
|
||
min-width: 0;
|
||
height: 26px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-marquee-dimensions {
|
||
grid-column: 1 / -1;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 26px;
|
||
gap: 5px;
|
||
align-items: center;
|
||
}
|
||
.ge-marquee-dimensions label {
|
||
display: grid;
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.ge-marquee-dimensions input {
|
||
width: 100%;
|
||
min-width: 0;
|
||
height: 26px;
|
||
padding: 2px 5px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-marquee-dimensions .ge-icon-btn { width: 26px; height: 26px; }
|
||
|
||
/* Right panel */
|
||
.ge-right-panel {
|
||
width: 340px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border-left: 1px solid var(--border);
|
||
background: var(--panel);
|
||
/* The whole panel scrolls so every tool control is reachable; the
|
||
layers-actions row uses position:sticky to stay pinned at the
|
||
bottom of the viewport while scrolling. */
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
/* Controls */
|
||
.ge-controls {
|
||
flex: 0 0 auto;
|
||
padding: 10px 10px 6px;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.ge-layer-geometry-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
#ge-layer-geometry-name {
|
||
margin-left: auto;
|
||
min-width: 0;
|
||
max-width: 110px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--color-muted);
|
||
font-size: 9px;
|
||
font-weight: 400;
|
||
text-transform: none;
|
||
}
|
||
.ge-layer-geometry-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 5px;
|
||
}
|
||
.ge-layer-geometry-grid label {
|
||
display: grid;
|
||
grid-template-columns: 12px minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 2px;
|
||
min-width: 0;
|
||
color: var(--color-muted);
|
||
font-size: 9px;
|
||
}
|
||
.ge-layer-geometry-grid input {
|
||
width: 100%;
|
||
height: 26px;
|
||
box-sizing: border-box;
|
||
min-width: 0;
|
||
padding: 3px 4px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
text-align: right;
|
||
appearance: textfield;
|
||
}
|
||
.ge-layer-geometry-grid input:focus { outline: none; border-color: var(--red); }
|
||
.ge-layer-geometry-grid input[readonly] { opacity: 0.58; cursor: default; }
|
||
@media (max-width: 700px) {
|
||
.ge-layer-geometry-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||
}
|
||
/* Brush controls (Color + Size) — give the two rows breathing room
|
||
between them, since neither sits inside a separator-bordered section. */
|
||
#ge-brush-controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
/* The dropdown shells set `display:flex` above, which otherwise beats the
|
||
UA `[hidden]{display:none}` rule and leaves every menu open on load. */
|
||
.ge-file-menu[hidden],
|
||
.ge-edit-menu[hidden],
|
||
.ge-layer-menu[hidden],
|
||
.ge-type-menu[hidden],
|
||
.ge-window-menu[hidden],
|
||
.ge-help-menu[hidden],
|
||
.ge-resize-menu[hidden],
|
||
.ge-edge-menu[hidden],
|
||
.ge-view-menu[hidden],
|
||
.ge-image-menu[hidden],
|
||
.ge-selection-menu[hidden],
|
||
.ge-filter-menu[hidden],
|
||
.ge-3d-menu[hidden],
|
||
.ge-plugins-menu[hidden],
|
||
.ge-save-menu[hidden] { display: none; }
|
||
.ge-brush-pressure-row { justify-content: space-between; gap: 6px; }
|
||
.ge-pressure-option { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; opacity: 0.7; }
|
||
.ge-pressure-option .toggle-switch { width: 24px; height: 14px; }
|
||
.ge-pressure-option .toggle-slider::before { width: 10px; height: 10px; }
|
||
.ge-pressure-option .toggle-switch input:checked + .toggle-slider::before { transform: translateX(10px); }
|
||
.ge-brush-preset-row select { flex: 1 1 auto; min-width: 0; }
|
||
.ge-brush-preset-row .ge-btn { flex: 0 0 auto; }
|
||
.ge-text-section,
|
||
.ge-shape-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
.ge-shape-gradient-extra-stops {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.ge-gradient-extra-stop {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
|
||
gap: 6px;
|
||
align-items: end;
|
||
}
|
||
.ge-gradient-stop-remove {
|
||
min-width: 28px;
|
||
height: 28px;
|
||
padding: 0 6px;
|
||
}
|
||
.ge-text-section textarea,
|
||
.ge-text-section select,
|
||
.ge-text-section input[type="number"],
|
||
.ge-shape-section input[type="number"] {
|
||
box-sizing: border-box;
|
||
min-width: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-text-section textarea {
|
||
width: 100%;
|
||
min-height: 58px;
|
||
resize: vertical;
|
||
padding: 6px 7px;
|
||
line-height: 1.35;
|
||
}
|
||
.ge-text-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(56px, 0.42fr) auto;
|
||
gap: 6px;
|
||
align-items: end;
|
||
}
|
||
.ge-text-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
min-width: 0;
|
||
font-size: 9px;
|
||
color: var(--color-muted);
|
||
}
|
||
.ge-text-field select,
|
||
.ge-text-field input { width: 100%; height: 28px; padding: 3px 6px; }
|
||
.ge-text-format-row { gap: 4px; }
|
||
.ge-text-toggle {
|
||
width: auto;
|
||
min-width: 24px;
|
||
flex: 1 1 24px;
|
||
height: 28px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
}
|
||
.ge-text-toggle:hover { background: color-mix(in srgb, var(--fg) 7%, transparent); }
|
||
.ge-text-toggle.active {
|
||
color: var(--red);
|
||
border-color: color-mix(in srgb, var(--red) 55%, var(--border));
|
||
background: color-mix(in srgb, var(--red) 12%, transparent);
|
||
}
|
||
.ge-text-format-sep { width: 1px; height: 20px; background: var(--border); margin: 0 2px; }
|
||
.ge-text-opentype-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||
.ge-text-inline-check { flex-direction: row; align-items: center; gap: 4px; }
|
||
.ge-text-inline-check > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.ge-text-inline-check input[type="checkbox"] { flex: 0 0 auto; width: auto; height: auto; min-width: 0; margin: 0; }
|
||
.ge-shape-types { display: flex; gap: 4px; }
|
||
.ge-shape-sides-field { max-width: 90px; }
|
||
.ge-direct-text-editor {
|
||
position: fixed;
|
||
z-index: 10020;
|
||
box-sizing: border-box;
|
||
padding: 4px;
|
||
overflow: hidden;
|
||
resize: both;
|
||
border: 1px dashed var(--red);
|
||
outline: 1px solid color-mix(in srgb, var(--red) 24%, transparent);
|
||
border-radius: 2px;
|
||
background: color-mix(in srgb, var(--bg) 8%, transparent);
|
||
caret-color: var(--red);
|
||
transform-origin: top left;
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* Floating Transform popup — horizontal layout, draggable from
|
||
anywhere on the popup (matches the FX / adjust popups). Defaults
|
||
to the right side of the editor (over the layers panel). */
|
||
/* Match the .ge-adj-popup layout convention: icon + title + [_][×]
|
||
header bar, then the body. Drag from the header (same as FX popups). */
|
||
.ge-transform-popup {
|
||
position: absolute;
|
||
z-index: 10020;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 0 0 8px;
|
||
/* Never let the single-row popup grow wider than the editor: its
|
||
min-content width otherwise pushes it left across the tool column.
|
||
Capping + wrapping keeps the tool buttons clickable. */
|
||
max-width: calc(100% - 90px);
|
||
min-width: 0;
|
||
background: color-mix(in srgb, var(--panel) 98%, var(--bg));
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border));
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
||
backdrop-filter: blur(6px);
|
||
user-select: none;
|
||
}
|
||
.ge-transform-popup-head {
|
||
/* Inherits .ge-adj-head styling — flex row, icon + title + buttons,
|
||
bottom border, grab cursor — with the title nudged down 2px for
|
||
visual balance and explicit horizontal padding so the icon doesn't
|
||
hug the left edge. */
|
||
display: flex !important;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 12px;
|
||
padding-top: 10px;
|
||
touch-action: none;
|
||
cursor: grab;
|
||
}
|
||
.ge-transform-popup-head:active { cursor: grabbing; }
|
||
.ge-transform-popup-head .ge-head-btns { margin-left: auto !important; }
|
||
.ge-transform-popup-head .ge-adj-title { position: relative; top: 2px; }
|
||
/* Icon nudged 4px lower than the title baseline so it reads as the
|
||
row's anchor rather than floating above it. */
|
||
.ge-transform-popup-head .ge-adj-icon { position: relative; top: 4px; }
|
||
/* Mobile-only: shift the head content UP to match the rest of the
|
||
editor's mobile popup styling. */
|
||
@media (max-width: 820px) {
|
||
.ge-transform-popup-head .ge-adj-title { top: -2px; }
|
||
.ge-transform-popup-head .ge-adj-icon { top: 0; }
|
||
}
|
||
/* Make sure the head-buttons cluster sits hard-right with a small
|
||
gap, matching the FX-popup convention. */
|
||
.ge-transform-popup-head .ge-head-btns { margin-left: auto; }
|
||
.ge-transform-min-hint {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
opacity: 0.45;
|
||
margin-right: -2px;
|
||
line-height: 1;
|
||
color: var(--fg-muted);
|
||
}
|
||
/* Small "Merge" text label next to each merge / flatten button so the
|
||
icons read at a glance instead of needing the title-tooltip. */
|
||
.ge-merge-label {
|
||
font-size: 10px;
|
||
margin-left: 4px;
|
||
opacity: 0.75;
|
||
}
|
||
.ge-transform-popup-body {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
row-gap: 6px;
|
||
gap: 6px;
|
||
padding: 4px 12px;
|
||
min-width: 0;
|
||
}
|
||
.ge-transform-popup-hint { padding: 0 12px; }
|
||
.ge-transform-field {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
position: relative;
|
||
}
|
||
.ge-transform-field label {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
opacity: 0.65;
|
||
min-width: 12px;
|
||
text-align: right;
|
||
}
|
||
/* Hide the native browser spin-buttons entirely — we render our own
|
||
themed ▲/▼ via .ge-transform-spin (see below). */
|
||
.ge-transform-popup-input {
|
||
width: 76px;
|
||
height: 24px;
|
||
padding: 0 18px 0 6px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
font-variant-numeric: tabular-nums;
|
||
text-align: right;
|
||
cursor: text;
|
||
-moz-appearance: textfield;
|
||
box-sizing: border-box;
|
||
}
|
||
/* Rotation field reserves extra room on the right for both the °
|
||
suffix AND the custom spinner so they don't sit on top of each other.
|
||
The suffix renders inside that reserved zone. */
|
||
.ge-transform-popup-input-rot { width: 76px; padding-right: 30px; }
|
||
.ge-transform-popup-input:focus { outline: none; border-color: var(--red); }
|
||
/* Custom themed spinner — two slim chevron buttons stacked to the right
|
||
of the input. Tight stack so the pair reads as one control instead of
|
||
two floating arrows. */
|
||
.ge-transform-spin {
|
||
position: absolute;
|
||
right: 3px;
|
||
top: 50%;
|
||
/* Nudged up 4px relative to the input's vertical center. */
|
||
transform: translateY(calc(-50% - 4px));
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 0;
|
||
height: 18px;
|
||
}
|
||
.ge-transform-spin button {
|
||
flex: 1 1 0;
|
||
width: 12px;
|
||
min-height: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: var(--fg-muted);
|
||
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
|
||
font-size: 7px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||
}
|
||
.ge-transform-spin button:first-child { border-radius: 3px 3px 0 0; border-bottom: 0; }
|
||
.ge-transform-spin button:last-child { border-radius: 0 0 3px 3px; }
|
||
.ge-transform-spin button:hover {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 25%, transparent);
|
||
color: var(--fg);
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border));
|
||
}
|
||
.ge-transform-spin button:active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
|
||
}
|
||
/* Position the ° suffix to the LEFT of the spinner so they don't
|
||
overlap. The rotation input's larger padding leaves room. */
|
||
.ge-transform-popup-suffix {
|
||
position: absolute;
|
||
right: 20px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 10px;
|
||
opacity: 0.55;
|
||
pointer-events: none;
|
||
}
|
||
.ge-transform-aspect-btn {
|
||
background: none;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
color: var(--fg-muted);
|
||
cursor: pointer;
|
||
/* 2px smaller than the W/H input height so it reads as a chip-style
|
||
toggle rather than another form field. Pulled up 2px so it tucks
|
||
between W and H rather than sitting on their baseline. */
|
||
height: 22px;
|
||
width: 22px;
|
||
padding: 0;
|
||
position: relative;
|
||
top: -3px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.5;
|
||
transition: opacity 0.12s, color 0.12s, border-color 0.12s;
|
||
}
|
||
.ge-transform-aspect-btn:hover { opacity: 1; }
|
||
.ge-transform-aspect-btn.active {
|
||
opacity: 1;
|
||
color: var(--accent, var(--red));
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border));
|
||
}
|
||
/* Quick-action cluster (flip H, flip V, rotate 90°) — sits between the
|
||
rotation input and the Apply button. Each button matches the input
|
||
height and uses the editor's accent palette on hover. */
|
||
.ge-transform-quick {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
padding: 0 4px;
|
||
/* Pulled up 2px so the flip/rotate icons sit slightly higher than the
|
||
W/H/rotation inputs, matching the visual weight of the head row. */
|
||
position: relative;
|
||
top: -2px;
|
||
border-left: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
||
}
|
||
.ge-transform-quick-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
padding: 0;
|
||
background: none;
|
||
color: var(--fg-muted);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.7;
|
||
transition: opacity 0.12s, color 0.12s, border-color 0.12s, background 0.12s;
|
||
}
|
||
.ge-transform-quick-btn:hover {
|
||
opacity: 1;
|
||
color: var(--fg);
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border));
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
|
||
}
|
||
.ge-transform-quick-btn:active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 25%, transparent);
|
||
}
|
||
.ge-transform-popup-hint {
|
||
font-size: 10px;
|
||
opacity: 0.5;
|
||
margin: 0;
|
||
line-height: 1.3;
|
||
}
|
||
.ge-transform-preserve-field {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 0 12px;
|
||
font-size: 10px;
|
||
color: var(--fg-muted);
|
||
cursor: pointer;
|
||
}
|
||
.ge-transform-preserve-field input {
|
||
accent-color: var(--accent, var(--red));
|
||
margin: 0;
|
||
}
|
||
|
||
/* Floating Inpaint prompt — pops next to the user's last brush stroke
|
||
so they can type a description and Generate without diverting to
|
||
the side panel. */
|
||
.ge-inpaint-popup {
|
||
position: fixed;
|
||
z-index: 280;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 8px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent);
|
||
}
|
||
.ge-inpaint-popup-input {
|
||
width: 240px;
|
||
padding: 6px 8px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 12px;
|
||
position: relative;
|
||
top: 0;
|
||
}
|
||
.ge-inpaint-popup-input:focus { outline: none; border-color: var(--red); }
|
||
.ge-inpaint-popup-run {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
position: relative;
|
||
top: 1px;
|
||
}
|
||
.ge-inpaint-popup-close {
|
||
background: none;
|
||
border: none;
|
||
color: var(--fg-muted);
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
padding: 2px 4px;
|
||
margin-left: 2px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
top: 0;
|
||
opacity: 0.7;
|
||
transition: opacity 0.15s, color 0.15s;
|
||
}
|
||
.ge-inpaint-popup-close:hover { opacity: 1; color: var(--fg); }
|
||
/* Loading state: the popup's panel chrome (bg/border/shadow) gets out
|
||
of the way so only the whirlpool floats over the canvas. */
|
||
.ge-inpaint-popup.ge-inpaint-popup-loading {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
box-shadow: none;
|
||
padding: 0;
|
||
min-width: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.ge-controls input[type="range"] {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
display: block;
|
||
}
|
||
.ge-control-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 11px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-control-row label {
|
||
flex-shrink: 0;
|
||
opacity: 0.7;
|
||
min-width: 36px;
|
||
}
|
||
.ge-color-picker {
|
||
width: 24px;
|
||
height: 24px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 50%;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
background: none;
|
||
overflow: hidden;
|
||
}
|
||
/* attachColorPicker swaps the native <input type="color"> for a styled
|
||
text input with the same `.ge-color-picker` class plus
|
||
`.cp-swatch-input`. Force the 24×24 circular swatch back on it so
|
||
it doesn't render as a wide text box stretched across the row. */
|
||
.ge-color-picker.cp-swatch-input {
|
||
width: 24px;
|
||
height: 24px;
|
||
flex: 0 0 24px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 50%;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
/* Hide the (read-only) text caret + selection so the element reads
|
||
as a swatch, not a text field. */
|
||
color: transparent;
|
||
text-shadow: none;
|
||
caret-color: transparent;
|
||
font-size: 0;
|
||
user-select: none;
|
||
}
|
||
.ge-color-picker.cp-swatch-input:focus {
|
||
outline: 1px solid var(--red);
|
||
outline-offset: 1px;
|
||
}
|
||
.ge-size-slider {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
height: 8px;
|
||
accent-color: var(--red);
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
margin: 0;
|
||
}
|
||
.ge-size-label { font-size: 10px; opacity: 0.5; min-width: 28px; text-align: right; }
|
||
|
||
/* Eraser controls: tighter rows + a small brush preview on the left of
|
||
each label so the user can see what each slider affects. The three
|
||
previews share the same dot but each leans on a different rendering
|
||
trick (alpha / scatter / blur) to mirror its slider's effect. */
|
||
.ge-eraser-row {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
gap: 8px;
|
||
padding: 2px 0;
|
||
/* Lets the trailing value span pin to the row's right edge so it
|
||
stays visible when the slider expands left over the label. */
|
||
position: relative;
|
||
}
|
||
/* Inline value chip next to the label text. (Previously absolutely-
|
||
positioned over the slider track to ride the expand animation, which
|
||
caused overlap now that the dynamic-slider behavior is off for these.) */
|
||
.ge-eraser-row label > span[id$="-label"] {
|
||
margin-left: 4px;
|
||
padding: 0 4px;
|
||
border-radius: 3px;
|
||
cursor: text;
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
/* When the value chip is pulled out of its label and placed AFTER the
|
||
slider (see DOM transform in galleryEditor.js init), it sits on the
|
||
row's right edge and naturally shortens the slider's flex space. */
|
||
.ge-eraser-row > span[id$="-label"].ge-slider-value {
|
||
flex: 0 0 auto;
|
||
margin: 0 0 0 6px;
|
||
padding: 0 4px;
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
font-variant-numeric: tabular-nums;
|
||
min-width: 34px;
|
||
text-align: right;
|
||
cursor: text;
|
||
border-radius: 3px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Per-tool model selector row (lives at the top of each AI tool's
|
||
section in the side panel). Mirrors the inpaint Model row layout. */
|
||
.ge-tool-model-row {
|
||
display: flex !important;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-bottom: 6px;
|
||
min-width: 0;
|
||
}
|
||
.ge-tool-model-row > label {
|
||
font-size: 11px;
|
||
opacity: 0.6;
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-tool-model {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
font-size: 10px;
|
||
padding: 2px 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* Section label inside a dropdown — small uppercase header. */
|
||
.dropdown-section-label {
|
||
font-size: 9px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.5;
|
||
font-weight: 600;
|
||
padding: 6px 8px 2px;
|
||
}
|
||
.dropdown-section-divider {
|
||
height: 1px;
|
||
background: var(--border);
|
||
margin: 4px 0;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* Inline numeric editor that replaces the value chip on click. */
|
||
.ge-slider-edit {
|
||
position: absolute;
|
||
z-index: 11;
|
||
font: inherit;
|
||
font-size: 10px;
|
||
font-variant-numeric: tabular-nums;
|
||
padding: 1px 4px;
|
||
border: 1px solid var(--red);
|
||
border-radius: 3px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
outline: none;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Merge dropdown — position is set by JS each time it opens (relative to
|
||
the button's bounding rect) because the parent .ge-layers has
|
||
overflow:hidden which would otherwise clip the popup. */
|
||
.ge-merge-menu {
|
||
position: fixed;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
padding: 3px;
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent);
|
||
z-index: 50;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 140px;
|
||
}
|
||
.ge-merge-menu .dropdown-item-compact {
|
||
background: none;
|
||
border: none;
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 5px 8px;
|
||
font-size: 11px;
|
||
gap: 8px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
}
|
||
|
||
/* Thin separator inside a tool section. */
|
||
.ge-section-divider {
|
||
border: 0;
|
||
border-top: 1px solid var(--border);
|
||
margin: 10px -2px;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* Small explanatory paragraph under AI tool section labels. */
|
||
.ge-section-hint {
|
||
font-size: 10.5px;
|
||
line-height: 1.45;
|
||
opacity: 0.55;
|
||
margin: 0 0 8px;
|
||
}
|
||
/* Clone-tool hint swaps between "Alt-click" (desktop) and "Double-tap"
|
||
(mobile) so the on-screen instruction matches the actual gesture. */
|
||
.ge-clone-hint-mobile { display: none; }
|
||
@media (max-width: 820px) {
|
||
.ge-clone-hint-desktop { display: none; }
|
||
.ge-clone-hint-mobile { display: inline; }
|
||
}
|
||
/* Section title + adjacent "?" affordance — collapses the long
|
||
explanatory paragraph into a hoverable tooltip so the side panel
|
||
stays compact. */
|
||
.ge-section-title-with-help {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
}
|
||
/* Drop the source-whitespace text node between the title text and the
|
||
? span so the chip hugs the title with no extra gap. */
|
||
.ge-section-title-with-help .ge-section-help { margin-left: 0; }
|
||
.ge-section-help {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
background: color-mix(in srgb, var(--fg) 12%, transparent);
|
||
color: var(--fg-muted);
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
cursor: help;
|
||
user-select: none;
|
||
transition: background 0.12s, color 0.12s;
|
||
}
|
||
.ge-section-help:hover,
|
||
.ge-section-help:focus-visible {
|
||
background: color-mix(in srgb, var(--fg) 24%, transparent);
|
||
color: var(--fg);
|
||
outline: none;
|
||
}
|
||
|
||
/* Lightweight section title — used inside the inpaint panel to group
|
||
the mask actions (Eye / Invert / Clear) under a "Selection" header. */
|
||
.ge-section-title {
|
||
margin: 10px 0 4px;
|
||
font-size: 10px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.5;
|
||
font-weight: 600;
|
||
}
|
||
/* Selection-row buttons stay compact so eye + invert + clear fit on
|
||
one line beside each other. */
|
||
.ge-inpaint-mask-row {
|
||
display: flex !important;
|
||
gap: 4px;
|
||
align-items: center;
|
||
}
|
||
.ge-inpaint-mask-row .ge-btn {
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-inpaint-mask-label {
|
||
font-size: 10px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.5;
|
||
font-weight: 600;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
/* Fixed label column so the three sliders start at the same x and have
|
||
the same length when unused. Without this, "Flow" (4 chars) leaves
|
||
more room than "Opacity" (7 chars). */
|
||
.ge-eraser-row label {
|
||
font-size: 10px;
|
||
opacity: 0.55;
|
||
flex: 0 0 78px;
|
||
white-space: nowrap;
|
||
display: inline-flex;
|
||
justify-content: space-between;
|
||
gap: 4px;
|
||
}
|
||
/* Fixed-size slider track for all side-panel sliders — no dynamic
|
||
width/height expansion on drag. Only the thumb grows on interaction
|
||
(see ::-webkit-slider-thumb / ::-moz-range-thumb below). */
|
||
.ge-eraser-row input[type="range"] {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
height: 8px;
|
||
accent-color: var(--red);
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
margin: 0;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.ge-eraser-row input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
background: var(--red);
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: width 0.12s ease 0.5s, height 0.12s ease 0.5s;
|
||
}
|
||
.ge-eraser-row input[type="range"]:hover::-webkit-slider-thumb,
|
||
.ge-eraser-row input[type="range"].is-using::-webkit-slider-thumb,
|
||
.ge-eraser-row input[type="range"]:active::-webkit-slider-thumb {
|
||
width: 16px; height: 16px;
|
||
transition: width 0.12s ease 0s, height 0.12s ease 0s;
|
||
}
|
||
.ge-eraser-row input[type="range"]::-moz-range-thumb {
|
||
width: 9px; height: 9px;
|
||
border-radius: 50%;
|
||
background: var(--red);
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: width 0.12s ease 0.5s, height 0.12s ease 0.5s;
|
||
}
|
||
.ge-eraser-row input[type="range"]:hover::-moz-range-thumb,
|
||
.ge-eraser-row input[type="range"].is-using::-moz-range-thumb,
|
||
.ge-eraser-row input[type="range"]:active::-moz-range-thumb {
|
||
width: 16px; height: 16px;
|
||
transition: width 0.12s ease 0s, height 0.12s ease 0s;
|
||
}
|
||
|
||
/* Help "?" icon next to a section title. Subtle by default, hover
|
||
brightens. Tooltip via native `title` attribute. */
|
||
.ge-section-help {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 14px;
|
||
height: 14px;
|
||
margin-left: 4px;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
border: 1px solid var(--border);
|
||
border-radius: 50%;
|
||
color: var(--fg-muted);
|
||
background: none;
|
||
cursor: help;
|
||
vertical-align: middle;
|
||
position: relative;
|
||
top: -1px;
|
||
opacity: 0.7;
|
||
transition: opacity 0.15s, color 0.15s, border-color 0.15s;
|
||
}
|
||
.ge-section-help:hover { opacity: 1; color: var(--fg); border-color: var(--fg-muted); }
|
||
|
||
/* Layer-FX popup — floating window bound to one layer. */
|
||
.ge-fx-popup {
|
||
position: fixed;
|
||
z-index: 300;
|
||
width: 320px;
|
||
max-height: 80vh;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 12px 32px rgba(0,0,0,0.45);
|
||
display: flex;
|
||
flex-direction: column;
|
||
font-size: 11px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-fx-popup-head {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 8px 10px;
|
||
border-bottom: 1px solid var(--border);
|
||
font-weight: 600;
|
||
}
|
||
.ge-fx-popup-title { font-size: 12px; }
|
||
.ge-fx-popup-close {
|
||
background: none; border: none; color: var(--fg-muted);
|
||
font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px;
|
||
}
|
||
.ge-fx-popup-close:hover { color: var(--fg); }
|
||
.ge-fx-popup-body { padding: 8px 10px; overflow-y: auto; }
|
||
.ge-fx-popup-foot {
|
||
display: flex; gap: 6px; padding: 6px 10px;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
.ge-fx-group-title {
|
||
font-size: 10px; font-weight: 600; opacity: 0.6;
|
||
text-transform: uppercase; letter-spacing: 0.5px;
|
||
margin: 8px 0 4px;
|
||
}
|
||
.ge-fx-group-title:first-child { margin-top: 0; }
|
||
.ge-fx-cb-tone {
|
||
font-size: 10px; opacity: 0.55; font-style: italic;
|
||
margin: 4px 0 2px;
|
||
}
|
||
.ge-fx-row {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 2px 0;
|
||
}
|
||
.ge-fx-row-label {
|
||
font-size: 10px; opacity: 0.7;
|
||
flex: 0 0 110px; white-space: nowrap;
|
||
}
|
||
.ge-fx-row input[type="range"] {
|
||
flex: 1 1 auto; min-width: 0;
|
||
height: 6px; accent-color: var(--red);
|
||
-webkit-appearance: none; appearance: none;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
}
|
||
.ge-fx-row input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 11px; height: 11px; border-radius: 50%;
|
||
background: var(--red); border: none; cursor: pointer;
|
||
}
|
||
.ge-fx-row input[type="range"]::-moz-range-thumb {
|
||
width: 11px; height: 11px; border-radius: 50%;
|
||
background: var(--red); border: none; cursor: pointer;
|
||
}
|
||
.ge-fx-row-value {
|
||
font-size: 10px; opacity: 0.7; font-variant-numeric: tabular-nums;
|
||
flex: 0 0 40px; text-align: right;
|
||
}
|
||
|
||
/* Layer-row FX button — tinted when the layer has any non-identity FX. */
|
||
.ge-layer-fx-btn {
|
||
opacity: 0.55;
|
||
}
|
||
.ge-layer-fx-btn:hover { opacity: 0.9; }
|
||
.ge-layer-fx-btn.active {
|
||
opacity: 1;
|
||
color: var(--accent, var(--red));
|
||
}
|
||
/* Add-mask button on each layer row. Subtle by default; lights up red
|
||
when a lasso/wand selection is live so the user instantly sees that
|
||
clicking it will bake that selection into a mask on this layer. */
|
||
.ge-layer-mask-btn {
|
||
opacity: 0.45;
|
||
}
|
||
.ge-layer-mask-btn:hover { opacity: 0.9; }
|
||
.ge-layer-mask-btn.from-selection {
|
||
opacity: 1;
|
||
color: var(--accent, var(--red));
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
|
||
}
|
||
|
||
/* Shared frosted-glass style for FX menu + adjustment popups. */
|
||
.ge-frosted {
|
||
background: color-mix(in srgb, var(--panel) 70%, transparent);
|
||
backdrop-filter: blur(14px) saturate(140%);
|
||
-webkit-backdrop-filter: blur(14px) saturate(140%);
|
||
border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
|
||
box-shadow: 0 12px 40px rgba(0,0,0,0.55);
|
||
}
|
||
|
||
/* FX dropdown menu from the layer-row fx icon. */
|
||
.ge-fx-menu {
|
||
position: fixed;
|
||
z-index: 305;
|
||
min-width: 200px;
|
||
padding: 4px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.ge-fx-menu-item {
|
||
background: none;
|
||
border: none;
|
||
text-align: left;
|
||
padding: 7px 10px;
|
||
font-size: 12px;
|
||
color: var(--fg);
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.ge-fx-menu-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
color: var(--fg-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-fx-menu-item:hover {
|
||
background: color-mix(in srgb, var(--fg) 12%, transparent);
|
||
}
|
||
.ge-fx-menu-item:hover .ge-fx-menu-icon { color: var(--fg); }
|
||
|
||
.ge-add-layer-menu {
|
||
position: fixed;
|
||
z-index: 10004;
|
||
width: 218px;
|
||
padding: 5px;
|
||
border-radius: 7px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
}
|
||
.ge-add-layer-menu button {
|
||
height: 30px;
|
||
padding: 0 9px;
|
||
border: 0;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-align: left;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-add-layer-menu button:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); }
|
||
.ge-add-layer-symbol {
|
||
width: 16px;
|
||
display: inline-flex;
|
||
justify-content: center;
|
||
color: var(--accent, var(--red));
|
||
font-size: 13px;
|
||
}
|
||
.ge-add-layer-divider { height: 1px; margin: 4px 5px; background: color-mix(in srgb, var(--fg) 10%, transparent); }
|
||
|
||
/* Per-type adjustment popup. */
|
||
.ge-adj-popup {
|
||
position: fixed;
|
||
z-index: 305;
|
||
width: 320px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
font-size: 11px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-adj-popup .ge-adj-head {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent);
|
||
cursor: grab;
|
||
user-select: none;
|
||
}
|
||
.ge-adj-popup .ge-head-btns { margin-left: auto; }
|
||
.ge-adj-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
color: var(--fg-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-adj-title { font-weight: 600; font-size: 12px; flex: 1 1 auto; }
|
||
.ge-adj-min, .ge-adj-close, .ge-history-close {
|
||
background: none; border: none; color: var(--fg-muted);
|
||
font-size: 16px; line-height: 1; cursor: pointer; padding: 0 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-history-close:hover { color: var(--fg); }
|
||
/* Mobile-only: shift head icons/buttons up and enlarge the minimise
|
||
glyph so it reads as a window-minimise affordance, not a centered
|
||
minus. Desktop keeps the original tight head styling. */
|
||
@media (max-width: 820px) {
|
||
.ge-adj-icon {
|
||
position: relative;
|
||
top: -4px;
|
||
}
|
||
.ge-adj-title {
|
||
position: relative;
|
||
top: -2px;
|
||
}
|
||
.ge-adj-min, .ge-adj-close, .ge-history-close {
|
||
position: relative;
|
||
top: -4px;
|
||
}
|
||
.ge-adj-min,
|
||
.ge-adj-popup .ge-adj-min,
|
||
.ge-history-head .ge-adj-min,
|
||
.ge-transform-popup-head .ge-adj-min {
|
||
font-size: 22px !important;
|
||
font-weight: 600 !important;
|
||
padding: 0 8px !important;
|
||
line-height: 0.6 !important;
|
||
position: relative;
|
||
top: 4px;
|
||
}
|
||
.ge-adj-head .ge-transform-aspect-btn {
|
||
position: relative;
|
||
top: -4px;
|
||
}
|
||
}
|
||
.ge-adj-min:hover, .ge-adj-close:hover { color: var(--fg); }
|
||
|
||
/* Floating dock at bottom-right for minimised FX popups. */
|
||
#ge-fx-dock {
|
||
position: fixed;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
bottom: 12px;
|
||
z-index: 304;
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 6px;
|
||
align-items: center;
|
||
pointer-events: none;
|
||
max-width: calc(100vw - 24px);
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
.ge-fx-dock-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 8px;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
max-width: 220px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
.ge-fx-dock-chip:hover {
|
||
background: color-mix(in srgb, var(--fg) 8%, transparent);
|
||
}
|
||
.ge-fx-dock-icon { display: inline-flex; align-items: center; }
|
||
.ge-fx-dock-close {
|
||
margin-left: 2px; opacity: 0.55; padding: 0 2px;
|
||
font-size: 14px; line-height: 1;
|
||
}
|
||
.ge-fx-dock-close:hover { opacity: 1; color: var(--red); }
|
||
|
||
/* Sub-layer row icon in the layer panel. */
|
||
.ge-adj-sub-name {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.ge-adj-sub-icon {
|
||
display: inline-flex; align-items: center;
|
||
color: var(--accent, var(--red));
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-adj-sub-icon svg { width: 11px; height: 11px; }
|
||
|
||
/* History panel — labeled timeline of edits. */
|
||
#ge-history-panel {
|
||
position: fixed;
|
||
z-index: 10004;
|
||
width: 240px;
|
||
max-height: 60vh;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
color: var(--fg);
|
||
font-size: 11px;
|
||
}
|
||
.ge-history-head {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 8px 10px;
|
||
border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent);
|
||
cursor: grab;
|
||
user-select: none;
|
||
}
|
||
.ge-history-title { font-weight: 600; font-size: 12px; flex: 1 1 auto; }
|
||
.ge-history-close {
|
||
background: none; border: none; color: var(--fg-muted);
|
||
font-size: 16px; line-height: 1; cursor: pointer; padding: 0 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-history-close:hover { color: var(--fg); }
|
||
.ge-history-list { overflow-y: auto; padding: 4px 0; }
|
||
.ge-history-row {
|
||
display: flex; align-items: center; gap: 8px;
|
||
width: 100%;
|
||
padding: 5px 10px;
|
||
background: none; border: none;
|
||
text-align: left; cursor: pointer;
|
||
color: var(--fg); font-size: 11px;
|
||
}
|
||
.ge-history-row:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); }
|
||
.ge-history-row.current { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); }
|
||
.ge-history-row.current .ge-history-row-dot { background: var(--accent, var(--red)); }
|
||
.ge-history-row.future { opacity: 0.45; }
|
||
.ge-history-row-dot {
|
||
width: 6px; height: 6px; border-radius: 50%;
|
||
background: var(--fg-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-history-row-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.ge-history-row-time { font-size: 9px; opacity: 0.55; flex-shrink: 0; }
|
||
|
||
/* Named snapshots (Photoshop-style restore points) — camera button + rows. */
|
||
.ge-history-snapshots { border-bottom: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); }
|
||
.ge-history-snap-head {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 7px 10px 4px;
|
||
}
|
||
.ge-history-snap-title {
|
||
flex: 1 1 auto;
|
||
font-weight: 600; font-size: 10px; letter-spacing: 0.04em;
|
||
text-transform: uppercase; opacity: 0.7;
|
||
}
|
||
.ge-history-snap-new {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
width: 22px; height: 22px;
|
||
background: none; border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent);
|
||
border-radius: 5px; color: var(--fg-muted); cursor: pointer; flex-shrink: 0;
|
||
}
|
||
.ge-history-snap-new:hover { color: var(--fg); border-color: color-mix(in srgb, var(--fg) 35%, transparent); }
|
||
.ge-history-snap-new svg { width: 13px; height: 13px; display: block; flex: 0 0 auto; }
|
||
.ge-history-snap-rows { padding: 0 0 4px; }
|
||
.ge-history-snap-empty { padding: 2px 10px 6px; font-size: 10px; opacity: 0.45; }
|
||
.ge-history-snap-row {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 0 6px 0 0;
|
||
}
|
||
.ge-history-snap-restore {
|
||
display: flex; align-items: center; gap: 8px;
|
||
flex: 1 1 auto; min-width: 0;
|
||
padding: 5px 4px 5px 10px;
|
||
background: none; border: none; text-align: left; cursor: pointer;
|
||
color: var(--fg); font-size: 11px;
|
||
}
|
||
.ge-history-snap-restore:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); }
|
||
.ge-history-snap-dot {
|
||
width: 6px; height: 6px; border-radius: 50%;
|
||
background: var(--accent, var(--red)); flex-shrink: 0;
|
||
}
|
||
.ge-history-snap-del {
|
||
background: none; border: none; color: var(--fg-muted);
|
||
font-size: 14px; line-height: 1; cursor: pointer; padding: 0 4px; flex-shrink: 0;
|
||
}
|
||
.ge-history-snap-del:hover { color: var(--accent, var(--red)); }
|
||
|
||
/* Docked History panel: the panel body *is* the timeline (Photoshop-style),
|
||
so it fills the dock and scrolls its own list. */
|
||
.ge-history-body { padding: 0; display: flex; flex-direction: column; min-height: 0; }
|
||
.ge-history-body .ge-history-list { flex: 1 1 auto; min-height: 90px; }
|
||
.ge-history-popout { margin: 6px 10px; align-self: flex-start; font-size: 10px; }
|
||
/* (The docked History panel dropped its "Open in window" button — the timeline
|
||
is already shown inline; the floating window stays on the topbar clock.) */
|
||
|
||
/* Tight button group in popup heads — min + close sit immediately next
|
||
to each other with no inherited flex gap, pushed to the right edge. */
|
||
.ge-head-btns {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-head-btns > button {
|
||
width: 26px;
|
||
height: 26px;
|
||
padding: 0 !important;
|
||
margin: 0;
|
||
font-size: 18px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 6px;
|
||
}
|
||
.ge-head-btns > button:hover {
|
||
background: color-mix(in srgb, var(--fg) 12%, transparent);
|
||
}
|
||
|
||
/* Detail-header heart toggle — sits next to the icon Edit button. */
|
||
.gallery-detail-fav-header { opacity: 0.7; transition: opacity 0.15s, color 0.15s; }
|
||
.gallery-detail-fav-header:hover { opacity: 1; }
|
||
.gallery-detail-fav-header.active { color: var(--red); opacity: 1; }
|
||
|
||
/* Canvas loading overlay — covers the canvas area while a blocking op
|
||
(rotation, big resize, etc.) runs. */
|
||
.ge-canvas-loading {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 50;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
border-radius: 8px;
|
||
pointer-events: auto;
|
||
}
|
||
.ge-canvas-loading-spinner {
|
||
width: 32px; height: 32px;
|
||
border: 3px solid color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-top-color: var(--accent, var(--red));
|
||
border-radius: 50%;
|
||
animation: ge-canvas-spin 0.8s linear infinite;
|
||
}
|
||
.ge-canvas-loading-msg { font-size: 12px; opacity: 0.85; }
|
||
|
||
/* Missing-dependency notice in tool sections (Bg Remove etc.). */
|
||
.ge-dep-notice {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 10px;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent);
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent);
|
||
border-radius: 6px;
|
||
margin: 4px 0 8px;
|
||
font-size: 11px;
|
||
}
|
||
.ge-dep-notice-text { flex: 1 1 auto; line-height: 1.4; }
|
||
.ge-dep-notice-text strong { display: block; margin-bottom: 2px; }
|
||
.ge-dep-notice-text code {
|
||
font-family: 'Fira Code', monospace;
|
||
font-size: 10px;
|
||
padding: 1px 4px;
|
||
border-radius: 3px;
|
||
background: color-mix(in srgb, var(--fg) 10%, transparent);
|
||
}
|
||
.ge-dep-notice .ge-btn { flex-shrink: 0; }
|
||
|
||
/* Brief highlight on a Cookbook deps row arrived-at via deep-link. */
|
||
.cookbook-pkg-flash { animation: cookbook-pkg-flash 1.4s ease-out; }
|
||
@keyframes cookbook-pkg-flash {
|
||
0% { background: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); }
|
||
100% { background: transparent; }
|
||
}
|
||
|
||
/* Flash highlight when an anchor-link ([Name](#task-/#skill-/#research-))
|
||
opens a panel and focuses a specific item. */
|
||
@keyframes anchor-item-flash {
|
||
0% { background: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); }
|
||
60% { background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); }
|
||
100% { background: transparent; }
|
||
}
|
||
.task-card-flash,
|
||
.skill-row-flash,
|
||
.research-card-flash {
|
||
animation: anchor-item-flash 2s ease-out;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.memory-item-focus {
|
||
animation: anchor-item-flash 2.2s ease-out;
|
||
outline: 1px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent);
|
||
outline-offset: 2px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Preview dot icons for the Blur sub-menu — visual cue for each blur
|
||
type (Gaussian = soft round, Zoom = radial streaks). */
|
||
.ge-blur-icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
flex-shrink: 0;
|
||
background: var(--fg);
|
||
}
|
||
.ge-blur-icon.ge-blur-gaussian {
|
||
background: radial-gradient(circle, var(--fg) 0%, var(--fg) 25%, transparent 75%);
|
||
filter: blur(1.5px);
|
||
}
|
||
.ge-blur-icon.ge-blur-zoom {
|
||
background: radial-gradient(circle, var(--fg) 0%, var(--fg) 20%, transparent 60%);
|
||
box-shadow:
|
||
0 -5px 0 -3px var(--fg),
|
||
0 5px 0 -3px var(--fg),
|
||
-5px 0 0 -3px var(--fg),
|
||
5px 0 0 -3px var(--fg),
|
||
-4px -4px 0 -3px var(--fg),
|
||
4px -4px 0 -3px var(--fg),
|
||
-4px 4px 0 -3px var(--fg),
|
||
4px 4px 0 -3px var(--fg);
|
||
}
|
||
|
||
/* Inline-swatch color picker — small square that fits next to a title. */
|
||
.ge-inline-swatch {
|
||
width: 16px !important;
|
||
height: 16px !important;
|
||
padding: 0 !important;
|
||
border: 1px solid var(--border) !important;
|
||
border-radius: 3px !important;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
background: none;
|
||
}
|
||
|
||
/* Drop overlay over the gallery editor while a file drag is in flight. */
|
||
.ge-drop-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 200;
|
||
pointer-events: none;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
|
||
border: 3px dashed color-mix(in srgb, var(--accent, var(--red)) 80%, transparent);
|
||
border-radius: 8px;
|
||
}
|
||
.ge-drop-overlay-msg {
|
||
padding: 12px 20px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
background: color-mix(in srgb, var(--panel) 70%, transparent);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* Small buttons that show an SVG icon + text label side-by-side. */
|
||
.ge-btn-iconlabel {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-btn-iconlabel svg {
|
||
flex-shrink: 0;
|
||
opacity: 0.85;
|
||
}
|
||
.ge-btn-iconlabel:hover svg { opacity: 1; }
|
||
|
||
.ge-adj-body {
|
||
padding: 8px 12px;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
.ge-adj-preset-field {
|
||
display: grid;
|
||
grid-template-columns: 46px 1fr;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 8px 12px 0;
|
||
color: color-mix(in srgb, var(--fg) 65%, transparent);
|
||
font-size: 10px;
|
||
}
|
||
.ge-adj-preset-select {
|
||
min-width: 0;
|
||
width: 100%;
|
||
padding: 5px 7px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: color-mix(in srgb, var(--panel) 90%, transparent);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-adj-preset-select:focus { outline: none; border-color: var(--accent, var(--red)); }
|
||
.ge-adj-popup { box-sizing: border-box; overflow: hidden; }
|
||
.ge-adj-foot {
|
||
display: flex; gap: 6px; justify-content: flex-end;
|
||
padding: 8px 12px;
|
||
border-top: 1px solid color-mix(in srgb, var(--fg) 10%, transparent);
|
||
}
|
||
.ge-adj-foot-spacer { flex: 1 1 auto; }
|
||
.ge-adj-compare-btn.is-active {
|
||
color: var(--accent, var(--red));
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 65%, transparent);
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
|
||
}
|
||
.ge-adj-channel-field {
|
||
display: grid;
|
||
grid-template-columns: 64px 1fr;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 0 0 8px;
|
||
color: color-mix(in srgb, var(--fg) 65%, transparent);
|
||
font-size: 10px;
|
||
}
|
||
.ge-adj-channel-select,
|
||
.ge-adj-selective-range {
|
||
width: 100%;
|
||
min-width: 0;
|
||
height: 27px;
|
||
padding: 0 25px 0 7px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-gradient-map-preview {
|
||
height: 24px;
|
||
margin-bottom: 9px;
|
||
border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent);
|
||
border-radius: 4px;
|
||
background: linear-gradient(90deg, var(--ge-gradient-start), var(--ge-gradient-end));
|
||
}
|
||
.ge-gradient-color-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.ge-gradient-color-row label {
|
||
display: grid;
|
||
grid-template-columns: 1fr 30px;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 10px;
|
||
opacity: .75;
|
||
}
|
||
.ge-gradient-color-row input[type="color"] {
|
||
width: 30px;
|
||
height: 24px;
|
||
padding: 1px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
}
|
||
.ge-adj-toggle-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
min-height: 28px;
|
||
font-size: 10px;
|
||
color: color-mix(in srgb, var(--fg) 70%, transparent);
|
||
}
|
||
.ge-adj-toggle-row input { accent-color: var(--red); }
|
||
.ge-curves-wrap {
|
||
width: 100%;
|
||
aspect-ratio: 14 / 9;
|
||
border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
|
||
background: #0b0d10;
|
||
overflow: hidden;
|
||
}
|
||
.ge-curves-canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; }
|
||
.ge-adj-help { margin: 7px 0 0; font-size: 9px; line-height: 1.35; opacity: .5; }
|
||
.ge-adjustment-layer-item .ge-layer-adjustment-marker { color: var(--accent, var(--red)); }
|
||
.ge-adjustment-layer-item .ge-layer-adjustment-marker svg { width: 12px; height: 12px; }
|
||
/* Mobile-only: oversize the foot Cancel/Apply buttons to match the
|
||
Transform popup. Desktop keeps the original ge-btn-sm sizing. */
|
||
@media (max-width: 820px) {
|
||
.ge-adj-foot .ge-adj-cancel-btn,
|
||
.ge-adj-foot .ge-adj-apply-btn {
|
||
padding: 8px 16px !important;
|
||
font-size: 13px !important;
|
||
min-width: 80px;
|
||
text-align: center;
|
||
}
|
||
}
|
||
.ge-adj-row {
|
||
display: flex; align-items: center; gap: 3px;
|
||
padding: 0;
|
||
margin: 0;
|
||
min-width: 0;
|
||
line-height: 1;
|
||
}
|
||
.ge-adj-row > label {
|
||
flex: 0 0 96px;
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
}
|
||
.ge-adj-row input[type="range"] {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
height: 6px;
|
||
accent-color: var(--red);
|
||
-webkit-appearance: none; appearance: none;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
}
|
||
/* Mobile-only: fixed-size sliders + taller rows so the thumb growing
|
||
on active doesn't push the row height around. */
|
||
@media (max-width: 820px) {
|
||
.ge-adj-row {
|
||
min-height: 28px;
|
||
}
|
||
/* Let the slider consume the space left after the label and value chip.
|
||
A fixed 160px track overflows the 320px phone sheet once the popup's
|
||
horizontal padding and controls are included. */
|
||
.ge-adj-row > label {
|
||
flex: 0 0 clamp(72px, 26vw, 96px);
|
||
min-width: 0;
|
||
}
|
||
.ge-adj-row input[type="range"] {
|
||
flex: 1 1 auto;
|
||
width: auto;
|
||
min-width: 56px;
|
||
max-width: none;
|
||
}
|
||
}
|
||
@media (max-width: 360px) {
|
||
/* Give Gradient Map's color labels and swatches a usable tap width on
|
||
narrow phones; the popup remains scrollable if the extra row is needed. */
|
||
.ge-gradient-color-row {
|
||
grid-template-columns: 1fr;
|
||
gap: 6px;
|
||
}
|
||
}
|
||
.ge-adj-row input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 12px; height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--red);
|
||
cursor: pointer; border: none;
|
||
transition: width 0.12s ease, height 0.12s ease;
|
||
}
|
||
.ge-adj-row input[type="range"]:hover::-webkit-slider-thumb,
|
||
.ge-adj-row input[type="range"]:active::-webkit-slider-thumb {
|
||
width: 18px; height: 18px;
|
||
}
|
||
.ge-adj-row input[type="range"]::-moz-range-thumb {
|
||
width: 12px; height: 12px;
|
||
border-radius: 50%; background: var(--red); border: none; cursor: pointer;
|
||
transition: width 0.12s ease, height 0.12s ease;
|
||
}
|
||
.ge-adj-row input[type="range"]:hover::-moz-range-thumb,
|
||
.ge-adj-row input[type="range"]:active::-moz-range-thumb {
|
||
width: 18px; height: 18px;
|
||
}
|
||
/* Value chip — fixed-width column, text aligned LEFT so the chip's
|
||
left edge always sits immediately after the slider regardless of
|
||
value length. Without a fixed width Hue's "180 °" made its slider
|
||
shorter than Saturation's "100" since slider was flex-1. */
|
||
.ge-adj-value {
|
||
flex: 0 0 34px;
|
||
margin-left: 0;
|
||
padding: 0 2px;
|
||
font-size: 10px; opacity: 0.7; text-align: left;
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
cursor: text;
|
||
}
|
||
.ge-adj-value:hover { opacity: 1; }
|
||
.ge-adj-cb-tone {
|
||
font-size: 10px; opacity: 0.55; font-style: italic;
|
||
margin: 2px 0 0;
|
||
line-height: 1;
|
||
}
|
||
.ge-adj-cb-row {
|
||
padding: 0;
|
||
gap: 3px;
|
||
}
|
||
.ge-adj-cb-tone-select {
|
||
width: 100%;
|
||
padding: 6px 8px;
|
||
font-size: 12px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--border);
|
||
background: color-mix(in srgb, var(--panel) 90%, transparent);
|
||
color: var(--fg);
|
||
}
|
||
.ge-adj-cb-tone-select:focus { outline: none; border-color: var(--red); }
|
||
.ge-adj-hist-details[open] > summary::before {
|
||
content: '▾ ';
|
||
}
|
||
.ge-adj-hist-details .ge-adj-hist-wrap {
|
||
margin-top: 4px;
|
||
}
|
||
.ge-adj-histogram {
|
||
display: block;
|
||
width: 100%;
|
||
height: 80px;
|
||
border-radius: 4px;
|
||
background: rgba(0,0,0,0.25);
|
||
}
|
||
.ge-adj-hist-wrap {
|
||
position: relative;
|
||
margin-bottom: 14px;
|
||
}
|
||
.ge-adj-clip-status {
|
||
margin-top: 3px;
|
||
color: var(--fg-muted);
|
||
font-size: 10px;
|
||
line-height: 1.25;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.ge-adj-hist-handles {
|
||
position: absolute;
|
||
left: 0; right: 0;
|
||
bottom: -10px;
|
||
height: 12px;
|
||
pointer-events: none;
|
||
}
|
||
.ge-adj-hist-handle {
|
||
position: absolute;
|
||
width: 12px;
|
||
height: 12px;
|
||
cursor: ew-resize;
|
||
pointer-events: auto;
|
||
border-style: solid;
|
||
border-width: 0 6px 10px 6px;
|
||
border-color: transparent transparent var(--fg-muted) transparent;
|
||
filter: drop-shadow(0 1px 1px rgba(0,0,0,0.4));
|
||
}
|
||
.ge-adj-hist-handle:hover { border-bottom-color: var(--fg); }
|
||
.ge-adj-hist-handle.hist-h-black { border-bottom-color: #111; }
|
||
.ge-adj-hist-handle.hist-h-white { border-bottom-color: #fff; }
|
||
.ge-adj-hist-handle.hist-h-gamma { border-bottom-color: var(--accent, var(--red)); }
|
||
/* Per-slider revert button. */
|
||
.ge-adj-revert {
|
||
background: none; border: none;
|
||
color: var(--fg-muted); cursor: pointer;
|
||
padding: 2px 4px; margin-left: 2px;
|
||
opacity: 0.55;
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
transition: opacity 0.15s, color 0.15s;
|
||
}
|
||
.ge-adj-revert:hover { opacity: 1; color: var(--fg); }
|
||
|
||
/* Develop panel polish: full-contrast value readouts, grouped heading labels,
|
||
and revert arrows that stay hidden until hover or a non-default value. */
|
||
.ge-adj-develop .ge-adj-value { flex: 0 0 52px; color: var(--fg); font-size: 12px; opacity: 1; }
|
||
.ge-adj-group-label {
|
||
font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
|
||
opacity: .55; margin: 10px 0 4px;
|
||
}
|
||
.ge-adj-develop .ge-adj-group-label:first-child { margin-top: 2px; }
|
||
.ge-adj-develop .ge-adj-revert {
|
||
min-width: 24px; min-height: 24px; margin-left: 6px; padding: 0;
|
||
opacity: 0; pointer-events: none;
|
||
}
|
||
.ge-adj-develop .ge-adj-row:hover .ge-adj-revert,
|
||
.ge-adj-develop .ge-adj-row.is-modified .ge-adj-revert {
|
||
opacity: 1; pointer-events: auto;
|
||
}
|
||
|
||
/* Adjustment sub-layer row in the layer panel — indented under parent. */
|
||
.ge-adj-sub-item {
|
||
padding-left: 18px;
|
||
opacity: 0.85;
|
||
border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent);
|
||
margin-left: 6px;
|
||
font-size: 10.5px;
|
||
}
|
||
.ge-adj-sub-item .ge-layer-name {
|
||
opacity: 0.85;
|
||
font-style: italic;
|
||
}
|
||
/* Mask sub-layer rows. Same indent as adj sub-layers but the
|
||
highlight bar uses fg-muted instead of accent — these aren't FX
|
||
effects, just selection-state. When activated (the click target
|
||
for paint / inpaint / generate), border + name go full strength. */
|
||
.ge-mask-sub-item {
|
||
border-left-color: color-mix(in srgb, var(--fg) 30%, transparent);
|
||
}
|
||
.ge-mask-sub-item.active {
|
||
border-left-color: var(--red);
|
||
opacity: 1;
|
||
}
|
||
.ge-clone-source-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-height: 24px;
|
||
margin: 2px 0 5px;
|
||
padding: 0 6px;
|
||
color: var(--fg);
|
||
opacity: 0.68;
|
||
font-size: 10px;
|
||
}
|
||
.ge-clone-source-status span { flex: 1; min-width: 0; }
|
||
.ge-clone-source-status .ge-icon-btn { margin-left: auto; }
|
||
.ge-mask-inline-thumb {
|
||
width: 34px;
|
||
height: 26px;
|
||
flex: 0 0 34px;
|
||
opacity: 0.78;
|
||
}
|
||
.ge-eraser-preview {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
background: var(--fg);
|
||
flex-shrink: 0;
|
||
display: inline-block;
|
||
position: relative;
|
||
}
|
||
.ge-actions {
|
||
display: flex;
|
||
gap: 4px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.ge-btn {
|
||
padding: 4px 8px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
}
|
||
.ge-btn:hover { background: color-mix(in srgb, var(--fg) 10%, var(--bg)); }
|
||
.ge-btn.active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 16%, var(--bg));
|
||
border-color: var(--accent, var(--red));
|
||
color: var(--accent, var(--red));
|
||
}
|
||
.ge-btn-sm { padding: 3px 6px; font-size: 10px; }
|
||
/* Busy state on AI tool buttons — whirlpool + verb text side by side. */
|
||
.ge-btn-processing {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
}
|
||
.ge-btn-busy-label { font-size: inherit; line-height: 1; }
|
||
/* Mask-vis eye button is icon-only — strip the inherited .ge-btn
|
||
background / border so it reads as a plain affordance, matching
|
||
the layer-row eye toggles. */
|
||
.ge-mask-vis-btn {
|
||
background: none !important;
|
||
border: none !important;
|
||
padding: 2px 4px !important;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.5;
|
||
transition: opacity 0.12s;
|
||
}
|
||
.ge-mask-vis-btn:hover { opacity: 0.85; background: none !important; }
|
||
.ge-mask-vis-btn.visible { opacity: 0.9; }
|
||
/* Paint/Erase segmented toggle in the Inpaint Brush section. */
|
||
.ge-inpaint-mode-btn {
|
||
font-size: 11px;
|
||
padding: 4px 8px;
|
||
opacity: 0.6;
|
||
border: 1px solid var(--border);
|
||
}
|
||
.ge-inpaint-mode-btn:hover { opacity: 0.85; }
|
||
.ge-inpaint-mode-btn.active {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
|
||
border-color: var(--accent, var(--red));
|
||
color: var(--accent, var(--red));
|
||
font-weight: 600;
|
||
}
|
||
/* Wand New / + Add / − Subtract segmented toggle. */
|
||
.ge-wand-mode-btn {
|
||
flex: 1 1 0;
|
||
font-size: 11px;
|
||
padding: 4px 6px;
|
||
opacity: 0.6;
|
||
border: 1px solid var(--border);
|
||
}
|
||
.ge-wand-mode-btn:hover { opacity: 0.85; }
|
||
.ge-wand-mode-btn.active {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
|
||
border-color: var(--accent, var(--red));
|
||
color: var(--accent, var(--red));
|
||
font-weight: 600;
|
||
}
|
||
.ge-wand-live-btn {
|
||
flex: 0 0 auto;
|
||
opacity: 0.6;
|
||
min-width: 46px;
|
||
height: 22px;
|
||
padding: 0 10px !important;
|
||
border-radius: 999px !important;
|
||
font-size: 10px;
|
||
line-height: 1;
|
||
background: color-mix(in srgb, var(--fg) 5%, transparent);
|
||
transition: opacity 0.15s, color 0.15s, border-color 0.15s, background 0.15s;
|
||
}
|
||
.ge-wand-live-btn:hover { opacity: 0.85; }
|
||
.ge-wand-live-btn.active {
|
||
opacity: 1;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
|
||
border-color: var(--accent, var(--red));
|
||
color: var(--accent, var(--red));
|
||
font-weight: 600;
|
||
}
|
||
/* Top-bar buttons (undo/redo, zoom +/-, Fit, 1:1, Resize, Edge, Import,
|
||
Download, Save) — visually their glyphs sit too low compared to the
|
||
surrounding labels, so shift contents up 2 px via asymmetric padding. */
|
||
/* Visually the buttons sit lower than the surrounding text labels on the
|
||
topbar. Keep the box padding symmetric but lift the whole box 2 px so it
|
||
centers against the adjacent label baselines. */
|
||
.ge-topbar .ge-btn,
|
||
.ge-topbar .ge-btn-sm,
|
||
.ge-topbar select,
|
||
.ge-topbar input { position: relative; top: -2px; }
|
||
/* The tiny "Gen" / "Inpaint" inline labels now look too high relative to
|
||
the shifted-up controls — nudge them down 2 px to re-align. */
|
||
.ge-topbar span[style*="font-size:9px"] { position: relative; top: 2px; }
|
||
/* The Gen and Inpaint model selects — must visually sit on the same line as
|
||
the small "Gen" / "Inpaint" labels (which are at top:2px). Use !important
|
||
so this beats the generic `.ge-topbar select` rule above no matter what
|
||
element-vs-class specificity quirks. */
|
||
.ge-topbar .ge-ai-model { position: relative !important; top: 1px !important; }
|
||
/* The ◢ glyph baseline reads lower than the surrounding text — shift it
|
||
up 1 px so it visually centers with the "Edge" label. */
|
||
.ge-edge-glyph { position: relative; top: -1px; }
|
||
/* Flex-line-break helper used in the mobile transform popup. Hidden by
|
||
default so desktop keeps its single-row layout. */
|
||
.ge-row-break { display: none; }
|
||
/* Stacked button — glyph on top, small uppercase label below. Currently
|
||
used for Undo/Redo so users get an obvious "UNDO" / "REDO" affordance
|
||
label without losing the compact icon. */
|
||
.ge-stacked-btn {
|
||
display: inline-flex !important;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
padding: 2px 8px !important;
|
||
line-height: 1;
|
||
}
|
||
.ge-stacked-btn .ge-stacked-glyph svg { display: block; }
|
||
.ge-stacked-btn .ge-stacked-caret {
|
||
margin-left: 2px;
|
||
font-size: 7px;
|
||
line-height: 1;
|
||
opacity: 0.55;
|
||
}
|
||
.ge-stacked-btn .ge-stacked-label {
|
||
font-size: 8px;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.65;
|
||
font-weight: 600;
|
||
position: relative;
|
||
top: 2px;
|
||
}
|
||
/* Zoom % indicator — magnifier glyph on top, percentage label below.
|
||
Matches the Undo/Redo stacked-button layout so the topbar reads as
|
||
one consistent row of stacked icons. */
|
||
.ge-zoom-stack {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
line-height: 1;
|
||
padding: 0 4px;
|
||
}
|
||
.ge-zoom-stack .ge-zoom-glyph {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.7;
|
||
/* Glyph nudged 1 px right (was 2 px left). */
|
||
position: relative;
|
||
left: -1px;
|
||
}
|
||
.ge-zoom-stack .ge-zoom-glyph svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
.ge-zoom-stack .ge-zoom-label {
|
||
font-size: 8px;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.65;
|
||
font-weight: 600;
|
||
position: relative;
|
||
top: 4px;
|
||
left: 1px;
|
||
}
|
||
/* + Add sits 1 px below the title baseline to match the surrounding
|
||
header buttons after the recent header restructure. */
|
||
.ge-layers-header #ge-add-layer { position: relative; top: 1px; }
|
||
.ge-layer-blend-row {
|
||
display: grid;
|
||
grid-template-columns: 38px minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 7px;
|
||
border-bottom: 1px solid color-mix(in srgb, var(--fg) 8%, transparent);
|
||
font-size: 10px;
|
||
color: color-mix(in srgb, var(--fg) 62%, transparent);
|
||
}
|
||
.ge-layer-opacity-head {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-layer-opacity-head input {
|
||
width: 48px;
|
||
height: 25px;
|
||
padding: 0 4px;
|
||
border-radius: 4px;
|
||
font-size: 10px;
|
||
text-align: right;
|
||
}
|
||
.ge-layer-blend-row select {
|
||
width: 100%;
|
||
min-width: 0;
|
||
height: 25px;
|
||
padding: 0 24px 0 7px;
|
||
border-radius: 4px;
|
||
font-size: 10px;
|
||
}
|
||
.ge-layer-selection-bar {
|
||
min-height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 3px 10px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg-secondary));
|
||
color: var(--fg);
|
||
font-size: 10px;
|
||
}
|
||
.ge-layer-selection-bar > span { flex: 1; opacity: 0.72; }
|
||
.ge-layer-selection-bar .ge-icon-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
min-width: 24px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
opacity: 0.72;
|
||
}
|
||
.ge-layer-selection-bar .ge-icon-btn.danger:hover { color: var(--red); }
|
||
/* Layer-row buttons in the right panel — the unicode glyphs (◉, ⤢) have
|
||
irregular baseline metrics so the box reads as too low; shift up 2 px. */
|
||
.ge-layer-vis,
|
||
.ge-layer-btn { position: relative; top: -2px; }
|
||
/* Delete (×) button glyph baseline reads even lower than the visibility
|
||
icon — push it up an extra 2 px so it visually centers. */
|
||
.ge-layer-btn.danger { top: -4px; }
|
||
/* Save / Save as new / Download dropdown anchored to the primary Save
|
||
button in the topbar. */
|
||
.ge-save-wrap {
|
||
position: relative;
|
||
display: inline-block;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-save-menu {
|
||
position: fixed;
|
||
min-width: 160px;
|
||
padding: 4px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent);
|
||
z-index: 10005;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.ge-save-state-icon { display: none; }
|
||
.ge-save-label { white-space: nowrap; min-width: 0; }
|
||
/* Save button: glyph on its own row, then the SAVE action label and the
|
||
live draft-status chip share the second row. */
|
||
#ge-save-menu-btn { flex-direction: row !important; flex-wrap: wrap; column-gap: 4px; }
|
||
#ge-save-menu-btn .ge-stacked-glyph { flex: 0 0 100%; }
|
||
#ge-save-menu-btn .ge-save-action-label { top: 0; }
|
||
#ge-save-menu-btn .ge-save-label { top: 0; }
|
||
.ge-save-state-saved { color: var(--fg); }
|
||
.ge-save-menu .dropdown-item-compact {
|
||
width: 100%;
|
||
background: none;
|
||
border: none;
|
||
text-align: left;
|
||
font: inherit;
|
||
}
|
||
|
||
/* Resize and Edge popups in the topbar — same anchored-dropdown pattern as
|
||
the Save menu. Resize lists preset canvas sizes; Edge is a tiny form.
|
||
Image + Filter menus reuse the same chrome. */
|
||
.ge-resize-wrap,
|
||
.ge-edge-wrap,
|
||
.ge-view-wrap,
|
||
.ge-image-wrap,
|
||
.ge-layer-wrap,
|
||
.ge-type-wrap,
|
||
.ge-selection-wrap,
|
||
.ge-filter-wrap,
|
||
.ge-3d-wrap,
|
||
.ge-plugins-wrap,
|
||
.ge-window-wrap,
|
||
.ge-file-wrap,
|
||
.ge-edit-wrap,
|
||
.ge-help-wrap { position: relative; display: inline-block; }
|
||
.ge-resize-menu,
|
||
.ge-edge-menu,
|
||
.ge-view-menu,
|
||
.ge-image-menu,
|
||
.ge-file-menu,
|
||
.ge-edit-menu,
|
||
.ge-layer-menu,
|
||
.ge-type-menu,
|
||
.ge-selection-menu,
|
||
.ge-filter-menu,
|
||
.ge-3d-menu,
|
||
.ge-plugins-menu,
|
||
.ge-window-menu,
|
||
.ge-help-menu {
|
||
position: absolute;
|
||
top: calc(100% + 2px);
|
||
right: 0;
|
||
padding: 4px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent);
|
||
z-index: 12;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
/* Mobile: escape the topbar's stacking context (z 5) so the dropdown
|
||
lands above the bottom-sheet panels (z 50 / 60). Switch to fixed and
|
||
pin under the topbar. Anchor to the LEFT of the viewport: the editor's
|
||
`.gallery-modal-content` ancestor carries a (identity) transform, so a
|
||
fixed dropdown's containing block is that ancestor rather than the
|
||
viewport. A `right` anchor resolved against the narrow topbar wrap and
|
||
pushed these ~224px menus off-screen (x ≈ -160 at 390px); a left
|
||
anchor keeps them fully on-screen. */
|
||
@media (max-width: 820px) {
|
||
.ge-resize-menu,
|
||
.ge-edge-menu,
|
||
.ge-view-menu,
|
||
.ge-image-menu,
|
||
.ge-file-menu,
|
||
.ge-edit-menu,
|
||
.ge-layer-menu,
|
||
.ge-type-menu,
|
||
.ge-selection-menu,
|
||
.ge-filter-menu,
|
||
.ge-3d-menu,
|
||
.ge-plugins-menu,
|
||
.ge-window-menu,
|
||
.ge-help-menu {
|
||
position: fixed;
|
||
top: 44px;
|
||
left: 8px;
|
||
right: auto;
|
||
z-index: 500;
|
||
max-width: calc(100vw - 16px);
|
||
}
|
||
}
|
||
.ge-resize-menu { min-width: 200px; }
|
||
.ge-edge-menu { min-width: 220px; padding: 10px; gap: 6px; }
|
||
.ge-view-menu { min-width: 174px; }
|
||
.ge-image-menu { min-width: 180px; }
|
||
.ge-file-menu { min-width: 208px; }
|
||
.ge-edit-menu { min-width: 180px; }
|
||
.ge-layer-menu { min-width: 200px; }
|
||
.ge-type-menu { min-width: 180px; }
|
||
.ge-selection-menu { min-width: 224px; }
|
||
.ge-filter-menu { min-width: 180px; }
|
||
.ge-3d-menu { min-width: 220px; }
|
||
.ge-plugins-menu { min-width: 200px; }
|
||
.ge-window-menu { min-width: 200px; }
|
||
.ge-help-menu { min-width: 200px; }
|
||
.ge-resize-menu .dropdown-item-compact,
|
||
.ge-view-menu .dropdown-item-compact,
|
||
.ge-image-menu .dropdown-item-compact,
|
||
.ge-file-menu .dropdown-item-compact,
|
||
.ge-edit-menu .dropdown-item-compact,
|
||
.ge-layer-menu .dropdown-item-compact,
|
||
.ge-type-menu .dropdown-item-compact,
|
||
.ge-selection-menu .dropdown-item-compact,
|
||
.ge-filter-menu .dropdown-item-compact,
|
||
.ge-3d-menu .dropdown-item-compact,
|
||
.ge-plugins-menu .dropdown-item-compact,
|
||
.ge-window-menu .dropdown-item-compact,
|
||
.ge-help-menu .dropdown-item-compact {
|
||
width: 100%;
|
||
background: none;
|
||
border: none;
|
||
text-align: left;
|
||
font: inherit;
|
||
}
|
||
.ge-view-toggle .ge-view-check { opacity: 0; }
|
||
.ge-view-toggle.active .ge-view-check { opacity: 1; color: var(--red); }
|
||
.ge-window-toggle .ge-window-check { opacity: 0; }
|
||
.ge-window-toggle.active .ge-window-check { opacity: 1; color: var(--red); }
|
||
.ge-view-grid-size {
|
||
display: grid;
|
||
grid-template-columns: 1fr 52px auto;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 4px 14px 4px 8px;
|
||
font-size: 10px;
|
||
color: var(--color-muted);
|
||
}
|
||
.ge-view-grid-size input {
|
||
width: 52px;
|
||
height: 24px;
|
||
box-sizing: border-box;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
text-align: right;
|
||
}
|
||
/* Sub-menu label inside the topbar dropdowns — small uppercase header so
|
||
"Blur" / "Transform" reads as a category, not an action. Shared by every
|
||
editor dropdown (`.ge-menu-label`) and the legacy filter class. */
|
||
.ge-filter-submenu-label,
|
||
.ge-menu-label {
|
||
font-size: 9px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
opacity: 0.72;
|
||
font-weight: 700;
|
||
color: var(--color-muted);
|
||
padding: 8px 8px 3px;
|
||
border-top: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
|
||
pointer-events: none;
|
||
}
|
||
/* The first header in a menu sits flush to the menu's own padding so the
|
||
dropdown does not show an empty band at the top (Photoshop menus start
|
||
with an item or a tight separator, never dead space). */
|
||
.ge-filter-submenu-label:first-child,
|
||
.ge-menu-label:first-child,
|
||
.dropdown-section-label:first-child { padding-top: 2px; }
|
||
/* Disabled dropdown items (e.g. Fill when there's no selection) read
|
||
as muted + non-interactive. */
|
||
.ge-image-menu .dropdown-item-compact:disabled,
|
||
.ge-layer-menu .dropdown-item-compact:disabled,
|
||
.ge-type-menu .dropdown-item-compact:disabled,
|
||
.ge-selection-menu .dropdown-item-compact:disabled,
|
||
.ge-filter-menu .dropdown-item-compact:disabled,
|
||
.ge-3d-menu .dropdown-item-compact:disabled,
|
||
.ge-plugins-menu .dropdown-item-compact:disabled,
|
||
.ge-file-menu .dropdown-item-compact:disabled,
|
||
.ge-edit-menu .dropdown-item-compact:disabled,
|
||
.ge-view-menu .dropdown-item-compact:disabled,
|
||
.ge-window-menu .dropdown-item-compact:disabled,
|
||
.ge-help-menu .dropdown-item-compact:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
/* Photoshop menu feel — dark chrome, hover highlight, right-aligned
|
||
shortcut hints, submenu chevrons, keyboard-focus ring. */
|
||
.ge-file-menu .dropdown-item-compact,
|
||
.ge-edit-menu .dropdown-item-compact,
|
||
.ge-image-menu .dropdown-item-compact,
|
||
.ge-layer-menu .dropdown-item-compact,
|
||
.ge-type-menu .dropdown-item-compact,
|
||
.ge-selection-menu .dropdown-item-compact,
|
||
.ge-filter-menu .dropdown-item-compact,
|
||
.ge-view-menu .dropdown-item-compact,
|
||
.ge-window-menu .dropdown-item-compact,
|
||
.ge-help-menu .dropdown-item-compact,
|
||
.ge-3d-menu .dropdown-item-compact,
|
||
.ge-plugins-menu .dropdown-item-compact {
|
||
position: relative;
|
||
justify-content: flex-start;
|
||
}
|
||
.ge-file-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-edit-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-image-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-layer-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-type-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-selection-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-filter-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-view-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-window-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-help-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-3d-menu .dropdown-item-compact:hover:not(:disabled),
|
||
.ge-plugins-menu .dropdown-item-compact:hover:not(:disabled) {
|
||
background: var(--accent, var(--red));
|
||
color: var(--panel);
|
||
}
|
||
.ge-file-menu .dropdown-item-compact:focus-visible,
|
||
.ge-edit-menu .dropdown-item-compact:focus-visible,
|
||
.ge-image-menu .dropdown-item-compact:focus-visible,
|
||
.ge-layer-menu .dropdown-item-compact:focus-visible,
|
||
.ge-type-menu .dropdown-item-compact:focus-visible,
|
||
.ge-selection-menu .dropdown-item-compact:focus-visible,
|
||
.ge-filter-menu .dropdown-item-compact:focus-visible,
|
||
.ge-view-menu .dropdown-item-compact:focus-visible,
|
||
.ge-window-menu .dropdown-item-compact:focus-visible,
|
||
.ge-help-menu .dropdown-item-compact:focus-visible,
|
||
.ge-3d-menu .dropdown-item-compact:focus-visible,
|
||
.ge-plugins-menu .dropdown-item-compact:focus-visible {
|
||
outline: none;
|
||
background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent);
|
||
color: var(--fg);
|
||
}
|
||
.ge-file-menu .dropdown-shortcut,
|
||
.ge-edit-menu .dropdown-shortcut,
|
||
.ge-image-menu .dropdown-shortcut,
|
||
.ge-layer-menu .dropdown-shortcut,
|
||
.ge-selection-menu .dropdown-shortcut,
|
||
.ge-filter-menu .dropdown-shortcut,
|
||
.ge-view-menu .dropdown-shortcut,
|
||
.ge-window-menu .dropdown-shortcut,
|
||
.ge-help-menu .dropdown-shortcut,
|
||
.ge-save-menu .dropdown-shortcut {
|
||
margin-left: auto;
|
||
font-size: 9.5px;
|
||
opacity: 0.5;
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
}
|
||
/* Menu items that open a submenu render a chevron on the right edge. */
|
||
.ge-topbar .dropdown-item-compact.ge-has-submenu::after {
|
||
content: '›';
|
||
margin-left: auto;
|
||
font-size: 13px;
|
||
line-height: 1;
|
||
opacity: 0.6;
|
||
}
|
||
/* --- Photoshop polish pass -------------------------------------------------
|
||
1. Flat text menu bar: title-case labels, no icon/box chrome, tight gaps.
|
||
2. Compact dropdown rows + real separators instead of faint headers.
|
||
3. Pin the right-hand action group so SAVE can never be clipped; the menu
|
||
row scrolls internally when the editor gets narrow. */
|
||
.ge-topbar { justify-content: flex-start; }
|
||
.ge-topbar-left {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-topbar-left::-webkit-scrollbar { display: none; }
|
||
.ge-topbar-right { flex: 0 0 auto; margin-left: auto; }
|
||
.ge-topbar-left .ge-stacked-btn {
|
||
flex-direction: row !important;
|
||
gap: 0 !important;
|
||
padding: 5px 7px !important;
|
||
background: transparent;
|
||
border: 0;
|
||
box-shadow: none;
|
||
border-radius: 4px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-topbar-left .ge-stacked-btn .ge-stacked-glyph,
|
||
.ge-topbar-left .ge-stacked-btn .ge-stacked-caret { display: none; }
|
||
.ge-topbar-left .ge-stacked-btn .ge-stacked-label {
|
||
position: static;
|
||
top: 0;
|
||
font-size: 12px;
|
||
letter-spacing: 0;
|
||
opacity: 1;
|
||
font-weight: 400;
|
||
text-transform: none;
|
||
color: inherit;
|
||
}
|
||
.ge-topbar-left .ge-stacked-btn:hover { background: var(--accent, var(--red)); color: var(--panel); }
|
||
/* Keep the menu bar Photoshop-short. The stacked buttons render as flat text
|
||
labels here, so they only need a tight row height. */
|
||
.ge-topbar { padding: 2px 10px; }
|
||
.ge-topbar-left .ge-stacked-btn { height: 26px; min-height: 0; padding: 0 8px !important; }
|
||
/* When the menu bar is tight, drop the right group's text first (icon-only
|
||
Import/Save) so all twelve menus stay visible instead of Window/Help
|
||
scrolling out of view. Toggled from JS by measuring overflow. */
|
||
.ge-topbar.ge-topbar-compact #ge-import-topbar { padding: 5px 7px !important; }
|
||
.ge-topbar.ge-topbar-compact #ge-import-topbar .ge-stacked-label,
|
||
.ge-topbar.ge-topbar-compact #ge-save-menu-btn .ge-save-action-label { display: none !important; }
|
||
.ge-topbar.ge-topbar-compact #ge-save-menu-btn { padding: 5px 8px !important; }
|
||
/* Non-essential right-group chrome hid so the full Photoshop menu bar
|
||
(through Help) always fits without scrolling out of view. */
|
||
.ge-topbar .ge-alpha-badge,
|
||
.ge-topbar #ge-shortcuts-btn { display: none; }
|
||
/* Compact dropdown rows (Photoshop menus are ~24px tall, 12-13px text). */
|
||
.ge-topbar .dropdown .dropdown-item-compact {
|
||
min-height: 0;
|
||
padding: 4px 10px;
|
||
font-size: 12.5px;
|
||
line-height: 1.35;
|
||
gap: 8px;
|
||
}
|
||
.ge-topbar .dropdown .dropdown-icon { width: 13px; height: 13px; }
|
||
.ge-topbar .dropdown .dropdown-shortcut { font-size: 11px; }
|
||
/* Section headers become real separators (Photoshop has no section labels);
|
||
the first one is dropped so a menu never starts with dead space. */
|
||
.ge-topbar .dropdown .ge-menu-label,
|
||
.ge-topbar .dropdown .ge-filter-submenu-label,
|
||
.ge-topbar .dropdown .dropdown-section-label {
|
||
font-size: 0;
|
||
line-height: 0;
|
||
letter-spacing: 0;
|
||
opacity: 1;
|
||
height: 0;
|
||
padding: 0;
|
||
margin: 5px 10px;
|
||
border-top: 1px solid var(--border);
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
}
|
||
.ge-topbar .dropdown > .ge-menu-label:first-child,
|
||
.ge-topbar .dropdown > .ge-filter-submenu-label:first-child,
|
||
.ge-topbar .dropdown > .dropdown-section-label:first-child { display: none; }
|
||
.ge-selection-save-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 28px;
|
||
gap: 5px;
|
||
padding: 3px 4px;
|
||
}
|
||
.ge-selection-save-row input {
|
||
min-width: 0;
|
||
height: 27px;
|
||
padding: 3px 7px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-saved-selection-list { display: flex; flex-direction: column; gap: 1px; }
|
||
.ge-saved-selection-row { display: grid; grid-template-columns: minmax(0, 1fr) 26px; align-items: center; }
|
||
.ge-saved-selection-load,
|
||
.ge-saved-selection-delete {
|
||
min-width: 0;
|
||
height: 26px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
}
|
||
.ge-saved-selection-load { padding: 3px 8px; overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
|
||
.ge-saved-selection-delete { opacity: 0.45; font-size: 15px; }
|
||
.ge-saved-selection-load:hover,
|
||
.ge-saved-selection-delete:hover { background: var(--hover); opacity: 1; }
|
||
.ge-saved-selection-empty { padding: 4px 8px 6px; font-size: 10px; opacity: 0.45; }
|
||
/* Filter live-preview modal — translucent backdrop over the editor
|
||
with a frosted parameter panel anchored top-right. The active layer
|
||
re-renders live as the user drags the sliders. */
|
||
.ge-filter-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 14;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: flex-end;
|
||
padding: 60px 20px 20px;
|
||
background: rgba(0, 0, 0, 0.18);
|
||
pointer-events: auto;
|
||
}
|
||
.ge-filter-modal {
|
||
width: 240px;
|
||
background: color-mix(in srgb, var(--panel) 95%, transparent);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
|
||
backdrop-filter: blur(10px);
|
||
padding: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.ge-filter-modal-head {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
opacity: 0.7;
|
||
}
|
||
.ge-filter-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.ge-filter-row label {
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.ge-filter-row-value {
|
||
font-variant-numeric: tabular-nums;
|
||
font-weight: 600;
|
||
opacity: 0.85;
|
||
}
|
||
.ge-filter-row input[type="range"] {
|
||
width: 100%;
|
||
}
|
||
.ge-filter-row input[type="color"] {
|
||
width: 100%;
|
||
height: 24px;
|
||
padding: 1px;
|
||
border: 1px solid var(--border, #333);
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
}
|
||
.ge-filter-modal-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
justify-content: flex-end;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* Controlled image export. */
|
||
.ge-export-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10020;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 16px;
|
||
background: rgba(0, 0, 0, 0.56);
|
||
}
|
||
.ge-export-dialog {
|
||
width: min(720px, calc(100vw - 32px));
|
||
max-height: calc(100vh - 32px);
|
||
overflow: auto;
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 18px 54px rgba(0, 0, 0, 0.48);
|
||
}
|
||
.ge-export-head,
|
||
.ge-export-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
}
|
||
.ge-export-head { border-bottom: 1px solid var(--border); }
|
||
.ge-export-head h2 {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
letter-spacing: 0;
|
||
}
|
||
.ge-export-close {
|
||
border: 0;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
font-size: 20px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
opacity: 0.6;
|
||
}
|
||
.ge-export-close:hover { opacity: 1; }
|
||
.ge-export-body {
|
||
display: grid;
|
||
grid-template-columns: minmax(260px, 1.25fr) minmax(220px, 0.75fr);
|
||
gap: 14px;
|
||
padding: 14px;
|
||
}
|
||
.ge-export-preview-wrap {
|
||
min-height: 300px;
|
||
display: grid;
|
||
place-items: center;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background-color: #22262d;
|
||
background-image:
|
||
linear-gradient(45deg, rgba(255,255,255,0.06) 25%, transparent 25%),
|
||
linear-gradient(-45deg, rgba(255,255,255,0.06) 25%, transparent 25%),
|
||
linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.06) 75%),
|
||
linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.06) 75%);
|
||
background-size: 20px 20px;
|
||
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||
}
|
||
.ge-export-preview {
|
||
display: block;
|
||
max-width: calc(100% - 24px);
|
||
max-height: 420px;
|
||
box-shadow: 0 4px 18px rgba(0,0,0,0.38);
|
||
}
|
||
.ge-export-fields {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.ge-export-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
font-size: 10px;
|
||
opacity: 0.78;
|
||
}
|
||
.ge-export-field > span {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.ge-export-field input[type="text"],
|
||
.ge-export-field input[type="number"] {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
padding: 7px 8px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
}
|
||
.ge-export-field input:focus { outline: none; border-color: var(--red); }
|
||
.ge-export-format {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
border: 1px solid var(--border);
|
||
border-radius: 5px;
|
||
overflow: hidden;
|
||
}
|
||
.ge-export-format button {
|
||
min-height: 30px;
|
||
border: 0;
|
||
border-right: 1px solid var(--border);
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 10px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-export-format button:last-child { border-right: 0; }
|
||
.ge-export-format button.active {
|
||
background: color-mix(in srgb, var(--red) 18%, var(--bg));
|
||
color: var(--red);
|
||
}
|
||
.ge-export-dimensions {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 28px minmax(0, 1fr);
|
||
gap: 5px;
|
||
align-items: end;
|
||
}
|
||
.ge-export-link {
|
||
width: 28px;
|
||
height: 29px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
opacity: 0.45;
|
||
cursor: pointer;
|
||
}
|
||
.ge-export-link.active { opacity: 1; color: var(--red); border-color: var(--red); }
|
||
.ge-export-quality input { width: 100%; accent-color: var(--red); }
|
||
.ge-export-surface-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
border-top: 1px solid var(--border);
|
||
padding-top: 10px;
|
||
}
|
||
.ge-export-toggle,
|
||
.ge-export-matte {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 10px;
|
||
}
|
||
.ge-export-matte.disabled { opacity: 0.35; pointer-events: none; }
|
||
.ge-export-footer {
|
||
border-top: 1px solid var(--border);
|
||
font-size: 10px;
|
||
}
|
||
@media (max-width: 700px) {
|
||
.ge-export-overlay { padding: 8px; place-items: end center; }
|
||
.ge-export-dialog { width: 100%; max-height: calc(100dvh - 16px); }
|
||
.ge-export-body { grid-template-columns: 1fr; padding: 10px; }
|
||
.ge-export-preview-wrap { min-height: 190px; max-height: 260px; }
|
||
.ge-export-preview { max-height: 230px; }
|
||
}
|
||
.ge-menu-divider {
|
||
height: 1px;
|
||
background: var(--border);
|
||
margin: 4px 0;
|
||
}
|
||
.ge-edge-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.ge-edge-label {
|
||
font-size: 11px;
|
||
opacity: 0.65;
|
||
}
|
||
.ge-edge-input {
|
||
padding: 6px 8px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 12px;
|
||
}
|
||
.ge-edge-input:focus { outline: none; border-color: var(--red); }
|
||
.ge-edge-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.ge-edge-actions .ge-btn { flex: 1; }
|
||
.ge-edge-hint {
|
||
margin: 0;
|
||
font-size: 10px;
|
||
opacity: 0.5;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* Styled "New canvas" prompt — replaces the native browser prompt(). */
|
||
.ge-canvas-prompt {
|
||
max-width: 430px;
|
||
width: 90vw;
|
||
}
|
||
.ge-canvas-prompt .modal-header h4 { margin: 0; font-size: 14px; }
|
||
.ge-canvas-prompt-row {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 10px;
|
||
justify-content: center;
|
||
margin: 6px 0 2px;
|
||
}
|
||
.ge-canvas-prompt-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.ge-canvas-prompt-field > span {
|
||
font-size: 11px;
|
||
opacity: 0.65;
|
||
}
|
||
.ge-canvas-prompt-field input {
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
font: inherit;
|
||
font-size: 14px;
|
||
}
|
||
.ge-canvas-prompt-field input:focus { outline: none; border-color: var(--red); }
|
||
.ge-canvas-prompt-x {
|
||
font-size: 18px;
|
||
opacity: 0.4;
|
||
align-self: center;
|
||
padding-bottom: 8px;
|
||
position: relative;
|
||
top: 8px;
|
||
}
|
||
.ge-canvas-prompt-hint {
|
||
margin: 0 auto 0 0;
|
||
font-size: 11px;
|
||
opacity: 0.5;
|
||
line-height: 1.35;
|
||
text-align: left;
|
||
}
|
||
.ge-canvas-prompt-section-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
margin: 2px 0 7px;
|
||
font-size: 10px;
|
||
font-weight: 650;
|
||
letter-spacing: .04em;
|
||
text-transform: uppercase;
|
||
opacity: .62;
|
||
}
|
||
.ge-canvas-units-field {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
margin: 8px 0 2px;
|
||
font-size: 11px;
|
||
opacity: .75;
|
||
}
|
||
.ge-canvas-control-label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
min-width: 0;
|
||
}
|
||
.ge-canvas-prompt-icon,
|
||
.ge-canvas-preset-icon {
|
||
flex: 0 0 auto;
|
||
opacity: .68;
|
||
}
|
||
.ge-canvas-units-field select,
|
||
.ge-canvas-interpolation-field select {
|
||
min-width: 112px;
|
||
padding: 5px 7px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
font: inherit;
|
||
}
|
||
.ge-canvas-anchor-label {
|
||
margin: 14px 0 6px;
|
||
font-size: 10px;
|
||
font-weight: 650;
|
||
letter-spacing: .04em;
|
||
text-transform: uppercase;
|
||
opacity: .62;
|
||
text-align: center;
|
||
}
|
||
.ge-canvas-anchor-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 22px);
|
||
gap: 5px;
|
||
justify-content: center;
|
||
}
|
||
.ge-canvas-anchor {
|
||
width: 22px;
|
||
height: 22px;
|
||
padding: 0;
|
||
border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
|
||
border-radius: 4px;
|
||
background: color-mix(in srgb, var(--fg) 6%, transparent);
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.ge-canvas-anchor:hover,
|
||
.ge-canvas-anchor.active {
|
||
border-color: var(--red);
|
||
background: color-mix(in srgb, var(--red) 12%, transparent);
|
||
}
|
||
.ge-canvas-resize-options {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-top: 12px;
|
||
}
|
||
.ge-canvas-lock-option {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 11px;
|
||
opacity: .72;
|
||
cursor: pointer;
|
||
}
|
||
.ge-canvas-interpolation-field {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 11px;
|
||
opacity: .72;
|
||
}
|
||
.ge-canvas-interpolation-field select {
|
||
min-width: 92px;
|
||
padding: 4px 6px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 5px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
}
|
||
.ge-canvas-preset-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 6px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-canvas-preset {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
flex: 0 0 auto;
|
||
min-height: 62px;
|
||
min-width: 0;
|
||
padding: 10px 9px;
|
||
line-height: 1.25;
|
||
border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
|
||
border-radius: 6px;
|
||
background: color-mix(in srgb, var(--fg) 4%, transparent);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
box-sizing: border-box;
|
||
}
|
||
.ge-canvas-preset:hover,
|
||
.ge-canvas-preset.active {
|
||
border-color: var(--red);
|
||
background: color-mix(in srgb, var(--red) 12%, transparent);
|
||
}
|
||
.ge-canvas-preset span,
|
||
.ge-canvas-preset strong {
|
||
display: block;
|
||
width: auto;
|
||
max-width: 100%;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-canvas-preset span { font-size: 11px; font-weight: 600; }
|
||
.ge-canvas-preset strong { font-size: 10px; font-weight: 500; opacity: .8; }
|
||
.ge-canvas-preview {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 2px 0 8px;
|
||
padding: 6px;
|
||
border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
|
||
border-radius: 6px;
|
||
background: color-mix(in srgb, var(--fg) 4%, transparent);
|
||
}
|
||
.ge-canvas-preview img { max-width: 100%; max-height: 120px; object-fit: contain; display: block; }
|
||
.ge-canvas-preview[hidden],
|
||
.ge-canvas-resolution-field[hidden] { display: none; }
|
||
.ge-canvas-resolution-field { display: inline-flex; align-items: center; gap: 6px; }
|
||
.ge-canvas-resolution-field input[type="number"] { width: 84px; }
|
||
.ge-canvas-resolution-unit { font-size: 11px; opacity: .6; }
|
||
.ge-canvas-prompt-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding-top: 8px;
|
||
border-top: 1px solid var(--border);
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.ge-btn-primary {
|
||
background: var(--red);
|
||
color: #fff;
|
||
border-color: var(--red);
|
||
font-weight: 600;
|
||
}
|
||
.ge-btn-primary:hover { background: color-mix(in srgb, var(--red) 85%, #000); }
|
||
.ge-zoom-label { font-size: 10px; opacity: 0.5; }
|
||
|
||
/* Nudge whirlpool spinners 1px down so they sit on the text baseline
|
||
when placed inside action buttons (Generate, Harmonize, etc). */
|
||
.ge-btn .ai-spinner-whirlpool,
|
||
.ge-btn .spinner-whirlpool,
|
||
button .ai-spinner-whirlpool,
|
||
button .spinner-whirlpool {
|
||
position: relative;
|
||
top: 1px;
|
||
}
|
||
|
||
/* Layers panel */
|
||
.ge-layers {
|
||
/* Natural height now that the whole right panel scrolls — flex:1
|
||
would collapse the list inside a scrolling parent. */
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
/* Vertical drag handle that sits on the LEFT edge of the right panel.
|
||
Drag horizontally to widen / narrow the entire side panel. Slim
|
||
by default; brighter on hover so the user notices it's interactive. */
|
||
.ge-panel-resize {
|
||
position: absolute;
|
||
left: -3px;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 6px;
|
||
cursor: ew-resize;
|
||
background: transparent;
|
||
z-index: 5;
|
||
transition: background 0.12s;
|
||
}
|
||
.ge-panel-resize:hover,
|
||
.ge-panel-resize:active {
|
||
background: color-mix(in srgb, var(--red) 30%, transparent);
|
||
}
|
||
/* The right panel needs `position: relative` so the handle (absolute)
|
||
anchors to it instead of the viewport. */
|
||
.ge-right-panel {
|
||
position: relative;
|
||
}
|
||
|
||
/* Docked right panel stack (desktop) — Photoshop-style: several panel
|
||
groups are visible at once, stacked vertically. Each group shows one
|
||
tab's panel, collapses to its tab strip, and resizes via the divider
|
||
below it. The panel wrappers only re-parent existing elements, so all
|
||
ids/classes/listeners keep working; inactive panels are `hidden`. */
|
||
.ge-right-panel--dock {
|
||
overflow: hidden;
|
||
}
|
||
.ge-dock {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 0 1 auto;
|
||
min-height: 0;
|
||
max-height: 68%;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
.ge-right-panel--dock > .ge-layers { flex: 1 1 auto; min-height: 0; }
|
||
|
||
.ge-dock-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 0 0 auto;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
.ge-dock-group-head {
|
||
display: flex;
|
||
align-items: stretch;
|
||
flex: 0 0 auto;
|
||
gap: 2px;
|
||
padding: 0 4px 0 6px;
|
||
background: color-mix(in srgb, var(--panel) 88%, var(--fg) 12%);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.ge-dock-tabs {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
scrollbar-width: none;
|
||
gap: 1px;
|
||
}
|
||
.ge-dock-tabs::-webkit-scrollbar { display: none; }
|
||
.ge-dock-tab {
|
||
flex: 0 0 auto;
|
||
padding: 5px 14px 5px 8px;
|
||
font: inherit;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.01em;
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
color: color-mix(in srgb, var(--fg) 72%, transparent);
|
||
background: transparent;
|
||
border: 0;
|
||
border-bottom: 2px solid transparent;
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-tab:hover { color: var(--fg); }
|
||
.ge-dock-tab.active {
|
||
color: var(--fg);
|
||
border-bottom-color: var(--red);
|
||
}
|
||
.ge-dock-tab:focus-visible {
|
||
outline: 2px solid var(--red);
|
||
outline-offset: -2px;
|
||
}
|
||
/* A tab + its tear-off ("undock") control. The undock button appears on
|
||
hover / focus and floats that single panel into its own window. It sits
|
||
inside the tab button (top-right corner), not beside it. */
|
||
.ge-dock-tab-item {
|
||
display: inline-flex;
|
||
align-items: stretch;
|
||
flex: 0 0 auto;
|
||
position: relative;
|
||
}
|
||
.ge-dock-tab-pop {
|
||
position: absolute;
|
||
top: 1px;
|
||
right: 1px;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 13px;
|
||
height: 13px;
|
||
padding: 0;
|
||
border: 0;
|
||
border-radius: 3px;
|
||
background: color-mix(in srgb, var(--panel) 88%, transparent);
|
||
color: color-mix(in srgb, var(--fg) 55%, transparent);
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-tab-item:hover .ge-dock-tab-pop,
|
||
.ge-dock-tab:focus-visible ~ .ge-dock-tab-pop { display: inline-flex; }
|
||
.ge-dock-tab-pop:hover { color: var(--fg); }
|
||
.ge-dock-collapse {
|
||
flex: 0 0 auto;
|
||
width: 26px;
|
||
height: 26px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--fg) 72%, transparent);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
transition: transform 0.12s;
|
||
}
|
||
.ge-dock-collapse:hover { color: var(--fg); }
|
||
.ge-dock-detach {
|
||
flex: 0 0 auto;
|
||
width: 26px;
|
||
height: 26px;
|
||
padding: 0;
|
||
margin-left: 2px;
|
||
border-left: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--fg) 70%, transparent);
|
||
background: transparent;
|
||
border-top: 0;
|
||
border-right: 0;
|
||
border-bottom: 0;
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-detach:hover { color: var(--fg); }
|
||
/* Collapsed groups shrink to their tab header only (never clip it). */
|
||
.ge-dock-group.is-collapsed {
|
||
flex: 0 0 auto !important;
|
||
height: auto !important;
|
||
}
|
||
.ge-dock-tabs-more {
|
||
flex: 0 0 auto;
|
||
width: 26px;
|
||
height: 26px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--fg) 72%, transparent);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-tabs-more:hover { color: var(--fg); }
|
||
.ge-dock-collapse svg, .ge-dock-detach svg, .ge-dock-tabs-more svg { width: 16px; height: 16px; flex: 0 0 auto; }
|
||
.ge-dock-tabs-menu {
|
||
position: fixed;
|
||
z-index: 10060;
|
||
min-width: 140px;
|
||
padding: 4px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
|
||
}
|
||
.ge-dock-tabs-menu-item {
|
||
text-align: left;
|
||
padding: 5px 8px;
|
||
font-size: 11.5px;
|
||
color: var(--fg);
|
||
background: transparent;
|
||
border: 0;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-tabs-menu-item:hover {
|
||
background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent);
|
||
}
|
||
.ge-dock-group.is-floating .ge-dock-detach { display: none; }
|
||
/* Detached panel group — floats above the editor in its own draggable
|
||
window with a Dock control to return it to the stack. */
|
||
.ge-float-window {
|
||
position: fixed;
|
||
z-index: 10050;
|
||
width: 260px;
|
||
max-width: calc(100vw - 16px);
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
|
||
overflow: hidden;
|
||
}
|
||
.ge-float-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 6px 5px 10px;
|
||
background: color-mix(in srgb, var(--panel) 88%, var(--fg) 12%);
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: grab;
|
||
touch-action: none;
|
||
}
|
||
.ge-float-title {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.03em;
|
||
text-transform: uppercase;
|
||
color: var(--fg);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-float-btns { display: inline-flex; align-items: center; gap: 2px; }
|
||
.ge-float-dock {
|
||
width: 28px;
|
||
height: 26px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: color-mix(in srgb, var(--fg) 70%, transparent);
|
||
background: transparent;
|
||
border: 0;
|
||
cursor: pointer;
|
||
}
|
||
.ge-float-dock svg { width: 16px; height: 16px; flex: 0 0 auto; }
|
||
.ge-float-dock:hover { color: var(--fg); }
|
||
.ge-float-body { max-height: 60vh; overflow-y: auto; }
|
||
.ge-float-body > .ge-dock-group { flex: 0 0 auto !important; height: auto !important; }
|
||
.ge-dock-group.is-collapsed .ge-dock-collapse { transform: rotate(-90deg); }
|
||
.ge-dock-group.is-collapsed .ge-dock-panels { display: none; }
|
||
|
||
.ge-dock-panels {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
flex: 1 1 auto;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
/* A visible thin scrollbar makes it obvious when a group's content
|
||
(e.g. the swatch grid) continues below the fold instead of looking
|
||
like it was clipped mid-row. */
|
||
scrollbar-width: thin;
|
||
scrollbar-color: color-mix(in srgb, var(--fg) 30%, transparent) transparent;
|
||
}
|
||
.ge-dock-panels::-webkit-scrollbar { width: 8px; }
|
||
.ge-dock-panels::-webkit-scrollbar-thumb {
|
||
background: color-mix(in srgb, var(--fg) 28%, transparent);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-dock-panels::-webkit-scrollbar-track { background: transparent; }
|
||
.ge-dock-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 0 0 auto;
|
||
min-width: 0;
|
||
}
|
||
.ge-dock-panel[hidden] { display: none; }
|
||
|
||
/* View > Panels visibility: hide a panel's body + its tab-strip item, a whole
|
||
group (and its divider) once every member is hidden, and the pinned Layers /
|
||
Tool Options strips. Tagged by panel-visibility.js. */
|
||
.ge-dock-panel[data-panel-hidden="1"],
|
||
.ge-dock-tab-item[data-panel-hidden="1"],
|
||
.ge-dock-group[data-panel-hidden="1"],
|
||
.ge-dock-divider[data-panel-hidden="1"],
|
||
.ge-layers[data-panel-hidden="1"],
|
||
.ge-controls[data-panel-hidden="1"],
|
||
.ge-dock[data-panel-hidden="1"],
|
||
.ge-right-panel[data-panel-hidden="1"] { display: none !important; }
|
||
/* When every dock group is hidden the rail must shrink to its remaining pinned
|
||
content instead of stretching to full height and leaving a blank column. */
|
||
.ge-right-panel[data-panel-dock-hidden="1"] { align-self: flex-start; height: auto; }
|
||
.ge-right-panel[data-panel-dock-hidden="1"] > .ge-layers { flex: 0 0 auto; max-height: 100%; }
|
||
.ge-dock-select option[data-panel-hidden="1"] { display: none; }
|
||
|
||
/* Drag divider between two stacked panel groups. */
|
||
.ge-dock-divider {
|
||
flex: 0 0 auto;
|
||
height: 5px;
|
||
margin: -1px 0;
|
||
background: transparent;
|
||
cursor: ns-resize;
|
||
transition: background 0.12s;
|
||
}
|
||
.ge-dock-divider:hover,
|
||
.ge-dock-divider:active {
|
||
background: color-mix(in srgb, var(--red) 35%, transparent);
|
||
}
|
||
|
||
/* Compact fallback (narrow desktop columns) — a single selector that
|
||
swaps one panel at a time. */
|
||
.ge-dock--compact .ge-dock-tabbar {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
padding: 7px 8px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: var(--panel);
|
||
}
|
||
.ge-dock-select {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
width: 100%;
|
||
padding: 5px 8px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.03em;
|
||
text-transform: uppercase;
|
||
color: var(--fg);
|
||
background-color: var(--bg, var(--panel));
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-dock-select:focus-visible {
|
||
outline: 2px solid var(--red);
|
||
outline-offset: 1px;
|
||
}
|
||
.ge-dock--compact .ge-dock-panels {
|
||
flex: 1 1 auto;
|
||
max-height: none;
|
||
}
|
||
|
||
/* Floating value bubble that pops above the slider thumb while
|
||
dragging. Solves the "I can't see the number" problem when the
|
||
slider's opaque plate covers the row's right-edge value chip. */
|
||
.ge-slider-bubble {
|
||
/* Fixed positioning so the bubble escapes any overflow:hidden/auto
|
||
on the slider's ancestor chain (the layers list clips, and used
|
||
to swallow the bubble entirely). JS sets viewport-relative
|
||
left/top; the translate centers and lifts the bubble above the
|
||
slider thumb. */
|
||
position: fixed;
|
||
transform: translate(-50%, -100%);
|
||
z-index: 10000;
|
||
padding: 5px 10px;
|
||
background: var(--red);
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
font-variant-numeric: tabular-nums;
|
||
border-radius: 6px;
|
||
box-shadow: 0 3px 10px color-mix(in srgb, var(--red) 40%, transparent);
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
opacity: 0;
|
||
transition: opacity 0.1s;
|
||
}
|
||
.ge-slider-bubble.visible { opacity: 1; }
|
||
|
||
/* Floating thumbnail shown when hovering a layer row. */
|
||
.ge-layer-thumb {
|
||
position: fixed;
|
||
z-index: 60;
|
||
padding: 4px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 6px 18px color-mix(in srgb, var(--fg) 30%, transparent);
|
||
pointer-events: none;
|
||
display: none;
|
||
}
|
||
.ge-layers-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
opacity: 0.85;
|
||
border-bottom: 1px solid var(--border);
|
||
position: relative;
|
||
}
|
||
.ge-layers-title { flex: 1; }
|
||
.ge-layers-header #ge-add-layer { flex: 0 0 auto; white-space: nowrap; }
|
||
.ge-layer-header-tools {
|
||
display: flex;
|
||
flex: 0 1 auto;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
/* Panel action icons live in a compact row at the BOTTOM of the panel
|
||
(Photoshop-style); #ge-layer-tools stays an empty hook for shared
|
||
selected-layer actions. */
|
||
.ge-layers-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 6px;
|
||
padding: 4px 8px;
|
||
border-top: 1px solid var(--border);
|
||
background: color-mix(in srgb, var(--bg-secondary, var(--panel)) 40%, var(--panel));
|
||
}
|
||
.ge-layers-grab { display: none; } /* mobile only */
|
||
.ge-layers-merge-wrap { position: relative; display: inline-flex; }
|
||
.ge-layers-merge-wrap #ge-layers-merge-btn { position: relative; top: 0; }
|
||
.ge-layers-merge-menu {
|
||
/* Fixed positioning so the menu escapes the layers panel's overflow
|
||
clipping (which made it look transparent). JS sets exact coords
|
||
via getBoundingClientRect on the trigger button at open time. */
|
||
position: fixed;
|
||
z-index: 200;
|
||
min-width: 160px;
|
||
padding: 4px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
pointer-events: auto;
|
||
}
|
||
.ge-layers-merge-menu .dropdown-item-compact {
|
||
width: 100%;
|
||
background: none;
|
||
border: none;
|
||
text-align: left;
|
||
font: inherit;
|
||
}
|
||
.ge-layers-actions {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 6px 8px;
|
||
border-top: 1px solid var(--border);
|
||
flex-wrap: wrap;
|
||
/* Pin the action row to the bottom of the right panel's scroll
|
||
viewport. Without this, when the tool-controls section is tall
|
||
enough to overflow the panel, the merge buttons get pushed below
|
||
the fold and require scrolling to reach. */
|
||
position: sticky;
|
||
bottom: 0;
|
||
background: var(--panel);
|
||
z-index: 4;
|
||
/* Centered cluster instead of stretching the buttons to fill the
|
||
panel width. */
|
||
justify-content: center;
|
||
}
|
||
/* Merge / flatten buttons sit content-sized so the cluster reads as a
|
||
centered group of three, not three stretched bars across the panel. */
|
||
.ge-layers-actions .ge-btn {
|
||
flex: 0 0 auto;
|
||
min-width: 0;
|
||
padding: 4px 8px;
|
||
font-size: 10px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-layers-actions .ge-icon-btn {
|
||
/* Icon-only buttons stay narrow — no risk of label overflow. */
|
||
flex: 0 0 auto;
|
||
padding: 4px 8px;
|
||
}
|
||
.ge-layers-actions .ge-btn svg {
|
||
flex-shrink: 0;
|
||
margin-right: 0 !important;
|
||
}
|
||
.ge-layers-list {
|
||
/* Cap the list so a huge layer stack scrolls within its own box
|
||
rather than pushing the whole panel arbitrarily tall. */
|
||
max-height: 320px;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
padding: 0 0 6px;
|
||
}
|
||
.ge-layer-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 3px 8px;
|
||
cursor: pointer;
|
||
border-left: 2px solid transparent;
|
||
transition: background 0.1s;
|
||
font-size: 11px;
|
||
color: var(--fg);
|
||
}
|
||
.ge-layer-item:focus-visible,
|
||
.ge-layer-group-row:focus-visible {
|
||
outline: 1px solid var(--accent, var(--red));
|
||
outline-offset: -1px;
|
||
}
|
||
@media (min-width: 701px) {
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) {
|
||
display: grid;
|
||
grid-template-columns: 12px 22px 28px minmax(0, 1fr) 24px;
|
||
row-gap: 2px;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-drag {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-vis {
|
||
grid-column: 2;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-inline-thumb {
|
||
grid-column: 3;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name-input {
|
||
grid-column: 4;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-lock-btn {
|
||
grid-column: 5;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row {
|
||
grid-column: 1 / -1;
|
||
grid-row: 2;
|
||
width: 100%;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-controls {
|
||
grid-column: 1 / -1;
|
||
grid-row: 3;
|
||
justify-content: flex-end;
|
||
min-width: 0;
|
||
}
|
||
/* Photoshop-style: an idle layer row is ONE compact line. The per-row
|
||
opacity slider and action strip stay in the DOM (so they remain
|
||
testable) but only surface on the active or hovered row. */
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-controls {
|
||
display: none;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-opacity-row,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-controls,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item):hover > .ge-layer-opacity-row,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item):hover > .ge-layer-controls {
|
||
display: flex;
|
||
}
|
||
}
|
||
.ge-layer-inline-thumb {
|
||
display: block;
|
||
width: 28px;
|
||
height: 21px;
|
||
flex: 0 0 28px;
|
||
object-fit: contain;
|
||
border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
|
||
border-radius: 3px;
|
||
background: #303030;
|
||
image-rendering: auto;
|
||
}
|
||
/* Row-hover tint removed — kept the layer row's background stable so
|
||
it doesn't shift visually as the user reaches for the opacity slider. */
|
||
.ge-layer-item.active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 52%, var(--bg-secondary, var(--bg)));
|
||
border-left-color: var(--accent, var(--red));
|
||
box-shadow: inset 3px 0 0 var(--accent, var(--red)), inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 80%, transparent);
|
||
}
|
||
.ge-layer-item.active > .ge-layer-name,
|
||
.ge-layer-group-row.active > .ge-layer-group-name { font-weight: 700; }
|
||
/* "Active parent" — the layer is selected, but a mask sub-layer below
|
||
it is the actual paint target. Soft hint so the user can see which
|
||
parent owns the active mask without competing with the mask's
|
||
strong red highlight. */
|
||
.ge-layer-item.active-parent {
|
||
background: color-mix(in srgb, var(--fg) 4%, transparent);
|
||
border-left-color: color-mix(in srgb, var(--fg) 30%, transparent);
|
||
}
|
||
.ge-layer-item.selected:not(.active):not(.active-parent) {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 22%, var(--bg));
|
||
box-shadow: inset 2px 0 0 color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
|
||
}
|
||
.ge-layer-group-row {
|
||
min-height: 36px;
|
||
display: grid;
|
||
grid-template-columns: 12px 18px 24px 34px minmax(0, 1fr) 24px;
|
||
grid-template-rows: auto auto auto;
|
||
align-items: center;
|
||
gap: 3px;
|
||
padding: 3px 7px 3px calc(7px + var(--group-depth, 0) * 14px);
|
||
border-bottom: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
|
||
background: color-mix(in srgb, var(--bg-secondary) 78%, var(--bg));
|
||
color: var(--fg);
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
.ge-layer-group-row .ge-layer-group-drag { grid-column: 1; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-group-toggle { grid-column: 2; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-vis { grid-column: 3; grid-row: 1; }
|
||
.ge-layer-group-row .ge-group-inline-thumb { grid-column: 4; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-group-name { grid-column: 5; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-lock-btn { grid-column: 6; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-opacity-row {
|
||
grid-column: 1 / -1;
|
||
grid-row: 2;
|
||
display: flex;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
.ge-layer-group-row .ge-layer-opacity-row .ge-layer-opacity {
|
||
flex: 1 1 auto;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
.ge-layer-opacity-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
}
|
||
.ge-layer-opacity-row .ge-layer-opacity { flex: 1 1 auto; width: auto; }
|
||
.ge-layer-group-row .ge-layer-controls {
|
||
grid-column: 1 / -1;
|
||
grid-row: 3;
|
||
justify-content: flex-end;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row {
|
||
grid-column: 1 / -1;
|
||
grid-row: 2;
|
||
width: 100%;
|
||
}
|
||
.ge-layer-group-drag { touch-action: none; }
|
||
.ge-layer-group-row.group-dragging {
|
||
opacity: 0.62;
|
||
outline: 1px solid color-mix(in srgb, var(--accent, var(--red)) 65%, transparent);
|
||
}
|
||
.ge-group-drop-line {
|
||
position: absolute;
|
||
z-index: 20;
|
||
left: 6px;
|
||
right: 6px;
|
||
height: 2px;
|
||
pointer-events: none;
|
||
background: var(--accent, var(--red));
|
||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--bg) 65%, transparent);
|
||
}
|
||
.ge-layer-group-row.active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--bg-secondary));
|
||
box-shadow: inset 2px 0 0 var(--accent, var(--red)), inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 50%, transparent);
|
||
}
|
||
.ge-layer-group-toggle {
|
||
width: 18px;
|
||
height: 22px;
|
||
padding: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
opacity: 0.62;
|
||
}
|
||
.ge-layer-group-toggle svg { transition: transform 120ms ease; }
|
||
.ge-layer-group-toggle.expanded svg { transform: rotate(90deg); }
|
||
.ge-layer-group-name {
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
overflow: hidden;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
}
|
||
.ge-layer-group-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.ge-group-mask-sub-item { padding-left: calc(20px + var(--group-mask-depth, 1) * 14px); }
|
||
.ge-layer-vis {
|
||
border: none;
|
||
background: none;
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
padding: 2px 4px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.4;
|
||
transition: opacity 0.12s;
|
||
}
|
||
.ge-layer-vis:hover { opacity: 0.8; }
|
||
.ge-layer-vis.visible { opacity: 0.9; }
|
||
.ge-layer-name {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
min-width: 0;
|
||
}
|
||
.ge-layer-clipped-marker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
color: var(--accent, var(--red));
|
||
opacity: 0.82;
|
||
}
|
||
.ge-layer-name-input {
|
||
flex: 1;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
color: var(--fg);
|
||
font-size: 11px;
|
||
padding: 1px 4px;
|
||
border-radius: 3px;
|
||
min-width: 0;
|
||
}
|
||
/* All sliders inside the image editor share the same visual language as
|
||
the layer opacity slider — rounded pill, dim track, red thumb. Track
|
||
AND thumb grow when interacting for finer tweaking. */
|
||
.gallery-editor-container input[type="range"] {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
height: 4px;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
accent-color: var(--red);
|
||
cursor: pointer;
|
||
transition: height 0.15s ease;
|
||
}
|
||
.gallery-editor-container input[type="range"]:hover,
|
||
.gallery-editor-container input[type="range"]:focus,
|
||
.gallery-editor-container input[type="range"]:active {
|
||
height: 10px;
|
||
}
|
||
.gallery-editor-container input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--red);
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: width 0.12s ease, height 0.12s ease;
|
||
}
|
||
.gallery-editor-container input[type="range"]::-moz-range-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--red);
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: width 0.12s ease, height 0.12s ease;
|
||
}
|
||
.gallery-editor-container input[type="range"]:hover::-webkit-slider-thumb,
|
||
.gallery-editor-container input[type="range"]:focus::-webkit-slider-thumb,
|
||
.gallery-editor-container input[type="range"]:active::-webkit-slider-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
.gallery-editor-container input[type="range"]:hover::-moz-range-thumb,
|
||
.gallery-editor-container input[type="range"]:focus::-moz-range-thumb,
|
||
.gallery-editor-container input[type="range"]:active::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
/* Layer-row opacity slider — fixed track, only thumb grows on
|
||
interaction. Takes a bit of row space at rest, but no jumpy expand
|
||
animation. */
|
||
.ge-layer-opacity {
|
||
width: 70px;
|
||
height: 6px;
|
||
accent-color: var(--red);
|
||
flex-shrink: 0;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
background: color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 999px;
|
||
position: relative;
|
||
top: 0;
|
||
}
|
||
.ge-layer-opacity:hover::-webkit-slider-thumb,
|
||
.ge-layer-opacity:active::-webkit-slider-thumb,
|
||
.ge-layer-opacity.dragging::-webkit-slider-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
.ge-layer-opacity:hover::-moz-range-thumb,
|
||
.ge-layer-opacity:active::-moz-range-thumb,
|
||
.ge-layer-opacity.dragging::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
.ge-layer-controls {
|
||
display: flex;
|
||
gap: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-layer-controls .ge-mask-inspect-btn.active {
|
||
color: var(--accent, var(--red));
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
|
||
}
|
||
.ge-mask-properties {
|
||
position: relative;
|
||
flex: 0 0 22px;
|
||
}
|
||
.ge-mask-properties > summary:hover,
|
||
.ge-mask-properties[open] > summary {
|
||
opacity: 0.8;
|
||
}
|
||
.ge-mask-properties[open] > summary { color: var(--accent, var(--red)); opacity: 0.95; }
|
||
.ge-mask-properties-panel {
|
||
position: fixed;
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
bottom: auto;
|
||
overflow: auto;
|
||
color: var(--fg);
|
||
z-index: 9999;
|
||
right: auto;
|
||
top: 0;
|
||
width: 190px;
|
||
padding: 8px;
|
||
display: grid;
|
||
gap: 7px;
|
||
border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
|
||
border-radius: 5px;
|
||
background: var(--bg-secondary, var(--bg));
|
||
box-shadow: 0 8px 24px rgba(0,0,0,.28);
|
||
}
|
||
.ge-mask-properties-panel:not(:popover-open) { display: none; }
|
||
.ge-mask-property-row {
|
||
display: grid;
|
||
grid-template-columns: 52px minmax(0, 1fr) 35px;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 10px;
|
||
}
|
||
.ge-mask-property-row input { min-width: 0; width: 100%; }
|
||
.ge-mask-apply-btn {
|
||
width: 100%;
|
||
min-height: 26px;
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border));
|
||
border-radius: 4px;
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
font-size: 10px;
|
||
}
|
||
.ge-mask-apply-btn:hover { background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); }
|
||
.ge-layer-tools {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 2px;
|
||
min-height: 34px;
|
||
padding: 3px 7px;
|
||
border-top: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
|
||
background: color-mix(in srgb, var(--bg-secondary) 72%, var(--bg));
|
||
}
|
||
.ge-layer-tools .ge-layer-controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
width: 100%;
|
||
}
|
||
.ge-eyedropper-live {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
min-height: 28px;
|
||
padding: 3px 6px;
|
||
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
|
||
border-radius: 5px;
|
||
background: color-mix(in srgb, var(--fg) 4%, transparent);
|
||
}
|
||
.ge-eyedropper-loupe {
|
||
width: 42px;
|
||
height: 42px;
|
||
flex: 0 0 42px;
|
||
border-radius: 4px;
|
||
image-rendering: pixelated;
|
||
background: var(--bg);
|
||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 22%, transparent);
|
||
}
|
||
.ge-eyedropper-live-swatch {
|
||
width: 20px;
|
||
height: 20px;
|
||
flex: 0 0 20px;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 22%, transparent);
|
||
}
|
||
.ge-eyedropper-live-values {
|
||
display: grid;
|
||
gap: 1px;
|
||
min-width: 0;
|
||
font-size: 10px;
|
||
line-height: 1.25;
|
||
}
|
||
.ge-eyedropper-live-values > span { opacity: .62; font-variant-numeric: tabular-nums; }
|
||
.ge-eyedropper-live-swatch.empty {
|
||
background: repeating-linear-gradient(135deg, transparent 0 4px, color-mix(in srgb, var(--fg) 18%, transparent) 4px 6px);
|
||
}
|
||
.ge-eyedropper-live code {
|
||
font-size: 11px;
|
||
font-variant-numeric: tabular-nums;
|
||
opacity: .75;
|
||
}
|
||
/* In the contextual options bar keep the eyedropper readout on ONE compact
|
||
row so the bar height matches the other tools and the centred canvas does
|
||
not jump between tools (which shifted the eyedropper sample point). The
|
||
loupe canvas keeps its 84x84 backing store; only its display size shrinks,
|
||
so the live pixel readout still works. */
|
||
.ge-controls--in-options .ge-eyedropper-live {
|
||
min-height: 0;
|
||
height: 26px;
|
||
padding: 0 6px;
|
||
gap: 6px;
|
||
}
|
||
.ge-controls--in-options .ge-eyedropper-loupe { width: 22px; height: 22px; flex: 0 0 22px; }
|
||
.ge-controls--in-options .ge-eyedropper-live-swatch { width: 14px; height: 14px; flex: 0 0 14px; }
|
||
.ge-controls--in-options .ge-eyedropper-live-values { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||
|
||
/* Left-edge drag handle on each layer row. */
|
||
.ge-layer-drag {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 12px;
|
||
margin-right: 2px;
|
||
opacity: 0.4;
|
||
cursor: grab;
|
||
touch-action: none;
|
||
user-select: none;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-layer-drag:hover { opacity: 0.85; }
|
||
/* While dragSort lifts the item via absolute positioning, the placeholder
|
||
slot shows where it'll land — give it a subtle red outline so users see
|
||
the target clearly. */
|
||
.ge-layers-list .drag-placeholder {
|
||
background: color-mix(in srgb, var(--red) 12%, transparent);
|
||
border: 1px dashed color-mix(in srgb, var(--red) 50%, transparent);
|
||
border-radius: 4px;
|
||
box-sizing: border-box;
|
||
}
|
||
.ge-layer-item.dragging {
|
||
background: var(--panel);
|
||
box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-layer-btn {
|
||
border: none;
|
||
background: none;
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
padding: 0 2px;
|
||
opacity: 0.4;
|
||
transition: opacity 0.1s;
|
||
}
|
||
.ge-layer-btn:hover { opacity: 0.8; }
|
||
.ge-layer-btn.active { color: var(--accent, var(--red)); opacity: 0.95; }
|
||
.ge-layer-btn:disabled { cursor: default; opacity: 0.16; }
|
||
.ge-layer-style-btn { font-style: italic; font-weight: 700; font-size: 13px; }
|
||
.ge-layer-style-menu {
|
||
animation: none;
|
||
position: fixed;
|
||
inset: auto;
|
||
margin: 0;
|
||
padding: 4px;
|
||
min-width: 170px;
|
||
max-width: calc(100vw - 12px);
|
||
max-height: calc(100dvh - 12px);
|
||
overflow-y: auto;
|
||
box-sizing: border-box;
|
||
background: var(--panel, var(--bg));
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
box-shadow: 0 8px 24px #0005;
|
||
}
|
||
.ge-layer-style-menu:not(:popover-open) { display: none; }
|
||
.ge-layer-style-menu:popover-open { display: block; }
|
||
.ge-layer-style-menu > button { width: 100%; border: 0; background: none; font: inherit; text-align: left; }
|
||
.ge-layer-btn.danger:hover { color: var(--red); opacity: 1; }
|
||
|
||
.ge-layer-lock-menu {
|
||
position: fixed;
|
||
z-index: 10004;
|
||
width: 176px;
|
||
padding: 4px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--panel, var(--bg));
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
||
}
|
||
.ge-layer-lock-option {
|
||
width: 100%;
|
||
height: 30px;
|
||
display: grid;
|
||
grid-template-columns: 20px minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 0 8px;
|
||
border: 0;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 11px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
}
|
||
.ge-layer-lock-option:hover,
|
||
.ge-layer-lock-option:focus-visible {
|
||
outline: none;
|
||
background: color-mix(in srgb, var(--fg) 8%, transparent);
|
||
}
|
||
.ge-layer-align-menu { width: 218px; }
|
||
.ge-layer-lock-check {
|
||
width: 16px;
|
||
height: 16px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid color-mix(in srgb, var(--fg) 28%, transparent);
|
||
border-radius: 3px;
|
||
color: var(--accent, var(--red));
|
||
font-size: 11px;
|
||
line-height: 1;
|
||
}
|
||
@media (max-width: 700px) {
|
||
.ge-layer-lock-menu { width: auto; }
|
||
.ge-layer-lock-option { height: 44px; font-size: 13px; }
|
||
}
|
||
|
||
/* Crop apply button */
|
||
.ge-crop-apply {
|
||
position: absolute;
|
||
z-index: 10;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 4px 6px;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||
}
|
||
.ge-crop-apply input[type="number"] {
|
||
width: 56px;
|
||
padding: 3px 4px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
-moz-appearance: textfield;
|
||
}
|
||
.ge-crop-x { font-size: 11px; opacity: 0.5; }
|
||
.ge-crop-apply-btn {
|
||
padding: 4px 10px;
|
||
background: var(--red);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
position: relative;
|
||
top: -2px;
|
||
}
|
||
.ge-crop-apply-btn:hover { background: color-mix(in srgb, var(--red) 85%, #000); }
|
||
|
||
/* Crop aspect preset buttons (options bar) */
|
||
.ge-crop-preset-row { display: flex; gap: 4px; }
|
||
.ge-crop-preset.active {
|
||
background: var(--accent, #4c8bf5);
|
||
border-color: transparent;
|
||
color: #fff;
|
||
}
|
||
|
||
/* Text input floating */
|
||
.ge-text-input {
|
||
background: var(--bg);
|
||
border: 1px solid var(--red);
|
||
color: var(--fg);
|
||
padding: 4px 8px;
|
||
font-size: 14px;
|
||
border-radius: 4px;
|
||
min-width: 120px;
|
||
}
|
||
.ge-brush-cursor {
|
||
position: fixed; pointer-events: none; z-index: 2147483647;
|
||
border: 1.5px solid #fff; border-radius: 50%;
|
||
box-shadow: 0 0 0 1px rgba(0,0,0,0.72);
|
||
display: none;
|
||
box-sizing: border-box;
|
||
}
|
||
/* Pixel brush: a hard square footprint instead of a feathered circle. */
|
||
.ge-brush-cursor.ge-brush-cursor--pixel {
|
||
border-radius: 0;
|
||
}
|
||
.ge-inpaint-section {
|
||
padding: 8px 0; border-top: 1px solid var(--border); margin-top: 4px;
|
||
}
|
||
.ge-inpaint-popover-head {
|
||
display: none;
|
||
}
|
||
@media (min-width: 821px) {
|
||
.ge-controls.ge-inpaint-popover-host {
|
||
padding: 0;
|
||
border-bottom: 0;
|
||
gap: 0;
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover {
|
||
position: fixed;
|
||
z-index: 10004;
|
||
width: 330px;
|
||
max-height: calc(100vh - 24px);
|
||
overflow-y: auto;
|
||
padding: 10px;
|
||
margin: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: var(--panel);
|
||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.42);
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin: -10px -10px 8px;
|
||
padding: 8px 10px;
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: grab;
|
||
user-select: none;
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover > .ge-section-title-with-help:not(.ge-inpaint-popover-title) {
|
||
display: none;
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-title {
|
||
margin: 0;
|
||
flex: 1 1 auto;
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-close {
|
||
flex: 0 0 auto;
|
||
position: relative;
|
||
top: -8px;
|
||
border: none;
|
||
background: none;
|
||
color: var(--fg-muted);
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
padding: 0 4px;
|
||
border-radius: 4px;
|
||
transition: color 0.14s ease, background-color 0.14s ease;
|
||
}
|
||
.ge-inpaint-section.ge-inpaint-popover .ge-inpaint-popover-close:hover {
|
||
color: var(--fg);
|
||
background: color-mix(in srgb, var(--fg) 10%, transparent);
|
||
}
|
||
}
|
||
.ge-inpaint-model-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.ge-inpaint-model-row label {
|
||
flex: 0 0 auto;
|
||
font-size: 10px;
|
||
opacity: 0.65;
|
||
}
|
||
.ge-inpaint-model-row #ge-ai-inpaint {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
font-size: 10px;
|
||
padding: 4px 6px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-inpaint-prompt {
|
||
width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);
|
||
border: 1px solid var(--border); border-radius: 4px; font-size: 12px;
|
||
font-family: inherit; margin-top: 4px; box-sizing: border-box;
|
||
}
|
||
.ge-inpaint-prompt:focus { border-color: var(--red); outline: none; }
|
||
|
||
/* Responsive: stack on narrow screens */
|
||
@media (max-width: 700px) {
|
||
/* The editor body holds the sidebar | canvas | layers panel; on
|
||
mobile we stack them as toolbar (top) → canvas (middle) → layers
|
||
(bottom). Without this the body stays a flex row and the canvas
|
||
ends up zero-width because the sidebar takes the full width. */
|
||
.ge-editor-body {
|
||
flex-direction: column;
|
||
}
|
||
/* Brush/Size-style rows: stack the label above its slider so the
|
||
slider gets a full row instead of getting smashed against the
|
||
panel's right edge. Doesn't touch .ge-eraser-row which has its own
|
||
value-chip-on-the-right layout. */
|
||
.ge-control-row:not(.ge-eraser-row):has(input[type="range"]) {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 2px;
|
||
}
|
||
.ge-control-row:not(.ge-eraser-row):has(input[type="range"]) > input[type="range"] {
|
||
width: 100%;
|
||
}
|
||
/* More breathing room around lasso/wand sliders on mobile so they
|
||
don't hug the right edge of the controls panel. */
|
||
#ge-lasso-section .ge-eraser-row,
|
||
#ge-wand-section .ge-eraser-row {
|
||
padding-right: 12px;
|
||
margin-top: 6px;
|
||
}
|
||
/* Lasso action buttons (Clear / Copy / To Mask / Bg Remove): pin to
|
||
the right edge of the row instead of left-aligned. */
|
||
#ge-lasso-section .ge-actions,
|
||
#ge-wand-section .ge-actions {
|
||
justify-content: flex-end;
|
||
}
|
||
/* Universal thumb-grow on active for every slider on mobile. */
|
||
.ge-controls input[type="range"]:active::-webkit-slider-thumb,
|
||
.ge-controls input[type="range"].is-using::-webkit-slider-thumb {
|
||
width: 22px;
|
||
height: 22px;
|
||
}
|
||
.ge-controls input[type="range"]:active::-moz-range-thumb,
|
||
.ge-controls input[type="range"].is-using::-moz-range-thumb {
|
||
width: 22px;
|
||
height: 22px;
|
||
}
|
||
.ge-controls input[type="range"]::-webkit-slider-thumb,
|
||
.ge-controls input[type="range"]::-moz-range-thumb {
|
||
transition: width 0.12s ease, height 0.12s ease;
|
||
}
|
||
/* Keep the right-hand actions (Import / Save / maximize) pinned and always
|
||
visible on phones: the menu row scrolls internally instead of dragging
|
||
the whole topbar sideways, which otherwise pushes Save off-screen. Uses a
|
||
child selector to beat the base `.ge-topbar-left{flex:0 0 auto}`. */
|
||
.ge-topbar > .ge-topbar-left {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-topbar > .ge-topbar-left::-webkit-scrollbar { display: none; }
|
||
.ge-topbar > .ge-topbar-right { flex: 0 0 auto; margin-left: 0; }
|
||
.ge-toolbar {
|
||
flex-direction: row;
|
||
width: 100%;
|
||
height: auto;
|
||
padding: 4px 0;
|
||
border-right: none;
|
||
border-bottom: 1px solid var(--border);
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
flex-shrink: 0;
|
||
gap: 0;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-toolbar::-webkit-scrollbar { display: none; }
|
||
/* Audit #5: the menu row and tool rail scroll sideways with hidden
|
||
scrollbars, so they read as static. Fade the overflowing edge (and drop
|
||
the fade on the side with nothing more to scroll to) so it's obvious there
|
||
are off-screen menus/tools. attachScrollAffordance() toggles the classes. */
|
||
.ge-topbar-left.ge-scroll-fade,
|
||
.ge-toolbar.ge-scroll-fade {
|
||
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 calc(100% - 42px), transparent 100%);
|
||
mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 calc(100% - 42px), transparent 100%);
|
||
}
|
||
.ge-topbar-left.ge-scroll-fade.is-at-left,
|
||
.ge-toolbar.ge-scroll-fade.is-at-left {
|
||
-webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 42px), transparent 100%);
|
||
mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 42px), transparent 100%);
|
||
}
|
||
.ge-topbar-left.ge-scroll-fade.is-at-right,
|
||
.ge-toolbar.ge-scroll-fade.is-at-right {
|
||
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 100%);
|
||
mask-image: linear-gradient(to right, transparent 0, #000 42px, #000 100%);
|
||
}
|
||
.ge-topbar-left.ge-scroll-fade.is-at-left.is-at-right,
|
||
.ge-toolbar.ge-scroll-fade.is-at-left.is-at-right {
|
||
-webkit-mask-image: none;
|
||
mask-image: none;
|
||
}
|
||
/* The horizontal mobile rail keeps a fixed short height; the fg/bg
|
||
swatch stack would stretch it and push tools under the layer sheet,
|
||
so it is hidden here (color stays reachable in the options bar). */
|
||
.ge-tool-swatches { display: none; }
|
||
/* Section labels in the toolbar were vertical breaks — turn them
|
||
into thin vertical dividers when the column flows horizontally. */
|
||
.ge-tool-sep {
|
||
border-top: none;
|
||
border-left: 1px solid var(--border);
|
||
margin: 0 4px;
|
||
padding: 0;
|
||
width: 1px;
|
||
height: 24px;
|
||
text-indent: -9999px;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-tool-btn {
|
||
flex-shrink: 0;
|
||
width: 48px;
|
||
}
|
||
/* Horizontal rail: slots become inline columns and the flyout caret is
|
||
always visible so touch users can open tool families without a hover. */
|
||
.ge-tool-slot {
|
||
flex-direction: row;
|
||
flex-shrink: 0;
|
||
}
|
||
.ge-tool-slot > .ge-tool-btn { width: 48px; }
|
||
.ge-tool-caret { display: inline-flex; }
|
||
/* Canvas is the main thing — give it as much vertical space as
|
||
the editor body can spare. min-height keeps it visible even when
|
||
the right panel is at its max. */
|
||
.ge-canvas-area {
|
||
flex: 1 1 auto;
|
||
min-height: 40vh;
|
||
}
|
||
/* Right panel = layers list. Docked as a bottom sheet with a peek
|
||
state: collapsed shows the header strip (and the active layer
|
||
row peeks through), expanded reveals all layers + actions. The
|
||
user swipes up to expand, down to collapse — the active layer is
|
||
never fully hidden. */
|
||
.ge-right-panel {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
max-height: 70vh;
|
||
height: 70vh;
|
||
background: var(--panel);
|
||
border-left: none;
|
||
border-top: 1px solid var(--border);
|
||
border-radius: 12px 12px 0 0;
|
||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
|
||
z-index: 50;
|
||
flex-shrink: 0;
|
||
/* Peek height grows with the layer count up to a cap. JS sets the
|
||
`--peek-height` custom property from _renderLayerPanel(); default
|
||
fallback is enough for the header + one row. */
|
||
transform: translateY(calc(100% - var(--peek-height, 110px)));
|
||
transition: transform 0.22s ease-out;
|
||
}
|
||
.ge-right-panel.expanded {
|
||
transform: translateY(0);
|
||
}
|
||
/* Minimized: panel slides almost fully off-screen — only a thin
|
||
handle strip remains visible so the user can swipe back up. */
|
||
.ge-right-panel.minimized {
|
||
transform: translateY(calc(100% - 24px));
|
||
}
|
||
.ge-layers-grab {
|
||
display: block !important;
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 40px;
|
||
height: 4px;
|
||
background: color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-radius: 2px;
|
||
}
|
||
.ge-layers-header {
|
||
padding-top: 16px;
|
||
position: relative;
|
||
}
|
||
/* Peek state: bound the whole layers section to the peek height so its
|
||
bottom always meets the viewport edge and the list scrolls inside it.
|
||
Previously only the list was capped (peek - 52px) while the section
|
||
itself grew past the peek, so the add/fx/delete footer hung ~89px
|
||
below the phone viewport with no way to reach it. */
|
||
.ge-right-panel:not(.expanded) .ge-layers {
|
||
max-height: var(--peek-height, 110px);
|
||
overflow: hidden;
|
||
}
|
||
.ge-right-panel:not(.expanded) .ge-layers-list {
|
||
flex: 1 1 auto;
|
||
min-height: 0;
|
||
max-height: none;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
/* Mobile layer-row buttons — bump hit targets up so fingers can
|
||
reliably tap visibility / fx / delete. */
|
||
.ge-layer-vis,
|
||
.ge-layer-btn,
|
||
.ge-layer-fx-btn {
|
||
width: 44px !important;
|
||
min-width: 44px !important;
|
||
height: 44px !important;
|
||
min-height: 44px !important;
|
||
font-size: 18px !important;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.ge-layer-btn svg,
|
||
.ge-layer-fx-btn svg {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
/* The desktop actions sit at 0.4 opacity, which reads as "nearly invisible"
|
||
against the pink selected-row overlay on a phone. Lift the resting opacity
|
||
so the fx / mask / duplicate icons stay legible. */
|
||
.ge-layer-btn,
|
||
.ge-layer-fx-btn {
|
||
opacity: 0.78;
|
||
}
|
||
.ge-layer-btn:hover,
|
||
.ge-layer-fx-btn:hover {
|
||
opacity: 1;
|
||
}
|
||
.ge-layers-list {
|
||
overflow-x: clip;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) {
|
||
display: grid;
|
||
grid-template-columns: 12px 44px 34px minmax(0, 1fr) 44px;
|
||
grid-template-rows: 44px auto;
|
||
align-items: center;
|
||
row-gap: 3px;
|
||
}
|
||
.ge-layers-list > .ge-layer-group-row {
|
||
grid-template-columns: 12px 44px 44px 34px minmax(0, 1fr) 44px;
|
||
grid-template-rows: 44px auto;
|
||
align-items: center;
|
||
row-gap: 3px;
|
||
}
|
||
.ge-layer-group-row .ge-layer-group-drag { grid-column: 1; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-group-toggle { grid-column: 2; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-vis { grid-column: 3; grid-row: 1; }
|
||
.ge-layer-group-row .ge-group-inline-thumb { grid-column: 4; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-group-name { grid-column: 5; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-lock-btn { grid-column: 6; grid-row: 1; }
|
||
.ge-layer-group-row .ge-layer-opacity-row {
|
||
grid-column: 1 / -1;
|
||
grid-row: 2;
|
||
margin-left: 52px;
|
||
width: auto;
|
||
}
|
||
.ge-layer-group-row .ge-layer-controls {
|
||
grid-column: 1 / -1;
|
||
grid-row: 3;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-drag {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-vis {
|
||
grid-column: 2;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-inline-thumb {
|
||
grid-column: 3;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name,
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-name-input {
|
||
grid-column: 4;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-lock-btn {
|
||
grid-column: 5;
|
||
grid-row: 1;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row {
|
||
grid-column: 1 / -1;
|
||
grid-row: 2;
|
||
display: flex;
|
||
width: auto;
|
||
min-width: 0;
|
||
margin-left: 52px;
|
||
}
|
||
/* Keep the active row's action strip on ONE line inside its row so it
|
||
can never wrap over the next layer; scroll it sideways instead.
|
||
(documents-gallery-editor.css sets flex-wrap:wrap on the .active row,
|
||
so this needs the extra .ge-right-panel scope to win.) */
|
||
.ge-right-panel .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-controls,
|
||
.ge-right-panel .ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active-parent > .ge-layer-controls,
|
||
.ge-right-panel .ge-layers-list > .ge-layer-group-row.active > .ge-layer-controls {
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.ge-mask-sub-item {
|
||
display: grid;
|
||
grid-template-columns: 40px 34px minmax(80px, 1fr) auto;
|
||
grid-template-rows: 40px;
|
||
align-items: center;
|
||
padding-left: 8px;
|
||
}
|
||
.ge-mask-sub-item > .ge-layer-vis { grid-column: 1; grid-row: 1; }
|
||
.ge-mask-sub-item > .ge-mask-inline-thumb { grid-column: 2; grid-row: 1; }
|
||
.ge-mask-sub-item > .ge-layer-name {
|
||
grid-column: 3;
|
||
grid-row: 1;
|
||
display: flex !important;
|
||
min-width: 0;
|
||
}
|
||
.ge-mask-sub-item > .ge-layer-controls {
|
||
grid-column: 4;
|
||
grid-row: 1;
|
||
min-width: 0;
|
||
justify-content: flex-start;
|
||
}
|
||
.ge-mask-sub-item > .ge-layer-controls .ge-layer-btn {
|
||
width: 30px !important;
|
||
min-width: 30px !important;
|
||
height: 30px !important;
|
||
font-size: 14px !important;
|
||
}
|
||
/* Layer opacity slider stays at its expanded size at all times on
|
||
mobile — there's room for it and the resize-on-hover felt jittery
|
||
when scrolling. */
|
||
.ge-layer-opacity,
|
||
.ge-layer-opacity:hover,
|
||
.ge-layer-opacity:active,
|
||
.ge-layer-opacity.dragging {
|
||
width: 160px !important;
|
||
height: 10px !important;
|
||
}
|
||
/* Transform / adjust popups dock at the TOP of the viewport on
|
||
mobile so they overlay the topbar instead of eating canvas space.
|
||
Fixed positioning escapes any transformed ancestor stacking. */
|
||
.ge-transform-popup,
|
||
.ge-adj-popup,
|
||
.ge-fx-popup {
|
||
position: fixed !important;
|
||
left: 8px !important;
|
||
right: 8px !important;
|
||
top: 32px !important;
|
||
bottom: auto !important;
|
||
max-width: calc(100vw - 16px) !important;
|
||
width: auto !important;
|
||
min-width: 0 !important;
|
||
min-height: 240px !important;
|
||
max-height: calc(100vh - 40px);
|
||
overflow-y: auto;
|
||
z-index: 10003 !important;
|
||
}
|
||
.ge-fx-menu {
|
||
position: fixed !important;
|
||
left: 8px !important;
|
||
right: 8px !important;
|
||
top: auto !important;
|
||
bottom: calc(env(safe-area-inset-bottom, 0px) + 8px) !important;
|
||
width: auto !important;
|
||
min-width: 0 !important;
|
||
max-width: calc(100vw - 16px) !important;
|
||
z-index: 10002 !important;
|
||
}
|
||
/* Hide both the minimise and × close buttons in the header on
|
||
mobile — a clearly labelled Cancel button next to Apply in the
|
||
body is more discoverable than tiny header icons. */
|
||
.ge-transform-popup #ge-transform-min,
|
||
.ge-transform-popup #ge-transform-cancel {
|
||
display: none !important;
|
||
}
|
||
/* Wrap the transform body into precision rows on mobile:
|
||
Row 1: X + Y
|
||
Row 2: W + H
|
||
Row 3: ↻ rotate + quick actions + Apply/Cancel */
|
||
.ge-transform-popup-body {
|
||
flex-wrap: wrap !important;
|
||
align-items: center;
|
||
row-gap: 8px;
|
||
column-gap: 6px;
|
||
}
|
||
.ge-transform-popup-body #ge-transform-rot {
|
||
width: 3.5ch !important;
|
||
min-width: 0 !important;
|
||
}
|
||
/* 3-row layout, enforced by explicit `.ge-row-break` elements in the
|
||
DOM (the only reliable way to force a flex-wrap line break).
|
||
Row 1: W input + H input
|
||
Row 2: aspect lock + ↻ rotate input
|
||
Row 3: flip-h, flip-v, rot-90 + Cancel + Apply */
|
||
.ge-transform-popup-body .ge-row-break {
|
||
display: block;
|
||
flex-basis: 100%;
|
||
height: 0;
|
||
width: 100%;
|
||
}
|
||
/* Flip buttons slightly bigger so they're easier to tap. */
|
||
.ge-transform-quick-btn {
|
||
padding: 8px 10px !important;
|
||
min-width: 40px;
|
||
min-height: 40px;
|
||
}
|
||
.ge-transform-quick-btn svg {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
/* Cancel + Apply share the right side of row 2, same size, paired. */
|
||
.ge-transform-popup-body #ge-transform-cancel-btn {
|
||
margin-left: auto;
|
||
}
|
||
.ge-transform-popup-body #ge-transform-cancel-btn,
|
||
.ge-transform-popup-body #ge-transform-apply {
|
||
padding: 8px 16px !important;
|
||
font-size: 13px !important;
|
||
min-width: 80px;
|
||
text-align: center;
|
||
}
|
||
/* Stepper-style number control on mobile:
|
||
[ − ] [ input ] [ + ]
|
||
Stack reverses so the − sits visually on the LEFT of the input, +
|
||
on the RIGHT. Big finger-sized squares, big +/- glyphs, both
|
||
buttons support tap-and-hold auto-repeat (JS) for fast scrubbing. */
|
||
.ge-transform-field {
|
||
display: inline-flex !important;
|
||
align-items: stretch;
|
||
gap: 0;
|
||
}
|
||
.ge-transform-field > label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding-right: 6px;
|
||
font-weight: 600;
|
||
}
|
||
.ge-transform-field:not(:has(#ge-transform-rot)) > label {
|
||
position: relative;
|
||
top: -1px;
|
||
}
|
||
.ge-transform-spin {
|
||
display: inline-flex !important;
|
||
flex-direction: row !important;
|
||
/* Use grid-order to swap positions of [−][+]: the − should sit
|
||
BEFORE the input, the + AFTER it. Achieve this by absolute
|
||
order — first child styled differently, second styled
|
||
differently, then we move the whole .ge-transform-spin's first
|
||
button visually before the input via reverse-flex. */
|
||
order: 0;
|
||
width: auto !important;
|
||
}
|
||
.ge-transform-field {
|
||
flex-direction: row;
|
||
}
|
||
/* Re-flow: label + [-] + input + [+]
|
||
We split the .ge-transform-spin's two buttons across the field's
|
||
flex line using order: order:0 for −, order:2 for input, order:3
|
||
for +. The spin <span> itself gets `display: contents` so its
|
||
children can be reordered independently against the input. */
|
||
.ge-transform-spin {
|
||
display: contents !important;
|
||
}
|
||
.ge-transform-field > input.ge-transform-popup-input {
|
||
order: 2;
|
||
text-align: center;
|
||
}
|
||
.ge-transform-spin button[data-spin="down"] {
|
||
order: 1;
|
||
}
|
||
.ge-transform-spin button[data-spin="up"] {
|
||
order: 3;
|
||
}
|
||
/* The buttons themselves — chunky, square, identical in size. Reset
|
||
the base `:first-child` rule (which strips the bottom border for
|
||
the old stacked layout) so both buttons render with the same
|
||
visual box in the new row layout. */
|
||
.ge-transform-spin button {
|
||
box-sizing: border-box !important;
|
||
width: 36px !important;
|
||
height: 36px !important;
|
||
min-width: 36px;
|
||
min-height: 36px;
|
||
padding: 0 0 0 0 !important;
|
||
font-size: 18px !important;
|
||
font-weight: 700;
|
||
line-height: 1 !important;
|
||
border: 1px solid var(--border) !important;
|
||
border-radius: 6px !important;
|
||
flex-shrink: 0;
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
top: -4px;
|
||
}
|
||
.ge-transform-spin button:first-child,
|
||
.ge-transform-spin button:last-child {
|
||
border-radius: 6px !important;
|
||
border-bottom: 1px solid var(--border) !important;
|
||
}
|
||
/* Graphic-editor adjustment buttons use text glyphs that sit visually low —
|
||
lift them to match the +/- button alignment. */
|
||
.ge-adj-close,
|
||
.ge-adj-min {
|
||
padding-top: 0 !important;
|
||
padding-bottom: 8px !important;
|
||
display: inline-flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
.ge-transform-spin button[data-spin="down"] { margin-right: 4px; }
|
||
.ge-transform-spin button[data-spin="up"] { margin-left: 4px; }
|
||
/* The input between them — generous tap area and clear typography. */
|
||
.ge-transform-field > input.ge-transform-popup-input {
|
||
height: 36px !important;
|
||
padding: 0 6px !important;
|
||
font-size: 14px !important;
|
||
min-width: 56px;
|
||
position: relative;
|
||
top: 2px;
|
||
}
|
||
/* Rotate input — compact so it fits on row 2 alongside the flip
|
||
quick buttons and the Cancel + Apply pair. */
|
||
.ge-transform-field > #ge-transform-rot.ge-transform-popup-input {
|
||
min-width: 0;
|
||
width: 64px !important;
|
||
}
|
||
/* Suffix (°) sits right after the rotate input but before the + button. */
|
||
.ge-transform-popup-suffix { order: 3; align-self: center; padding: 0 2px; }
|
||
/* Slider thumb grows only while actively dragged — keeps the resting
|
||
row compact, thumb pops larger when the user grabs it. */
|
||
.gallery-editor-container input[type="range"]:active::-webkit-slider-thumb,
|
||
.ge-layer-opacity:active::-webkit-slider-thumb,
|
||
.ge-layer-opacity.dragging::-webkit-slider-thumb {
|
||
width: 22px !important;
|
||
height: 22px !important;
|
||
}
|
||
.gallery-editor-container input[type="range"]:active::-moz-range-thumb,
|
||
.ge-layer-opacity:active::-moz-range-thumb,
|
||
.ge-layer-opacity.dragging::-moz-range-thumb {
|
||
width: 22px !important;
|
||
height: 22px !important;
|
||
}
|
||
/* Slider value bubble on mobile — bigger text/padding for
|
||
readability. Position is JS-driven via `position: fixed` from the
|
||
base rule, so don't override top/bottom here — `bottom: 100%`
|
||
with fixed positioning was pushing the bubble off-screen. */
|
||
.ge-slider-bubble {
|
||
font-size: 14px !important;
|
||
padding: 5px 10px !important;
|
||
}
|
||
/* Editor topbar — scrolls horizontally on narrow viewports. Buttons
|
||
fill more vertical+horizontal space so each is a comfortable
|
||
finger-tap target instead of feeling cramped. */
|
||
.ge-topbar {
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
flex-wrap: nowrap;
|
||
justify-content: flex-start;
|
||
gap: 6px;
|
||
padding: 3px 8px;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-topbar::-webkit-scrollbar { display: none; }
|
||
.ge-topbar-left,
|
||
.ge-topbar-right {
|
||
flex-shrink: 0;
|
||
gap: 6px;
|
||
}
|
||
.ge-alpha-badge {
|
||
display: none;
|
||
}
|
||
#ge-undo {
|
||
position: sticky !important;
|
||
left: 8px;
|
||
background: var(--panel);
|
||
z-index: 8;
|
||
box-shadow: 8px 0 10px -10px color-mix(in srgb, var(--fg) 55%, transparent);
|
||
}
|
||
.ge-topbar .ge-btn,
|
||
.ge-topbar .ge-btn-sm {
|
||
padding: 4px 9px !important;
|
||
font-size: 12px !important;
|
||
min-height: 28px;
|
||
line-height: 1;
|
||
top: 0 !important;
|
||
}
|
||
.ge-topbar .ge-btn svg,
|
||
.ge-topbar .ge-btn-sm svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
.ge-topbar select,
|
||
.ge-topbar input,
|
||
.ge-topbar .ge-ai-model {
|
||
padding: 6px 8px !important;
|
||
font-size: 12px !important;
|
||
min-height: 32px;
|
||
top: 0 !important;
|
||
}
|
||
.ge-topbar .ge-canvas-size {
|
||
font-size: 12px !important;
|
||
padding: 0 6px;
|
||
line-height: 34px;
|
||
}
|
||
#ge-history-panel {
|
||
left: 0 !important;
|
||
right: 0 !important;
|
||
top: auto !important;
|
||
bottom: 0 !important;
|
||
width: auto !important;
|
||
max-height: min(60dvh, 420px);
|
||
border-radius: 12px 12px 0 0 !important;
|
||
z-index: 10001 !important;
|
||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
#ge-history-panel .ge-history-head { cursor: default; }
|
||
#ge-shortcuts-btn {
|
||
display: none !important;
|
||
}
|
||
/* Topbar popups that need real interaction (Resize sliders, Edge
|
||
form, Save with sub-items) become bottom-sheet slide-ups on
|
||
mobile so there's room to interact. Image and Filter are tight
|
||
single-column dropdowns — they stay anchored to their button. */
|
||
.ge-resize-menu,
|
||
.ge-edge-menu,
|
||
.ge-save-menu {
|
||
position: fixed !important;
|
||
left: 0 !important;
|
||
right: 0 !important;
|
||
top: auto !important;
|
||
bottom: 0 !important;
|
||
min-width: 0 !important;
|
||
max-width: 100vw !important;
|
||
width: auto;
|
||
padding: 18px 16px 24px !important;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px 12px 0 0 !important;
|
||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
|
||
z-index: 10005;
|
||
animation: ge-controls-slide-up 0.2s ease-out;
|
||
}
|
||
/* Image / Filter stay as anchored dropdowns; just cap width
|
||
so they don't run off-screen on narrow viewports. */
|
||
.ge-image-menu,
|
||
.ge-selection-menu,
|
||
.ge-filter-menu {
|
||
max-width: calc(100vw - 16px);
|
||
}
|
||
.ge-resize-menu::before,
|
||
.ge-edge-menu::before,
|
||
.ge-save-menu::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 40px;
|
||
height: 4px;
|
||
background: color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-radius: 2px;
|
||
}
|
||
/* The Edge form needs a bit of breathing room when it spans the
|
||
viewport; the W/H inputs in the crop apply pop should stay compact. */
|
||
.ge-edge-menu { padding: 12px; }
|
||
.ge-canvas-prompt {
|
||
width: calc(100vw - 16px) !important;
|
||
max-width: none !important;
|
||
}
|
||
.ge-canvas-prompt-footer {
|
||
flex-wrap: wrap;
|
||
}
|
||
.ge-canvas-prompt-footer .ge-canvas-prompt-hint {
|
||
flex: 1 0 100%;
|
||
margin-bottom: 2px;
|
||
}
|
||
/* Inpaint floating prompt — keep it inside the canvas area on
|
||
mobile so it doesn't run off the right edge. */
|
||
.ge-inpaint-popup {
|
||
max-width: calc(100vw - 16px);
|
||
}
|
||
.ge-inpaint-popup-input { width: 160px; }
|
||
|
||
/* Tool-specific controls (brush size/color, eraser opacity/flow,
|
||
lasso feather, inpaint prompt panel, etc.) lift out of the right
|
||
panel and dock as a slide-up bottom sheet so they have actual room
|
||
to breathe on a phone. A short grab-handle visually separates it
|
||
from the canvas. */
|
||
.ge-controls {
|
||
position: fixed !important;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 60;
|
||
border-bottom: none;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--panel);
|
||
border-radius: 12px 12px 0 0;
|
||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
padding: 18px 16px 24px;
|
||
gap: 12px;
|
||
/* Slide-up entrance animation when the controls become visible. */
|
||
animation: ge-controls-slide-up 0.18s ease-out;
|
||
}
|
||
.ge-controls::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 40px;
|
||
height: 4px;
|
||
background: color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-radius: 2px;
|
||
}
|
||
@keyframes ge-controls-slide-up {
|
||
from { transform: translateY(100%); }
|
||
to { transform: translateY(0); }
|
||
}
|
||
/* User swiped the sheet down — keep tool active but hide its
|
||
controls. Re-tapping the same tool button toggles back. */
|
||
.ge-controls.dismissed {
|
||
transform: translateY(100%);
|
||
pointer-events: none;
|
||
/* Smooth slide-out matching the slide-in duration. */
|
||
animation: none;
|
||
transition: transform 0.18s ease-in;
|
||
}
|
||
/* The layers right-panel (z 50) and the controls sheet (z 60) both
|
||
dock to the bottom on phones, covering the footer's history group
|
||
and making Undo/Redo unreachable by touch. Lift the history controls
|
||
above both sheets so a touch stroke can still be undone. */
|
||
.ge-footer-history {
|
||
position: relative;
|
||
z-index: 65;
|
||
}
|
||
/* Once moved into the layer-sheet header the pill no longer needs the
|
||
overlay z-index/opacity lift — keep it crisp and flush right. */
|
||
.ge-layers-header .ge-footer-history {
|
||
z-index: auto;
|
||
opacity: 1;
|
||
margin-left: auto;
|
||
}
|
||
}
|
||
|
||
/* ── Group Chat ────────────────────────────────────── */
|
||
.msg-group .role {
|
||
font-weight: 600;
|
||
}
|
||
#group-toggle-btn.active,
|
||
.overflow-menu-item#overflow-group-btn.active {
|
||
color: var(--red);
|
||
background: color-mix(in srgb, var(--red) 12%, transparent);
|
||
}
|
||
/* Group model picker — match app theme */
|
||
#group-model-picker .modal-content {
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
}
|
||
#group-model-picker .modal-header {
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
#group-model-picker .memory-item {
|
||
border-radius: 6px;
|
||
transition: background 0.1s;
|
||
}
|
||
#group-model-picker .memory-item:hover {
|
||
background: color-mix(in srgb, var(--fg) 6%, transparent);
|
||
}
|
||
|
||
/* Select and Mask workspace */
|
||
.ge-sam-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10050;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(3, 5, 8, 0.72);
|
||
}
|
||
.ge-sam-panel {
|
||
width: min(560px, 94vw);
|
||
max-height: 92vh;
|
||
overflow: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--panel, #14181f);
|
||
color: var(--fg, #e8ecf1);
|
||
border: 1px solid var(--border, #2a313c);
|
||
border-radius: 10px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
|
||
}
|
||
.ge-sam-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 10px 14px;
|
||
border-bottom: 1px solid var(--border, #2a313c);
|
||
}
|
||
.ge-sam-title {
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
.ge-sam-output-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 11px;
|
||
color: var(--color-muted, #97a1ad);
|
||
}
|
||
.ge-sam-body {
|
||
display: flex;
|
||
gap: 14px;
|
||
padding: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.ge-sam-preview {
|
||
background: #11151b;
|
||
border: 1px solid var(--border, #2a313c);
|
||
border-radius: 6px;
|
||
cursor: crosshair;
|
||
touch-action: none;
|
||
}
|
||
.ge-sam-controls {
|
||
flex: 1 1 200px;
|
||
min-width: 200px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
font-size: 11px;
|
||
}
|
||
.ge-sam-controls label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.ge-sam-brush-row {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.ge-sam-brush-add.active,
|
||
.ge-sam-brush-subtract.active {
|
||
border-color: var(--accent, #4c8bf5);
|
||
color: var(--accent, #4c8bf5);
|
||
}
|
||
.ge-sam-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding: 10px 14px;
|
||
border-top: 1px solid var(--border, #2a313c);
|
||
}
|
||
|
||
/* Color & analysis dock panels (Phase 7) */
|
||
.ge-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 10px 12px;
|
||
color: var(--fg, #e6e9ef);
|
||
}
|
||
.ge-panel-title {
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
color: var(--color-muted, #8b93a1);
|
||
}
|
||
.ge-panel-empty {
|
||
font-size: 12px;
|
||
opacity: .6;
|
||
padding: 6px 0;
|
||
}
|
||
.ge-swatch-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(20px, 1fr));
|
||
gap: 4px;
|
||
}
|
||
.ge-swatch {
|
||
width: 100%;
|
||
aspect-ratio: 1 / 1;
|
||
border: 1px solid var(--border, #2a313c);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
.ge-swatch.active {
|
||
outline: 2px solid var(--accent, #4c8bf5);
|
||
outline-offset: 1px;
|
||
}
|
||
.ge-current-swatch {
|
||
height: 26px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border, #2a313c);
|
||
}
|
||
/* SWATCHES panel hosts the shared color picker's trigger swatch
|
||
(colorPicker.js) and, next to it, harmony Suggestions + Recent colours. */
|
||
.ge-swatch-body { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; }
|
||
.ge-swatch-body .ge-color-input.cp-swatch-input {
|
||
width: 44px;
|
||
height: 44px;
|
||
flex: 0 0 auto;
|
||
min-width: 0;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border, #2a313c);
|
||
cursor: pointer;
|
||
background: none;
|
||
}
|
||
.ge-swatch-body .ge-color-input.cp-swatch-input::selection { background: transparent; }
|
||
.ge-swatch-col { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-width: 0; }
|
||
.ge-swatch-row-label { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.78; }
|
||
.ge-swatch-row { display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 4px; scrollbar-width: none; }
|
||
.ge-swatch-row::-webkit-scrollbar { display: none; }
|
||
.ge-mini-swatch {
|
||
width: 16px;
|
||
height: 16px;
|
||
padding: 0;
|
||
flex: 0 0 auto;
|
||
border: 1px solid color-mix(in srgb, var(--fg) 22%, transparent);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-mini-swatch:hover { transform: scale(1.12); }
|
||
.ge-mini-empty { font-size: 10px; opacity: 0.45; }
|
||
/* Always-visible SV + hue picker inside the SWATCHES panel (panels.js). Reuses
|
||
the shared .cp-sl/.cp-hue classes; this container just lays them out inline
|
||
instead of inside the floating .cp-popover, so the panel reads as a picker
|
||
without a click. */
|
||
.ge-swatch-body { flex-wrap: wrap; }
|
||
.ge-inline-picker {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
flex: 1 1 100%;
|
||
width: 100%;
|
||
margin-top: 6px;
|
||
}
|
||
.ge-inline-picker .cp-sl { height: 80px; }
|
||
.ge-inline-picker .cp-hue { margin-top: 4px; }
|
||
.ge-hex-row { display: flex; align-items: center; gap: 4px; flex: 1 1 100%; width: 100%; margin-top: 6px; background: color-mix(in srgb, var(--fg) 6%, transparent); border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; }
|
||
.ge-hex-hash { color: color-mix(in srgb, var(--fg) 65%, transparent); font-size: 12px; font-weight: 600; }
|
||
.ge-hex-input { flex: 1 1 auto; min-width: 0; background: transparent; border: 0; outline: 0; color: var(--fg); font: inherit; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; padding: 0; }
|
||
.ge-gradient-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||
gap: 6px;
|
||
}
|
||
.ge-gradient-swatch {
|
||
height: 24px;
|
||
border-radius: 5px;
|
||
border: 1px solid var(--border, #2a313c);
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
.ge-channel-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
max-height: 220px;
|
||
overflow-y: auto;
|
||
}
|
||
.ge-channel-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 6px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
}
|
||
.ge-channel-row.active {
|
||
background: color-mix(in srgb, var(--accent, #4c8bf5) 22%, transparent);
|
||
}
|
||
.ge-channel-eye {
|
||
width: 20px;
|
||
text-align: center;
|
||
opacity: .8;
|
||
}
|
||
.ge-channel-name {
|
||
flex: 1 1 auto;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-histogram-canvas,
|
||
.ge-navigator-canvas {
|
||
width: 100%;
|
||
background: #0b0d10;
|
||
border: 1px solid var(--border, #2a313c);
|
||
border-radius: 5px;
|
||
}
|
||
.ge-navigator-canvas {
|
||
cursor: grab;
|
||
touch-action: none;
|
||
}
|
||
.ge-navigator-canvas:active {
|
||
cursor: grabbing;
|
||
}
|
||
.ge-info-rows {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.ge-info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
}
|
||
.ge-info-label {
|
||
color: var(--color-muted, #8b93a1);
|
||
}
|
||
.ge-info-value {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.ge-info-swatch {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 3px;
|
||
border: 1px solid var(--border, #2a313c);
|
||
display: inline-block;
|
||
}
|
||
/* Command palette (Phase 11) */
|
||
.ge-command-palette {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10100;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
padding-top: 12vh;
|
||
background: rgba(6, 8, 11, 0.45);
|
||
}
|
||
.ge-command-palette[hidden] {
|
||
display: none;
|
||
}
|
||
.ge-command-panel {
|
||
width: min(520px, 92vw);
|
||
max-height: 60vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--panel, #161a21);
|
||
border: 1px solid var(--border, #2a313c);
|
||
border-radius: 10px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||
overflow: hidden;
|
||
}
|
||
.ge-command-input {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 12px 14px;
|
||
border: 0;
|
||
border-bottom: 1px solid var(--border, #2a313c);
|
||
background: transparent;
|
||
color: var(--fg, #e6e9ef);
|
||
font-size: 14px;
|
||
outline: none;
|
||
}
|
||
.ge-command-list {
|
||
overflow-y: auto;
|
||
padding: 6px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.ge-command-item {
|
||
text-align: left;
|
||
padding: 8px 10px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: var(--fg, #e6e9ef);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-command-item.active,
|
||
.ge-command-item:hover {
|
||
background: var(--accent, #4c8bf5);
|
||
color: #fff;
|
||
}
|
||
.ge-command-empty {
|
||
margin: 0;
|
||
padding: 12px;
|
||
color: var(--color-muted, #8b93a1);
|
||
font-size: 13px;
|
||
text-align: center;
|
||
}
|
||
/* Extra studio dock panels (Properties / Character / Paths / Patterns / Adjustments / History) */
|
||
.ge-panel-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 8px 10px;
|
||
}
|
||
.ge-text-align-row {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
.ge-path-list {
|
||
gap: 4px;
|
||
}
|
||
.ge-adjustment-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 3px;
|
||
padding: 4px 6px;
|
||
}
|
||
.ge-adjustment-grid .ge-btn {
|
||
width: 100%;
|
||
text-align: left;
|
||
}
|
||
/* Actions panel (Phase 11) */
|
||
.ge-actions-name-row,
|
||
.ge-actions-add-row,
|
||
.ge-actions-controls,
|
||
.ge-actions-batch-row {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.ge-actions-batch-select {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
.ge-actions-name {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
.ge-actions-command {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
.ge-actions-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
max-height: 240px;
|
||
overflow-y: auto;
|
||
}
|
||
.ge-actions-step,
|
||
.ge-actions-saved-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.ge-actions-step-index {
|
||
width: 18px;
|
||
color: var(--color-muted, #8b93a1);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.ge-actions-step-label {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.ge-action-remove {
|
||
border: none;
|
||
background: none;
|
||
color: var(--color-muted, #8b93a1);
|
||
cursor: pointer;
|
||
}
|
||
.ge-actions-controls .ge-btn.active {
|
||
border-color: var(--accent, #4c8bf5);
|
||
color: var(--accent, #4c8bf5);
|
||
}
|
||
|
||
/* Studio dock panels (PhotoCraft parity: Properties/Character/Adjustments/Paths/Patterns/History) */
|
||
.ge-panel-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
padding: 8px 10px;
|
||
}
|
||
.ge-text-align-row {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
.ge-adjustment-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 3px;
|
||
padding: 4px 6px;
|
||
}
|
||
.ge-adjustment-grid .ge-btn {
|
||
width: 100%;
|
||
justify-content: flex-start;
|
||
font-size: 11px;
|
||
}
|
||
.ge-path-list {
|
||
gap: 4px;
|
||
}
|
||
|
||
/* ── Full-viewport editor (Photoshop-style) ──────────────────────────────
|
||
The editor opens filling the whole viewport instead of the small
|
||
gallery sheet. `#ge-maximize-toggle` flips to a remembered windowed
|
||
mode by adding `.ge-editor-windowed` to #gallery-editor-container. */
|
||
.gallery-modal-content:has(#gallery-editor-container:not(.ge-editor-windowed)) {
|
||
width: 100vw;
|
||
max-width: 100vw;
|
||
height: 100vh;
|
||
max-height: 100vh;
|
||
margin: 0;
|
||
border-radius: 0;
|
||
}
|
||
#gallery-modal:has(#gallery-editor-container:not(.ge-editor-windowed)) {
|
||
padding: 0;
|
||
align-items: stretch;
|
||
/* Escape the desktop "chat area" narrowing (left = icon-rail + sidebar) so
|
||
the editor really fills the whole viewport, over the nav. */
|
||
left: 0;
|
||
width: 100vw;
|
||
}
|
||
#ge-maximize-toggle.is-windowed { color: var(--fg); }
|
||
|
||
/* Multi-document editor tabs live in the gallery tab bar. The container is
|
||
display:contents so each extra tab is a direct flex child of .gallery-tabs,
|
||
matching the primary #gallery-editor-tab. */
|
||
.gallery-editor-tabs-extra { display: contents; }
|
||
.gallery-tab.gallery-tab-new {
|
||
min-width: 26px;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
opacity: 0.75;
|
||
}
|
||
.gallery-tab.gallery-tab-new:hover { opacity: 1; }
|
||
|
||
/* Draggable dialog grab bars (Image Size / canvas-size prompt). */
|
||
.ge-draggable-handle {
|
||
cursor: move;
|
||
user-select: none;
|
||
touch-action: none;
|
||
}
|
||
.ge-draggable-handle:active { cursor: grabbing; }
|
||
|
||
/* ============================================================================
|
||
* Editor control kit
|
||
* ----------------------------------------------------------------------------
|
||
* One shared, theme-token style for the interactive controls scattered across
|
||
* the editor (options bar, adjustment dialogs, Image/Canvas Size, transform
|
||
* popup, layers, brush settings, filters). control-kit.js adds the .ge-ctl*
|
||
* classes; these rules make every such control look like the rest of Odysseus
|
||
* (matches .cookbook-slider / .admin-slider / .toggle-switch). Older, more
|
||
* specific rules (transform popup, eraser/fx rows) still win on purpose.
|
||
* ==========================================================================*/
|
||
|
||
/* -- Range sliders --------------------------------------------------------*/
|
||
.ge-ctl-range {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
max-width: 100%;
|
||
height: 14px;
|
||
margin: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
vertical-align: middle;
|
||
--ge-pct: 50%;
|
||
--ge-accent: var(--accent, var(--red));
|
||
}
|
||
.ge-ctl-range::-webkit-slider-runnable-track {
|
||
height: 6px;
|
||
border-radius: 4px;
|
||
background: linear-gradient(
|
||
to right,
|
||
var(--ge-accent) 0 var(--ge-pct),
|
||
color-mix(in srgb, var(--fg) 14%, transparent) var(--ge-pct) 100%
|
||
);
|
||
}
|
||
.ge-ctl-range::-moz-range-track {
|
||
height: 6px;
|
||
border-radius: 4px;
|
||
background: color-mix(in srgb, var(--fg) 14%, transparent);
|
||
}
|
||
.ge-ctl-range::-moz-range-progress {
|
||
height: 6px;
|
||
border-radius: 4px;
|
||
background: var(--ge-accent);
|
||
}
|
||
.ge-ctl-range::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 14px;
|
||
height: 14px;
|
||
margin-top: -4px;
|
||
border-radius: 50%;
|
||
background: var(--fg);
|
||
border: 2px solid var(--panel);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
||
cursor: pointer;
|
||
transition: transform 0.1s, box-shadow 0.1s;
|
||
}
|
||
.ge-ctl-range::-moz-range-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--fg);
|
||
border: 2px solid var(--panel);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
||
cursor: pointer;
|
||
}
|
||
.ge-ctl-range:hover::-webkit-slider-thumb {
|
||
transform: scale(1.15);
|
||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--ge-accent) 22%, transparent);
|
||
}
|
||
.ge-ctl-range:active::-webkit-slider-thumb {
|
||
transform: scale(1.25);
|
||
box-shadow: 0 0 0 6px color-mix(in srgb, var(--ge-accent) 28%, transparent);
|
||
}
|
||
.ge-ctl-range:hover::-moz-range-thumb {
|
||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--ge-accent) 22%, transparent);
|
||
}
|
||
.ge-ctl-range:focus-visible {
|
||
outline: none;
|
||
}
|
||
.ge-ctl-range:focus-visible::-webkit-slider-thumb {
|
||
box-shadow: 0 0 0 3px var(--panel), 0 0 0 5px color-mix(in srgb, var(--ge-accent) 55%, transparent);
|
||
}
|
||
.ge-ctl-range:disabled {
|
||
opacity: 0.45;
|
||
cursor: default;
|
||
}
|
||
.ge-ctl-range:disabled::-webkit-slider-thumb { transform: none; box-shadow: none; }
|
||
|
||
/* -- Number fields (no native spinners) -----------------------------------*/
|
||
.ge-num-field {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
.ge-ctl-num {
|
||
width: auto;
|
||
max-width: 100%;
|
||
height: 26px;
|
||
padding: 0 6px;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
font-variant-numeric: tabular-nums;
|
||
text-align: right;
|
||
-moz-appearance: textfield;
|
||
appearance: textfield;
|
||
box-sizing: border-box;
|
||
}
|
||
.ge-ctl-num::-webkit-outer-spin-button,
|
||
.ge-ctl-num::-webkit-inner-spin-button {
|
||
-webkit-appearance: none;
|
||
margin: 0;
|
||
}
|
||
.ge-ctl-num:focus {
|
||
outline: none;
|
||
border-color: var(--accent, var(--red));
|
||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent);
|
||
}
|
||
.ge-ctl-num:disabled,
|
||
.ge-ctl-num[readonly] { opacity: 0.5; cursor: default; }
|
||
|
||
/* Small stacked/horizontal stepper next to a number field */
|
||
.ge-stepper {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
margin-left: 3px;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
}
|
||
.ge-stepper button {
|
||
width: 16px;
|
||
height: 12px;
|
||
padding: 0;
|
||
border: 0;
|
||
background: color-mix(in srgb, var(--fg) 8%, transparent);
|
||
color: var(--fg);
|
||
font-size: 9px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
}
|
||
.ge-stepper button:hover { background: var(--accent, var(--red)); color: var(--panel); }
|
||
.ge-stepper button:active { background: color-mix(in srgb, var(--accent, var(--red)) 75%, #000); color: var(--panel); }
|
||
|
||
/* -- Selects --------------------------------------------------------------*/
|
||
.ge-ctl-select {
|
||
height: 26px;
|
||
max-width: 100%;
|
||
padding: 0 22px 0 8px;
|
||
background-color: var(--bg);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
||
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
||
background-position: calc(100% - 12px) 11px, calc(100% - 8px) 11px;
|
||
background-size: 4px 4px, 4px 4px;
|
||
background-repeat: no-repeat;
|
||
cursor: pointer;
|
||
}
|
||
.ge-ctl-select:hover { border-color: color-mix(in srgb, var(--fg) 45%, var(--border)); }
|
||
.ge-ctl-select:focus {
|
||
outline: none;
|
||
border-color: var(--accent, var(--red));
|
||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent);
|
||
}
|
||
.ge-ctl-select option { background: var(--select-option-bg, var(--panel)); color: var(--select-option-fg, var(--fg)); }
|
||
|
||
/* -- Checkboxes -----------------------------------------------------------*/
|
||
input.ge-ctl-check {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
width: 14px;
|
||
height: 14px;
|
||
margin: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: 3px;
|
||
background: var(--bg);
|
||
cursor: pointer;
|
||
position: relative;
|
||
vertical-align: middle;
|
||
flex: 0 0 auto;
|
||
}
|
||
input.ge-ctl-check:hover { border-color: color-mix(in srgb, var(--fg) 45%, var(--border)); }
|
||
input.ge-ctl-check:checked {
|
||
background: var(--accent, var(--red));
|
||
border-color: var(--accent, var(--red));
|
||
}
|
||
input.ge-ctl-check:checked::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 4px;
|
||
top: 1px;
|
||
width: 3px;
|
||
height: 7px;
|
||
border: solid var(--panel);
|
||
border-width: 0 2px 2px 0;
|
||
transform: rotate(45deg);
|
||
}
|
||
input.ge-ctl-check:focus-visible {
|
||
outline: none;
|
||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent);
|
||
}
|
||
|
||
/* -- Small icon/text buttons ----------------------------------------------*/
|
||
.ge-ctl-btn {
|
||
height: 24px;
|
||
min-width: 24px;
|
||
padding: 0 8px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
background: color-mix(in srgb, var(--fg) 6%, transparent);
|
||
color: var(--fg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
}
|
||
.ge-ctl-btn:hover { background: color-mix(in srgb, var(--fg) 14%, transparent); }
|
||
.ge-ctl-btn:active { background: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); }
|
||
.ge-ctl-btn:focus-visible {
|
||
outline: none;
|
||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent);
|
||
}
|
||
.ge-ctl-btn:disabled { opacity: 0.45; cursor: default; }
|
||
|
||
/* Scrubby numeric label — drag horizontally to change the value */
|
||
.ge-scrub {
|
||
cursor: ew-resize;
|
||
user-select: none;
|
||
touch-action: none;
|
||
}
|
||
.ge-scrub::after { content: '\2194'; font-size: 8px; opacity: 0.4; margin-left: 3px; }
|
||
.ge-scrub.ge-scrubbing { color: var(--accent, var(--red)); }
|
||
|
||
/* On touch/mobile the topbar menus are icon/touch driven — hide keyboard shortcut hints. */
|
||
@media (max-width: 820px) {
|
||
.ge-topbar .dropdown .dropdown-shortcut { display: none; }
|
||
}
|
||
|
||
/* Editor landing ("Image Editor" chooser + Saved projects) — keep it
|
||
scrollable at EVERY width, not just <=620px. The base rule centres the
|
||
content vertically (justify-content: center) with no overflow; once the
|
||
saved-project list grows taller than the sheet (worst in list view), flex
|
||
centring pushes the launch controls AND the grid/list toggle ABOVE the
|
||
sheet, while #gallery-editor-container's overflow: hidden makes that top
|
||
region unreachable — the user could not scroll back and got stuck. Using
|
||
auto margins to centre collapses them to 0 on overflow, so the top stays
|
||
reachable while short content still looks vertically centred. */
|
||
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing {
|
||
justify-content: flex-start;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing::before,
|
||
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) .gallery-editor-landing::after {
|
||
content: '';
|
||
flex: 0 0 auto;
|
||
margin: auto;
|
||
}
|
||
|
||
/* Tool-aware canvas right-click menu (canvas-context-menu.js). */
|
||
.ge-canvas-menu {
|
||
position: fixed;
|
||
z-index: 100000;
|
||
min-width: 208px;
|
||
padding: 5px;
|
||
background: #2b2b2b;
|
||
border: 1px solid #454545;
|
||
border-radius: 6px;
|
||
box-shadow: 0 10px 32px rgba(0, 0, 0, 0.55);
|
||
font-size: 13px;
|
||
color: #e6e6e6;
|
||
user-select: none;
|
||
}
|
||
.ge-canvas-menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
border: 0;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: inherit;
|
||
font: inherit;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
}
|
||
.ge-canvas-menu-item:hover:not([disabled]),
|
||
.ge-canvas-menu-item:focus-visible:not([disabled]) {
|
||
background: #3d6ea5;
|
||
color: #fff;
|
||
outline: none;
|
||
}
|
||
.ge-canvas-menu-item[disabled] {
|
||
color: #6d6d6d;
|
||
cursor: default;
|
||
}
|
||
.ge-canvas-menu-lead {
|
||
width: 12px;
|
||
flex: 0 0 12px;
|
||
text-align: center;
|
||
color: #9ecbff;
|
||
}
|
||
.ge-canvas-menu-label { flex: 1 1 auto; white-space: nowrap; }
|
||
.ge-canvas-menu-hint {
|
||
flex: 0 0 auto;
|
||
margin-left: 20px;
|
||
color: #9a9a9a;
|
||
font-size: 11px;
|
||
}
|
||
.ge-canvas-menu-item:hover .ge-canvas-menu-hint { color: #d8e6f5; }
|
||
.ge-canvas-menu-caret { flex: 0 0 auto; margin-left: 16px; color: #b8b8b8; }
|
||
.ge-canvas-menu-sep {
|
||
height: 1px;
|
||
margin: 4px 6px;
|
||
background: #454545;
|
||
}
|
||
.ge-canvas-menu-sub { min-width: 172px; max-height: 60vh; overflow-y: auto; }
|
||
|
||
/* Editor setup checklist (preflight) — shown over the canvas on open. */
|
||
.ge-preflight {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 260;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 16px;
|
||
background: rgba(8, 10, 14, 0.55);
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.ge-preflight[hidden] { display: none; }
|
||
.ge-preflight-card {
|
||
width: min(560px, 100%);
|
||
max-height: 100%;
|
||
overflow: auto;
|
||
background: #23262c;
|
||
color: #e8eaed;
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
border-radius: 12px;
|
||
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
|
||
padding: 16px 18px;
|
||
}
|
||
.ge-preflight-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||
.ge-preflight-head h3 { margin: 0 0 3px; font-size: 15px; }
|
||
.ge-preflight-head p { margin: 0; font-size: 12px; color: #a9b0ba; }
|
||
.ge-preflight-close {
|
||
background: transparent; border: 0; color: #a9b0ba; font-size: 20px; line-height: 1;
|
||
cursor: pointer; padding: 2px 6px; border-radius: 6px;
|
||
}
|
||
.ge-preflight-close:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
|
||
.ge-preflight-rows { display: flex; flex-direction: column; gap: 8px; margin: 14px 0; }
|
||
.ge-preflight-row {
|
||
display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 9px;
|
||
background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
.ge-preflight-row.is-done { border-color: rgba(90, 200, 130, 0.35); }
|
||
.ge-preflight-row.is-todo { border-color: rgba(230, 120, 110, 0.4); }
|
||
.ge-preflight-badge {
|
||
flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 12px; font-weight: 700; margin-top: 1px;
|
||
}
|
||
.ge-preflight-row.is-done .ge-preflight-badge { background: rgba(70, 180, 110, 0.2); color: #5fd08a; }
|
||
.ge-preflight-row.is-todo .ge-preflight-badge { background: rgba(220, 90, 80, 0.18); color: #ff8a7d; }
|
||
.ge-preflight-row.is-optional { border-color: rgba(210, 180, 90, 0.35); }
|
||
.ge-preflight-row.is-optional .ge-preflight-badge { background: rgba(210, 180, 90, 0.16); color: #e0c266; }
|
||
.ge-preflight-opt {
|
||
margin-left: 6px;
|
||
padding: 1px 6px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
color: var(--fg-muted);
|
||
font-size: 9px;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
vertical-align: middle;
|
||
}
|
||
.ge-preflight-body { flex: 1 1 auto; min-width: 0; }
|
||
.ge-preflight-title { font-size: 13px; font-weight: 600; }
|
||
.ge-preflight-hint { font-size: 11.5px; color: #a9b0ba; margin-top: 2px; }
|
||
.ge-preflight-detail { font-size: 11px; color: #7f8792; margin-top: 3px; font-style: italic; }
|
||
.ge-preflight-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
|
||
.ge-preflight-act {
|
||
background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.14);
|
||
color: #dfe3e8; font-size: 11px; padding: 4px 9px; border-radius: 7px; cursor: pointer; white-space: nowrap;
|
||
}
|
||
.ge-preflight-act:hover { background: rgba(255, 255, 255, 0.16); }
|
||
.ge-preflight-manual { display: flex; align-items: center; cursor: pointer; }
|
||
.ge-preflight-foot {
|
||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08); padding-top: 12px;
|
||
}
|
||
.ge-preflight-remember { font-size: 11.5px; color: #a9b0ba; display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
||
.ge-preflight-foot-actions { display: flex; align-items: center; gap: 10px; }
|
||
.ge-preflight-summary { font-size: 11.5px; color: #8fd6a8; }
|
||
.ge-preflight-start {
|
||
background: #3d6ea5; border: 0; color: #fff; font-size: 12.5px; font-weight: 600;
|
||
padding: 7px 16px; border-radius: 8px; cursor: pointer;
|
||
}
|
||
.ge-preflight-start:hover { background: #4a80bd; }
|
||
.ge-setup-btn {
|
||
background: transparent; border: 1px solid rgba(255, 255, 255, 0.16); color: #c8cdd4;
|
||
font-size: 11px; padding: 2px 8px; border-radius: 6px; cursor: pointer; margin-left: 8px;
|
||
}
|
||
.ge-setup-btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
|
||
@media (max-width: 700px) {
|
||
.ge-preflight-card { padding: 13px 14px; }
|
||
}
|
||
|
||
/* ── Quick Edit — premium look ────────────────────────────────────────── */
|
||
@property --ge-qe-angle {
|
||
syntax: '<angle>';
|
||
initial-value: 0deg;
|
||
inherits: false;
|
||
}
|
||
.ge-ai-command {
|
||
border: 1px solid transparent;
|
||
border-radius: 15px;
|
||
padding: 9px;
|
||
background:
|
||
linear-gradient(
|
||
color-mix(in srgb, var(--panel, #232323) 95%, transparent),
|
||
color-mix(in srgb, var(--panel, #232323) 90%, transparent)
|
||
) padding-box,
|
||
conic-gradient(from 215deg,
|
||
color-mix(in srgb, var(--accent, var(--red)) 78%, transparent),
|
||
color-mix(in srgb, #7aa2ff 52%, transparent) 34%,
|
||
color-mix(in srgb, var(--accent, var(--red)) 18%, transparent) 58%,
|
||
color-mix(in srgb, var(--accent, var(--red)) 78%, transparent)
|
||
) border-box;
|
||
box-shadow:
|
||
0 16px 44px color-mix(in srgb, #000 46%, transparent),
|
||
0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 10%, transparent),
|
||
inset 0 1px 0 color-mix(in srgb, #fff 9%, transparent);
|
||
backdrop-filter: blur(16px) saturate(1.25);
|
||
}
|
||
.ge-ai-command::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -1px;
|
||
z-index: 0;
|
||
border-radius: inherit;
|
||
padding: 1px;
|
||
background: conic-gradient(from var(--ge-qe-angle),
|
||
transparent 0 60%,
|
||
color-mix(in srgb, var(--accent, var(--red)) 88%, transparent) 78%,
|
||
color-mix(in srgb, #d6e3ff 96%, transparent) 84%,
|
||
transparent 92%);
|
||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask-composite: exclude;
|
||
opacity: 0.8;
|
||
pointer-events: none;
|
||
animation: ge-qe-arc 7s linear infinite;
|
||
}
|
||
@keyframes ge-qe-arc { to { --ge-qe-angle: 360deg; } }
|
||
.ge-ai-command-head { padding-bottom: 8px; }
|
||
.ge-ai-command-title {
|
||
letter-spacing: 0.03em;
|
||
background: linear-gradient(90deg,
|
||
color-mix(in srgb, var(--fg) 92%, #fff),
|
||
color-mix(in srgb, var(--accent, var(--red)) 70%, var(--fg)));
|
||
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||
}
|
||
.ge-ai-command .ge-btn-ai-mark {
|
||
background: linear-gradient(135deg,
|
||
color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 22%),
|
||
#9ec1ff);
|
||
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||
animation: ge-qe-spark 3.6s ease-in-out infinite;
|
||
}
|
||
@keyframes ge-qe-spark {
|
||
0%, 100% { opacity: 0.82; transform: scale(1) rotate(0); }
|
||
50% { opacity: 1; transform: scale(1.14) rotate(18deg); }
|
||
}
|
||
.ge-ai-command-input {
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 22%, var(--border));
|
||
background:
|
||
linear-gradient(color-mix(in srgb, #fff 5%, transparent), transparent 55%),
|
||
color-mix(in srgb, var(--bg) 72%, #000 28%);
|
||
box-shadow: inset 0 1px 2px color-mix(in srgb, #000 42%, transparent);
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
|
||
}
|
||
.ge-ai-command-input:focus {
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 82%, transparent);
|
||
box-shadow:
|
||
inset 0 1px 2px color-mix(in srgb, #000 42%, transparent),
|
||
0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent);
|
||
}
|
||
.ge-ai-command-clear { height: 36px; }
|
||
.ge-ai-command-run {
|
||
height: 36px !important;
|
||
min-width: 36px;
|
||
width: 36px;
|
||
border-radius: 10px;
|
||
background: linear-gradient(135deg,
|
||
color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 14%),
|
||
color-mix(in srgb, var(--accent, var(--red)) 62%, #6f8dff));
|
||
box-shadow:
|
||
0 6px 18px color-mix(in srgb, var(--accent, var(--red)) 46%, transparent),
|
||
inset 0 1px 0 color-mix(in srgb, #fff 32%, transparent);
|
||
transition: transform 0.12s ease, box-shadow 0.16s ease, filter 0.16s ease, background 0.16s ease;
|
||
}
|
||
.ge-ai-command-run:hover {
|
||
background: linear-gradient(135deg,
|
||
color-mix(in srgb, var(--accent, var(--red)) 100%, #fff 20%),
|
||
color-mix(in srgb, var(--accent, var(--red)) 60%, #7a95ff));
|
||
filter: brightness(1.06);
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 9px 24px color-mix(in srgb, var(--accent, var(--red)) 58%, transparent);
|
||
}
|
||
.ge-ai-command-run:active { transform: translateY(0); filter: brightness(0.98); }
|
||
.ge-ai-command-suggestions {
|
||
border-radius: 12px;
|
||
border-color: color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border));
|
||
background: color-mix(in srgb, var(--panel, #232323) 97%, transparent);
|
||
box-shadow: 0 16px 36px color-mix(in srgb, #000 42%, transparent);
|
||
}
|
||
.ge-ai-command-suggestion {
|
||
border-radius: 8px;
|
||
transition: background 0.12s ease, transform 0.12s ease;
|
||
}
|
||
.ge-ai-command-suggestion:hover,
|
||
.ge-ai-command-suggestion.active {
|
||
background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent);
|
||
transform: translateX(1px);
|
||
}
|
||
.ge-ai-command-status { font-weight: 600; }
|
||
.ge-ai-command-collapsed {
|
||
border-radius: 999px;
|
||
padding: 3px 13px;
|
||
}
|
||
.ge-ai-command-collapsed:hover,
|
||
.ge-ai-command-collapsed:focus-within {
|
||
box-shadow:
|
||
0 10px 26px color-mix(in srgb, #000 30%, transparent),
|
||
0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent);
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.ge-ai-command::before,
|
||
.ge-ai-command .ge-btn-ai-mark { animation: none; }
|
||
}
|
||
|
||
/* Quick Edit routing-mode selector (Auto / Quick / Agent / Image): uses the
|
||
standard ctl-select look, pushed to the right of the header row. */
|
||
.ge-ai-command .ge-ai-command-mode {
|
||
margin-left: auto;
|
||
margin-right: 6px;
|
||
max-width: 104px;
|
||
}
|
||
|
||
/* Mobile footer fix (audit 2026-10-11 #1): the footer overflowed horizontally
|
||
(scrollWidth 532 vs clientWidth 378) so the zoom controls sat off-screen.
|
||
On phones drop the wide text readouts and tighten the controls so history +
|
||
zoom fit. */
|
||
@media (max-width: 700px) {
|
||
.ge-editor-footer {
|
||
gap: 6px;
|
||
padding: 3px 6px;
|
||
overflow: hidden;
|
||
min-width: 0;
|
||
}
|
||
.ge-editor-footer .ge-status-tool,
|
||
.ge-editor-footer .ge-status-model,
|
||
.ge-editor-footer .ge-canvas-size {
|
||
display: none;
|
||
}
|
||
.ge-footer-history { margin-right: 2px; }
|
||
.ge-footer-zoom { gap: 3px; min-width: 0; }
|
||
.ge-footer-zoom > .ge-btn,
|
||
.ge-footer-history > .ge-btn { min-width: 26px; }
|
||
/* Float the footer above the peek/expanded layers sheet so its zoom/setup
|
||
controls stay tappable (position/left/right/bottom set in JS). */
|
||
.ge-editor-footer.ge-footer-floating {
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: var(--panel);
|
||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
|
||
}
|
||
}
|
||
|
||
/* EVAL: an editor toast fired while a floating panel is open (Adjust/Develop,
|
||
FX, Transform, filter modal, History) is moved to the bottom-centre by
|
||
ui.js so it never covers the panel's controls. */
|
||
body.ge-editor-open .toast.toast-editor-bottom {
|
||
top: auto;
|
||
bottom: 84px;
|
||
left: 12px;
|
||
right: 12px;
|
||
max-width: none;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* Audit #7 / EVAL: on a phone, a toast at the viewport top overlaps the
|
||
editor's menu row AND the top-docked Transform/Adjust/FX popups (Develop,
|
||
Filters). While the editor is open, anchor shared toasts at the bottom,
|
||
centred just above the floating footer, so they never cover the controls.
|
||
The horizontal slide animation is preserved (the toast stays full-width). */
|
||
@media (max-width: 700px) {
|
||
body.ge-editor-open .toast {
|
||
top: auto;
|
||
bottom: 84px;
|
||
left: 12px;
|
||
right: 12px;
|
||
max-width: none;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
/* Mobile touch targets (audit 2026-10-11 #3): keep the primary editor controls
|
||
at least 44x44 CSS px for a comfortable tap. Scoped to the open editor on
|
||
phones — desktop is untouched. */
|
||
@media (max-width: 700px) {
|
||
.ge-toolbar .ge-tool-btn {
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
height: 44px;
|
||
}
|
||
/* Tap targets: only the primary in-sheet controls (layers footer, editor
|
||
footer, panels overlay) get the full 44px box. Applying this to every
|
||
editor button inflated the top menu row and clipped its labels, so the
|
||
topbar/menu buttons keep their natural size. */
|
||
.ge-layers-footer .ge-btn,
|
||
.ge-editor-footer .ge-btn,
|
||
.ge-mobile-panels .ge-btn {
|
||
min-height: 44px;
|
||
min-width: 44px;
|
||
}
|
||
body.ge-editor-open .ge-ai-command-toggle,
|
||
body.ge-editor-open .ge-setup-btn {
|
||
min-height: 44px;
|
||
}
|
||
body.ge-editor-open .ge-layer-opacity,
|
||
body.ge-editor-open .ge-ctl-select,
|
||
body.ge-editor-open .ge-ctl-num {
|
||
min-height: 44px;
|
||
}
|
||
/* Horizontal-scroll fix (audit 2026-10-11 #4): the menu row and tool rail stay
|
||
single-row (wrapping them made the chrome 2-3x taller), but their sideways
|
||
scroll is contained so it never drags the page or overshoots the fade. */
|
||
.ge-topbar-left,
|
||
.ge-toolbar,
|
||
.ge-dock-tabs {
|
||
overscroll-behavior-x: contain;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
}
|
||
|
||
|
||
/* Colour panels: chips are round and the redundant inner title is dropped
|
||
(the dock tab strip already names each panel). Adjustments get SVG icons. */
|
||
.ge-mini-swatch,
|
||
.ge-swatch-grid .ge-swatch,
|
||
.ge-swatch-body .ge-color-input.cp-swatch-input {
|
||
border-radius: 50%;
|
||
}
|
||
.ge-gradient-swatch { border-radius: 999px; }
|
||
#ge-panel-swatches > .ge-panel-title,
|
||
#ge-panel-gradients > .ge-panel-title,
|
||
#ge-panel-patterns > .ge-panel-title,
|
||
#ge-panel-adjustments > .ge-panel-title,
|
||
#ge-panel-properties > .ge-panel-title,
|
||
#ge-panel-histogram > .ge-panel-title {
|
||
display: none;
|
||
}
|
||
.ge-adjustment-grid .ge-adj-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
min-height: 0;
|
||
padding: 3px 6px;
|
||
}
|
||
.ge-adj-icon { display: inline-flex; flex: 0 0 auto; }
|
||
.ge-adj-icon svg { width: 14px; height: 14px; display: block; }
|
||
.ge-adj-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
|
||
/* Mobile panel access (≤ 700 px). The desktop dock is bypassed on phones, so
|
||
the studio panels are mounted in a compact overlay opened from a "Panels"
|
||
button in the layers header (see right-panel.js). */
|
||
@media (max-width: 700px) {
|
||
.ge-layers-panels-btn {
|
||
order: 1;
|
||
margin-left: 8px;
|
||
min-height: 44px;
|
||
min-width: 44px;
|
||
padding: 0 12px;
|
||
border-radius: 8px;
|
||
}
|
||
.ge-mobile-panels {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 70;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 62vh;
|
||
background: var(--panel, #1b1f27);
|
||
border-top: 1px solid var(--border, #2a313c);
|
||
border-radius: 12px 12px 0 0;
|
||
box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.35);
|
||
overflow: hidden;
|
||
}
|
||
.ge-mobile-panels[hidden] { display: none; }
|
||
.ge-mobile-panels-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid var(--border, #2a313c);
|
||
}
|
||
.ge-mobile-panels-title {
|
||
font-size: 12px;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
opacity: 0.7;
|
||
}
|
||
.ge-mobile-panels-close { min-width: 44px; min-height: 44px; }
|
||
.ge-mobile-panels .ge-dock-tabbar { padding: 8px 12px 4px; }
|
||
.ge-mobile-panels .ge-dock-select { width: 100%; min-height: 44px; }
|
||
.ge-mobile-panels .ge-dock-panels { overflow-y: auto; padding: 4px 12px 16px; }
|
||
}
|
||
|
||
/* Mobile layer rows: only the ACTIVE layer shows its opacity slider, and the
|
||
slider stays a thin fixed-width track instead of stretching edge-to-edge
|
||
over the thumbnail and the fx/action strip. The panel's Blend/Opacity row is
|
||
the primary touch control for opacity. */
|
||
@media (max-width: 700px) {
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item) > .ge-layer-opacity-row {
|
||
display: none;
|
||
}
|
||
.ge-layers-list > .ge-layer-item:not(.ge-adj-sub-item):not(.ge-mask-sub-item).active > .ge-layer-opacity-row {
|
||
display: flex;
|
||
}
|
||
body.ge-editor-open .ge-layer-item .ge-layer-opacity {
|
||
min-height: 0 !important;
|
||
height: 18px !important;
|
||
flex: 0 0 auto !important;
|
||
width: 140px !important;
|
||
}
|
||
}
|
||
|
||
/* Mobile layers footer: keep the add / fx / merge / Fill tools on ONE row
|
||
(they were wrapping and orphaning "Fill" on its own line). */
|
||
@media (max-width: 700px) {
|
||
.ge-layers-footer {
|
||
flex-wrap: nowrap;
|
||
gap: 4px;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.ge-layers-footer .ge-layer-header-tools,
|
||
.ge-layers-footer .ge-layer-tools {
|
||
flex-wrap: nowrap;
|
||
gap: 3px;
|
||
}
|
||
/* Spread the tool row across the full sheet width instead of leaving a
|
||
big empty right gutter (the empty #ge-layer-tools hook used to hold the
|
||
right edge via space-between). */
|
||
.ge-layers-footer .ge-layer-header-tools {
|
||
flex: 1 1 auto;
|
||
justify-content: space-between;
|
||
}
|
||
.ge-layers-footer .ge-layer-tools:empty { display: none; }
|
||
.ge-layers-footer .ge-btn {
|
||
min-width: 40px !important;
|
||
flex: 0 0 auto;
|
||
}
|
||
.ge-layers-footer #ge-layer-fill {
|
||
min-width: 52px !important;
|
||
width: auto !important;
|
||
padding: 0 10px !important;
|
||
}
|
||
}
|
||
|
||
/* Tool-rail "more tools" chevron. The rail is wrapped (see galleryEditor.js)
|
||
so an overlay chevron can sit on its right edge on phones, outside the
|
||
scrolling element (and thus outside its mask fade). */
|
||
.ge-toolbar-wrap { display: contents; }
|
||
.ge-tool-more { display: none; }
|
||
@media (max-width: 700px) {
|
||
.ge-toolbar-wrap {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
width: 100%;
|
||
position: relative;
|
||
align-items: stretch;
|
||
}
|
||
.ge-toolbar-wrap > .ge-toolbar { flex: 1 1 auto; min-width: 0; }
|
||
.ge-tool-more {
|
||
display: none;
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
right: 0;
|
||
width: 22px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--fg, #e6e9ef);
|
||
font-size: 20px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
z-index: 2;
|
||
}
|
||
.ge-tool-more.is-visible { display: flex; }
|
||
/* Keep the floating editor footer from stacking on the Quick Edit card. The
|
||
card owns the bottom of the canvas while it is open, so the footer (zoom /
|
||
Setup / AI settings) hides until it closes. */
|
||
body:has(.ge-ai-command:not(.ge-ai-command-collapsed)) .ge-editor-footer.ge-footer-floating {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* On phones the Quick Edit card sits between the tool rail and the peek layers
|
||
sheet, so the suggestion popup cannot grow upward without being clipped by
|
||
the rail. Show the suggestions as a compact horizontal chip row inside the
|
||
card instead. */
|
||
@media (max-width: 700px) {
|
||
.ge-ai-command:not(.ge-ai-command-collapsed) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.ge-ai-command-head { order: 0; }
|
||
.ge-ai-command-suggestions {
|
||
position: static;
|
||
order: 1;
|
||
left: auto;
|
||
right: auto;
|
||
bottom: auto;
|
||
z-index: auto;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
gap: 6px;
|
||
max-height: none;
|
||
padding: 0 0 6px;
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
}
|
||
.ge-ai-command-suggestions::-webkit-scrollbar { display: none; }
|
||
.ge-ai-command-form { order: 2; }
|
||
.ge-ai-command-status { order: 3; }
|
||
.ge-ai-command-stack { order: 4; }
|
||
.ge-ai-command-progress { order: 5; }
|
||
.ge-ai-command-suggestion {
|
||
width: auto;
|
||
flex: 0 0 auto;
|
||
grid-template-columns: none;
|
||
gap: 0;
|
||
min-height: 30px;
|
||
padding: 5px 11px;
|
||
border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border));
|
||
border-radius: 16px;
|
||
background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent);
|
||
}
|
||
.ge-ai-command-suggestion-icon,
|
||
.ge-ai-command-suggestion-kind,
|
||
.ge-ai-command-suggestion-hint {
|
||
display: none;
|
||
}
|
||
.ge-ai-command-suggestion-main {
|
||
white-space: nowrap;
|
||
font-size: 11px;
|
||
font-weight: 550;
|
||
}
|
||
}
|
||
|
||
/* While the editor is open on a phone the app-shell chat selector sits right
|
||
above the editor chrome; lift it above the 70% default so it stays legible. */
|
||
@media (max-width: 700px) {
|
||
body.ge-editor-open .chat-meta-overlay {
|
||
display: none !important;
|
||
}
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
body.ge-editor-open {
|
||
font-family: var(--font-family, 'Fira Code', monospace);
|
||
}
|
||
}
|
||
|
||
/* Phone menu bar: tighten the Photoshop menus and keep the Save control
|
||
icon-only so the twelve menus + Import + Save are not crowded. */
|
||
@media (max-width: 700px) {
|
||
.ge-topbar-left .ge-stacked-btn {
|
||
font-size: 11px;
|
||
padding: 0 6px !important;
|
||
}
|
||
.ge-topbar.ge-topbar-compact #ge-draft-status {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* Phone editor: dock the zoom/Setup footer flush to the Layers sheet. The
|
||
footer spans the full width with square corners so canvas bottom == footer
|
||
top == sheet top, with no rounded-card seam or dead space between them, and
|
||
the sheet's grab handle (which would read as a stray strip) is hidden. */
|
||
@media (max-width: 700px) {
|
||
.ge-editor-footer.ge-footer-floating {
|
||
border-radius: 0;
|
||
border-left: 0;
|
||
border-right: 0;
|
||
border-bottom: 0;
|
||
}
|
||
body.ge-footer-docked .ge-right-panel .ge-layers-grab {
|
||
display: none !important;
|
||
}
|
||
}
|
||
|
||
/* Long dropdown menus (Filter especially) can grow past the viewport bottom;
|
||
cap them to the space below the topbar and let them scroll. */
|
||
.ge-image-menu,
|
||
.ge-layer-menu,
|
||
.ge-filter-menu,
|
||
.ge-edit-menu,
|
||
.ge-selection-menu,
|
||
.ge-type-menu,
|
||
.ge-3d-menu,
|
||
.ge-view-menu,
|
||
.ge-window-menu,
|
||
.ge-plugins-menu,
|
||
.ge-help-menu {
|
||
max-height: min(560px, 70vh) !important;
|
||
overflow-y: auto !important;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: auto !important;
|
||
scrollbar-color: auto !important;
|
||
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background-color: var(--red);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background: var(--red);
|
||
border-radius: 4px;
|
||
border: 2px solid var(--panel);
|
||
}
|
||
/* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so
|
||
draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */
|
||
.ge-menu-thumb {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 2px;
|
||
width: 3px;
|
||
min-height: 18px;
|
||
border-radius: 2px;
|
||
background: var(--red);
|
||
pointer-events: none;
|
||
display: none;
|
||
z-index: 3;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy pixel filter commits on a large document. */
|
||
.ge-busy-veil {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 12px;
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-top-color: var(--red);
|
||
border-radius: 50%;
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows + a viewport-bounded height so long
|
||
lists (the 19 Image > Adjustments entries) all fit or scroll. */
|
||
.ge-menu-parent + .ge-submenu .dropdown-item-compact {
|
||
min-height: 0 !important;
|
||
height: auto !important;
|
||
padding: 3px 10px !important;
|
||
line-height: 1.2 !important;
|
||
font-size: 12px !important;
|
||
}
|
||
.ge-submenu {
|
||
max-height: calc(100vh - 180px) !important;
|
||
overflow-y: auto !important;
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image >
|
||
Adjustments entries) fit without clipping, and let the panel scroll. */
|
||
.ge-submenu .dropdown-item-compact {
|
||
padding-top: 2px;
|
||
padding-bottom: 2px;
|
||
}
|
||
.ge-submenu {
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
/* Release the parent menu's overflow clip while a flyout is open so the
|
||
panel is not cut off at the menu edge. */
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The onboarding "Pro tip" tour hint floats over the editor menus; hide it
|
||
while the editor is open so it never occludes a dropdown or flyout. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* Long dropdown menus (Filter especially) can grow past the viewport bottom;
|
||
cap them to the space below the topbar and let them scroll. */
|
||
.ge-image-menu,
|
||
.ge-layer-menu,
|
||
.ge-filter-menu,
|
||
.ge-edit-menu,
|
||
.ge-selection-menu,
|
||
.ge-type-menu,
|
||
.ge-3d-menu,
|
||
.ge-view-menu,
|
||
.ge-window-menu,
|
||
.ge-plugins-menu,
|
||
.ge-help-menu {
|
||
max-height: min(560px, 70vh) !important;
|
||
overflow-y: auto !important;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: auto !important;
|
||
scrollbar-color: auto !important;
|
||
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background-color: var(--red);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background: var(--red);
|
||
border-radius: 4px;
|
||
border: 2px solid var(--panel);
|
||
}
|
||
/* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so
|
||
draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */
|
||
.ge-menu-thumb {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 2px;
|
||
width: 3px;
|
||
min-height: 18px;
|
||
border-radius: 2px;
|
||
background: var(--red);
|
||
pointer-events: none;
|
||
display: none;
|
||
z-index: 3;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy pixel filter commits on a large document. */
|
||
.ge-busy-veil {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 12px;
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-top-color: var(--red);
|
||
border-radius: 50%;
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows + a viewport-bounded height so long
|
||
lists (the 19 Image > Adjustments entries) all fit or scroll. */
|
||
.ge-menu-parent + .ge-submenu .dropdown-item-compact {
|
||
min-height: 0 !important;
|
||
height: auto !important;
|
||
padding: 3px 10px !important;
|
||
line-height: 1.2 !important;
|
||
font-size: 12px !important;
|
||
}
|
||
.ge-submenu {
|
||
max-height: calc(100vh - 180px) !important;
|
||
overflow-y: auto !important;
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image >
|
||
Adjustments entries) fit without clipping, and let the panel scroll. */
|
||
.ge-submenu .dropdown-item-compact {
|
||
padding-top: 2px;
|
||
padding-bottom: 2px;
|
||
}
|
||
.ge-submenu {
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
/* Release the parent menu's overflow clip while a flyout is open so the
|
||
panel is not cut off at the menu edge. */
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The onboarding "Pro tip" tour hint floats over the editor menus; hide it
|
||
while the editor is open so it never occludes a dropdown or flyout. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* Phone editor: while the Quick Edit card is open it owns the bottom of the
|
||
canvas. Force the layers sheet back to its peek height and hide the floating
|
||
zoom/Setup footer so neither the sheet nor the footer covers the card. */
|
||
@media (max-width: 700px) {
|
||
body.ge-qe-open .ge-editor-footer.ge-footer-floating {
|
||
display: none !important;
|
||
}
|
||
body.ge-qe-open .ge-right-panel:not(.minimized) {
|
||
transform: translateY(calc(100% - var(--peek-height, 110px))) !important;
|
||
}
|
||
}
|
||
|
||
/* Long dropdown menus (Filter especially) can grow past the viewport bottom;
|
||
cap them to the space below the topbar and let them scroll. */
|
||
.ge-image-menu,
|
||
.ge-layer-menu,
|
||
.ge-filter-menu,
|
||
.ge-edit-menu,
|
||
.ge-selection-menu,
|
||
.ge-type-menu,
|
||
.ge-3d-menu,
|
||
.ge-view-menu,
|
||
.ge-window-menu,
|
||
.ge-plugins-menu,
|
||
.ge-help-menu {
|
||
max-height: min(560px, 70vh) !important;
|
||
overflow-y: auto !important;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: auto !important;
|
||
scrollbar-color: auto !important;
|
||
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background-color: var(--red);
|
||
border-radius: 4px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar,
|
||
.ge-layer-menu::-webkit-scrollbar,
|
||
.ge-filter-menu::-webkit-scrollbar,
|
||
.ge-edit-menu::-webkit-scrollbar,
|
||
.ge-selection-menu::-webkit-scrollbar,
|
||
.ge-type-menu::-webkit-scrollbar,
|
||
.ge-3d-menu::-webkit-scrollbar,
|
||
.ge-view-menu::-webkit-scrollbar,
|
||
.ge-window-menu::-webkit-scrollbar,
|
||
.ge-plugins-menu::-webkit-scrollbar,
|
||
.ge-help-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-track,
|
||
.ge-layer-menu::-webkit-scrollbar-track,
|
||
.ge-filter-menu::-webkit-scrollbar-track,
|
||
.ge-edit-menu::-webkit-scrollbar-track,
|
||
.ge-selection-menu::-webkit-scrollbar-track,
|
||
.ge-type-menu::-webkit-scrollbar-track,
|
||
.ge-3d-menu::-webkit-scrollbar-track,
|
||
.ge-view-menu::-webkit-scrollbar-track,
|
||
.ge-window-menu::-webkit-scrollbar-track,
|
||
.ge-plugins-menu::-webkit-scrollbar-track,
|
||
.ge-help-menu::-webkit-scrollbar-track {
|
||
background: var(--panel);
|
||
}
|
||
.ge-image-menu::-webkit-scrollbar-thumb,
|
||
.ge-layer-menu::-webkit-scrollbar-thumb,
|
||
.ge-filter-menu::-webkit-scrollbar-thumb,
|
||
.ge-edit-menu::-webkit-scrollbar-thumb,
|
||
.ge-selection-menu::-webkit-scrollbar-thumb,
|
||
.ge-type-menu::-webkit-scrollbar-thumb,
|
||
.ge-3d-menu::-webkit-scrollbar-thumb,
|
||
.ge-view-menu::-webkit-scrollbar-thumb,
|
||
.ge-window-menu::-webkit-scrollbar-thumb,
|
||
.ge-plugins-menu::-webkit-scrollbar-thumb,
|
||
.ge-help-menu::-webkit-scrollbar-thumb {
|
||
background: var(--red);
|
||
border-radius: 4px;
|
||
border: 2px solid var(--panel);
|
||
}
|
||
/* Custom scroll indicator: macOS/headless browsers hide overlay scrollbars, so
|
||
draw a thin 3px thumb driven by scrollTop inside each dropdown menu. */
|
||
.ge-menu-thumb {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 2px;
|
||
width: 3px;
|
||
min-height: 18px;
|
||
border-radius: 2px;
|
||
background: var(--red);
|
||
pointer-events: none;
|
||
display: none;
|
||
z-index: 3;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy pixel filter commits on a large document. */
|
||
.ge-busy-veil {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font: inherit;
|
||
font-size: 12px;
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 25%, transparent);
|
||
border-top-color: var(--red);
|
||
border-radius: 50%;
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows + a viewport-bounded height so long
|
||
lists (the 19 Image > Adjustments entries) all fit or scroll. */
|
||
.ge-menu-parent + .ge-submenu .dropdown-item-compact {
|
||
min-height: 0 !important;
|
||
height: auto !important;
|
||
padding: 3px 10px !important;
|
||
line-height: 1.2 !important;
|
||
font-size: 12px !important;
|
||
}
|
||
.ge-submenu {
|
||
max-height: calc(100vh - 180px) !important;
|
||
overflow-y: auto !important;
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* Flyout submenu panels: compact rows so long lists (e.g. the 19 Image >
|
||
Adjustments entries) fit without clipping, and let the panel scroll. */
|
||
.ge-submenu .dropdown-item-compact {
|
||
padding-top: 2px;
|
||
padding-bottom: 2px;
|
||
}
|
||
.ge-submenu {
|
||
scrollbar-width: auto;
|
||
scrollbar-color: auto;
|
||
}
|
||
.ge-submenu::-webkit-scrollbar { width: 8px; height: 8px; }
|
||
.ge-submenu::-webkit-scrollbar-track { background: var(--panel); }
|
||
.ge-submenu::-webkit-scrollbar-thumb { background: var(--red); border-radius: 4px; border: 2px solid var(--panel); }
|
||
/* Release the parent menu's overflow clip while a flyout is open so the
|
||
panel is not cut off at the menu edge. */
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent.open) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The global "Pro tip" hint card can overlap the editor menus; hide it while
|
||
the editor is open. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* The onboarding "Pro tip" tour hint floats over the editor menus; hide it
|
||
while the editor is open so it never occludes a dropdown or flyout. */
|
||
body.ge-editor-open .tour-hint {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Busy veil shown while a heavy filter renders on a large document. */
|
||
.ge-busy-veil {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 10, 14, 0.45);
|
||
z-index: 40;
|
||
}
|
||
.ge-busy-veil.is-on { display: flex; }
|
||
.ge-busy-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 16px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
color: var(--fg);
|
||
font-size: 12px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
}
|
||
.ge-busy-spin {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid color-mix(in srgb, var(--fg) 30%, transparent);
|
||
border-top-color: var(--red);
|
||
animation: ge-busy-spin 0.8s linear infinite;
|
||
}
|
||
@keyframes ge-busy-spin { to { transform: rotate(360deg); } }
|
||
|
||
/* Photoshop-style nested submenus (Filter groups + Image > Adjustments).
|
||
A parent row shows a chevron and reveals a flyout panel on hover/click.
|
||
Children stay inside the same menu subtree so the delegated
|
||
[data-filter-action] / [data-image-action] handlers still fire. */
|
||
.dropdown-item-compact.ge-menu-parent {
|
||
position: relative;
|
||
justify-content: space-between;
|
||
}
|
||
.ge-submenu-caret {
|
||
margin-left: auto;
|
||
color: var(--color-muted);
|
||
}
|
||
.ge-submenu {
|
||
display: none;
|
||
position: absolute;
|
||
left: calc(100% - 2px);
|
||
top: 0;
|
||
min-width: 190px;
|
||
max-height: min(70vh, 560px);
|
||
overflow-y: auto;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||
z-index: 20;
|
||
padding: 4px;
|
||
}
|
||
.ge-menu-parent.open + .ge-submenu,
|
||
.ge-menu-parent:hover + .ge-submenu,
|
||
.ge-menu-parent:focus-within + .ge-submenu,
|
||
.ge-submenu:hover {
|
||
display: block;
|
||
}
|
||
/* The dropdowns clip overflow to draw the custom scroll thumb; release the
|
||
clip while a flyout is open/hovered so it can extend past the menu edge. */
|
||
.ge-filter-menu:has(.ge-menu-parent.open),
|
||
.ge-filter-menu:has(.ge-menu-parent:hover),
|
||
.ge-filter-menu:has(.ge-submenu:hover),
|
||
.ge-image-menu:has(.ge-menu-parent.open),
|
||
.ge-image-menu:has(.ge-menu-parent:hover),
|
||
.ge-image-menu:has(.ge-submenu:hover),
|
||
.ge-selection-menu:has(.ge-menu-parent.open),
|
||
.ge-selection-menu:has(.ge-menu-parent:hover),
|
||
.ge-selection-menu:has(.ge-submenu:hover) {
|
||
overflow: visible !important;
|
||
-webkit-mask-image: none;
|
||
mask-image: none;
|
||
}
|
||
/* The Window menu's Workspace flyout needs the same clip release. */
|
||
.ge-window-menu:has(.ge-menu-parent.open),
|
||
.ge-window-menu:has(.ge-menu-parent:hover),
|
||
.ge-window-menu:has(.ge-submenu:hover) {
|
||
overflow: visible !important;
|
||
-webkit-mask-image: none;
|
||
mask-image: none;
|
||
}
|
||
|
||
/* Perspective Crop overlay (tools/perspective-crop.js). */
|
||
.ge-pcrop-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10050;
|
||
pointer-events: none;
|
||
}
|
||
.ge-pcrop-svg {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
}
|
||
.ge-pcrop-svg polygon {
|
||
fill: color-mix(in srgb, var(--red) 12%, transparent);
|
||
stroke: var(--red);
|
||
stroke-width: 1.5;
|
||
vector-effect: non-scaling-stroke;
|
||
}
|
||
.ge-pcrop-handle {
|
||
position: absolute;
|
||
width: 14px;
|
||
height: 14px;
|
||
margin: -7px 0 0 -7px;
|
||
border-radius: 50%;
|
||
background: #fff;
|
||
border: 2px solid var(--red);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||
cursor: grab;
|
||
pointer-events: auto;
|
||
touch-action: none;
|
||
}
|
||
.ge-pcrop-handle:active { cursor: grabbing; }
|
||
.ge-pcrop-bar {
|
||
position: absolute;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 5px 8px;
|
||
border-radius: 8px;
|
||
background: var(--panel, #222);
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
|
||
pointer-events: auto;
|
||
transform: translateY(-100%);
|
||
}
|
||
.ge-pcrop-bar .ge-pcrop-title {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
}
|
||
.ge-pcrop-bar .ge-pcrop-size {
|
||
font-size: 11px;
|
||
opacity: 0.75;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.ge-pcrop-spacer { flex: 1 1 auto; min-width: 12px; }
|
||
|
||
/* Mobile (390) usability: several editor controls sat well below the 44px
|
||
touch-target guideline, so they were easy to miss with a thumb. Give the
|
||
primary top-menu buttons, the Layers-header action row, the layer-opacity
|
||
slider and the Quick Edit field/run button a comfortable hit area on phones.
|
||
Sizing only — no layout/order change. */
|
||
@media (max-width: 700px) {
|
||
.ge-topbar .ge-btn-sm,
|
||
.ge-topbar .ge-stacked-btn {
|
||
min-height: 34px;
|
||
}
|
||
#ge-undo,
|
||
#ge-redo,
|
||
#ge-history-btn,
|
||
#ge-compare-btn {
|
||
min-width: 38px;
|
||
min-height: 38px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.ge-layer-opacity {
|
||
height: 30px;
|
||
}
|
||
#ge-ai-command-input {
|
||
min-height: 40px;
|
||
}
|
||
#ge-ai-command-run {
|
||
min-width: 42px;
|
||
min-height: 42px;
|
||
}
|
||
}
|