mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 06:52:20 +02:00
Integrate lab fff55a78 into PR #40 (cc25d5ba). Lab's modular email backend/frontend, modular settings, split stylesheets (static/style.css stays deleted), procfs compatibility, and request-scoped TurnContract authority win; PR #40's routing classifiers, editor/email/task features, and style.css changes are ported into lab's module and stylesheet homes. Integration fixes: - settings/api.js imports ui.js under its canonical versioned URL - browser observations keep legacy CAPTCHA/access-block evidence - artifact turns do not re-trigger broad-web research recovery - env reference documents PR test-tool variables; page regenerated PR #40 defects surfaced by lab gates and fixed here: - web_fetch generic schema drops top-level anyOf (OpenAI contract); the compact preview contract still requires url or urls - get_weather registered as a brokered network read - new lazy editor modules precached for offline use - SearXNG pin mirrored into GPU standalone compose files - image model picker again skips offline endpoints Tests updated where PR #40 changed behaviour on purpose, and PR tests moved onto lab's document_source helpers.
4903 lines
136 KiB
CSS
4903 lines
136 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;
|
||
}
|
||
|
||
/* 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: space-between;
|
||
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;
|
||
font-size: 11px;
|
||
color: color-mix(in srgb, var(--fg) 65%, transparent);
|
||
}
|
||
.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: 8px 8px 8px 0;
|
||
background: var(--panel);
|
||
border-right: 1px solid var(--border);
|
||
width: 64px;
|
||
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;
|
||
}
|
||
/* 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: 2px;
|
||
padding: 0 2px;
|
||
height: 42px;
|
||
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: 18px; line-height: 1; }
|
||
.ge-tool-label { font-size: 10px; line-height: 1.25; }
|
||
/* 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);
|
||
}
|
||
|
||
/* 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: 14px;
|
||
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;
|
||
}
|
||
.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-scope {
|
||
margin-left: 7px;
|
||
padding-left: 7px;
|
||
border-left: 1px solid var(--border);
|
||
color: var(--fg-muted);
|
||
font-size: 9px;
|
||
line-height: 1;
|
||
text-transform: uppercase;
|
||
}
|
||
.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-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;
|
||
margin-left: auto;
|
||
}
|
||
.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: 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-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);
|
||
}
|
||
/* 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;
|
||
}
|
||
|
||
/* 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: 250px;
|
||
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: 9px;
|
||
}
|
||
.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-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;
|
||
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;
|
||
gap: 6px;
|
||
padding: 4px 12px;
|
||
}
|
||
.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; }
|
||
|
||
/* 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); }
|
||
|
||
/* 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);
|
||
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-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; }
|
||
.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-selection-wrap,
|
||
.ge-filter-wrap { position: relative; display: inline-block; }
|
||
.ge-resize-menu,
|
||
.ge-edge-menu,
|
||
.ge-view-menu,
|
||
.ge-image-menu,
|
||
.ge-selection-menu,
|
||
.ge-filter-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. */
|
||
@media (max-width: 820px) {
|
||
.ge-resize-menu,
|
||
.ge-edge-menu,
|
||
.ge-view-menu,
|
||
.ge-image-menu,
|
||
.ge-selection-menu,
|
||
.ge-filter-menu {
|
||
position: fixed;
|
||
top: 44px;
|
||
right: 8px;
|
||
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-selection-menu { min-width: 224px; }
|
||
.ge-filter-menu { min-width: 180px; }
|
||
.ge-resize-menu .dropdown-item-compact,
|
||
.ge-view-menu .dropdown-item-compact,
|
||
.ge-image-menu .dropdown-item-compact,
|
||
.ge-selection-menu .dropdown-item-compact,
|
||
.ge-filter-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-view-grid-size {
|
||
display: grid;
|
||
grid-template-columns: 1fr 52px auto;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 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 Filter / Image dropdown — small uppercase
|
||
header so "Blur" / "Selection" reads as a category, not an action. */
|
||
.ge-filter-submenu-label {
|
||
font-size: 9px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
opacity: 0.45;
|
||
font-weight: 600;
|
||
padding: 4px 8px 2px;
|
||
pointer-events: none;
|
||
}
|
||
/* Disabled dropdown items (e.g. Fill when there's no selection) read
|
||
as muted + non-interactive. */
|
||
.ge-image-menu .dropdown-item-compact:disabled,
|
||
.ge-selection-menu .dropdown-item-compact:disabled,
|
||
.ge-filter-menu .dropdown-item-compact:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
.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;
|
||
}
|
||
.ge-canvas-preset {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 3px;
|
||
min-width: 0;
|
||
padding: 8px 9px;
|
||
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: left;
|
||
cursor: pointer;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
.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: 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: .62; }
|
||
.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;
|
||
}
|
||
|
||
/* 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;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 10px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
opacity: 0.8;
|
||
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 0 100%;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.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: 5px 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 34px minmax(0, 1fr) 24px;
|
||
row-gap: 5px;
|
||
}
|
||
.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;
|
||
}
|
||
}
|
||
.ge-layer-inline-thumb {
|
||
display: block;
|
||
width: 34px;
|
||
height: 26px;
|
||
flex: 0 0 34px;
|
||
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;
|
||
}
|
||
/* 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); }
|
||
|
||
/* 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;
|
||
}
|
||
.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;
|
||
}
|
||
.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; }
|
||
/* 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;
|
||
}
|
||
/* 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;
|
||
}
|
||
/* In peek state, the layers list takes the full panel height (~70vh)
|
||
and never overflows so its own scrollbar doesn't activate. Constrain
|
||
the list to the visible peek area so it scrolls properly when there
|
||
are more layers than the peek can show. */
|
||
.ge-right-panel:not(.expanded) .ge-layers-list {
|
||
max-height: calc(var(--peek-height, 110px) - 52px);
|
||
-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: 40px !important;
|
||
height: 40px !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;
|
||
}
|
||
.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 40px 34px minmax(0, 1fr) 40px;
|
||
grid-template-rows: 40px auto 40px;
|
||
align-items: center;
|
||
row-gap: 3px;
|
||
}
|
||
.ge-layers-list > .ge-layer-group-row {
|
||
grid-template-columns: 12px 40px 40px 34px minmax(0, 1fr) 40px;
|
||
grid-template-rows: 40px auto 40px;
|
||
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;
|
||
}
|
||
.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: 6px 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: 7px 11px !important;
|
||
font-size: 13px !important;
|
||
min-height: 34px;
|
||
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;
|
||
}
|
||
}
|
||
|
||
/* ── 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);
|
||
}
|