| 1 |
@import "tailwindcss" source(none) important; |
| 2 |
@import "../tailwind.css" source(none); |
| 3 |
@source "."; |
| 4 |
|
| 5 |
:root { |
| 6 |
color-scheme: only light; |
| 7 |
} |
| 8 |
|
| 9 |
@theme { |
| 10 |
/* Body-level bars/menus render outside WP admin's font context, so they |
| 11 |
need an explicit system UI stack. */ |
| 12 |
--font-qe: |
| 13 |
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, |
| 14 |
Cantarell, "Helvetica Neue", sans-serif; |
| 15 |
} |
| 16 |
|
| 17 |
/* Once a block is being edited in Quick Edit, the agent's selected-block |
| 18 |
X-close (rendered as a div[role="button"] inside its portal at the cover's |
| 19 |
top-center) reads as orphaned — the user moved past the agent's |
| 20 |
block-selection step and now owns Cancel via the QE toolbar. Suppress it |
| 21 |
while a QE canvas is mounted. |
| 22 |
|
| 23 |
Wrapped in @layer utilities so this `!important` rule competes in the same |
| 24 |
layer as the agent bundle's Tailwind `.extendify-agent .flex { |
| 25 |
display: flex !important }`. Per the cascade-layer spec, important rules |
| 26 |
in any declared layer beat important rules outside layers — without this |
| 27 |
wrapping, the layered .flex !important would always win regardless of |
| 28 |
selector specificity. */ |
| 29 |
@layer utilities { |
| 30 |
/* no-prefix */ |
| 31 |
html:has(.extendify-quick-edit-canvas) #extendify-agent-dom-mount |
| 32 |
div[role="button"] { |
| 33 |
@apply hidden!; |
| 34 |
} |
| 35 |
} |
| 36 |
|
| 37 |
/* Admin-bar Edit Mode pill. Lives outside our scope wrapper, so |
| 38 |
each rule carries a `no-prefix` marker. */ |
| 39 |
|
| 40 |
/* no-prefix */ |
| 41 |
#wpadminbar #wp-admin-bar-extendify-quick-edit-toggle > a.ab-item { |
| 42 |
@apply inline-flex! items-center! gap-[8px]!; |
| 43 |
} |
| 44 |
|
| 45 |
/* no-prefix */ |
| 46 |
#wpadminbar |
| 47 |
#wp-admin-bar-extendify-quick-edit-toggle |
| 48 |
.extendify-quick-edit-toggle-pill { |
| 49 |
@apply inline-block! relative! shrink-0! h-[16px]! w-[28px]! rounded-full! bg-[rgba(255,255,255,0.25)]!; |
| 50 |
transition: background 0.18s ease !important; |
| 51 |
} |
| 52 |
|
| 53 |
/* no-prefix */ |
| 54 |
#wpadminbar |
| 55 |
#wp-admin-bar-extendify-quick-edit-toggle |
| 56 |
.extendify-quick-edit-toggle-thumb { |
| 57 |
/* Literal color, not bg-white — the theme vars don't reach |
| 58 |
#wpadminbar, so var-based utilities silently no-op here. */ |
| 59 |
@apply absolute! top-[2px]! left-[2px]! h-[12px]! w-[12px]! rounded-full! bg-[#fff]!; |
| 60 |
transition: transform 0.18s ease !important; |
| 61 |
} |
| 62 |
|
| 63 |
/* On state — pill turns the AI Agent button's blue (not the partner |
| 64 |
color), thumb slides right. */ |
| 65 |
/* no-prefix */ |
| 66 |
html.extendify-quick-edit-on #wpadminbar |
| 67 |
#wp-admin-bar-extendify-quick-edit-toggle |
| 68 |
.extendify-quick-edit-toggle-pill { |
| 69 |
@apply bg-[#3858e9]!; |
| 70 |
} |
| 71 |
|
| 72 |
/* no-prefix */ |
| 73 |
html.extendify-quick-edit-on #wpadminbar |
| 74 |
#wp-admin-bar-extendify-quick-edit-toggle |
| 75 |
.extendify-quick-edit-toggle-thumb { |
| 76 |
@apply translate-x-[12px]!; |
| 77 |
} |
| 78 |
|
| 79 |
/* Above WP admin bar (z 100000). */ |
| 80 |
#extendify-quick-edit-root { |
| 81 |
@apply relative z-[100001]; |
| 82 |
} |
| 83 |
|
| 84 |
/* The agent's highlighter-mode CSS sets pointer-events: none on |
| 85 |
page-wide buttons/links so the selector-tool can capture clicks. |
| 86 |
Re-enable them inside our UI surfaces. `html` prefix is required |
| 87 |
to win specificity against the agent's `!important` rule. |
| 88 |
|
| 89 |
The canvas inclusion is what keeps the LinkControl popover (and |
| 90 |
any other BlockTools-slotted popover) clickable when the agent |
| 91 |
sidebar is open — `BlockTools` puts its popover Slot inside the |
| 92 |
canvas, so the popover renders inside `.wp-site-blocks`, in the |
| 93 |
highlighter-mode scope. */ |
| 94 |
/* no-prefix */ |
| 95 |
html .extendify-quick-edit-bar, |
| 96 |
html .extendify-quick-edit-bar *, |
| 97 |
html .extendify-quick-edit-floating-bar, |
| 98 |
html .extendify-quick-edit-floating-bar *, |
| 99 |
html .extendify-quick-edit-image-menu, |
| 100 |
html .extendify-quick-edit-image-menu *, |
| 101 |
html .extendify-quick-edit-canvas, |
| 102 |
html .extendify-quick-edit-canvas * { |
| 103 |
@apply pointer-events-auto!; |
| 104 |
} |
| 105 |
|
| 106 |
/* Restore pointer-events on `.wp-block-button` wrapper divs inside |
| 107 |
highlighter-mode. The agent ships a secondary rule (in agent.css) that |
| 108 |
drops pointer-events on `.wp-block-button` whenever the parent |
| 109 |
`.wp-block-buttons` contains exactly one button: |
| 110 |
.extendify-agent-highlighter-mode |
| 111 |
.wp-block-buttons:has(> .wp-block-button):not(:has(> |
| 112 |
.wp-block-button:nth-of-type(2))) > .wp-block-button |
| 113 |
That selector's specificity is (0,6,0) once `:has` / `:not` are scored, |
| 114 |
so a plain `html.extendify-quick-edit-on .extendify-agent-highlighter-mode |
| 115 |
.wp-block-button` rescue loses the cascade even with !important. Mirror |
| 116 |
the same `:has` / `:not` chain to outweigh it; the rescue still fires only |
| 117 |
when the agent's rule fires, which is exactly the bug scope. |
| 118 |
|
| 119 |
The inner `<a class="wp-block-button__link">` still has pointer-events:none |
| 120 |
from the broader `.extendify-agent-highlighter-mode a` rule, so link-nav |
| 121 |
prevention is intact — we only restore the wrapper div so hover/click |
| 122 |
targeting resolves to the button via QE's findTagged walk-up. */ |
| 123 |
/* no-prefix */ |
| 124 |
html.extendify-quick-edit-on .extendify-agent-highlighter-mode |
| 125 |
.wp-block-buttons:has(> .wp-block-button):not( |
| 126 |
:has(> .wp-block-button:nth-of-type(2)) |
| 127 |
) |
| 128 |
> .wp-block-button { |
| 129 |
@apply pointer-events-auto!; |
| 130 |
} |
| 131 |
|
| 132 |
/* Cursor contract for edit mode. Tagged blocks read as selectable |
| 133 |
(crosshair); anchors and form controls inside them keep their native |
| 134 |
cursors so the click rule (lib/click-rule.js) telegraphs which |
| 135 |
branch a click will hit before the user clicks. Scoped to |
| 136 |
`html.extendify-quick-edit-on` so cursors don't bleed into normal |
| 137 |
browsing. */ |
| 138 |
/* no-prefix */ |
| 139 |
html.extendify-quick-edit-on [data-extendify-agent-block-id], |
| 140 |
html.extendify-quick-edit-on [data-extendify-part-block-id], |
| 141 |
html.extendify-quick-edit-on [data-extendify-quick-edit-product-id], |
| 142 |
html.extendify-quick-edit-on [data-extendify-quick-edit-wpform-field-id] { |
| 143 |
@apply cursor-crosshair; |
| 144 |
} |
| 145 |
/* no-prefix */ |
| 146 |
html.extendify-quick-edit-on [data-extendify-agent-block-id] a[href], |
| 147 |
html.extendify-quick-edit-on [data-extendify-part-block-id] a[href], |
| 148 |
html.extendify-quick-edit-on [data-extendify-quick-edit-product-id] a[href], |
| 149 |
html.extendify-quick-edit-on |
| 150 |
[data-extendify-quick-edit-wpform-field-id] |
| 151 |
a[href] { |
| 152 |
@apply cursor-pointer; |
| 153 |
} |
| 154 |
/* `revert` restores the UA-default cursor — text on inputs/textarea, |
| 155 |
default/pointer on select/button — so form controls inside tagged |
| 156 |
blocks still feel like form controls. */ |
| 157 |
/* no-prefix */ |
| 158 |
html.extendify-quick-edit-on [data-extendify-agent-block-id] |
| 159 |
:is(input, textarea, select, button), |
| 160 |
html.extendify-quick-edit-on |
| 161 |
[data-extendify-part-block-id] |
| 162 |
:is(input, textarea, select, button), |
| 163 |
html.extendify-quick-edit-on |
| 164 |
[data-extendify-quick-edit-product-id] |
| 165 |
:is(input, textarea, select, button), |
| 166 |
html.extendify-quick-edit-on |
| 167 |
[data-extendify-quick-edit-wpform-field-id] |
| 168 |
:is(input, textarea, select, button) { |
| 169 |
@apply cursor-[revert]; |
| 170 |
} |
| 171 |
|
| 172 |
/* keyboard-entry.js adds tabindex="0" to every tagged block so Tab can |
| 173 |
walk between blocks for accessibility. Side effect: a mouse click on |
| 174 |
a tagged block focuses it, the browser paints its :focus-visible |
| 175 |
ring (1px solid blue in Chrome), and the ring lingers on after the |
| 176 |
QE click-rule / Esc clears the committed selection — because focus |
| 177 |
stays on the element until something else takes it. The hover bar's |
| 178 |
showBar (driven by both mouseover AND focusin) already paints the |
| 179 |
dashed outline as the visual focus indicator, so the browser ring |
| 180 |
is both redundant and visually conflicting. Suppress it on every |
| 181 |
tagged-block flavour while edit mode is on. */ |
| 182 |
/* no-prefix */ |
| 183 |
html.extendify-quick-edit-on [data-extendify-agent-block-id]:focus, |
| 184 |
html.extendify-quick-edit-on [data-extendify-agent-block-id]:focus-visible, |
| 185 |
html.extendify-quick-edit-on [data-extendify-part-block-id]:focus, |
| 186 |
html.extendify-quick-edit-on [data-extendify-part-block-id]:focus-visible, |
| 187 |
html.extendify-quick-edit-on [data-extendify-quick-edit-product-id]:focus, |
| 188 |
html.extendify-quick-edit-on |
| 189 |
[data-extendify-quick-edit-product-id]:focus-visible, |
| 190 |
html.extendify-quick-edit-on [data-extendify-quick-edit-wpform-field-id]:focus, |
| 191 |
html.extendify-quick-edit-on |
| 192 |
[data-extendify-quick-edit-wpform-field-id]:focus-visible { |
| 193 |
@apply outline-none; |
| 194 |
} |
| 195 |
|
| 196 |
/* WP's `<InputControl>` (and `<TextControl>` via it) layers a visual |
| 197 |
`__backdrop` div over the input element to draw the focus border. |
| 198 |
By WP design it carries `pointer-events: none` so clicks pass through |
| 199 |
to the input behind. Our broader `.extendify-quick-edit-canvas *` |
| 200 |
rule above forces every descendant to `pointer-events: auto`, which |
| 201 |
re-enables the backdrop and makes it eat real-user clicks on the |
| 202 |
LinkControl URL input (the "click does nothing" symptom). Pin the backdrop back to none — scoped to the LinkControl |
| 203 |
shell + the QE canvas's WP popovers so we don't touch input-controls |
| 204 |
in unrelated UI (the AI Agent's chat input, WP admin forms, etc.). */ |
| 205 |
/* no-prefix */ |
| 206 |
html.extendify-quick-edit-on .block-editor-link-control |
| 207 |
.components-input-control__backdrop, |
| 208 |
html.extendify-quick-edit-on |
| 209 |
.extendify-quick-edit-canvas |
| 210 |
.components-popover |
| 211 |
.components-input-control__backdrop { |
| 212 |
@apply pointer-events-none!; |
| 213 |
} |
| 214 |
|
| 215 |
/* The tagged-descendant cursor rules above scope by `[data-extendify-*]` |
| 216 |
ancestor. WP's LinkControl popover (Quick Edit canvas → BlockTools → |
| 217 |
Popover.Slot) can end up portaled inside a tagged element when the |
| 218 |
live block's parent is itself a tagged container; the form-control |
| 219 |
override on `:is(input, ...)` matches the `<input>` itself, but |
| 220 |
WP wraps the input in `.components-input-control` + similar divs |
| 221 |
that catch hover events around the input edge and read as crosshair. |
| 222 |
Pin the cursor on the popover root + every interactive descendant |
| 223 |
so the LinkControl always feels like a form, regardless of which |
| 224 |
tagged ancestor it lands under. */ |
| 225 |
/* no-prefix */ |
| 226 |
html.extendify-quick-edit-on .block-editor-link-control, |
| 227 |
html.extendify-quick-edit-on .block-editor-link-control *, |
| 228 |
html.extendify-quick-edit-on .extendify-quick-edit-canvas .components-popover, |
| 229 |
html.extendify-quick-edit-on |
| 230 |
.extendify-quick-edit-canvas |
| 231 |
.components-popover |
| 232 |
* { |
| 233 |
@apply cursor-[revert]; |
| 234 |
} |
| 235 |
html.extendify-quick-edit-on .block-editor-link-control input, |
| 236 |
html.extendify-quick-edit-on .block-editor-link-control textarea, |
| 237 |
html.extendify-quick-edit-on |
| 238 |
.extendify-quick-edit-canvas |
| 239 |
.components-popover |
| 240 |
input, |
| 241 |
html.extendify-quick-edit-on |
| 242 |
.extendify-quick-edit-canvas |
| 243 |
.components-popover |
| 244 |
textarea { |
| 245 |
@apply cursor-text; |
| 246 |
} |
| 247 |
|
| 248 |
/* The `cursor: revert` block above reverts buttons inside |
| 249 |
the LinkControl popover (pencil / unlink / copy) to the UA default, |
| 250 |
which is the arrow cursor — not pointer. WP's `.components-button` |
| 251 |
normally sets `cursor: pointer` but our blanket revert wins. Pin |
| 252 |
pointer on the link-control's `<button>` and `<a>` (URL preview) |
| 253 |
so every interactive element in the popover feels clickable. */ |
| 254 |
/* no-prefix */ |
| 255 |
html.extendify-quick-edit-on .block-editor-link-control button, |
| 256 |
html.extendify-quick-edit-on .block-editor-link-control a, |
| 257 |
html.extendify-quick-edit-on |
| 258 |
.extendify-quick-edit-canvas |
| 259 |
.components-popover |
| 260 |
button, |
| 261 |
html.extendify-quick-edit-on |
| 262 |
.extendify-quick-edit-canvas |
| 263 |
.components-popover |
| 264 |
a { |
| 265 |
@apply cursor-pointer; |
| 266 |
} |
| 267 |
|
| 268 |
/* The URL preview (`.block-editor-link-control__preview-title`, |
| 269 |
an `<a>` rendered by `<ExternalLink>`) renders as white text on the |
| 270 |
popover's white background when the QE canvas hosts a button block — |
| 271 |
the canvas inherits Extendable's button-text color (white) and the |
| 272 |
popover anchor picks it up via `color: inherit` somewhere in the |
| 273 |
ExternalLink chain. `!important` wins the cascade unambiguously; |
| 274 |
the rule is already QE-scoped to `html.extendify-quick-edit-on` so |
| 275 |
it can't bleed into other surfaces. */ |
| 276 |
/* no-prefix */ |
| 277 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title, |
| 278 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title:hover, |
| 279 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title:focus, |
| 280 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title *, |
| 281 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title:hover *, |
| 282 |
html.extendify-quick-edit-on .block-editor-link-control__preview-title:focus * { |
| 283 |
/* Literal color — the theme vars don't reach this portaled popover. */ |
| 284 |
@apply text-[#1e1e1e]! opacity-100!; |
| 285 |
} |
| 286 |
|
| 287 |
/* Hide core's developer-facing "Additional CSS class(es)" row (hardcoded, no |
| 288 |
filter to remove it) — the only Advanced setting rendered in a fieldset. */ |
| 289 |
/* no-prefix */ |
| 290 |
html.extendify-quick-edit-on .block-editor-link-control__settings |
| 291 |
.block-editor-link-control__setting:has(> fieldset) { |
| 292 |
@apply hidden!; |
| 293 |
} |
| 294 |
|
| 295 |
/* Hover bar (plain DOM, see lib/hover-bar.js). Body-scoped so |
| 296 |
every rule needs `no-prefix`. The ::before bridges the gap between |
| 297 |
bar and block so the cursor can cross without losing hover. */ |
| 298 |
|
| 299 |
/* The bar carries `pointer-events: auto` so its ::before hover bridge |
| 300 |
catches the cursor when traversing block → pill (with |
| 301 |
`none`, cursor passed under the bar and would resolve to whatever |
| 302 |
tagged ancestor was below, sometimes an unsupported one — the bar |
| 303 |
then cleared mid-gesture). Wheel events on the bar are forwarded to |
| 304 |
the page scroller via a JS handler in hover-bar.js#renderBar — pills |
| 305 |
stay clickable AND the page still scrolls when the cursor is over |
| 306 |
the bar. */ |
| 307 |
/* no-prefix */ |
| 308 |
.extendify-quick-edit-bar { |
| 309 |
@apply pointer-events-auto fixed z-high inline-flex max-w-none flex-row flex-nowrap gap-[6px] p-0 whitespace-nowrap; |
| 310 |
font-family: |
| 311 |
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, |
| 312 |
Cantarell, "Helvetica Neue", sans-serif; |
| 313 |
} |
| 314 |
|
| 315 |
/* Transparent 12px bridge to the block (above or below the bar) |
| 316 |
so the cursor can cross the gap without losing hover. */ |
| 317 |
/* no-prefix */ |
| 318 |
.extendify-quick-edit-bar::before { |
| 319 |
@apply absolute left-0 right-0 h-[12px] bg-transparent; |
| 320 |
content: ""; |
| 321 |
} |
| 322 |
|
| 323 |
/* no-prefix */ |
| 324 |
.extendify-quick-edit-bar[data-extendify-quick-edit-placement="above"]::before { |
| 325 |
@apply -bottom-[12px]; |
| 326 |
} |
| 327 |
|
| 328 |
/* no-prefix */ |
| 329 |
.extendify-quick-edit-bar[data-extendify-quick-edit-placement="below"]::before { |
| 330 |
@apply -top-[12px]; |
| 331 |
} |
| 332 |
|
| 333 |
/* When the bar is pinned inside a viewport-filling block (e.g. a |
| 334 |
hero Cover) it already overlaps the block — no bridge needed. */ |
| 335 |
/* no-prefix */ |
| 336 |
.extendify-quick-edit-bar[data-extendify-quick-edit-placement="inside"]::before { |
| 337 |
@apply hidden; |
| 338 |
} |
| 339 |
|
| 340 |
/* no-prefix */ |
| 341 |
.extendify-quick-edit-pill { |
| 342 |
@apply inline-flex cursor-pointer items-center gap-[6px] rounded-full border-0 bg-gray-800 px-[12px] py-[6px] text-[12px] font-medium leading-[1.4] text-white; |
| 343 |
box-shadow: |
| 344 |
0 4px 12px rgba(15, 23, 42, 0.25), |
| 345 |
0 0 0 1px rgba(255, 255, 255, 0.15); |
| 346 |
transition: |
| 347 |
background 0.15s ease, |
| 348 |
transform 0.15s ease, |
| 349 |
box-shadow 0.15s ease; |
| 350 |
} |
| 351 |
|
| 352 |
/* no-prefix */ |
| 353 |
.extendify-quick-edit-pill:hover { |
| 354 |
@apply -translate-y-px bg-gray-900; |
| 355 |
box-shadow: |
| 356 |
0 6px 16px rgba(15, 23, 42, 0.35), |
| 357 |
0 0 0 1px rgba(255, 255, 255, 0.2); |
| 358 |
} |
| 359 |
|
| 360 |
/* no-prefix */ |
| 361 |
.extendify-quick-edit-pill:focus-visible { |
| 362 |
@apply outline-offset-[3px]; |
| 363 |
outline: 2px solid var(--color-design-main); |
| 364 |
} |
| 365 |
|
| 366 |
/* no-prefix */ |
| 367 |
.extendify-quick-edit-pill-close { |
| 368 |
@apply px-[9px] text-[13px] leading-none; |
| 369 |
} |
| 370 |
|
| 371 |
/* Hidden rather than removed: the bar would change width on every pin. */ |
| 372 |
/* no-prefix */ |
| 373 |
.extendify-quick-edit-pill-close:disabled { |
| 374 |
@apply invisible; |
| 375 |
} |
| 376 |
|
| 377 |
/* no-prefix */ |
| 378 |
.extendify-quick-edit-pill-ai { |
| 379 |
background: #3858e9; |
| 380 |
} |
| 381 |
|
| 382 |
/* no-prefix */ |
| 383 |
.extendify-quick-edit-pill-ai:hover { |
| 384 |
background: #2145e6; |
| 385 |
} |
| 386 |
|
| 387 |
/* Hover outline — drawn as a body-level `position: fixed` overlay, |
| 388 |
NOT as a class on the block element itself. See |
| 389 |
`lib/hover-bar.js#ensureOutline` for the why: putting the outline |
| 390 |
on the block puts it inside whatever ancestor `overflow: hidden` |
| 391 |
clip region the theme establishes, so it gets cropped on full- |
| 392 |
width pattern containers (the user's report: "only see it on the |
| 393 |
top"). Body-level fixed sidesteps that. |
| 394 |
|
| 395 |
Visual style mirrors the Agent's DOMHighlighter so hover and |
| 396 |
selection feel like the same UI: |
| 397 |
- Theme primary color (--wp--preset--color--primary) instead |
| 398 |
of a hardcoded blue — matches whatever the partner brand is |
| 399 |
set to in Site Editor → Styles → Colors. |
| 400 |
- 4px dashed stroke instead of 2px solid — chunkier, reads |
| 401 |
more clearly as "this is selectable." |
| 402 |
- mix-blend-mode: hard-light — keeps the stroke visible |
| 403 |
against any background (light hero / dark cover / patterned). |
| 404 |
- Spring-like transition via cubic-bezier with a slight |
| 405 |
overshoot — approximates framer-motion's spring without the |
| 406 |
runtime cost of importing framer-motion just for this. |
| 407 |
Coefficients picked to match the agent's |
| 408 |
(stiffness: 700, damping: 40, mass: 0.25) feel — quick |
| 409 |
expand, gentle settle. */ |
| 410 |
/* no-prefix */ |
| 411 |
.extendify-quick-edit-hover-outline { |
| 412 |
/* z-index 99997: below hover bar (99999) but above page content */ |
| 413 |
@apply pointer-events-none fixed z-[99997] box-border rounded-[4px] opacity-0 outline-offset-0; |
| 414 |
outline: 4px dashed var(--wp--preset--color--primary, #3b82f6); |
| 415 |
/* Subtle overshoot — `cubic-bezier(0.34, 1.4, 0.64, 1)` was too |
| 416 |
bouncy in practice (peak ~140% of final). Tightened to ~108% |
| 417 |
peak — feels alive without obviously springing. */ |
| 418 |
transition: |
| 419 |
top 0.2s cubic-bezier(0.34, 1.08, 0.64, 1), |
| 420 |
left 0.2s cubic-bezier(0.34, 1.08, 0.64, 1), |
| 421 |
width 0.2s cubic-bezier(0.34, 1.08, 0.64, 1), |
| 422 |
height 0.2s cubic-bezier(0.34, 1.08, 0.64, 1), |
| 423 |
opacity 0.1s ease-out; |
| 424 |
} |
| 425 |
/* Matches the agent's selected outline (see lib/over-media.js). */ |
| 426 |
/* no-prefix */ |
| 427 |
.extendify-quick-edit-hover-outline.is-over-media { |
| 428 |
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.2); |
| 429 |
} |
| 430 |
/* no-prefix */ |
| 431 |
.extendify-quick-edit-hover-outline.is-visible { |
| 432 |
@apply opacity-100; |
| 433 |
} |
| 434 |
|
| 435 |
/* wp.media frame: lock to a single tab + hide filters/details so |
| 436 |
the modal is just "pick an image". Scoped to the QE-only mode class |
| 437 |
(added to `frame.modal.$el` in InlineEditor.jsx) rather than `body` so |
| 438 |
frames opened by the AI Agent / other features aren't affected — the |
| 439 |
mode class is added by QE alone, so it's the discriminator on its own. |
| 440 |
It lands on wp.media's outer modal wrapper, which carries no |
| 441 |
`.media-modal` class of its own; the old `.media-modal.*` prefix |
| 442 |
therefore matched nothing and the chrome leaked back in. */ |
| 443 |
/* no-prefix */ |
| 444 |
.extendify-quick-edit-mode-browse .media-frame-router, |
| 445 |
.extendify-quick-edit-mode-upload .media-frame-router { |
| 446 |
@apply hidden!; |
| 447 |
} |
| 448 |
|
| 449 |
/* no-prefix */ |
| 450 |
.extendify-quick-edit-mode-browse .media-frame-content |
| 451 |
.attachments-browser |
| 452 |
.media-toolbar, |
| 453 |
.extendify-quick-edit-mode-upload |
| 454 |
.media-frame-content |
| 455 |
.attachments-browser |
| 456 |
.media-toolbar { |
| 457 |
@apply hidden!; |
| 458 |
} |
| 459 |
/* no-prefix */ |
| 460 |
.extendify-quick-edit-mode-browse .media-frame-content .media-sidebar, |
| 461 |
.extendify-quick-edit-mode-upload .media-frame-content .media-sidebar { |
| 462 |
@apply hidden!; |
| 463 |
} |
| 464 |
/* wp.media still reserves absolute offsets for the chrome hidden above; |
| 465 |
core's own `hide-router` class is no use — MediaFrame re-toggles it |
| 466 |
off on every state change. */ |
| 467 |
/* no-prefix */ |
| 468 |
.extendify-quick-edit-mode-browse .media-frame-content, |
| 469 |
.extendify-quick-edit-mode-upload .media-frame-content { |
| 470 |
@apply top-[50px]!; |
| 471 |
} |
| 472 |
/* no-prefix */ |
| 473 |
.extendify-quick-edit-mode-browse .attachments-browser:not(.has-load-more) |
| 474 |
.attachments, |
| 475 |
.extendify-quick-edit-mode-browse |
| 476 |
.attachments-browser.has-load-more |
| 477 |
.attachments-wrapper, |
| 478 |
.extendify-quick-edit-mode-browse .attachments-browser .uploader-inline, |
| 479 |
.extendify-quick-edit-mode-upload |
| 480 |
.attachments-browser:not(.has-load-more) |
| 481 |
.attachments, |
| 482 |
.extendify-quick-edit-mode-upload |
| 483 |
.attachments-browser.has-load-more |
| 484 |
.attachments-wrapper, |
| 485 |
.extendify-quick-edit-mode-upload .attachments-browser .uploader-inline { |
| 486 |
/* Arbitrary [0px], not top-0/right-0 — spacing utilities compile to |
| 487 |
calc(var(--spacing)*0) and the var doesn't reach this body-level |
| 488 |
modal, so the rule would silently no-op. */ |
| 489 |
@apply top-[0px]! right-[0px]!; |
| 490 |
} |
| 491 |
|
| 492 |
/* While an upload is in flight, cover wp.media's content with our own |
| 493 |
loading state so the library/gallery view doesn't flash mid-upload. |
| 494 |
Modal-scoped — without that, this overlay painted a white sheet over |
| 495 |
the AI Agent's media frame too, which read as "screen goes blank" |
| 496 |
the instant the agent's media library responded to a click. */ |
| 497 |
/* no-prefix */ |
| 498 |
.extendify-quick-edit-media-uploading .media-frame-content::after { |
| 499 |
@apply absolute inset-[0px] z-[5] bg-[#fff]; |
| 500 |
content: ""; |
| 501 |
} |
| 502 |
/* no-prefix */ |
| 503 |
.extendify-quick-edit-media-uploading .media-frame-content::before { |
| 504 |
@apply absolute top-1/2 left-1/2 z-[6] mr-[0px] mb-[0px] -mt-[16px] -ml-[16px] h-[32px] w-[32px] rounded-full border-[3px] border-[#ddd] border-t-[color:var(--ext-design-main,#2271b1)]; |
| 505 |
content: ""; |
| 506 |
animation: extendify-quick-edit-media-uploading-spin 0.8s linear infinite; |
| 507 |
} |
| 508 |
/* no-prefix */ |
| 509 |
@keyframes extendify-quick-edit-media-uploading-spin { |
| 510 |
to { |
| 511 |
transform: rotate(360deg); |
| 512 |
} |
| 513 |
} |
| 514 |
|
| 515 |
/* Offset the wp.media modal past the Agent sidebar (which has |
| 516 |
`inert` removed while open). */ |
| 517 |
/* no-prefix */ |
| 518 |
html:has(#extendify-agent-sidebar:not([inert])) .media-modal-backdrop { |
| 519 |
@apply left-[384px]!; |
| 520 |
} |
| 521 |
/* no-prefix */ |
| 522 |
html:has(#extendify-agent-sidebar:not([inert])) .media-modal { |
| 523 |
@apply left-[calc(384px+30px)]!; |
| 524 |
} |
| 525 |
|
| 526 |
/* BlockEditor-based text editor (BlockTextEditor.jsx). */ |
| 527 |
|
| 528 |
/* Hide every Ariakit tooltip portal while a QE canvas is mounted. |
| 529 |
Many rounds of trying to land both 36px-custom-button |
| 530 |
tooltips and 48px-BlockToolbar-button tooltips in a clear spot |
| 531 |
below the bar (pointer-events tricks, top placement, height |
| 532 |
equalization, Floating-UI-aware transforms) all hit one or the |
| 533 |
other case. The dev's call: drop the tooltips here and revisit |
| 534 |
later. |
| 535 |
|
| 536 |
Scope is `body:has(.extendify-quick-edit-floating-bar)` so this |
| 537 |
ONLY hides tooltips while a QE canvas is mounted. Outside an |
| 538 |
active editing session — AI Agent sidebar, WP admin chrome, |
| 539 |
other plugins' tooltips — is completely untouched. Inside a |
| 540 |
session, LinkControl + other in-canvas popovers share this |
| 541 |
portal too and lose their tooltips alongside the bar's, which |
| 542 |
is fine for the editing-session contract. |
| 543 |
|
| 544 |
Each trigger keeps its `aria-label` so screen readers still |
| 545 |
announce. */ |
| 546 |
/* no-prefix */ |
| 547 |
body:has(.extendify-quick-edit-floating-bar) [id^="portal/tooltip"] { |
| 548 |
@apply hidden; |
| 549 |
} |
| 550 |
|
| 551 |
/* no-prefix */ |
| 552 |
.extendify-quick-edit-floating-bar { |
| 553 |
@apply pointer-events-auto; |
| 554 |
} |
| 555 |
|
| 556 |
/* The portaled BlockToolbar ships with extra Gutenberg chrome we |
| 557 |
don't want — block transform menu, block settings 3-dots, |
| 558 |
parent-selector, and the format-toolbar's "More" overflow chevron |
| 559 |
(which surfaces Highlight / Inline code / Strikethrough / etc.). |
| 560 |
With our custom Text-color + Highlight buttons rendered alongside |
| 561 |
the toolbar, the More overflow is redundant — and it kept slipping |
| 562 |
in alongside our buttons making the toolbar look cluttered. Hide |
| 563 |
it. */ |
| 564 |
/* no-prefix */ |
| 565 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-mover, |
| 566 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-parent-selector, |
| 567 |
.extendify-quick-edit-floating-bar-inner |
| 568 |
.block-editor-block-toolbar__group-collapsed-toolbar, |
| 569 |
/* Block-level settings 3-dot menu */ |
| 570 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-settings-menu, |
| 571 |
/* Block transform / switcher button (the "Heading" icon you can click |
| 572 |
to convert to another block type — out of scope for Quick Edit) */ |
| 573 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-switcher, |
| 574 |
/* The chevron that surfaces overflow toolbar groups. */ |
| 575 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-toolbar__group-collapsed-button, |
| 576 |
/* RichText format toolbar's "More" overflow toggle (down arrow). Hide its |
| 577 |
dropdown wrapper too — left visible, the 0-width wrapper still counts as |
| 578 |
a flex item and costs one phantom gap after the last format button. */ |
| 579 |
.extendify-quick-edit-floating-bar-inner [aria-label="More"], |
| 580 |
.extendify-quick-edit-floating-bar-inner |
| 581 |
.components-dropdown-menu:has(> [aria-label="More"]) { |
| 582 |
@apply hidden!; |
| 583 |
} |
| 584 |
|
| 585 |
/* no-prefix */ |
| 586 |
.extendify-quick-edit-floating-bar-inner |
| 587 |
.block-editor-block-contextual-toolbar::before { |
| 588 |
@apply absolute left-0 top-1/2 h-[14px] w-px -translate-y-1/2 bg-gray-300; |
| 589 |
content: ""; |
| 590 |
} |
| 591 |
/* BlockToolbar wrapper owns the layout↔format divider on its left, 12px each side. */ |
| 592 |
/* no-prefix */ |
| 593 |
.extendify-quick-edit-floating-bar-inner |
| 594 |
.block-editor-block-contextual-toolbar { |
| 595 |
@apply relative mt-0! mr-0! mb-0! ml-[8px]! pt-0! pr-0! pb-0! pl-[12px]!; |
| 596 |
} |
| 597 |
/* core/button has no heading/alignment group, so the toolbar opens the |
| 598 |
bar and its left divider reads as a stray leading `|`. */ |
| 599 |
/* no-prefix */ |
| 600 |
.extendify-quick-edit-floating-bar-inner |
| 601 |
.block-editor-block-contextual-toolbar:first-child { |
| 602 |
@apply ml-0! pl-0!; |
| 603 |
} |
| 604 |
/* no-prefix */ |
| 605 |
.extendify-quick-edit-floating-bar-inner |
| 606 |
.block-editor-block-contextual-toolbar:first-child::before { |
| 607 |
@apply hidden; |
| 608 |
} |
| 609 |
|
| 610 |
/* Strip BlockToolbar's own group dividers (border-right on each |
| 611 |
.components-toolbar-group in Gutenberg). We deliberately don't add |
| 612 |
our own dividers between BlockToolbar's internal groups: the format |
| 613 |
toolbar leaves a thin invisible group after B/I/link (wrapping the |
| 614 |
"More" overflow we hide via display:none on the chevron button) — |
| 615 |
adding a `:not(:first-of-type)::before` rule for those internal |
| 616 |
groups draws a stray divider right next to the colors group's own |
| 617 |
divider. Easier and cleaner to treat the whole BlockToolbar as one |
| 618 |
visual segment and only divide between BlockToolbar, colors, and |
| 619 |
actions. */ |
| 620 |
/* no-prefix */ |
| 621 |
.extendify-quick-edit-floating-bar-inner .components-toolbar-group, |
| 622 |
.extendify-quick-edit-floating-bar-inner .components-toolbar, |
| 623 |
/* The portaled BlockToolbar's contextual-toolbar wrapper carries a core |
| 624 |
`0 1px 0 0` bottom shadow (a faux border) that draws a line under our |
| 625 |
pill. We provide our own pill shadow on the inner — strip the core one. */ |
| 626 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-contextual-toolbar { |
| 627 |
@apply border-l-0! border-r-0! shadow-none!; |
| 628 |
} |
| 629 |
/* Zero Gutenberg's per-group `padding: 0 6px` + -1px margin so the bar's own |
| 630 |
`gap` owns spacing. Not the contextual-toolbar — it owns the divider above. */ |
| 631 |
/* no-prefix */ |
| 632 |
.extendify-quick-edit-floating-bar-inner .components-toolbar-group, |
| 633 |
.extendify-quick-edit-floating-bar-inner .components-toolbar { |
| 634 |
@apply p-0! m-0!; |
| 635 |
} |
| 636 |
/* Clamp Gutenberg's 48px toolbar chrome to the button height so the bar |
| 637 |
hugs its row instead of floating the buttons in dead vertical space. */ |
| 638 |
/* no-prefix */ |
| 639 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-contextual-toolbar, |
| 640 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-toolbar, |
| 641 |
.extendify-quick-edit-floating-bar-inner .components-accessible-toolbar, |
| 642 |
.extendify-quick-edit-floating-bar-inner .components-toolbar-group, |
| 643 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-toolbar__slot { |
| 644 |
@apply h-[28px]! min-h-0!; |
| 645 |
} |
| 646 |
/* B/I/link get no gap from Gutenberg (group + inner slot), so they rendered |
| 647 |
edge-to-edge; gap both to match the bar's rhythm. */ |
| 648 |
/* no-prefix */ |
| 649 |
.extendify-quick-edit-floating-bar-inner .components-toolbar-group, |
| 650 |
.extendify-quick-edit-floating-bar-inner .block-editor-block-toolbar__slot { |
| 651 |
@apply gap-[4px]!; |
| 652 |
} |
| 653 |
|
| 654 |
/* One box model for all buttons — custom heading/align/color + native |
| 655 |
B/I/link — so Gutenberg's square, dark-pressed defaults match the pill. */ |
| 656 |
/* no-prefix */ |
| 657 |
.extendify-quick-edit-floating-bar-inner .components-button { |
| 658 |
@apply min-w-[28px]! h-[28px]! px-[4px]! py-0! rounded-[6px]! bg-transparent! text-gray-900! shadow-none!; |
| 659 |
} |
| 660 |
/* Drop Gutenberg's inset ::before hover/pressed bar — it fights our |
| 661 |
full-button rounded states. */ |
| 662 |
/* no-prefix */ |
| 663 |
.extendify-quick-edit-floating-bar-inner .components-button::before { |
| 664 |
@apply hidden!; |
| 665 |
} |
| 666 |
/* no-prefix */ |
| 667 |
.extendify-quick-edit-floating-bar-inner |
| 668 |
.components-button:hover:not(:disabled) { |
| 669 |
@apply bg-[rgba(0,0,0,0.05)]!; |
| 670 |
} |
| 671 |
/* Active format (bold/italic/link) — a subtle filled state instead of |
| 672 |
Gutenberg's near-black pressed background. */ |
| 673 |
/* no-prefix */ |
| 674 |
.extendify-quick-edit-floating-bar-inner .components-button.is-pressed, |
| 675 |
.extendify-quick-edit-floating-bar-inner |
| 676 |
.components-button.is-pressed:hover:not(:disabled) { |
| 677 |
@apply bg-[rgba(15,23,42,0.1)]! text-gray-900!; |
| 678 |
} |
| 679 |
|
| 680 |
/* no-prefix */ |
| 681 |
.extendify-quick-edit-text-align-popover .components-popover__content { |
| 682 |
@apply p-[6px] min-w-[auto]; |
| 683 |
} |
| 684 |
|
| 685 |
/* no-prefix */ |
| 686 |
.extendify-quick-edit-text-align-popover-inner { |
| 687 |
@apply flex flex-col gap-[2px]; |
| 688 |
} |
| 689 |
|
| 690 |
/* no-prefix */ |
| 691 |
.extendify-quick-edit-text-align-popover |
| 692 |
.extendify-quick-edit-text-align-option.components-button { |
| 693 |
@apply h-[32px] justify-start gap-[8px] px-[10px] py-0 text-[13px] font-medium; |
| 694 |
} |
| 695 |
|
| 696 |
/* Box model + hover inherited from the shared .components-button rule above; |
| 697 |
the heading button only adds its text label's weight/size + a bit more |
| 698 |
horizontal padding for the "H2" / "H3" labels. */ |
| 699 |
/* no-prefix */ |
| 700 |
.extendify-quick-edit-floating-bar-inner |
| 701 |
.extendify-quick-edit-heading-level-button.components-button { |
| 702 |
@apply px-[6px]! py-0! text-[13px] font-semibold; |
| 703 |
} |
| 704 |
|
| 705 |
/* no-prefix */ |
| 706 |
.extendify-quick-edit-heading-level-popover .components-popover__content { |
| 707 |
@apply p-[6px] min-w-[auto]; |
| 708 |
} |
| 709 |
|
| 710 |
/* no-prefix */ |
| 711 |
.extendify-quick-edit-heading-level-popover-inner { |
| 712 |
@apply inline-flex gap-[2px] p-[2px]; |
| 713 |
} |
| 714 |
|
| 715 |
/* no-prefix */ |
| 716 |
.extendify-quick-edit-heading-level-popover |
| 717 |
.extendify-quick-edit-heading-level-option.components-button { |
| 718 |
@apply h-[32px] min-w-[36px] px-[8px] py-0 text-[13px] font-semibold; |
| 719 |
} |
| 720 |
|
| 721 |
/* Box model + hover inherited from the shared .components-button rule above. */ |
| 722 |
|
| 723 |
/* no-prefix */ |
| 724 |
.extendify-quick-edit-color-popover .components-popover__content { |
| 725 |
@apply p-[12px] min-w-[220px]; |
| 726 |
} |
| 727 |
|
| 728 |
/* no-prefix */ |
| 729 |
.extendify-quick-edit-color-popover-inner |
| 730 |
.components-circular-option-picker__option-wrapper { |
| 731 |
@apply m-[4px]; |
| 732 |
} |
| 733 |
|
| 734 |
/* Explicit "Clear" button below the swatches — the ColorPalette's |
| 735 |
built-in clear is easy to miss. */ |
| 736 |
/* no-prefix */ |
| 737 |
.extendify-quick-edit-color-popover |
| 738 |
.extendify-quick-edit-color-clear.components-button { |
| 739 |
@apply block w-full mt-[8px] px-[12px]! py-[8px]! border-t! border-t-gray-300! rounded-none! text-left! text-gray-700! font-medium!; |
| 740 |
} |
| 741 |
/* no-prefix */ |
| 742 |
.extendify-quick-edit-color-popover |
| 743 |
.extendify-quick-edit-color-clear.components-button:hover { |
| 744 |
@apply bg-gray-50! text-gray-900!; |
| 745 |
} |
| 746 |
|
| 747 |
/* no-prefix */ |
| 748 |
.extendify-quick-edit-host { |
| 749 |
/* The host overlays the live block 1:1. Its position is set by |
| 750 |
BlockTextEditor's align() effect; CSS just zeros margin so theme |
| 751 |
blockGap doesn't push us. |
| 752 |
|
| 753 |
The blue outline lives HERE (not on the inner canvas). The host |
| 754 |
has its width / min-height set synchronously to match the live |
| 755 |
block on insert, so its outline paints at full size on the very |
| 756 |
first frame. The canvas inside is empty until blocks load — |
| 757 |
putting the outline on the canvas previously meant a 0-height |
| 758 |
"narrow stripe" between insert and content load. |
| 759 |
|
| 760 |
`outline-offset: 0` (flush with edge) is deliberate. With +4px, |
| 761 |
the outline extends outside the host by 4px — for full-width |
| 762 |
pattern containers at the page edge, those 4px land in |
| 763 |
`body`/`html`'s `overflow-x: hidden` clip region, and the |
| 764 |
outline disappears on the bottom + sides ("only see it on the |
| 765 |
top," as the user reported). Flush with the edge keeps the |
| 766 |
outline inside the host's bounds and visible everywhere. */ |
| 767 |
@apply m-0! rounded-[4px] outline-offset-0; |
| 768 |
outline: 2px solid |
| 769 |
color-mix( |
| 770 |
in srgb, |
| 771 |
var(--wp--preset--color--primary, #3b82f6) 70%, |
| 772 |
transparent |
| 773 |
); |
| 774 |
} |
| 775 |
|
| 776 |
/* no-prefix */ |
| 777 |
.extendify-quick-edit-canvas { |
| 778 |
@apply pointer-events-auto; |
| 779 |
} |
| 780 |
|
| 781 |
/* While the rich-text editor is focused, transition the outline to |
| 782 |
fully opaque so the active edit state is unambiguous. */ |
| 783 |
/* no-prefix */ |
| 784 |
.extendify-quick-edit-host:focus-within { |
| 785 |
outline-color: var(--wp--preset--color--primary, #3b82f6); |
| 786 |
} |
| 787 |
|
| 788 |
/* Pin link / button-link color in the canvas to the live element's resting |
| 789 |
value (snapshot into --qe-link-color in BlockTextEditor.jsx). Without this, |
| 790 |
the theme's :hover on .wp-block-button__link fires as soon as the user's |
| 791 |
cursor sits over the text they're editing, flipping the button text to |
| 792 |
the hover color while the background stays non-hover — broken-looking. |
| 793 |
Falls back to currentColor when no link is in the live block (e.g. a |
| 794 |
plain heading or paragraph), making the rule a no-op. */ |
| 795 |
/* no-prefix */ |
| 796 |
.extendify-quick-edit-host a:hover, |
| 797 |
.extendify-quick-edit-host .wp-block-button__link:hover, |
| 798 |
.extendify-quick-edit-host .wp-block-button:hover .wp-block-button__link { |
| 799 |
@apply text-[var(--qe-link-color,currentColor)]!; |
| 800 |
} |
| 801 |
|
| 802 |
/* BlockEditor's BlockList adds margin-top: 16px to its child blocks |
| 803 |
(and 40.2px bottom). When our canvas is positioned exactly over |
| 804 |
the live block, those margins push the rendered block 16px below |
| 805 |
where the live one sits — the user sees the heading drift down on |
| 806 |
click. Zero out the margins for the block immediately inside our |
| 807 |
canvas so the rendered block top lines up with the live element. |
| 808 |
|
| 809 |
Also zero PADDING on the block list + block wrappers — the editor |
| 810 |
stylesheet adds horizontal padding (for the inserter chip / block- |
| 811 |
selector chrome) that narrows the inner text's effective width, so |
| 812 |
the same heading wraps at a different word boundary than the live |
| 813 |
element (line-break parity). With padding zeroed and the |
| 814 |
host's width matching liveRect.width, the text wraps at the same |
| 815 |
word as the live element. */ |
| 816 |
/* no-prefix */ |
| 817 |
.extendify-quick-edit-canvas .block-editor-block-list__layout, |
| 818 |
.extendify-quick-edit-canvas .block-editor-block-list__block { |
| 819 |
@apply mt-0! mb-0! px-0!; |
| 820 |
} |
| 821 |
|
| 822 |
/* core/text-color wraps its target text in `<mark>`. Browsers AND many |
| 823 |
themes give `<mark>` a yellow background by default; combined with our |
| 824 |
text-color format's inline `background-color: transparent` (set when |
| 825 |
only text color is picked), users sometimes still saw a yellow band |
| 826 |
around their colored text — "changing one color changed |
| 827 |
both". Neutralize the background in our canvas. Inline |
| 828 |
`background-color: <color>` (set when the user also picks a |
| 829 |
highlight) wins via specificity and renders normally. */ |
| 830 |
/* no-prefix */ |
| 831 |
.extendify-quick-edit-canvas mark { |
| 832 |
background: none; |
| 833 |
} |
| 834 |
|
| 835 |
/* Hide Gutenberg's own block-level selection / hover indicators |
| 836 |
* inside our canvas. BlockList draws a faint gray outline (or a |
| 837 |
* "::before" rect, depending on Gutenberg version) on the selected |
| 838 |
* block — when stacked inside our blue canvas outline it reads as a |
| 839 |
* confusing "double box." We want OUR outline to be the one and only |
| 840 |
* editing-state cue; the contenteditable's blinking caret is enough |
| 841 |
* to indicate where the cursor is, no extra block highlight needed. */ |
| 842 |
/* no-prefix */ |
| 843 |
.extendify-quick-edit-canvas .block-editor-block-list__block, |
| 844 |
.extendify-quick-edit-canvas .block-editor-block-list__block.is-selected, |
| 845 |
.extendify-quick-edit-canvas .block-editor-block-list__block.is-hovered, |
| 846 |
.extendify-quick-edit-canvas .block-editor-block-list__block:focus, |
| 847 |
.extendify-quick-edit-canvas .block-editor-rich-text__editable, |
| 848 |
.extendify-quick-edit-canvas .block-editor-rich-text__editable:focus { |
| 849 |
@apply outline-none! shadow-none!; |
| 850 |
} |
| 851 |
/* no-prefix */ |
| 852 |
.extendify-quick-edit-canvas .block-editor-block-list__block::before, |
| 853 |
.extendify-quick-edit-canvas .block-editor-block-list__block::after { |
| 854 |
@apply hidden!; |
| 855 |
} |
| 856 |
|
| 857 |
/* The BlockEditor canvas needs writing flow + its standard editor |
| 858 |
styles to behave naturally (selection, format toolbar tracking). |
| 859 |
We don't load editor-styles globally — these are minimal restorations |
| 860 |
so contenteditable inside .block-editor-rich-text__editable works. */ |
| 861 |
/* no-prefix */ |
| 862 |
.extendify-quick-edit-canvas .block-editor-block-list__layout { |
| 863 |
@apply cursor-text; |
| 864 |
} |
| 865 |
|
| 866 |
/* Force visibility of blocks with the Extendable `ext-animate--on` |
| 867 |
class (and any element using its initial opacity:0 animation |
| 868 |
pattern) INSIDE the canvas. The theme's animation JS only runs |
| 869 |
on page-load scroll events; a freshly mounted BlockEditor block |
| 870 |
never gets animated in, so opacity stays 0 and the user sees an |
| 871 |
empty canvas. We deliberately keep the class on the block (it's |
| 872 |
part of attrs.className and round-trips through serialize) and |
| 873 |
override visibility cosmetically here. |
| 874 |
|
| 875 |
`transform: none !important` is the matching half: the theme's |
| 876 |
pre-animation rule is `.ext-animate--on:not(.ext-animate--off) { |
| 877 |
transform: scale(0.8); opacity: 0; }`, and a sibling rule |
| 878 |
`.ext-animate { transform: none; opacity: 1; }` flips both when |
| 879 |
the JS adds `ext-animate` on scroll-into-view. Inside the canvas |
| 880 |
the BlockEditor renders the parsed markup with `ext-animate--on` |
| 881 |
but NOT `ext-animate` (that class is JS-added, not saved), so |
| 882 |
without this override headings/paragraphs render at 80% scale |
| 883 |
while the live element sits at scale 1 — the user reported this |
| 884 |
as "the text gets a little smaller when I click Quick Edit." */ |
| 885 |
/* no-prefix */ |
| 886 |
.extendify-quick-edit-canvas .ext-animate--on, |
| 887 |
.extendify-quick-edit-canvas .block-editor-block-list__block.ext-animate--on, |
| 888 |
.extendify-quick-edit-canvas .ext-animate--on * { |
| 889 |
@apply opacity-100! transform-none!; |
| 890 |
} |
| 891 |
|
| 892 |
/* Modals portal to <body>, so the no-prefix marker keeps these out of the |
| 893 |
.extendify-quick-edit scope. The agent sidebar uses z-higher (999999), |
| 894 |
so the modal overlay sits at 1_000_000 to stay on top when both are open. */ |
| 895 |
|
| 896 |
/* no-prefix */ |
| 897 |
.components-modal__screen-overlay:has(.extendify-quick-edit-modal) { |
| 898 |
@apply z-[1000000]!; |
| 899 |
} |
| 900 |
|
| 901 |
/* When wp.media's frame opens on top of our modal (e.g. SiteIdentityModal's |
| 902 |
"Change logo" → MediaUpload), our modal would otherwise sit on top |
| 903 |
(z-index 1000000) and block clicks on the media library grid. Hide our |
| 904 |
modal while wp.media is open; it reappears when the user picks an image |
| 905 |
or closes the frame. |
| 906 |
|
| 907 |
Trigger on `body.modal-open` — wp.media (legacy Backbone code) adds |
| 908 |
that class directly on open and removes it on close. Round-3 used |
| 909 |
`:has(.media-modal)`, which stayed true after wp.media closed (wp.media |
| 910 |
hides via display:none and leaves the element in the DOM). |
| 911 |
|
| 912 |
Our own QE modals MUST NOT trip this rule. @wordpress/components Modal |
| 913 |
also adds `body.modal-open` on mount, so every QE modal hid itself on |
| 914 |
first open. Each QE modal now passes |
| 915 |
`bodyOpenClassName={QE_MODAL_BODY_OPEN_CLASS}` — see modal-root.js. */ |
| 916 |
/* no-prefix */ |
| 917 |
body.modal-open .components-modal__frame:has(.extendify-quick-edit-modal), |
| 918 |
body.modal-open |
| 919 |
.components-modal__screen-overlay:has(.extendify-quick-edit-modal) { |
| 920 |
@apply invisible!; |
| 921 |
} |
| 922 |
|
| 923 |
/* ---------------------------------------------------------------- |
| 924 |
* Modal polish — keep visual language aligned with the auto-launch |
| 925 |
* page + AI Agent so the user-perceived experience feels like one |
| 926 |
* product. Agent's tokens: |
| 927 |
* --ext-design-main primary brand (banner / primary buttons) |
| 928 |
* --ext-design-secondary, --ext-design-tertiary (accents, less used) |
| 929 |
* |
| 930 |
* The agent's sidebar uses rounded-2xl + shadow-lg + bg-white; we |
| 931 |
* mirror that on the modal frame so the modal feels like a natural |
| 932 |
* extension of the agent's UI rather than a stock WP component. |
| 933 |
* |
| 934 |
* Falls back to neutral defaults when the partner data hasn't set |
| 935 |
* the design vars (single-color sites, themes that don't import the |
| 936 |
* Extendable design system). Keeps the modal looking polished even |
| 937 |
* outside the auto-launch flow. |
| 938 |
* ---------------------------------------------------------------- */ |
| 939 |
|
| 940 |
/* no-prefix */ |
| 941 |
.components-modal__frame:has(.extendify-quick-edit-modal), |
| 942 |
.extendify-quick-edit-modal-root .components-modal__frame { |
| 943 |
@apply overflow-hidden rounded-[16px]!; |
| 944 |
box-shadow: |
| 945 |
0 10px 15px -3px rgba(0, 0, 0, 0.12), |
| 946 |
0 4px 6px -4px rgba(0, 0, 0, 0.08) !important; |
| 947 |
} |
| 948 |
|
| 949 |
/* no-prefix */ |
| 950 |
.components-modal__frame:has(.extendify-quick-edit-modal) |
| 951 |
.components-modal__header { |
| 952 |
@apply border-b border-b-gray-300 px-[24px] py-[18px]; |
| 953 |
} |
| 954 |
/* no-prefix */ |
| 955 |
.components-modal__frame:has(.extendify-quick-edit-modal) |
| 956 |
.components-modal__header-heading { |
| 957 |
@apply text-[15px] font-semibold tracking-[-0.01em]; |
| 958 |
} |
| 959 |
/* no-prefix */ |
| 960 |
.components-modal__frame:has(.extendify-quick-edit-modal) |
| 961 |
.components-modal__content { |
| 962 |
@apply px-[24px] pt-[18px] pb-[22px]; |
| 963 |
} |
| 964 |
|
| 965 |
/* Primary save button uses the brand color so the action feels |
| 966 |
continuous with the agent's "Send" pill / launch CTAs. */ |
| 967 |
/* no-prefix */ |
| 968 |
.extendify-quick-edit-modal-actions .components-button.is-primary { |
| 969 |
@apply bg-[var(--ext-design-main,#2c39bd)]! shadow-none! rounded-[8px]! px-[14px]! py-[6px]! font-medium!; |
| 970 |
} |
| 971 |
/* no-prefix */ |
| 972 |
.extendify-quick-edit-modal-actions |
| 973 |
.components-button.is-primary:hover:not(:disabled) { |
| 974 |
@apply bg-[var(--ext-design-main,#2c39bd)]! brightness-[1.08]; |
| 975 |
} |
| 976 |
/* no-prefix */ |
| 977 |
.extendify-quick-edit-modal-actions |
| 978 |
.components-button.is-primary:focus:not(:disabled) { |
| 979 |
box-shadow: |
| 980 |
0 0 0 2px #fff, |
| 981 |
0 0 0 4px var(--ext-design-main, #2c39bd) !important; |
| 982 |
} |
| 983 |
/* no-prefix */ |
| 984 |
.extendify-quick-edit-modal-actions .components-button.is-tertiary { |
| 985 |
@apply rounded-[8px]! px-[12px]! py-[6px]! font-medium! text-gray-700!; |
| 986 |
} |
| 987 |
/* no-prefix */ |
| 988 |
.extendify-quick-edit-modal-actions |
| 989 |
.components-button.is-tertiary:hover:not(:disabled) { |
| 990 |
@apply bg-gray-100! text-gray-800!; |
| 991 |
} |
| 992 |
|
| 993 |
/* Tighter, consistent control rhythm inside modals — replaces |
| 994 |
wp-components' default 24px gaps with 14px so the modal feels |
| 995 |
compact and intentional, matching the agent's chat density. */ |
| 996 |
/* no-prefix */ |
| 997 |
.extendify-quick-edit-modal .components-base-control { |
| 998 |
@apply mb-[14px]; |
| 999 |
} |
| 1000 |
/* no-prefix */ |
| 1001 |
.extendify-quick-edit-modal .components-base-control__label { |
| 1002 |
/* normal-case: WP defaults to uppercase via inheritance in some themes */ |
| 1003 |
@apply mb-[6px] text-[12px] font-semibold normal-case tracking-[0] text-gray-800; |
| 1004 |
} |
| 1005 |
/* no-prefix */ |
| 1006 |
.extendify-quick-edit-modal .components-text-control__input, |
| 1007 |
.extendify-quick-edit-modal .components-textarea-control__input { |
| 1008 |
@apply rounded-[8px] border border-gray-300; |
| 1009 |
transition: |
| 1010 |
border-color 0.12s ease, |
| 1011 |
box-shadow 0.12s ease; |
| 1012 |
} |
| 1013 |
/* no-prefix */ |
| 1014 |
.extendify-quick-edit-modal .components-text-control__input:focus, |
| 1015 |
.extendify-quick-edit-modal .components-textarea-control__input:focus { |
| 1016 |
@apply border-[color:var(--ext-design-main,#2c39bd)]! outline-none!; |
| 1017 |
box-shadow: 0 0 0 3px |
| 1018 |
color-mix(in srgb, var(--ext-design-main, #2c39bd) 18%, transparent) !important; |
| 1019 |
} |
| 1020 |
|
| 1021 |
/* no-prefix */ |
| 1022 |
.extendify-quick-edit-modal-actions { |
| 1023 |
@apply flex justify-end gap-[8px] mt-[18px] pt-[14px] border-t border-t-gray-300; |
| 1024 |
} |
| 1025 |
|
| 1026 |
/* no-prefix */ |
| 1027 |
.extendify-quick-edit-modal-label { |
| 1028 |
@apply mt-[12px] mr-0 mb-[6px] ml-0 text-[11px] font-medium uppercase text-gray-700; |
| 1029 |
} |
| 1030 |
|
| 1031 |
/* no-prefix */ |
| 1032 |
.extendify-quick-edit-link-current { |
| 1033 |
@apply mt-[8px] mr-0 mb-[12px] ml-0 rounded-[6px] bg-gray-50 px-[12px] py-[8px] text-[12px] text-gray-700; |
| 1034 |
} |
| 1035 |
/* no-prefix */ |
| 1036 |
.extendify-quick-edit-link-current code { |
| 1037 |
@apply text-gray-900; |
| 1038 |
} |
| 1039 |
|
| 1040 |
/* LinkControl + format-toolbar popovers inside the canvas. WP's Popover |
| 1041 |
writes the anchor's viewport rect into style.top/left and renders as |
| 1042 |
`position: absolute`, which interprets those coords relative to the |
| 1043 |
nearest positioned ancestor. In our setup that's |
| 1044 |
`.extendify-quick-edit-host` (position: absolute, sized to track the |
| 1045 |
live block), so the popover drifts by host.top/left and its width is |
| 1046 |
capped by host.width — the user reads this as "edit box too short" + |
| 1047 |
"hard to click where intended." Forcing position: fixed lets the |
| 1048 |
coords land in viewport space and frees the popover from the canvas |
| 1049 |
width. */ |
| 1050 |
/* no-prefix */ |
| 1051 |
.extendify-quick-edit-canvas .components-popover { |
| 1052 |
@apply fixed!; |
| 1053 |
} |
| 1054 |
|
| 1055 |
/* Without a positioned offsetParent, floating-ui's coords ignore the admin |
| 1056 |
bar's html margin-top and the popover renders 32px above its anchor. */ |
| 1057 |
/* no-prefix */ |
| 1058 |
.extendify-quick-edit-popover-slot { |
| 1059 |
@apply relative; |
| 1060 |
} |
| 1061 |
|
| 1062 |
/* LinkControl in edit mode (forceIsEditingLink) renders its own Apply / Cancel |
| 1063 |
below the URL input. Our modal already has Save / Cancel for that, and |
| 1064 |
the user reads two action rows as broken — hide the inner one. The link |
| 1065 |
value still propagates via the onChange we pass to LinkControl. */ |
| 1066 |
/* no-prefix */ |
| 1067 |
.extendify-quick-edit-modal-nav .block-editor-link-control__search-actions, |
| 1068 |
.extendify-quick-edit-modal-nav .block-editor-link-control__search-submit, |
| 1069 |
.extendify-quick-edit-modal-nav |
| 1070 |
.block-editor-link-control__search-input-actions, |
| 1071 |
.extendify-quick-edit-modal-nav |
| 1072 |
.block-editor-link-control |
| 1073 |
.block-editor-link-control__settings { |
| 1074 |
@apply hidden!; |
| 1075 |
} |
| 1076 |
|
| 1077 |
/* no-prefix */ |
| 1078 |
.extendify-quick-edit-logo-row { |
| 1079 |
@apply flex items-center gap-[16px] mt-[8px]; |
| 1080 |
} |
| 1081 |
|
| 1082 |
/* no-prefix */ |
| 1083 |
.extendify-quick-edit-logo-preview { |
| 1084 |
@apply max-h-[80px] max-w-[120px] rounded-[6px] border border-gray-300 bg-gray-50 object-contain p-[4px]; |
| 1085 |
} |
| 1086 |
|
| 1087 |
/* no-prefix */ |
| 1088 |
.extendify-quick-edit-logo-buttons { |
| 1089 |
@apply flex flex-col gap-[6px]; |
| 1090 |
} |
| 1091 |
|
| 1092 |
/* Product price modal: currency-prefixed regular + sale inputs. */ |
| 1093 |
/* no-prefix */ |
| 1094 |
.extendify-quick-edit-price-fields { |
| 1095 |
@apply flex flex-col gap-[12px]; |
| 1096 |
} |
| 1097 |
/* no-prefix */ |
| 1098 |
.extendify-quick-edit-price-row { |
| 1099 |
@apply flex items-end gap-[8px]; |
| 1100 |
} |
| 1101 |
/* The modal-rhythm margin (above) would misalign input and prefix under |
| 1102 |
items-end; the fields' gap handles spacing. */ |
| 1103 |
/* no-prefix */ |
| 1104 |
.extendify-quick-edit-price-row .components-base-control { |
| 1105 |
@apply mb-0; |
| 1106 |
} |
| 1107 |
/* no-prefix */ |
| 1108 |
.extendify-quick-edit-price-prefix { |
| 1109 |
/* 32px = the WP TextControl input height, so the symbol centers on it. */ |
| 1110 |
@apply flex h-[32px] items-center text-[14px] font-medium text-gray-700; |
| 1111 |
} |
| 1112 |
|
| 1113 |
/* AI generation + Unsplash image-picker modals */ |
| 1114 |
|
| 1115 |
/* no-prefix */ |
| 1116 |
.extendify-quick-edit-ai-form { |
| 1117 |
@apply flex flex-col gap-[16px]; |
| 1118 |
} |
| 1119 |
|
| 1120 |
/* no-prefix */ |
| 1121 |
.extendify-quick-edit-ai-generating { |
| 1122 |
@apply inline-flex items-center gap-[8px] rounded-[6px] bg-gray-50 px-[12px] py-[8px] text-[13px] text-gray-700; |
| 1123 |
} |
| 1124 |
|
| 1125 |
/* no-prefix */ |
| 1126 |
.extendify-quick-edit-ai-credits { |
| 1127 |
@apply text-[12px] text-gray-600 text-right; |
| 1128 |
} |
| 1129 |
|
| 1130 |
/* no-prefix */ |
| 1131 |
.extendify-quick-edit-ai-preview { |
| 1132 |
@apply flex max-h-[60vh] items-center justify-center overflow-hidden rounded-[6px] bg-gray-50; |
| 1133 |
} |
| 1134 |
/* no-prefix */ |
| 1135 |
.extendify-quick-edit-ai-preview img { |
| 1136 |
@apply max-h-[60vh] max-w-full object-contain; |
| 1137 |
} |
| 1138 |
|
| 1139 |
/* The padding-top:100% trick beats theme/WP button min-height rules |
| 1140 |
that override aspect-ratio. */ |
| 1141 |
/* no-prefix */ |
| 1142 |
.extendify-quick-edit-image-grid { |
| 1143 |
@apply grid grid-cols-3 gap-[8px] mt-[12px]; |
| 1144 |
/* No inner scroll — the WP Modal body already scrolls. Two |
| 1145 |
scrollbars (modal + grid) was confusing and trapped the user |
| 1146 |
on the inner scroller while the outer modal still had room. */ |
| 1147 |
} |
| 1148 |
/* no-prefix */ |
| 1149 |
.extendify-quick-edit-image-grid-item-credit { |
| 1150 |
@apply pointer-events-none absolute bottom-0 left-0 right-0 px-[8px] py-[6px] text-[11px] leading-[1.3] text-white opacity-0; |
| 1151 |
background: linear-gradient( |
| 1152 |
to top, |
| 1153 |
rgba(0, 0, 0, 0.65) 0%, |
| 1154 |
rgba(0, 0, 0, 0) 100% |
| 1155 |
); |
| 1156 |
transition: opacity 0.15s ease; |
| 1157 |
} |
| 1158 |
/* no-prefix */ |
| 1159 |
.extendify-quick-edit-image-grid-item:hover |
| 1160 |
.extendify-quick-edit-image-grid-item-credit, |
| 1161 |
.extendify-quick-edit-image-grid-item:focus-visible |
| 1162 |
.extendify-quick-edit-image-grid-item-credit { |
| 1163 |
@apply opacity-100; |
| 1164 |
} |
| 1165 |
/* no-prefix */ |
| 1166 |
.extendify-quick-edit-image-grid-item-credit a { |
| 1167 |
@apply pointer-events-auto text-inherit underline; |
| 1168 |
} |
| 1169 |
/* no-prefix */ |
| 1170 |
.extendify-quick-edit-image-attribution { |
| 1171 |
@apply mt-[12px] pt-[10px] border-t border-t-gray-300 text-[11px] text-gray-600; |
| 1172 |
} |
| 1173 |
/* no-prefix */ |
| 1174 |
.extendify-quick-edit-image-attribution a { |
| 1175 |
@apply text-inherit underline; |
| 1176 |
} |
| 1177 |
/* no-prefix */ |
| 1178 |
.extendify-quick-edit-image-grid-item { |
| 1179 |
/* min-h-0: defeat WP button-style min-height rules */ |
| 1180 |
@apply relative w-full min-h-0 cursor-pointer overflow-hidden rounded-[6px] border-0 bg-transparent p-0; |
| 1181 |
transition: transform 0.12s ease; |
| 1182 |
} |
| 1183 |
/* no-prefix */ |
| 1184 |
.extendify-quick-edit-image-grid-item::before { |
| 1185 |
@apply block pt-[100%]; |
| 1186 |
content: ""; |
| 1187 |
} |
| 1188 |
/* no-prefix */ |
| 1189 |
.extendify-quick-edit-image-grid-item:hover:not(:disabled) { |
| 1190 |
@apply scale-[1.02]; |
| 1191 |
} |
| 1192 |
/* no-prefix */ |
| 1193 |
.extendify-quick-edit-image-grid-item:disabled { |
| 1194 |
@apply cursor-not-allowed; |
| 1195 |
} |
| 1196 |
/* no-prefix */ |
| 1197 |
.extendify-quick-edit-image-grid-item img { |
| 1198 |
@apply absolute inset-0 block h-full w-full object-cover; |
| 1199 |
} |
| 1200 |
/* no-prefix */ |
| 1201 |
.extendify-quick-edit-image-grid-item-overlay { |
| 1202 |
@apply absolute inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.45)]; |
| 1203 |
} |
| 1204 |
|
| 1205 |
/* Reduced motion (a11y M7). Stop the infinite upload spinner and drop the |
| 1206 |
hover outline's spring so it tracks instantly. All `!important`: the |
| 1207 |
surrounding environment ships a global `prefers-reduced-motion` reset |
| 1208 |
that forces a tiny non-zero duration (~0.01ms) onto everything, so these |
| 1209 |
must win over it — and over the base rules — for Extendify's own |
| 1210 |
reduced-motion intent to be authoritative rather than theme-dependent. |
| 1211 |
Selectors are page-level (the body-level fixed outline, wp.media's |
| 1212 |
modal), so they carry `no-prefix` like the originals. */ |
| 1213 |
@media (prefers-reduced-motion: reduce) { |
| 1214 |
/* no-prefix */ |
| 1215 |
.extendify-quick-edit-media-uploading .media-frame-content::before { |
| 1216 |
@apply animate-none!; |
| 1217 |
} |
| 1218 |
/* no-prefix */ |
| 1219 |
.extendify-quick-edit-hover-outline { |
| 1220 |
/* Shorthand, not transition-none — only the shorthand zeroes the |
| 1221 |
duration the global reset forces to 0.01ms. */ |
| 1222 |
transition: none !important; |
| 1223 |
} |
| 1224 |
} |
| 1225 |
|