PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | admin/css/metasync-dashboard.css +337 -63 2.6.6 → 2.7.2 View file →
@@ -9,9 +9,11 @@
9 9 [data-theme="dark"] {
10 10 --dashboard-bg: #0f1419;
11 11 --dashboard-card-bg: #1a1f26;
12 12 --dashboard-card-hover: #222831;
13 + --dashboard-input-bg: #22272e;
13 14 --dashboard-text-primary: #ffffff;
15 + --dashboard-text: #ffffff;
14 16 --dashboard-text-secondary: #9ca3af;
15 17 --dashboard-accent: #3b82f6;
16 18 --dashboard-accent-hover: #2563eb;
17 19 --dashboard-success: #10b981;
@@ -28,9 +30,11 @@
28 30 [data-theme="light"] {
29 31 --dashboard-bg: #f8f9fa;
30 32 --dashboard-card-bg: #ffffff;
31 33 --dashboard-card-hover: #f1f3f5;
34 + --dashboard-input-bg: #ffffff;
32 35 --dashboard-text-primary: #1a1f26;
36 + --dashboard-text: #1a1f26;
33 37 --dashboard-text-secondary: #6b7280;
34 38 --dashboard-accent: #3b82f6;
35 39 --dashboard-accent-hover: #2563eb;
36 40 --dashboard-success: #10b981;
@@ -123,8 +127,19 @@
123 127 border-left-color: var(--dashboard-error) !important;
124 128 background: rgba(239, 68, 68, 0.15) !important;
125 129 }
126 130
131 +/* Specific warning notice styling ONLY in plugin.
132 + Without this, warning notices fell through to the base .notice rule above
133 + (blue border-left, plain card background) because that rule is !important
134 + and the .notice-warning colours defined further down this file are not —
135 + !important always wins regardless of source order, so the warning never
136 + actually rendered yellow/amber. Mirrors the .notice-error override above. */
137 +.metasync-dashboard-wrap .notice.notice-warning {
138 + border-left-color: var(--dashboard-warning) !important;
139 + background: rgba(245, 158, 11, 0.15) !important;
140 +}
141 +
127 142 /* License mismatch notice specific styling ONLY in plugin */
128 143 .metasync-dashboard-wrap .notice:has(.dashicons-warning):before,
129 144 .metasync-dashboard-wrap .notice[class*="license"]:before,
130 145 .metasync-dashboard-wrap .notice[class*="mismatch"]:before {
@@ -283,8 +298,32 @@
283 298 .metasync-header[data-current-theme="dark"] .metasync-logo-light {
284 299 display: none;
285 300 }
286 301
302 +/* Light theme must explicitly show the light logo: its base state is
303 + display:none, and hiding the dark logo alone would leave no logo visible. */
304 +[data-theme="light"] .metasync-logo-light,
305 +.metasync-header[data-current-theme="light"] .metasync-logo-light {
306 + display: block;
307 +}
308 +
309 +/* Default (non-whitelabel) SA logo: invert on light theme so the white asset
310 + stays visible against a light background. Dark theme keeps the original. */
311 +[data-theme="light"] .metasync-logo-default,
312 +.metasync-header[data-current-theme="light"] .metasync-logo-default {
313 + filter: invert(1);
314 +}
315 +
316 +[data-theme="light"] .metasync-logo-default:hover,
317 +.metasync-header[data-current-theme="light"] .metasync-logo-default:hover {
318 + filter: invert(1) brightness(1.1) contrast(1.1);
319 +}
320 +
321 +[data-theme="dark"] .metasync-logo-default,
322 +.metasync-header[data-current-theme="dark"] .metasync-logo-default {
323 + filter: brightness(1) contrast(1);
324 +}
325 +
287 326 /* Page Title Section - Below Header */
288 327 .metasync-dashboard-wrap .metasync-page-title {
289 328 margin-bottom: 2rem;
290 329 padding: 0;
@@ -541,9 +580,10 @@
541 580 .metasync-dashboard-wrap .form-table {
542 581 background: transparent;
543 582 border: none;
544 583 border-collapse: separate;
545 - border-spacing: 0;
584 + /* Vertical spacing between each settings row (tr); horizontal stays 0 so th/td join */
585 + border-spacing: 0 12px;
546 586 width: 100%;
547 587 }
548 588
549 589 .metasync-dashboard-wrap .form-table th {
@@ -567,24 +607,9 @@
567 607 padding: 16px 20px;
568 608 vertical-align: top;
569 609 }
570 610
571 -.metasync-dashboard-wrap .form-table tr {
572 - margin-bottom: 16px;
573 -}
574 -
575 -.metasync-dashboard-wrap .form-table tr:not(:last-child) th,
576 -.metasync-dashboard-wrap .form-table tr:not(:last-child) td {
577 - border-bottom: none;
578 -}
579 -
580 -/* Simple, clean spacing between form table rows */
581 -.metasync-dashboard-wrap .form-table tr + tr th,
582 -.metasync-dashboard-wrap .form-table tr + tr td {
583 - padding-top: 24px;
584 -}
585 -
586 -/* Ensure proper internal padding for all cells */
611 +/* Each row is a self-contained card; vertical gaps come from border-spacing above */
587 612 .metasync-dashboard-wrap .form-table th,
588 613 .metasync-dashboard-wrap .form-table td {
589 614 padding: 16px 12px;
590 615 }
@@ -997,15 +1022,62 @@
997 1022 gap: 8px;
998 1023 font-size: 14px;
999 1024 }
1000 1025
1001 -.metasync-dashboard-wrap .button:hover,
1002 -.metasync-dashboard-wrap .button-primary:hover {
1026 +.metasync-dashboard-wrap .button .dashicons,
1027 +.metasync-dashboard-wrap .button-primary .dashicons,
1028 +.metasync-dashboard-wrap .button-secondary .dashicons {
1029 + font-size: 15px !important;
1030 + width: 15px !important;
1031 + height: 15px !important;
1032 + line-height: 15px !important;
1033 + margin: 0 !important;
1034 + vertical-align: middle;
1035 +}
1036 +
1037 +.metasync-dashboard-wrap .button:hover:not(:disabled):not([disabled]),
1038 +.metasync-dashboard-wrap .button-primary:hover:not(:disabled):not([disabled]) {
1003 1039 background: var(--dashboard-gradient-accent);
1004 1040 transform: translateY(-1px);
1005 1041 box-shadow: var(--dashboard-shadow);
1006 1042 }
1007 1043
1044 +/* Disabled state — needed because the base .button-primary rule keeps the
1045 + full gradient even when prop('disabled', true) is set. Without this,
1046 + the Sync Now button stays blue/purple while the cooldown countdown is
1047 + active (QA report).
1048 +
1049 + The .is-throttled class is the primary driver for the Sync Now cooldown
1050 + visual because earlier code in the AJAX callback chain calls
1051 + .prop('disabled', false) before our refresh runs, and in some browsers
1052 + that ordering can leave the disabled attribute off even though we set
1053 + it again afterwards. The class-based path doesn't depend on the
1054 + disabled attribute reaching the DOM and survives any prop juggling. */
1055 +.metasync-dashboard-wrap .button:disabled,
1056 +.metasync-dashboard-wrap .button-primary:disabled,
1057 +.metasync-dashboard-wrap .button-secondary:disabled,
1058 +.metasync-dashboard-wrap .button[disabled],
1059 +.metasync-dashboard-wrap .button-primary[disabled],
1060 +.metasync-dashboard-wrap .button-secondary[disabled],
1061 +.metasync-dashboard-wrap .button.is-throttled,
1062 +.metasync-dashboard-wrap .button-primary.is-throttled,
1063 +.metasync-dashboard-wrap .button-secondary.is-throttled {
1064 + /* Replace the blue/purple gradient with a solid grey. The base rule sets
1065 + `background: var(--dashboard-gradient-primary)`, so opacity + grayscale
1066 + alone left the button a dimmed blue (QA: "still blue on Elementor").
1067 + background/filter/pointer-events carry !important so a page-builder's
1068 + admin stylesheet (Elementor ships more than Divi) can't override them. */
1069 + background: #d5d7da !important;
1070 + background-image: none !important;
1071 + color: #6b7280 !important;
1072 + opacity: 0.7 !important;
1073 + cursor: not-allowed !important;
1074 + transform: none !important;
1075 + box-shadow: none !important;
1076 + filter: grayscale(100%) !important;
1077 + pointer-events: none !important;
1078 +}
1079 +
1008 1080 .metasync-dashboard-wrap .button-secondary {
1009 1081 background: transparent;
1010 1082 border: 1px solid var(--dashboard-border);
1011 1083 color: var(--dashboard-text-secondary);
@@ -1282,9 +1354,13 @@
1282 1354 border-color: var(--dashboard-error);
1283 1355 /* Removed translateY animation - was too distracting */
1284 1356 }
1285 1357
1286 -/* Connect Status Messages - Dashboard Style */
1358 +/* Connect Status Messages - Dashboard Style
1359 + The box holds two block children — .metasync-sa-connect-status-content and
1360 + .metasync-sa-connect-actions — so it must stack. As a row, the actions block
1361 + sized to its buttons while the content block (flex: 1, so flex-basis: 0) was
1362 + squeezed into whatever was left, wrapping the message one word per line. */
1287 1363 .metasync-dashboard-wrap .metasync-sa-connect-status,
1288 1364 .metasync-dashboard-wrap #sa-connect-status-message {
1289 1365 margin-top: 16px;
1290 1366 padding: 16px 20px;
@@ -1289,9 +1365,10 @@
1289 1365 margin-top: 16px;
1290 1366 padding: 16px 20px;
1291 1367 border-radius: 12px;
1292 1368 display: flex;
1293 - align-items: flex-start;
1369 + flex-direction: column;
1370 + align-items: stretch;
1294 1371 gap: 12px;
1295 1372 font-weight: 500;
1296 1373 /* Removed slideInNotice animation - was too distracting */
1297 1374 backdrop-filter: blur(10px);
@@ -1547,9 +1624,14 @@
1547 1624 }
1548 1625
1549 1626 /* Connect Status Message Content */
1550 1627 .metasync-dashboard-wrap .metasync-sa-connect-status-content {
1551 - flex: 1;
1628 + /* min-width: 0 keeps a long unbroken string (a pasted URL, a UUID) from
1629 + setting the flex item's min-content width and re-inflating the box. */
1630 + flex: 0 0 auto;
1631 + min-width: 0;
1632 + /* Spacing between content and actions is the container's gap now. */
1633 + margin-bottom: 0;
1552 1634 }
1553 1635
1554 1636 .metasync-dashboard-wrap .metasync-sa-connect-status-title {
1555 1637 font-weight: 600;
@@ -1554,11 +1636,15 @@
1554 1636 .metasync-dashboard-wrap .metasync-sa-connect-status-title {
1555 1637 font-weight: 600;
1556 1638 font-size: 15px;
1557 1639 margin-bottom: 8px;
1558 - display: flex;
1559 - align-items: center;
1560 - gap: 8px;
1640 + /* Block, not flex. WordPress core's emoji script rewrites the leading
1641 + emoji into <img class="emoji">; as a flex container that image became a
1642 + flex item of its own and, under align-items: center, floated to the
1643 + vertical middle of a wrapped title. Inline flow keeps it on the first
1644 + line and wraps with the text. The title string already carries the
1645 + separating space, so the old gap is not needed. */
1646 + display: block;
1561 1647 }
1562 1648
1563 1649 .metasync-dashboard-wrap .metasync-sa-connect-status-message {
1564 1650 font-size: 14px;
@@ -1563,14 +1649,14 @@
1563 1649 .metasync-dashboard-wrap .metasync-sa-connect-status-message {
1564 1650 font-size: 14px;
1565 1651 line-height: 1.5;
1566 1652 opacity: 0.9;
1567 - margin-bottom: 12px;
1653 + margin-bottom: 0;
1568 1654 }
1569 1655
1570 1656 /* Existing Connect Actions Container */
1571 1657 .metasync-dashboard-wrap .metasync-sa-connect-actions {
1572 - margin-top: 16px;
1658 + margin-top: 0;
1573 1659 display: flex;
1574 1660 gap: 12px;
1575 1661 flex-wrap: wrap;
1576 1662 align-items: center;
@@ -1647,10 +1733,8 @@
1647 1733
1648 1734 .metasync-dashboard-wrap .metasync-sa-connect-status {
1649 1735 padding: 16px;
1650 1736 margin-top: 12px;
1651 - flex-direction: column;
1652 - align-items: flex-start;
1653 1737 gap: 8px;
1654 1738 }
1655 1739
1656 1740 .metasync-dashboard-wrap .metasync-sa-connect-status-title {
@@ -2402,9 +2486,13 @@
2402 2486 /* Sync Log Header */
2403 2487 .sync-log-header {
2404 2488 display: flex;
2405 2489 justify-content: space-between;
2406 - align-items: flex-start;
2490 + /* Centred rather than flex-start: the title section used to be a two-line
2491 + * block (24px heading + description) that read fine top-aligned, but it is
2492 + * now a single line of copy sitting beside a 34px control row. Top-aligning
2493 + * a one-line block against that row leaves the text visibly high. */
2494 + align-items: center;
2407 2495 margin-bottom: 30px;
2408 2496 gap: 20px;
2409 2497 }
2410 2498
@@ -2411,14 +2499,11 @@
2411 2499 .sync-log-title-section {
2412 2500 flex: 1;
2413 2501 }
2414 2502
2415 -.sync-log-title-section h2 {
2416 - color: var(--dashboard-text-primary);
2417 - margin-bottom: 8px;
2418 - font-size: 24px;
2419 - font-weight: 600;
2420 -}
2503 +/* The card no longer renders an <h2> — the page title and description come
2504 + * from the layout header (render_layout_open), and the card carries only the
2505 + * 90-day retention hint. See admin/class-metasync-admin.php. */
2421 2506
2422 2507 /* Sync Log Filters */
2423 2508 .sync-log-filters {
2424 2509 flex-shrink: 0;
@@ -2424,34 +2509,73 @@
2424 2509 flex-shrink: 0;
2425 2510 display: flex;
2426 2511 align-items: center;
2427 2512 justify-content: flex-end;
2513 + gap: 10px;
2428 2514 }
2429 2515
2430 2516 .sync-filters-form {
2431 - display: grid;
2432 - grid-template-columns: 1fr 1fr;
2517 + display: flex !important;
2518 + flex-direction: row !important;
2519 + flex-wrap: nowrap;
2433 2520 gap: 12px;
2434 2521 align-items: center;
2435 - justify-content: end;
2436 - width: auto;
2437 - min-width: 280px;
2438 2522 }
2439 2523
2440 2524 /* Large screens - optimize filter spacing */
2441 2525 @media (min-width: 1200px) {
2442 2526 .sync-filters-form {
2443 - min-width: 320px;
2444 2527 gap: 16px;
2445 2528 }
2446 -
2529 +
2447 2530 .sync-filter-select {
2448 2531 min-width: 150px;
2449 2532 }
2450 2533 }
2451 2534
2535 +/*
2536 + * Changes Log toolbar — one shared height for the Clear Log button and the two
2537 + * filter selects.
2538 + *
2539 + * The button is styled inline in the renderer (padding:6px 14px, no height) and
2540 + * rendered ~29px, while the selects picked up WordPress core's own
2541 + * `select { min-height: 40px }` (wp-admin forms.css) on top of
2542 + * .sync-filter-select's padding — so the button sat noticeably shorter than the
2543 + * controls beside it.
2544 + *
2545 + * min-height is the load-bearing declaration here: core's min-height:40px beats
2546 + * a plain height:34px no matter how specific the selector, because min-height
2547 + * wins over height by definition. Setting both pins the row at 34px.
2548 + */
2549 +#metasync-clear-sync-log-btn,
2550 +.sync-log-filters .sync-filter-select {
2551 + height: 34px;
2552 + min-height: 34px;
2553 + box-sizing: border-box;
2554 +}
2555 +
2556 +/* Vertical padding is zeroed so the fixed height above is what the row sees;
2557 + * .sync-filter-select's own padding:8px 12px would otherwise fight it. The base
2558 + * padding-right (34px, reserving space for the chevron) is !important there and
2559 + * still applies. */
2560 +.sync-log-filters .sync-filter-select {
2561 + padding-top: 0;
2562 + padding-bottom: 0;
2563 + line-height: 32px;
2564 +}
2565 +
2566 +/* The button keeps its inline padding:6px 14px, which leaves a 20px content box
2567 + * inside the 34px border-box height — too short for a 32px line-height, so the
2568 + * label would sit off-centre. Flex centering makes line-height irrelevant. */
2569 +#metasync-clear-sync-log-btn {
2570 + display: inline-flex;
2571 + align-items: center;
2572 + justify-content: center;
2573 + line-height: 1;
2574 +}
2575 +
2452 2576 .sync-filter-select {
2453 - background: var(--dashboard-card-bg);
2577 + background-color: var(--dashboard-card-bg);
2454 2578 border: 1px solid var(--dashboard-border);
2455 2579 color: var(--dashboard-text-primary);
2456 2580 padding: 8px 12px;
2457 2581 border-radius: 6px;
@@ -2459,16 +2583,16 @@
2459 2583 width: 100%;
2460 2584 min-width: 130px;
2461 2585 transition: all 0.2s ease;
2462 2586 cursor: pointer;
2463 - appearance: none;
2464 - -webkit-appearance: none;
2465 - -moz-appearance: none;
2466 - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e");
2467 - background-repeat: no-repeat;
2468 - background-position: right 8px center;
2469 - background-size: 16px;
2470 - padding-right: 32px;
2587 + appearance: none !important;
2588 + -webkit-appearance: none !important;
2589 + -moz-appearance: none !important;
2590 + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d1d5db' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e") !important;
2591 + background-repeat: no-repeat !important;
2592 + background-position: right 10px center !important;
2593 + background-size: 14px !important;
2594 + padding-right: 34px !important;
2471 2595 white-space: nowrap;
2472 2596 box-sizing: border-box;
2473 2597 }
2474 2598
@@ -2494,8 +2618,39 @@
2494 2618 color: var(--dashboard-text-primary);
2495 2619 padding: 8px;
2496 2620 }
2497 2621
2622 +/* Type field dropdown arrow — Local Business Configuration */
2623 +.metasync-type-select-wrap {
2624 + position: relative;
2625 + display: inline-block;
2626 + width: 100%;
2627 + max-width: 400px;
2628 +}
2629 +
2630 +.metasync-type-select-wrap select {
2631 + appearance: none;
2632 + -webkit-appearance: none;
2633 + -moz-appearance: none;
2634 + padding-right: 36px;
2635 + width: 100%;
2636 + cursor: pointer;
2637 +}
2638 +
2639 +.metasync-type-select-wrap::after {
2640 + content: '';
2641 + position: absolute;
2642 + right: 12px;
2643 + top: 50%;
2644 + transform: translateY(-50%);
2645 + width: 16px;
2646 + height: 16px;
2647 + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e");
2648 + background-repeat: no-repeat;
2649 + background-size: contain;
2650 + pointer-events: none;
2651 +}
2652 +
2498 2653 /* Sync Log List */
2499 2654 .sync-log-list {
2500 2655 background: var(--dashboard-card-bg);
2501 2656 border-radius: 12px;
@@ -2611,13 +2766,20 @@
2611 2766 opacity: 0.6;
2612 2767 color: var(--dashboard-text-secondary);
2613 2768 }
2614 2769
2615 -.sync-log-empty h3 {
2770 +/* The empty-state heading is an <h2>: the card's own <h2> is gone, so the
2771 + * layout's <h1> is the only heading above it and an <h3> here would skip a
2772 + * level. Scoped through .metasync-dashboard-wrap to out-specify
2773 + * ".metasync-dashboard-wrap .dashboard-card h2" (0,3,1) above, which would
2774 + * otherwise impose its 1.5rem card-title sizing and flex display on what is
2775 + * a centred empty-state message. Sizing matches the previous <h3>. */
2776 +.metasync-dashboard-wrap .sync-log-empty h2 {
2616 2777 color: var(--dashboard-text-primary);
2617 - margin-bottom: 8px;
2778 + margin: 0 0 8px 0;
2618 2779 font-size: 18px;
2619 2780 font-weight: 600;
2781 + display: block;
2620 2782 }
2621 2783
2622 2784 .sync-log-empty p {
2623 2785 font-size: 14px;
@@ -2647,8 +2809,61 @@
2647 2809 align-items: center;
2648 2810 gap: 8px;
2649 2811 }
2650 2812
2813 +/*
2814 + * Rows-per-page selector on the Changes Log footer.
2815 + *
2816 + * .metasync-per-page-select is emitted by Metasync_Per_Page_Helper::render_selector()
2817 + * on four screens (Redirections, 404 Monitor, Changes Log, Media Library), but
2818 + * only the other three style it. On the Changes Log it fell through to the
2819 + * generic ".metasync-dashboard-wrap select" rule — width:100%, max-width:400px,
2820 + * padding:12px 16px — so a two-digit control rendered ~295px wide and 64px tall
2821 + * next to 34px dashboard controls.
2822 + *
2823 + * Scoped to .sync-log-pagination-info, a Changes Log-only wrapper, rather than
2824 + * to the shared class: the other three screens already size this control via
2825 + * their own rules (".wrap .tablenav select" for Redirections and 404 Monitor,
2826 + * "#metasync-image-library-form .tablenav select" for Media Library), and a
2827 + * shared rule here would be one more thing competing with them. Keeping it
2828 + * page-scoped means those screens are untouched by construction, not by
2829 + * winning a specificity tie.
2830 + *
2831 + * min-height pairs with height for the same reason as the toolbar rule above —
2832 + * core's select{min-height:40px} would otherwise inflate it back to 40px.
2833 + *
2834 + * Sizing only: admin/js/metasync-per-page.js drives behaviour off the element's
2835 + * data-* attributes, which CSS must not disturb.
2836 + */
2837 +.sync-log-pagination-info .metasync-per-page-select {
2838 + height: 34px;
2839 + min-height: 34px;
2840 + line-height: 32px;
2841 + padding: 0 28px 0 10px;
2842 + font-size: 13px;
2843 + width: auto;
2844 + border-radius: 6px;
2845 + box-sizing: border-box;
2846 + vertical-align: middle;
2847 + cursor: pointer;
2848 + appearance: none;
2849 + -webkit-appearance: none;
2850 + -moz-appearance: none;
2851 + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d1d5db' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3E");
2852 + background-repeat: no-repeat;
2853 + background-position: right 10px center;
2854 + background-size: 14px;
2855 +}
2856 +
2857 +/* Mirrors .sync-filter-select option. The rule above is the first to apply
2858 + * appearance:none to this control, and dark-theme <option> popups need their
2859 + * colours stated explicitly rather than inherited. */
2860 +.sync-log-pagination-info .metasync-per-page-select option {
2861 + background: var(--dashboard-card-bg);
2862 + color: var(--dashboard-text-primary);
2863 + padding: 8px;
2864 +}
2865 +
2651 2866 .sync-pagination-btn {
2652 2867 display: inline-flex;
2653 2868 align-items: center;
2654 2869 justify-content: center;
@@ -2700,16 +2915,16 @@
2700 2915 justify-content: center;
2701 2916 }
2702 2917
2703 2918 .sync-filters-form {
2704 - display: grid;
2705 - grid-template-columns: 1fr 1fr;
2919 + display: flex !important;
2920 + flex-direction: row !important;
2921 + flex-wrap: nowrap;
2706 2922 gap: 10px;
2923 + align-items: center;
2707 2924 justify-content: center;
2708 - min-width: 260px;
2709 - width: auto;
2710 2925 }
2711 -
2926 +
2712 2927 .sync-filter-select {
2713 2928 min-width: 120px;
2714 2929 font-size: 13px;
2715 2930 }
@@ -2741,16 +2956,20 @@
2741 2956 }
2742 2957
2743 2958 @media (max-width: 480px) {
2744 2959 .sync-filters-form {
2745 - display: grid;
2746 - grid-template-columns: 1fr 1fr;
2960 + display: flex !important;
2961 + flex-direction: row !important;
2962 + flex-wrap: nowrap;
2747 2963 gap: 8px;
2748 - min-width: 240px;
2749 2964 width: 100%;
2750 - max-width: 320px;
2751 2965 }
2752 2966
2967 + /* The vertical half of this padding is intentionally overridden by
2968 + * ".sync-log-filters .sync-filter-select" (0,2,0) above, which pins the
2969 + * toolbar row at 34px. Do NOT re-declare padding here under that
2970 + * descendant selector: it would tie on specificity, win on source order,
2971 + * and silently restore the misalignment this rule set exists to avoid. */
2753 2972 .sync-filter-select {
2754 2973 min-width: 110px;
2755 2974 font-size: 13px;
2756 2975 padding: 6px 8px;
@@ -3271,5 +3490,60 @@
3271 3490 font-size: 24px;
3272 3491 width: 24px;
3273 3492 height: 24px;
3274 3493 color: var(--dashboard-text-secondary);
3494 +}
3495 +
3496 +/* Dismiss button on plugin notices.
3497 + The dashboard script appends a .notice-dismiss to every notice inside
3498 + .metasync-dashboard-wrap, but the notice itself was never given a
3499 + positioning context, so WordPress core's `position: absolute` on the
3500 + button resolved against whatever ancestor happened to be positioned
3501 + instead of the notice box — landing the icon mid-text rather than in the
3502 + notice's own top-right corner. Anchor it explicitly to the notice. */
3503 +.metasync-dashboard-wrap .notice {
3504 + position: relative;
3505 +}
3506 +
3507 +.metasync-dashboard-wrap .notice-dismiss {
3508 + position: absolute;
3509 + top: 8px;
3510 + right: 8px;
3511 + width: 28px;
3512 + height: 28px;
3513 + display: flex;
3514 + align-items: center;
3515 + justify-content: center;
3516 +}
3517 +
3518 +/* WordPress core colours the dismiss icon a near-black grey (#1e1e1e), which
3519 + reads fine on the light theme but is effectively invisible against the dark
3520 + theme's card background. Give it a theme-aware colour instead. !important
3521 + because the icon's colour otherwise wins on specificity alone, the same way
3522 + the base .notice rule above needs !important to beat WP core.
3523 + `.metasync-dashboard-wrap[data-theme="dark"]` matches the common case: the
3524 + wrap carries data-theme itself (set server-side on page load). The plain
3525 + `[data-theme="dark"] .metasync-dashboard-wrap` descendant form is kept too,
3526 + since the in-page theme switcher additionally stamps data-theme on <html>
3527 + once the user toggles it, and either could be the one present. */
3528 +.metasync-dashboard-wrap[data-theme="dark"] .notice-dismiss::before,
3529 +[data-theme="dark"] .metasync-dashboard-wrap .notice-dismiss::before {
3530 + color: var(--dashboard-text-secondary) !important;
3531 +}
3532 +
3533 +.metasync-dashboard-wrap[data-theme="dark"] .notice-dismiss:hover::before,
3534 +.metasync-dashboard-wrap[data-theme="dark"] .notice-dismiss:focus::before,
3535 +[data-theme="dark"] .metasync-dashboard-wrap .notice-dismiss:hover::before,
3536 +[data-theme="dark"] .metasync-dashboard-wrap .notice-dismiss:focus::before {
3537 + color: var(--dashboard-text-primary) !important;
3538 +}
3539 +
3540 +/* Remove WordPress footer space on MetaSync admin pages. */
3541 +body:has(.metasync-dashboard-wrap) #wpbody-content,
3542 +body:has(.metasync-otto-debug) #wpbody-content {
3543 + padding-bottom: 0;
3544 +}
3545 +
3546 +body:has(.metasync-dashboard-wrap) #wpfooter,
3547 +body:has(.metasync-otto-debug) #wpfooter {
3548 + display: none;
3275 3549 }