/* ProcessImageLibrary — admin styles.
 *
 * Colors flow through AdminThemeUikit's --pw-* custom properties
 * so light / dark mode follows the theme automatically. Hard-coded
 * fallbacks (light-theme values) are kept in each var() so legacy
 * themes (Reno, Default) still render readable. */

/* Module design tokens — one source for repeated values. Namespaced --ml-*
   so they can't collide with the theme's --pw-* vars; on :root so body-level
   dialogs / popups (appended outside .ml-root) inherit them too. */
:root {
	--ml-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
	--ml-danger: #c33;        /* inline delete / armed-confirm red */
	--ml-confirm: #2a8a2a;    /* inline confirm green */
	/* Neutral row hover/active tint (the tag-manager tint, reused widely). */
	--ml-tint-hover: color-mix(in srgb, var(--pw-main-background, #f5f5f5) 60%, var(--pw-text-color, #000) 6%);
}

.ml-root {
	margin: 0 0 1rem;
}

/* Filter bar — InputfieldForm-rendered. The admin theme handles
   most layout, spacing and labels; module-specific overrides
   below tighten the rest. */
/* margin-top needs !important to beat two admin rules that otherwise push the
   filter box down from the bookmark tabs (gap + the active underline floating):
     body.modal .InputfieldForm { margin-top: 20px }   (modal iframe only)
     *+.uk-margin { margin-top: 20px !important }       (global, hence !important) */
.ml-filter-bar {
	margin: 0 !important;
}

/* Cap the tag-filter checkbox grid so a large tag pool stays
   usable instead of pushing the rest of the filter UI off-screen.
   Scrolls inside the fieldset when it overflows. */
.ml-filter-bar .Inputfield_tags .InputfieldContent {
	max-height: 14rem;
	overflow-y: auto;
}

/* The actions sub-fieldset carries skipLabelHeader on the PHP
   side; this hides any residual header chrome the admin theme
   might still emit so the wrapper is pure layout grouping with
   zero visual chrome. */
.ml-filter-bar .Inputfield_mlActions > .InputfieldHeader {
	display: none;
}

/* Action row: Apply + Reset pinned to the left edge, primary
   (Apply) first. The two buttons are hand-rendered UIkit buttons
   inside an InputfieldMarkup, wrapped in .ml-filter-actions. */
.ml-filter-actions {
	display: flex;
	justify-content: flex-start;
	align-items: center;
	gap: 0.5rem;
}

/* Module-settings cog. Sits absolute in the .ml-root so it lifts
   into the page-heading row regardless of where the admin theme
   draws that row. Adjust top offset if the theme's heading sits
   at a different height. */
.ml-root {
	position: relative;
}
.ml-config-link {
	position: absolute;
	top: -3rem;
	right: 0;
	z-index: 5;
}

/* Horizontal-scroll wrapper so the wide table degrades to a swipe
   on narrow viewports rather than blowing out the layout. */
.ml-table-scroll {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	margin-top: 1rem;
}

/* Table. The wrapper .ml-table also carries the .AdminDataTable
   class server-side so the admin theme's table chrome (font sizing,
   responsive helper, etc.) applies. Module-side rules below tighten
   the rest. */
.ml-table {
	width: 100%;
	border-collapse: collapse;
	color: var(--pw-text-color, inherit);
}
.ml-table thead th {
	white-space: nowrap;
	color: var(--pw-muted-color, #6c8dae);
	border-bottom: 1px solid var(--pw-border-color, #ddd);
}
/* Sortable-header colours and arrows come from AdminThemeUikit's
   own .tablesorter-* rules — we just emit the class names it
   targets (see renderSortableHeader). Markup mirrors the native
   PW shape: <th class="tablesorter-headerAsc/Desc/UnSorted"> →
   <a href> (our click wrapper) → <div class="tablesorter-header-
   inner"> (the theme-styled element).
   The anchor is neutralised so AdminThemeUikit's global
   "a { color: var(--pw-main-color) }" doesn't paint the header in
   the accent. The div inside picks up colour + arrow from the
   theme via the tablesorter-header* state classes on the <th>. */
.ml-th-sortable a,
.ml-th-sortable a:link,
.ml-th-sortable a:visited,
.ml-th-sortable a:hover,
.ml-th-sortable a:focus,
.ml-th-sortable a:active {
	color: inherit;
	text-decoration: none;
	display: block;
}
.ml-table tbody tr {
	border-bottom: 1px solid var(--pw-border-color, #eee);
}
/* Row-level hover suppressed on purpose — cell-level hover for
   editable cells (.ml-cell-editable:hover, further down) is the
   only highlight we want, so the user's eye lands on the click
   target, not the whole row. */
/* Thumb cell — dimensions driven by --ml-thumb-longer (keep-ratio
   path) and --ml-thumb-w / --ml-thumb-h (crop path), set on
   .ml-root from the module config. Keep-ratio variant caps BOTH
   axes to the same longer-side value so portraits cap on height
   and landscapes cap on width — aspect always preserved. Crop
   variant fixes the visible box to the exact configured W × H
   and lets the browser handle the center crop via object-fit, so
   the source can stay PW's lazily-generated 260 px admin
   variation. */
.ml-cell-thumb img {
	display: block;
	max-width: calc(var(--ml-thumb-longer, 100px) * var(--ml-thumb-scale, 1));
	max-height: calc(var(--ml-thumb-longer, 100px) * var(--ml-thumb-scale, 1));
	width: auto;
	height: auto;
	border: 1px solid var(--pw-border-color, #e5e5e5);
	background: var(--pw-inputs-background, #fafafa);
}
.ml-cell-thumb img.ml-thumb-crop {
	width: calc(var(--ml-thumb-w, 120px) * var(--ml-thumb-scale, 1));
	height: calc(var(--ml-thumb-h, 80px) * var(--ml-thumb-scale, 1));
	max-width: none;
	max-height: none;
	object-fit: cover;
}
/* Clickable when host page is editable — JS opens the PW image
   editor in a modal iframe. data-file-hash is only set in that case
   (and is the matching key for finding the gridImage item there). */
.ml-cell-thumb[data-file-hash] {
	cursor: pointer;
}
.ml-cell-thumb[data-file-hash] img:hover {
	outline: 2px solid var(--pw-main-color, #5b8cd6);
}
/* Thumb cell — pinned to the configured max thumb dimension so all
   rows share the same column width regardless of each image's actual
   orientation. position:relative makes it the anchor for the Replace
   button overlay. */
.ml-cell-thumb {
	position: relative;
	width: calc(var(--ml-thumb-cell-width, 100px) * var(--ml-thumb-scale, 1));
}
/* Per-row action icons (Replace + Delete): pinned to opposite top
   corners of the thumb cell so finger-tap on mobile doesn't risk
   hitting the wrong action — Delete sits on the LEFT, Replace on
   the RIGHT, the thumb itself between them. Both fade in on row
   hover, sized at 32 px for a comfortable touch target without
   eating the thumbnail. */
.ml-replace-btn,
.ml-delete-btn {
	position: absolute;
	top: 4px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	padding: 0;
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 3px;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, #333);
	font-size: 1rem;
	line-height: 1;
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.12s ease-out;
}
.ml-delete-btn  { left: 4px; }
.ml-replace-btn { right: 4px; }
.ml-table tbody tr:hover .ml-replace-btn,
.ml-table tbody tr:hover .ml-delete-btn,
.ml-card:hover .ml-replace-btn,
.ml-card:hover .ml-delete-btn,
.ml-card:focus-within .ml-replace-btn,
.ml-card:focus-within .ml-delete-btn,
.ml-replace-btn:focus-visible,
.ml-delete-btn:focus-visible {
	opacity: 1;
}
.ml-replace-btn:hover { color: var(--pw-main-color, #5b8cd6); }
.ml-delete-btn:hover  { color: var(--ml-danger); }

/* Picker mode: the library embedded (modal iframe) to choose existing
   image(s) for a page's image field. Selection is via the normal checkboxes
   (table + masonry); a "Use selected" bar sits above and below the results. */
.ml-picker-hint {
	margin: 0 0 0.7rem;
	color: var(--pw-muted-color, #6c8dae);
}
.ml-picker-bar {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.6rem 0;
}
.ml-picker-bar--top {
	position: sticky;
	top: 0;
	z-index: 5;
	background: var(--pw-main-background, #fff);
	border-bottom: 1px solid var(--pw-border-color, #e0e0e0);
	/* Sit 1px below the filter box so its 1px outline (drawn just outside the
	   box) stays visible instead of being painted over by this bar's bg. */
	margin-top: 1px;
}
.ml-picker-bar--bottom {
	border-top: 1px solid var(--pw-border-color, #e0e0e0);
}
.ml-root--picker .ml-card {
	position: relative;
}
/* Whole tile is the click target in the picker (click = select). */
.ml-root--picker .ml-card .ml-cell-thumb {
	cursor: pointer;
}
/* Clear selected state on the tile so a click reads as a pick. */
.ml-root--picker .ml-card:has(.ml-select-row:checked) {
	outline: 3px solid var(--pw-main-color, #eb1d61);
	outline-offset: -3px;
}
.ml-root--picker .ml-card:has(.ml-select-row:checked) .ml-thumb {
	filter: brightness(0.92);
}
/* Selection checkbox overlay on a masonry tile — bottom-left, clear of the
   replace / delete icons (top) and the duplicate badge (bottom-right). Just
   the checkbox on a minimal backdrop: no padding, no rounded corners. */
.ml-card-select {
	position: absolute;
	bottom: 6px;
	left: 6px;
	z-index: 3;
	margin: 0;
	padding: 0;
	border-radius: 0;
	line-height: 0;
	background: rgba(255, 255, 255, 0.85);
	cursor: pointer;
}
.ml-card-select .ml-select-row {
	display: block;
	width: 20px;
	height: 20px;
	margin: 0;
	cursor: pointer;
}
/* Normal masonry view (NOT the picker, where it's always visible): the
   checkbox hover-reveals exactly like the replace/delete buttons, but stays
   visible once checked so the selection reads at a glance. */
.ml-root:not(.ml-root--picker) .ml-card .ml-card-select {
	opacity: 0;
	transition: opacity 0.12s ease-out;
}
.ml-root:not(.ml-root--picker) .ml-card:hover .ml-card-select,
.ml-root:not(.ml-root--picker) .ml-card:focus-within .ml-card-select,
.ml-root:not(.ml-root--picker) .ml-card:has(.ml-select-row:checked) .ml-card-select {
	opacity: 1;
}
/* Selected-tile outline, same affordance as the picker. */
.ml-root:not(.ml-root--picker) .ml-card:has(.ml-select-row:checked) {
	outline: 3px solid var(--pw-main-color, #eb1d61);
	outline-offset: -3px;
}

/* Duplicate indicator pill ("N", bottom-right of the thumbnail). On a
   cluster head row it doubles as the expand/collapse toggle (.ml-dup-toggle
   below adds the cursor + caret). */
.ml-dup-count {
	position: absolute;
	top: 4px;
	right: 4px;
	background: var(--pw-main-color, #eb1d61);
	color: #fff;
	font-size: 1rem;
	font-weight: 600;
	line-height: 1;
	padding: 5px 9px;
	border-radius: 11px;
}
.ml-cell-thumb .ml-dup-count {
	top: auto;
	bottom: 4px;
}

/* Duplicates filter accordion: the "N×" indicator on a cluster head row is
   a button that expands / collapses the hidden copy rows. Larger than the
   plain badge so it reads as the tap target it is. */
/* Same pill as the plain indicator (size lives on .ml-dup-count); the
   toggle only adds the cursor + the expand/collapse caret. */
.ml-dup-toggle {
	cursor: pointer;
}
.ml-dup-toggle:hover,
.ml-dup-toggle:focus-visible {
	filter: brightness(1.12);
	outline: none;
}
/* Caret hint: ▸ collapsed, ▾ expanded. */
.ml-dup-toggle::after {
	content: "\025B8";
	margin-left: 4px;
	font-size: 1.8em;
	line-height: 0;
	vertical-align: -2px;
}
.ml-dup-toggle.ml-dup-open::after {
	content: "\025BE";
}
/* Expanded duplicate cluster: tint the revealed copy rows — and the head row
   while it's open — so the group reads as one block. Applied to the cells (not
   the <tr>) so it sits above the theme's own row background. The tint is the
   theme background nudged ~6% toward the text colour (same color-mix pattern as
   the hover rows), so it adapts to light / dark instead of a hardcoded grey. */
.ml-table tbody tr.ml-dup-member:not([hidden]) > td,
.ml-table tbody tr.ml-dup-head:has(.ml-dup-toggle.ml-dup-open) > td {
	background: color-mix(in srgb, var(--pw-main-background, #fff) 94%, var(--pw-text-color, #000) 6%);
}
/* Fade-out animation for rows being deleted — JS adds the class
   right before the row is removed from the DOM so the disappear is
   visible instead of jarring. */
.ml-row-deleting {
	transition: opacity 0.2s ease-out;
	opacity: 0;
}
/* ------------------------------------------------------------------
   SHARED DIALOG CHROME
   ------------------------------------------------------------------
   Four "panel" dialogs share the exact same outer styling: native
   <dialog>, same border, padding, background, shadow and backdrop;
   same <header> / <footer> rhythm. Per-dialog rules below only set
   the WIDTH (each dialog has a different content density). The
   image-modal is intentionally not in here — it's a near-fullscreen
   iframe shell with its own bar instead of a header / footer pair —
   but it shares the backdrop colour so the overlay matches. */
.ml-popup-editor,
.ml-columns-dialog,
.ml-bulk-result-dialog,
.ml-delete-confirm,
.ml-bookmark-dialog,
.ml-usage-dialog,
.ml-collections-dialog {
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 4px;
	padding: 1.25rem;
	max-height: 90vh;
	overflow: auto;
	background: var(--pw-blocks-background, #fff);
	color: var(--pw-text-color, inherit);
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
}
.ml-popup-editor::backdrop,
.ml-columns-dialog::backdrop,
.ml-bulk-result-dialog::backdrop,
.ml-delete-confirm::backdrop,
.ml-bookmark-dialog::backdrop,
.ml-image-modal::backdrop,
.ml-cluster-modal::backdrop,
.ml-usage-dialog::backdrop,
.ml-collections-dialog::backdrop {
	background: var(--pw-modal-color, rgba(0, 0, 0, 0.4));
}
.ml-popup-editor > header,
.ml-columns-dialog > header,
.ml-bulk-result-dialog > header,
.ml-delete-confirm > header,
.ml-bookmark-dialog > header,
.ml-usage-dialog > header,
.ml-collections-dialog > header {
	font-weight: 600;
	font-size: 1.05em;
	margin: 0 0 0.75rem;
	padding: 0;
	border: 0;
}
.ml-popup-editor > footer,
.ml-columns-dialog > footer,
.ml-bulk-result-dialog > footer,
.ml-delete-confirm > footer,
.ml-bookmark-dialog > footer,
.ml-usage-dialog > footer,
.ml-collections-dialog > footer {
	display: flex;
	justify-content: flex-end;
	gap: 0.5rem;
	margin-top: 1rem;
	padding: 0;
	border: 0;
}
/* Per-dialog widths — each dialog's content density gets its own
   target. min(_, 92vw) keeps things sane on narrow viewports. */
.ml-popup-editor       { width: min(720px, 92vw); }
.ml-bulk-result-dialog { width: min(640px, 92vw); }
.ml-delete-confirm     { width: min(480px, 92vw); }
.ml-columns-dialog     { width: min(420px, 92vw); }
.ml-bookmark-dialog    { width: min(420px, 92vw); }
.ml-usage-dialog       { width: min(480px, 92vw); }
.ml-collections-dialog { width: min(460px, 92vw); }

/* Delete-confirm content bits (preview list + warning line) — the
   structural chrome is inherited from the shared block above. */
.ml-delete-confirm-list,
.ml-delete-confirm-solo {
	margin: 0.5rem 0 0;
	font-family: var(--ml-mono);
	font-size: 0.95em;
	color: var(--pw-text-color, #333);
}
.ml-delete-confirm-list {
	padding: 0 0 0 1.25rem;
	max-height: 180px;
	overflow: auto;
}
.ml-delete-confirm-list-more {
	margin: 0.25rem 0 0;
	padding-left: 1.25rem;
	font-family: var(--ml-mono);
	font-size: 0.95em;
	color: var(--pw-muted-color, #666);
}
/* Single-file delete — bullet on a one-item list is daft. Render
   the basename indented as a code-style line instead. */
.ml-delete-confirm-solo {
	padding: 0.35rem 0.6rem;
	background: var(--pw-inputs-background, #f4f4f4);
	border-radius: 3px;
	display: inline-block;
}
/* Tail block under the file chip — a single slot that resolves to
   either the "still referenced in" list (when refs exist) or the
   bare "this cannot be undone" warning (when none do). Both take
   the same red 600-weight heading style so the urgency reads the
   same regardless of which one fills the slot. */
.ml-delete-confirm-usage {
	margin-top: 1rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--pw-border-color, #eee);
}
.ml-delete-confirm-usage-h {
	margin: 0 0 0.4rem;
	font-size: 0.95em;
	font-weight: 600;
	color: var(--pw-error-inline-text-color, #c33);
}
/* Same rhythm as the red delete warning heading, but neutral — the
   post-rename summary is a success confirmation, not a warning. */
.ml-rename-done-usage-h {
	margin: 0 0 0.4rem;
	font-size: 0.95em;
	font-weight: 600;
	color: var(--pw-text-color, #333);
}
.ml-delete-confirm-usage-list,
.ml-delete-confirm-usage-list ul {
	margin: 0;
	padding: 0 0 0 1.25rem;
	font-size: 0.95em;
	color: var(--pw-text-color, #333);
	list-style: disc;
}
.ml-delete-confirm-usage-list {
	max-height: 220px;
	overflow: auto;
}
/* Grouped variant (batch deletes): the per-file <li> carries the
   filename as a non-bulleted header, refs nest below it. */
.ml-delete-confirm-usage-list-grouped {
	list-style: none;
	padding-left: 0;
}
.ml-delete-confirm-usage-list-grouped > li {
	margin-bottom: 0.4rem;
}
.ml-delete-confirm-usage-list-grouped > li > strong {
	display: inline-block;
	margin-bottom: 0.15rem;
	font-family: var(--ml-mono);
	font-weight: 600;
}
/* Drop-target highlight: the single row currently under the file
   drag pointer. Uses the same soft tint as inline-edit hover so the
   visual language matches the rest of the editor. */
.ml-row-drop-target > td {
	background: var(--pw-notes-background, #fffbe6);
}
/* While the replace request is in flight, dim the row so it's
   obviously busy. JS adds + removes the class around the fetch. */
.ml-row-uploading > td {
	opacity: 0.55;
	pointer-events: none;
}
.ml-cell-page a {
	font-weight: 500;
}
.ml-table code {
	font-size: 0.85em;
	word-break: break-all;
	color: var(--pw-code-color, inherit);
}
.ml-cell-desc,
.ml-cell-textarea {
	min-width: 14rem;
	max-width: 28rem;
}
/* Clamp the VISIBLE height of textarea-backed cell values (description
   + FieldtypeTextarea customs) to a few lines with an ellipsis so long
   prose can't stretch the row. The full value still lives in the DOM
   inside this box — the inline editor reads td.textContent, so it
   always opens with the COMPLETE text regardless of this cap. Line
   count is tunable via --ml-clamp-lines (≈150 chars at the column's
   width with the default of 3). */
.ml-clamp {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: var(--ml-clamp-lines, 3);
	line-clamp: var(--ml-clamp-lines, 3);
	overflow: hidden;
}
/* Text-typed custom subfields — short prose. Narrower than textarea
   so columns don't sprawl, but wide enough that "credit" / "summary"
   one-liners stay readable instead of wrapping every word. */
.ml-cell-text {
	min-width: 8rem;
	max-width: 18rem;
}
.ml-cell-nowrap {
	white-space: nowrap;
}

/* Non-applicable custom-field cell — the row's image field doesn't
   host this custom, so the cell can't be edited. Muted + not-allowed
   cursor signal that visually; a title attribute names the reason. */
.ml-cell-na {
	color: var(--pw-muted-color, #c4c4c4);
	cursor: not-allowed;
	text-align: center;
	user-select: none;
}

/* Inline edit */
.ml-cell-editable {
	/* pointer (not text) so iOS Safari fires click events on non-native
	   interactive elements — without this, taps just trigger :hover. */
	cursor: pointer;
	position: relative;
	/* Eliminate the 300ms tap delay on iOS and prevent double-tap zoom on cells. */
	touch-action: manipulation;
}
.ml-cell-editable:hover {
	background: var(--pw-notes-background, #fffbe6);
}
.ml-cell-editable:empty::before {
	content: "—";
	color: var(--pw-muted-color, #ccc);
}
/* Typed custom cells (checkbox / date / select / page) display their
   value and open the native per-image editor on click — same hover
   affordance + "—" empty placeholder as the inline-editable cells. */
.ml-cell-native[data-file-hash] {
	cursor: pointer;
	touch-action: manipulation;
}
.ml-cell-native[data-file-hash]:hover {
	background: var(--pw-notes-background, #fffbe6);
}
.ml-cell-native:empty::before {
	content: "—";
	color: var(--pw-muted-color, #ccc);
}
.ml-editing {
	background: var(--pw-notes-background, #fffce8);
	padding: 4px;
}
.ml-cell-input {
	width: 100%;
	box-sizing: border-box;
	font: inherit;
	border: 1px solid var(--pw-border-color, #888);
	border-radius: 2px;
	padding: 4px 6px;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, inherit);
}
textarea.ml-cell-input {
	resize: vertical;
	min-height: 4.5em;
}
.ml-cell-saving {
	opacity: 0.6;
}
.ml-cell-saved {
	background: var(--pw-alert-success, #e6f7e1) !important;
	transition: background 0.3s ease-out;
}
.ml-cell-error {
	background: var(--pw-alert-danger, #fde2e2) !important;
	transition: background 0.3s ease-out;
}

/* AJAX re-render: dim the results region while a fetch is in flight,
   and block clicks so users don't pile up overlapping requests. */
.ml-results.ml-loading {
	opacity: 0.5;
	pointer-events: none;
	transition: opacity 0.15s ease-out;
}

/* Bulk-selection checkbox column — narrow, vertical-centered. */
.ml-cell-select {
	width: 2rem;
	text-align: center;
	vertical-align: middle !important;
	cursor: default;
	/* Drop only the left padding so the checkbox sits flush with the table
	   edge. !important to beat the AdminDataTable theme's td/th padding,
	   same as vertical-align above. */
	padding-left: 0 !important;
}
.ml-cell-select input[type="checkbox"] {
	cursor: pointer;
}

/* Tag editor — checkbox grid for useTags=2 whitelist mode. */
.ml-tag-editor {
	display: flex;
	flex-direction: column;
	gap: 6px;
	background: var(--pw-blocks-background, #fff);
	border: 1px solid var(--pw-border-color, #888);
	border-radius: 2px;
	padding: 6px;
	min-width: 12rem;
	color: var(--pw-text-color, inherit);
}
.ml-tag-editor-list {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 12px;
	max-height: 12rem;
	overflow-y: auto;
}
.ml-tag-editor-list label {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	white-space: nowrap;
	cursor: pointer;
	font-weight: normal;
}
.ml-tag-editor-done {
	align-self: flex-start;
	padding: 2px 10px;
	cursor: pointer;
}

/* Batch-edit mode picker (Add default, Replace). Shown only when the
   cell being edited belongs to the active selection. Commit happens on
   blur or click-outside-cell, not on radio click. */
.ml-batch-mode {
	display: flex;
	gap: 12px;
	margin-top: 6px;
}
.ml-batch-mode label {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	cursor: pointer;
	font-weight: normal;
}
.ml-batch-mode input[type="radio"] {
	margin-top: -2px;
}

/* Pagination row — flex with two groups: left (summary + prev/next),
   right (per-page picker + columns-dialog icon). The right group's
   gap is wider than the row gap so the icon doesn't crowd the
   picker. margin-left:auto pins the right group to the far edge. */
.ml-pagination {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin: 1rem 0 0;
}
.ml-pagination-left {
	display: flex;
	gap: 1rem;
	align-items: center;
}
.ml-pagination-right {
	display: flex;
	gap: 1.25rem;
	align-items: center;
	margin-left: auto;
}
.ml-pagination-summary {
	color: var(--pw-muted-color, #555);
}
.ml-pagination-link {
	text-decoration: none;
}
.ml-columns-toggle {
	font-size: 1rem;
	line-height: 1;
	color: var(--pw-muted-color, #555);
	background: transparent;
	border: 0;
	/* padding: 0.25rem 0.4rem; */
	cursor: pointer;
}
.ml-columns-toggle:hover,
.ml-columns-toggle:focus {
	color: var(--pw-text-color, #111);
	/* Beat AdminThemeUikit's "a:hover { text-decoration: underline }"
	   — class+pseudo (0,2,0) outweighs type+pseudo (0,1,1), so no
	   !important needed. */
	text-decoration: none;
}
.ml-columns-toggle .fa {
	display: block;
}
.ml-page-size {
	color: var(--pw-muted-color, #555);
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
}
.ml-page-size-picker {
	font: inherit;
	padding: 0.1rem 0.3rem;
}

/* Thumbnail size slider — sits in the pagination-right toolbar next to
   the per-page picker. The empty span is turned into a jQuery-UI slider
   by the JS (same widget + theme as PW's own InputfieldImage size
   slider), so its look comes from the admin's jQuery-UI CSS; we only
   set the track width here. Scales the table thumbnails live via
   --ml-thumb-scale (per-user, persisted). */
.ml-thumb-size {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	color: var(--pw-muted-color, #555);
}
.ml-thumb-size .fa {
	font-size: 0.95rem;
}
.ml-thumb-size-slider.ui-slider {
	width: 100px;   /* matches InputfieldImage's native size slider */
	margin: 0;
}

/* View-mode toggle — three bare icons (grid / masonry / table). Sizes and
   spacing mirror InputfieldImage's native list-mode toggle: ~1rem icons
   (no 20px override) and a ~0.75em gap between them (native uses
   padding-right: 0.75em on each .InputfieldImageListToggle). The active
   mode renders in the admin's primary colour; the inactive one is muted
   and brightens on hover. (Native instead colours all three alike and
   dims the active with opacity: 0.5 — kept our colour model for a clearer
   active cue.) */
.ml-view-toggle {
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
}
.ml-view-btn {
	font-size: 1rem;
	line-height: 1;
	color: var(--pw-muted-color, #555);
	background: transparent;
	border: 0;
	cursor: pointer;
	text-decoration: none;
}
.ml-vicon {
	/* Mask-painted icon shared by the view toggle, the columns opener and the
	   bookmarks/collections "Manage" gear. Each modifier bundles the official
	   FontAwesome 6 glyph as a CSS mask, so all of them render identically on
	   EVERY core regardless of its FontAwesome version (stable cores ship
	   FA4.7, the dev core FA6). background-color: currentColor makes each icon
	   follow the colour its host link sets (muted / hover / active). Sources:
	   FA6 free-solid table-cells, chart-simple, table-list, table-columns,
	   sliders — URL-encoded. */
	display: inline-block;
	vertical-align: middle;
	width: 1em;
	height: 1em;
	background-color: currentColor;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-size: contain;
	mask-size: contain;
}
.ml-vicon-grid {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M64%2032C28.7%2032%200%2060.7%200%2096V416c0%2035.3%2028.7%2064%2064%2064H448c35.3%200%2064-28.7%2064-64V96c0-35.3-28.7-64-64-64H64zm88%2064v64H64V96h88zm56%200h88v64H208V96zm240%200v64H360V96h88zM64%20224h88v64H64V224zm232%200v64H208V224h88zm64%200h88v64H360V224zM152%20352v64H64V352h88zm56%200h88v64H208V352zm240%200v64H360V352h88z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M64%2032C28.7%2032%200%2060.7%200%2096V416c0%2035.3%2028.7%2064%2064%2064H448c35.3%200%2064-28.7%2064-64V96c0-35.3-28.7-64-64-64H64zm88%2064v64H64V96h88zm56%200h88v64H208V96zm240%200v64H360V96h88zM64%20224h88v64H64V224zm232%200v64H208V224h88zm64%200h88v64H360V224zM152%20352v64H64V352h88zm56%200h88v64H208V352zm240%200v64H360V352h88z'/%3E%3C/svg%3E");
}
.ml-vicon-masonry {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3E%3Cpath%20d='M160%2080c0-26.5%2021.5-48%2048-48h32c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H208c-26.5%200-48-21.5-48-48V80zM0%20272c0-26.5%2021.5-48%2048-48H80c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H48c-26.5%200-48-21.5-48-48V272zM368%2096h32c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H368c-26.5%200-48-21.5-48-48V144c0-26.5%2021.5-48%2048-48z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3E%3Cpath%20d='M160%2080c0-26.5%2021.5-48%2048-48h32c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H208c-26.5%200-48-21.5-48-48V80zM0%20272c0-26.5%2021.5-48%2048-48H80c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H48c-26.5%200-48-21.5-48-48V272zM368%2096h32c26.5%200%2048%2021.5%2048%2048V432c0%2026.5-21.5%2048-48%2048H368c-26.5%200-48-21.5-48-48V144c0-26.5%2021.5-48%2048-48z'/%3E%3C/svg%3E");
	/* flip the bars to hang from the top — reads as the masonry layout */
	transform: rotate(180deg);
}
.ml-vicon-table {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%2096C0%2060.7%2028.7%2032%2064%2032H448c35.3%200%2064%2028.7%2064%2064V416c0%2035.3-28.7%2064-64%2064H64c-35.3%200-64-28.7-64-64V96zm64%200v64h64V96H64zm384%200H192v64H448V96zM64%20224v64h64V224H64zm384%200H192v64H448V224zM64%20352v64h64V352H64zm384%200H192v64H448V352z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%2096C0%2060.7%2028.7%2032%2064%2032H448c35.3%200%2064%2028.7%2064%2064V416c0%2035.3-28.7%2064-64%2064H64c-35.3%200-64-28.7-64-64V96zm64%200v64h64V96H64zm384%200H192v64H448V96zM64%20224v64h64V224H64zm384%200H192v64H448V224zM64%20352v64h64V352H64zm384%200H192v64H448V352z'/%3E%3C/svg%3E");
}
.ml-vicon-columns {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%2096C0%2060.7%2028.7%2032%2064%2032H448c35.3%200%2064%2028.7%2064%2064V416c0%2035.3-28.7%2064-64%2064H64c-35.3%200-64-28.7-64-64V96zm64%2064V416H224V160H64zm384%200H288V416H448V160z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%2096C0%2060.7%2028.7%2032%2064%2032H448c35.3%200%2064%2028.7%2064%2064V416c0%2035.3-28.7%2064-64%2064H64c-35.3%200-64-28.7-64-64V96zm64%2064V416H224V160H64zm384%200H288V416H448V160z'/%3E%3C/svg%3E");
}
.ml-vicon-sliders {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%20416c0%2017.7%2014.3%2032%2032%2032l54.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48L480%20448c17.7%200%2032-14.3%2032-32s-14.3-32-32-32l-246.7%200c-12.3-28.3-40.5-48-73.3-48s-61%2019.7-73.3%2048L32%20384c-17.7%200-32%2014.3-32%2032zm128%200a32%2032%200%201%201%2064%200%2032%2032%200%201%201%20-64%200zM320%20256a32%2032%200%201%201%2064%200%2032%2032%200%201%201%20-64%200zm32-80c-32.8%200-61%2019.7-73.3%2048L32%20224c-17.7%200-32%2014.3-32%2032s14.3%2032%2032%2032l246.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48l54.7%200c17.7%200%2032-14.3%2032-32s-14.3-32-32-32l-54.7%200c-12.3-28.3-40.5-48-73.3-48zM192%20128a32%2032%200%201%201%200-64%2032%2032%200%201%201%200%2064zm73.3-64C253%2035.7%20224.8%2016%20192%2016s-61%2019.7-73.3%2048L32%2064C14.3%2064%200%2078.3%200%2096s14.3%2032%2032%2032l86.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48L480%20128c17.7%200%2032-14.3%2032-32s-14.3-32-32-32L265.3%2064z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3E%3Cpath%20d='M0%20416c0%2017.7%2014.3%2032%2032%2032l54.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48L480%20448c17.7%200%2032-14.3%2032-32s-14.3-32-32-32l-246.7%200c-12.3-28.3-40.5-48-73.3-48s-61%2019.7-73.3%2048L32%20384c-17.7%200-32%2014.3-32%2032zm128%200a32%2032%200%201%201%2064%200%2032%2032%200%201%201%20-64%200zM320%20256a32%2032%200%201%201%2064%200%2032%2032%200%201%201%20-64%200zm32-80c-32.8%200-61%2019.7-73.3%2048L32%20224c-17.7%200-32%2014.3-32%2032s14.3%2032%2032%2032l246.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48l54.7%200c17.7%200%2032-14.3%2032-32s-14.3-32-32-32l-54.7%200c-12.3-28.3-40.5-48-73.3-48zM192%20128a32%2032%200%201%201%200-64%2032%2032%200%201%201%200%2064zm73.3-64C253%2035.7%20224.8%2016%20192%2016s-61%2019.7-73.3%2048L32%2064C14.3%2064%200%2078.3%200%2096s14.3%2032%2032%2032l86.7%200c12.3%2028.3%2040.5%2048%2073.3%2048s61-19.7%2073.3-48L480%20128c17.7%200%2032-14.3%2032-32s-14.3-32-32-32L265.3%2064z'/%3E%3C/svg%3E");
}
.ml-view-btn:hover,
.ml-view-btn:focus {
	color: var(--pw-text-color, #111);
	text-decoration: none;
}
.ml-view-btn.ml-view-active,
.ml-view-btn.ml-view-active:hover {
	color: var(--pw-main-color, #5b8cd6);
}

/* Masonry / gallery view — TRUE masonry: thumbnails at their NATURAL
   aspect ratio (width 100% of the column, height auto) so nothing is
   cropped, and the columns fill the container width edge-to-edge.

   Layout is done by JS (layoutGallery): it distributes the flat .ml-card
   tiles round-robin into N equal flex columns, so the reading order
   stays left-to-right (row by row) and the size slider can change N
   live. N and the column wrappers come from JS; CSS only styles them.

   Before that JS runs — or if it's disabled — the :not([data-cols])
   fallback lays the same tiles out with CSS multi-column: same no-crop,
   full-width result, only the reading order is column-major. So the
   gallery is never broken, even pre-hydration.

   --ml-gap is the single spacing token (column gap + vertical gap),
   scaled by the size slider. */
.ml-masonry {
	--ml-gap: calc(10px * var(--ml-thumb-scale, 1));
	margin: 1.5rem 0 1.75rem;
}
/* JS-laid-out state: equal-width flex columns, left-to-right order. */
.ml-masonry[data-cols] {
	display: flex;
	align-items: flex-start;
	gap: var(--ml-gap);
}
.ml-masonry-col {
	flex: 1 1 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: var(--ml-gap);
}
/* Fallback before JS hydrates: CSS multi-column — still no crop, still
   full width, just column-major order. */
.ml-masonry:not([data-cols]) {
	column-width: calc(150px * var(--ml-thumb-scale, 1));
	column-gap: var(--ml-gap);
}
.ml-masonry:not([data-cols]) > .ml-card {
	break-inside: avoid;
	-webkit-column-break-inside: avoid;
	margin-bottom: var(--ml-gap);
}
.ml-card {
	position: relative;
}
/* Natural-ratio thumbnail — fills the column width, height follows the
   image's own proportions. No object-fit: cover, so no crop. */
.ml-card .ml-cell-thumb {
	display: block;
	width: 100%;
}
.ml-card .ml-cell-thumb img,
.ml-card .ml-cell-thumb img.ml-thumb-crop {
	display: block;
	width: 100%;
	height: auto;
	max-width: none;
	max-height: none;
}
/* Hover affordance drawn INSIDE the tile (negative offset) so it can
   never bleed a stray line into a neighbouring cell. */
.ml-card .ml-cell-thumb[data-file-hash] img:hover {
	outline: 2px solid var(--pw-main-color, #5b8cd6);
	outline-offset: -2px;
}

/* Grid view — uniform SQUARE tiles in a plain CSS grid, mirroring
   ProcessWire's native "grid" file view. No JS column-packing (that's the
   masonry path): a single auto-fill grid whose cell size tracks the
   thumbnail-size slider via --ml-thumb-scale. Each thumb is cropped to a
   square with object-fit: cover. The .ml-card tile markup is shared with
   masonry; only the wrapper + these overrides differ. */
.ml-grid {
	--ml-grid-cell: calc(120px * var(--ml-thumb-scale, 1));
	--ml-gap: calc(10px * var(--ml-thumb-scale, 1));
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(var(--ml-grid-cell), 1fr));
	gap: var(--ml-gap);
	margin: 1.5rem 0 1.75rem;
}
.ml-grid .ml-card .ml-cell-thumb {
	display: block;
	width: 100%;
	aspect-ratio: 1 / 1;
}
.ml-grid .ml-card .ml-cell-thumb img,
.ml-grid .ml-card .ml-cell-thumb img.ml-thumb-crop {
	width: 100%;
	height: 100%;
	object-fit: cover;
	max-width: none;
	max-height: none;
}

/* Pure-integer count columns (Variations, Used in) are centre-aligned —
   header and cells — so single-digit counts read consistently down the
   column instead of drifting to an edge. */
.ml-cell-variations,
.ml-cell-usedin,
.ml-table thead th[data-col="variations"],
.ml-table thead th[data-col="usedIn"] {
	text-align: center;
}
.ml-cell-variations {
	color: var(--pw-muted-color, #555);
}
/* Collections cell — managers can click it to assign (caret affordance). */
.ml-cell-coll-edit {
	cursor: pointer;
}
.ml-coll-cell-caret {
	opacity: 0.5;
	font-size: 0.85em;
}
/* Assign popover: every collection is a checkable row (folder model), indented
   by depth. Rows reuse .ml-popup-checklist styling. */
.ml-coll-assign-list {
	max-height: 50vh;
	overflow-y: auto;
}

/* Mobile tweaks. Filter controls already stack via the admin theme's
   columnWidth handling; here we let the pagination flex-wrap so the
   two zones stack vertically on narrow viewports, and shrink the
   gallery columns so a phone shows several thumbnails per row. */
@media (max-width: 640px) {
	.ml-pagination {
		flex-wrap: wrap;
		row-gap: 0.5rem;
	}
	.ml-pagination-right {
		/* Stay right-aligned on mobile too: margin-left:auto (from the base
		   rule) pushes the group right when it wraps to its own line, and
		   justify-content:flex-end keeps its controls right-aligned if they
		   wrap across lines. */
		flex-wrap: wrap;
		justify-content: flex-end;
	}
	/* Pre-JS masonry fallback: smaller column on phones so the brief
	   un-hydrated state isn't one giant column. (The hydrated flex layout
	   gets its column count from layoutGallery in JS.) */
	.ml-masonry:not([data-cols]) {
		column-width: calc(88px * var(--ml-thumb-scale, 1));
	}
	/* Smaller square cells on phones so several tiles fit per row. */
	.ml-grid {
		--ml-grid-cell: calc(88px * var(--ml-thumb-scale, 1));
	}
	/* Table view keeps the small inline thumbnail cap. */
	.ml-table .ml-cell-thumb img {
		max-width: calc(80px * var(--ml-thumb-scale, 1));
	}
	.ml-popup-editor {
		width: 100vw;
		max-width: 100vw;
		max-height: 95vh;
		border-radius: 0;
	}
	.ml-image-modal {
		width: 100vw;
		height: 100vh;
		border-radius: 0;
	}
}

/* Popup editor for textarea-typed subfields (description, custom
   FieldtypeTextarea customs like "summary"). Native <dialog>, so
   the browser handles the backdrop + Esc + focus trap for us.
   Outer chrome (border / padding / background / shadow / backdrop /
   header / footer / width) is set in the shared dialog block. */

/* Secondary help text inside a popup (e.g. rename placeholder
   syntax). Muted so it sits below the title without competing
   with the input itself. */
.ml-popup-hint {
	margin: -0.25rem 0 0.75rem;
	color: var(--pw-muted-color, #777);
	font-size: 0.85em;
}
.ml-popup-editor textarea {
	display: block;
	width: 100%;
	min-height: 14em;
	box-sizing: border-box;
	font: inherit;
	padding: 0.5rem;
	border: 1px solid var(--pw-border-color, #c0c0c0);
	border-radius: 3px;
	resize: vertical;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, inherit);
}
/* No focus indicator anywhere on module-owned UI — kills both
   outline AND the box-shadow focus rings UIkit / PW apply to inputs
   and buttons. !important is needed because UIkit's own focus rules
   carry equal specificity but later cascade position; rather than
   chase load order we just win unconditionally. Covers .ml-root
   (the admin page region) plus every <dialog> the JS spawns into
   <body>, and the elements themselves (not just descendants).
   :focus-within is intentionally NOT included — it matches every
   ancestor of the focused element, and PW uses box-shadow for the
   structural outline on InputfieldHeader / wrapper rows. Killing it
   on ancestors would erase those structural shadows whenever any
   child got focused. */
.ml-root:focus, .ml-root:focus-visible,
.ml-root *:focus, .ml-root *:focus-visible,
.ml-popup-editor:focus, .ml-popup-editor:focus-visible,
.ml-popup-editor *:focus, .ml-popup-editor *:focus-visible,
.ml-image-modal:focus, .ml-image-modal:focus-visible,
.ml-image-modal *:focus, .ml-image-modal *:focus-visible,
.ml-bulk-result-dialog:focus, .ml-bulk-result-dialog:focus-visible,
.ml-bulk-result-dialog *:focus, .ml-bulk-result-dialog *:focus-visible,
.ml-columns-dialog:focus, .ml-columns-dialog:focus-visible,
.ml-columns-dialog *:focus, .ml-columns-dialog *:focus-visible {
	outline: none !important;
	box-shadow: none !important;
}
.ml-popup-editor input[type="text"],
.ml-popup-editor input[type="date"],
.ml-popup-editor input[type="datetime-local"],
.ml-popup-editor input[type="number"],
.ml-popup-editor select.ml-popup-input {
	display: block;
	width: 100%;
	box-sizing: border-box;
	font: inherit;
	padding: 0.5rem;
	border: 1px solid var(--pw-border-color, #c0c0c0);
	border-radius: 3px;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, inherit);
}
/* Boolean custom subfield — checkbox + label on one line. */
.ml-popup-checkbox {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	cursor: pointer;
	font: inherit;
}

/* Page-reference widget container — PW's configured Inputfield
   gets rendered into here by ___executeWidget. The "loading" stub
   is a single muted line; once the HTML lands it lives in
   .ml-popup-pageref-holder and PW's own inputfield CSS does the
   heavy lifting (we don't try to re-style PageAutocomplete etc.). */
.ml-popup-pageref {
	min-height: 3rem;
}
.ml-popup-pageref-loading {
	color: var(--pw-muted-color, #777);
	font-size: 0.9em;
	padding: 0.5rem 0;
}
.ml-popup-pageref-holder {
	font-size: inherit;
}

/* Multi-select via checkbox list — used for multi-value page-ref
   and FieldtypeOptions customs. <select multiple> is hostile UX
   (ctrl/cmd-click, no touch behaviour, uk-select's appearance:none
   hides the option list); a checkbox column is honest, scrollable
   on large sets, touch-friendly. */
.ml-popup-checklist {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	max-height: 18rem;
	overflow-y: auto;
	padding: 0.5rem 0.75rem;
	border: 1px solid var(--pw-border-color, #c0c0c0);
	border-radius: 3px;
	background: var(--pw-inputs-background, #fff);
}
.ml-popup-checklist-item {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	cursor: pointer;
	font: inherit;
}

/* Rename popup: stem input + locked extension chip side by side.
   The extension stays inert (no focus, no editing) and visually
   tags itself as immutable so the user doesn't try to type into it. */
.ml-popup-filename {
	display: flex;
	align-items: stretch;
	gap: 0;
}
.ml-popup-filename .ml-popup-input {
	flex: 1 1 auto;
	border-top-right-radius: 0;
	border-bottom-right-radius: 0;
	border-right: 0;
}
.ml-popup-filename-ext {
	display: inline-flex;
	align-items: center;
	padding: 0 0.5rem;
	font: inherit;
	font-family: var(--ml-mono);
	color: var(--pw-muted-color, #777);
	background: var(--pw-main-background, #f5f5f5);
	border: 1px solid var(--pw-border-color, #c0c0c0);
	border-top-right-radius: 3px;
	border-bottom-right-radius: 3px;
	user-select: none;
}

/* Filename cell — split into stem + extension so the inline edit
   only touches the stem. Extension reads muted so the immutable
   part is visually distinguishable at a glance. */
.ml-cell-filename code {
	display: inline;
}
.ml-cell-filename .ml-fn-ext {
	color: var(--pw-muted-color, #999);
}
/* Multilang tabs inside the popup — minimal styling that matches
   the rest of the popup (tab bar above a single visible pane). */
.ml-langtabs {
	display: flex;
	flex-direction: column;
}
.ml-langtabs-bar {
	display: flex;
	gap: 0.25rem;
	border-bottom: 1px solid var(--pw-border-color, #d0d0d0);
	margin-bottom: 0.5rem;
}
.ml-langtabs-tab {
	background: var(--pw-main-background, #f5f5f5);
	border: 1px solid var(--pw-border-color, #d0d0d0);
	border-bottom: 0;
	border-radius: 3px 3px 0 0;
	padding: 0.35rem 0.75rem;
	font: inherit;
	cursor: pointer;
	color: var(--pw-muted-color, #555);
}
.ml-langtabs-tab:hover {
	background: color-mix(in srgb, var(--pw-main-background, #e5e5e5) 60%, var(--pw-text-color, #000) 8%);
}
.ml-langtabs-tab.ml-langtabs-tab-active {
	background: var(--pw-blocks-background, #fff);
	border-color: var(--pw-border-color, #c0c0c0);
	color: var(--pw-text-color, #111);
	font-weight: 600;
	margin-bottom: -1px;
}
.ml-langtabs-pane {
	display: block;
	width: 100%;
	box-sizing: border-box;
	font: inherit;
	padding: 0.5rem;
	border: 1px solid var(--pw-border-color, #c0c0c0);
	border-radius: 3px;
	resize: vertical;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, inherit);
}
textarea.ml-langtabs-pane {
	min-height: 12em;
}

/* Whitelist-mode tags: grid of checkboxes. Caps the height so a
   long whitelist scrolls inside the dialog instead of pushing the
   footer off-screen. */
.ml-popup-tag-list {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
	gap: 0 0.75rem;
	max-height: 50vh;
	overflow-y: auto;
	padding: 0;
}
.ml-popup-tag-list label {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	cursor: pointer;
	padding: 0.2rem 0.3rem;
	border-radius: 2px;
}
.ml-popup-tag-list label:hover {
	background: var(--ml-tint-hover);
}
/* A predefined-tag chip: the checkbox/label plus (for managers) hover-revealed
   rename/delete controls that act library-wide. The chip is the grid item; its
   label flexes to fill, the buttons sit at the trailing edge. */
.ml-popup-tag-list .ml-tag-chip {
	display: flex;
	align-items: center;
	gap: 0.1rem;
	min-height: 1.6rem;
}
.ml-popup-tag-list .ml-tag-chip > label {
	flex: 1 1 auto;
	min-width: 0;
}
/* AdminThemeUikit gives .uk-checkbox a negative margin-top to baseline-align it
   in normal flow — in our flex rows that shoves it up ("too high"). Zero it so
   align-items:center can do its job. */
.ml-popup-tag-list .uk-checkbox {
	margin: 0;
}
.ml-tag-manage {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 1.7rem;
	height: 1.7rem;
	border: 0;
	background: none;
	padding: 0;
	font-size: 1.15rem;
	line-height: 1;
	color: var(--pw-muted-color, #888);
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.12s ease-out, color 0.12s ease-out;
}
.ml-tag-chip:hover .ml-tag-manage,
.ml-tag-manage:focus-visible {
	opacity: 1;
}
.ml-tag-rename:hover { color: var(--pw-main-color, #5b8cd6); }
.ml-tag-delete:hover { color: var(--ml-danger); }
/* While inline-renaming, the ✎ has become a ✓ — keep it visible + tinted. */
.ml-tag-manage.ml-tag-confirm {
	opacity: 1;
	color: var(--ml-confirm);
}
/* Inline delete confirm (armed by the first × click): both controls stay
   visible, the × becomes a red confirm ✓, the tag goes struck-through red, and
   the affected-image count shows inline — no second modal. */
.ml-tag-chip.ml-tag-deleting .ml-tag-manage {
	opacity: 1;
}
.ml-tag-chip.ml-tag-deleting > label {
	font-weight: 700;
	text-decoration: line-through;
}
.ml-tag-manage.ml-tag-confirm-del {
	color: var(--ml-danger);
}
.ml-tag-count {
	flex: 0 0 auto;
	font-size: 0.85em;
	color: var(--pw-muted-color, #888);
	white-space: nowrap;
}
/* Inline rename input — replaces the chip's label (tags) / row name
   (collections manager) in place. */
.ml-tag-edit-input,
.ml-coll-edit-input {
	flex: 1 1 auto;
	min-width: 0;
	height: auto;
	padding: 0.15rem 0.35rem;
	font-size: 0.95em;
}
/* The affected-count line in the tag delete dialog. */
.ml-tag-affected {
	font-style: italic;
}
/* "Add a new tag" row beneath the predefined checkboxes (tags mode 3). The
   input carries the field's autocomplete datalist; Enter / comma turns what's
   typed into a checked tag chip. */
.ml-popup-tag-add {
	margin-top: 1rem;
}
.ml-popup-tag-add .ml-popup-input {
	width: 100%;
}

/* Responsive tag chooser — placed AFTER the base .ml-popup-tag-list /
   .ml-tag-chip rules above so this override wins on equal specificity
   (a @media query adds no specificity; later source order decides).
   Phones just collapse the two-column grid to one column.

   NOTE: do NOT enlarge .ml-tag-manage here. The button is a flex child
   of the chip row and keeps its layout box even at opacity:0 — making
   it taller inflates EVERY row's height (the cause of the too-large
   mobile row spacing). It stays at the 1.7rem base, matching desktop. */
@media (max-width: 640px) {
	.ml-popup-tag-list {
		grid-template-columns: 1fr;
		gap: 0;
		max-height: 60vh;
	}
}

/* Column visibility — picker lives inside .ml-columns-dialog,
   opened from the "Columns…" button in the pagination row. CSS
   hides matching [data-col] cells; state syncs to $user->meta
   via the user-prefs endpoint. */
.ml-col-hidden {
	display: none !important;
}
/* Columns dialog — outer chrome inherited from the shared dialog
   block; only the hint paragraph is unique here. */
.ml-columns-hint {
	margin: 0 0 0.75rem;
	color: var(--pw-muted-color, #777);
	font-size: 0.9em;
}

/* Bulk-result modal — replaces the old alert() for the
   selection-as-paintbrush save report. Outer chrome inherited from
   the shared dialog block; the inner list (which scrolls when the
   failure report runs long) is unique. */
.ml-bulk-result-list {
	list-style: none;
	padding: 0;
	margin: 0;
	max-height: 50vh;
	overflow-y: auto;
	border: 1px solid var(--pw-border-color, #e5e5e5);
	border-radius: 3px;
	background: var(--pw-inputs-background, #fafafa);
	font-family: var(--ml-mono);
	font-size: 0.85em;
}
.ml-bulk-result-list[hidden] {
	display: none;
}
.ml-bulk-result-list li {
	padding: 0.35rem 0.6rem;
	border-bottom: 1px solid var(--pw-border-color, #eee);
	word-break: break-word;
	color: var(--pw-error-inline-text-color, #c0392b);
}
.ml-bulk-result-list li:last-child {
	border-bottom: 0;
}
/* Single-column stack so the dialog's top-to-bottom reading order
   matches the table's left-to-right column order. Each <li> is a
   full-width row carrying checkbox + label + up/down reorder
   buttons; the dialog itself caps height at 85vh and scrolls
   internally when the list grows past the viewport. */
.ml-columns-list {
	list-style: none;
	padding: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
}
.ml-columns-list li {
	display: flex;
	align-items: center;
	gap: 0.25rem;
	cursor: grab;
	padding: 0.15rem 0.3rem;
	border-radius: 3px;
}
.ml-columns-list li:active {
	cursor: grabbing;
}
.ml-columns-list li.ml-dragging {
	opacity: 0.35;
}
.ml-columns-list label {
	flex: 1 1 auto;
	display: flex;
	align-items: center;
	gap: 0.4rem;
	cursor: pointer;
	padding: 0.2rem 0.3rem;
	min-width: 0;
}
.ml-col-move,
.ml-coll-move {
	flex: 0 0 auto;
	background: transparent;
	border: 0;
	color: var(--pw-muted-color, #888);
	cursor: pointer;
	padding: 0.2rem 0.35rem;
	line-height: 1;
	font-size: 0.85em;
	border-radius: 2px;
}
.ml-col-move:hover,
.ml-coll-move:hover {
	background: color-mix(in srgb, var(--pw-main-background, #f0f0f0) 60%, var(--pw-text-color, #000) 8%);
	color: var(--pw-text-color, #333);
}
/* Column picker: reveal the reorder arrows on row hover (cleaner resting
   state — matches the collections manager). Keyboard reveal is keyed on the
   BUTTON's :focus-visible, not the row's :focus-within — otherwise the
   dialog's auto-focused checkbox would reveal the first row on open, and a
   mouse-clicked arrow would stay stuck visible (focus lingers on it). */
.ml-columns-list .ml-col-move {
	opacity: 0;
	transition: opacity 0.12s ease-out;
}
@media (hover: hover) {
	.ml-columns-list li:hover {
		background: var(--ml-tint-hover);
	}
	.ml-columns-list li:hover .ml-col-move {
		opacity: 1;
	}
}
.ml-columns-list .ml-col-move:focus-visible {
	opacity: 1;
}
/* Touch: no hover (and a tap's :hover sticks to the old screen slot after a
   reorder). Reveal the arrows for the ACTIVE row only (JS-driven, follows the
   moved <li>); hidden arrows are non-interactive so a tap on an inactive row's
   arrow area just activates it. */
@media (hover: none) {
	.ml-columns-list .ml-col-move {
		pointer-events: none;
	}
	.ml-columns-list li.ml-col-item--active {
		background: var(--ml-tint-hover);
	}
	.ml-columns-list li.ml-col-item--active .ml-col-move {
		opacity: 1;
		pointer-events: auto;
	}
}

/* Collections manager — drag-and-drop tree of collections (one level deep).
   Mirrors the column-picker list; rows are draggable, children are indented,
   and drop targets show a line (reorder) or a box (nest). */
/* "+ New collection" — a plain link-style action above the manager list.
   No bespoke colour: it inherits the dialog text colour. */
/* Manager tab bar: the uk-tab strip (Collections / Bookmarks) on the left and a
   single right-aligned "+ New" link on the same row. */
.ml-mgr-tabbar {
	display: flex;
	align-items: baseline;   /* "+ New" text sits on the tab-label baseline */
	margin: 0 0 0.8rem;
}
.ml-mgr-tabs {
	margin: 0;
}
/* "+ New" — a real link in the theme's link colour, pushed to the right of the
   tab row, its text baseline-aligned with the tab labels. */
.ml-coll-new {
	margin-left: auto;
	white-space: nowrap;
	cursor: pointer;
}
.ml-collections-list,
.ml-bookmarks-list {
	list-style: none;
	padding: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
}
/* Bookmark-row type icon (folder vs filter bookmark) in the manager list. */
.ml-coll-typeicon {
	opacity: 0.7;
	width: 1em;
	text-align: center;
}
.ml-coll-row {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.25rem 0.3rem;
	border-radius: 3px;
	cursor: grab;
	border: 1px solid transparent;   /* reserved so drop highlights don't shift layout */
}
.ml-coll-row:active { cursor: grabbing; }
.ml-coll-row.ml-dragging { opacity: 0.35; }
/* Light-grey highlight — the exact tint the tag manager uses for its chip rows.
   DESKTOP uses transient :hover (follows the mouse). TOUCH has no hover (and a
   tap's :hover sticks), so there the highlight is driven by an explicit JS
   "active" class — exactly ONE row, the one last acted on / just reordered, so
   it follows the moved element instead of lighting up the row in its old slot. */
@media (hover: hover) {
	.ml-coll-row:hover {
		background: var(--ml-tint-hover);
	}
}
@media (hover: none) {
	.ml-coll-row--active {
		background: var(--ml-tint-hover);
	}
}
/* Collections are all team now — no personal/shared distinction, so no italic
   marker on manager rows. */
.ml-coll-row--shared { font-style: normal; }
/* Collapse caret for parents; a same-width spacer keeps leaf names aligned.
   (Indent per nesting level is applied inline as padding-left.) */
.ml-coll-caret {
	flex: 0 0 auto;
	width: 1.1rem;
	background: transparent;
	border: 0;
	padding: 0;
	color: var(--pw-muted-color, #999);
	cursor: pointer;
	font-size: 0.95em;
	line-height: 1;
	text-align: center;
}
.ml-coll-caret--leaf { cursor: default; }
.ml-coll-name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.ml-coll-btns {
	flex: 0 0 auto;
	display: flex;
	gap: 0.05rem;
}
/* Row controls are hidden at rest and revealed for the ACTIVE row (explicit JS
   class — touch-reliable, exactly one row), on desktop hover, or the button's
   own :focus-visible (keyboard). Hover is desktop-only so a sticky touch :hover
   doesn't reveal a second row's controls next to the active one. */
.ml-coll-move {
	opacity: 0;
	transition: opacity 0.12s ease-out;
}
@media (hover: hover) {
	.ml-coll-row:hover .ml-coll-move {
		opacity: 1;
	}
}
.ml-coll-move:focus-visible {
	opacity: 1;
}
@media (hover: none) {
	/* Hidden controls are non-interactive on touch, so a tap on an inactive
	   row's (invisible) buttons falls through to the row and just activates it;
	   once active the controls show AND become tappable. (The collapse caret is
	   a separate, always-visible control, unaffected.) */
	.ml-coll-move {
		pointer-events: none;
	}
	.ml-coll-row--active .ml-coll-move {
		opacity: 1;
		pointer-events: auto;
	}
}
/* Inline delete (like the tag manager): × goes red on hover; once armed it
   becomes a red ✓, the row's controls stay visible and the name is struck
   through until you confirm or it auto-disarms. */
.ml-coll-del:hover { color: var(--ml-danger); }
.ml-coll-del.ml-coll-armed { color: var(--ml-danger); opacity: 1; }
.ml-coll-move.ml-coll-confirm { color: var(--ml-confirm); opacity: 1; }   /* green ✓ while renaming */
.ml-coll-row-deleting .ml-coll-move { opacity: 1; }
.ml-coll-row-deleting .ml-coll-name {
	text-decoration: line-through;
	font-weight: 700;
}
/* Drop indicators set during dragover. */
.ml-coll-row.ml-drop-before { box-shadow: inset 0 2px 0 0 var(--pw-primary, #3480e3); }
.ml-coll-row.ml-drop-after  { box-shadow: inset 0 -2px 0 0 var(--pw-primary, #3480e3); }
.ml-coll-row.ml-drop-into {
	border-color: var(--pw-primary, #3480e3);
	background: color-mix(in srgb, var(--pw-main-background, #fff) 80%, var(--pw-primary, #3480e3) 20%);
}
/* "Team" separator + empty state. */
.ml-coll-sep {
	margin: 0.5rem 0 0.15rem;
	font-size: 0.8em;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--pw-muted-color, #999);
}
.ml-coll-empty {
	color: var(--pw-muted-color, #999);
	font-style: italic;
	padding: 0.25rem 0.3rem;
}
/* "Manage" — icon-only, sitting directly after the bookmarks/collections (the
   "New" link follows it), NOT floated to the far right. Nudged down so the gear
   lines up optically with the tab text baseline. The icon is now a mask glyph
   (vertical-align: middle), so this top offset is the single alignment knob. */
.ml-collections-manage > a {
	margin-right: 0;     /* override the tab strip's 30px del-button gutter */
	color: var(--pw-muted-color, #888);
	position: relative;
	top: 4px;
}
.ml-collections-manage > a:hover { color: var(--pw-text-color, inherit); }

/* Nested collections in the tab strip: only the top level shows as a tab;
   descendants live in a hover flyout (indent per relative depth set inline).
   The caret marks a tab that has a flyout. */
.ml-coll-has-children { position: relative; }
.ml-coll-tab-caret {
	font-size: 0.85em;
	opacity: 0.6;
	/* Gap before the caret (the JS no longer emits a text-node space, so this is
	   the whole gap for top-level tabs; flyout parents override with auto). */
	margin-left: 0.3rem;
	/* Never let the parent link's hover underline run under the caret icon. */
	display: inline-block;
	text-decoration: none;
}
/* On TOUCH only: kill the sticky :hover underline a tap leaves on bar/flyout
   links (the text next to the caret). Desktop keeps its real :hover / :active
   feedback - this is gated to (hover: none) so it never touches mouse devices. */
@media (hover: none) {
	.ml-bookmarks-tabs a.ml-bookmark,
	.ml-bookmarks-tabs a.ml-bookmark:hover,
	.ml-coll-flyout a.ml-bookmark,
	.ml-coll-flyout a.ml-bookmark:hover {
		text-decoration: none;
	}
}
.ml-coll-flyout {
	position: absolute;
	top: 100%;
	left: 0;
	z-index: 60;
	width: max-content;   /* only as wide as the longest item, no fixed width */
	margin: 0;
	padding: 0.25rem 0;
	list-style: none;
	background: var(--pw-blocks-background, #fff);
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 4px;
	box-shadow: 0 6px 24px rgba(0, 0, 0, 0.15);
	display: none;
}
/* Cascading: a flyout (or sub-flyout) shows while its parent item is hovered
   (desktop) or while it's tap-opened (.ml-flyout-open, touch — no hover there).
   The flyout is a DOM descendant of that item, so the whole chain stays open. */
@media (hover: hover) {
	.ml-coll-has-children:hover > .ml-coll-flyout { display: block; }
}
.ml-coll-has-children.ml-flyout-open > .ml-coll-flyout { display: block; }
/* A NESTED flyout (under a flyout item) opens to the SIDE; the top-level one
   stays directly below its tab (inherited top:100%/left:0). */
.ml-coll-flyout-item > .ml-coll-flyout {
	top: -0.3rem;
	left: 100%;
}
.ml-coll-flyout-item { position: relative; }
.ml-coll-flyout-item > a.ml-bookmark {
	display: flex;
	align-items: center;
	padding: 0.3rem 0.85rem;
	white-space: nowrap;
	margin: 0;
}
/* A flyout item with a submenu: name stays LEFT, caret pushed to the right edge
   via margin-left:auto. (justify-content:space-between would centre the name
   when a curate glyph also sits in the row.) padding-left gives a guaranteed
   gap between name and caret that margin-left:auto can't (the widest item, which
   sets the menu's max-content width, has no free space for the auto margin, so
   without the padding its caret would touch the name); the padding also widens
   max-content by the same amount, so every caret stays right-aligned. */
.ml-coll-flyout-parent > a .ml-coll-tab-caret { margin-left: auto; padding-left: 0.75rem; }
/* Items WITH a caret (flyout parents + top-level parent tabs): on hover move
   the underline OFF the <a> and onto the name span only, so the caret/arrow is
   never underlined (relying on inline-block exclusion proved unreliable). Leaf
   items keep their normal <a>:hover underline. Gated to (hover:hover) so a
   sticky tap on touch leaves nothing underlined. */
@media (hover: hover) {
	.ml-coll-flyout-parent > a.ml-bookmark:hover,
	.ml-bookmarks-tabs > li.ml-coll-has-children > a.ml-bookmark:hover {
		text-decoration: none;
	}
	.ml-coll-flyout-parent > a.ml-bookmark:hover .ml-bm-label,
	.ml-bookmarks-tabs > li.ml-coll-has-children > a.ml-bookmark:hover .ml-bm-label {
		text-decoration: underline;
	}
}
.ml-coll-flyout-item.uk-active > a {
	background: color-mix(in srgb, var(--pw-blocks-background, #fff) 80%, var(--pw-primary, #3480e3) 20%);
}
/* Hover tint desktop-only — on touch :hover sticks to the last-tapped item.
   (The name keeps its hover underline via the .ml-bm-label rule above; the
   caret is a sibling of that span, so it is never underlined.) */
@media (hover: hover) {
	.ml-coll-flyout-item > a:hover {
		background: color-mix(in srgb, var(--pw-blocks-background, #fff) 80%, var(--pw-primary, #3480e3) 20%);
	}
}

/* Visually-hidden live region — kept in the DOM and announced by
   screen readers, invisible to sighted users. Same idiom as
   Bootstrap's .visually-hidden, but local to the module. */
.ml-live-region {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* Editable cells advertise themselves as buttons for keyboard
   users — show a focus ring so the active cell is obvious. */

/* Image-editor modal — wraps PW's ProcessPageEdit in an iframe so
   the user gets the native crop / focus / variations UI plus all
   page-level field editing. Sized near-fullscreen to give the
   inner admin chrome room to breathe. */
.ml-image-modal {
	width: 95vw;
	height: 95vh;
	max-width: none;
	max-height: none;
	padding: 0;
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 4px;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	background: var(--pw-blocks-background, #fff);
}
.ml-image-modal-bar {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0.5rem 1rem;
	border-bottom: 1px solid var(--pw-border-color, #e5e5e5);
	background: var(--pw-main-background, #f5f5f5);
	gap: 1rem;
}
.ml-image-modal-title {
	font-weight: 600;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.ml-image-modal-iframe {
	flex: 1 1 auto;
	width: 100%;
	border: 0;
	background: var(--pw-blocks-background, #fff);
}

/* Masonry duplicate-cluster modal: same chrome as the image modal, but the
   body holds the (editable) cluster table and scrolls. Sized to content,
   capped to the viewport. */
.ml-cluster-modal {
	width: 95vw;
	max-width: 1000px;
	max-height: 90vh;
	padding: 0;
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 4px;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	background: var(--pw-blocks-background, #fff);
}
/* Vertical scroll for tall clusters; horizontal scrolling is left to the
   table's own .ml-table-scroll wrapper (same as the main view) so the wide
   table scrolls INSIDE the modal on narrow screens instead of the modal or
   page overflowing. */
.ml-cluster-modal-body {
	flex: 1 1 auto;
	overflow-x: hidden;
	overflow-y: auto;
	padding: 0.5rem 1rem 1rem;
}
.ml-cluster-modal-body .ml-table-scroll {
	max-width: 100%;
}

/* "Used in" column. The count is a plain link (it inherits the admin
   theme's anchor colour, like the Page cell); a muted dash marks images
   embedded nowhere. No bespoke control. */
.ml-usage-link {
	font-weight: 500;
	cursor: pointer;
}
.ml-usage-none {
	color: var(--pw-muted-color, #999);
}

/* "Used in" dialog — all chrome (border, padding, header, footer, width,
   backdrop) is inherited from the shared dialog block above; the page/field
   list reuses .ml-delete-confirm-usage-list. Only the empty-state line is
   unique here. */
.ml-usage-dialog-empty {
	margin: 0;
	color: var(--pw-muted-color, #777);
}

/* Masonry: the duplicate tile reads as a "stack" you open, not an editor. */
.ml-card .ml-cell-thumb.ml-dup-cluster {
	cursor: zoom-in;
}
.ml-popup-editor .ml-batch-mode {
	margin: 0.75rem 0 0;
}

/* Export / Import — collapsed fieldset at the bottom of the page.
   The download is a direct <a>, the upload is AJAX-posted so the
   user stays on their current filter view. */
.ml-export-import {
	margin-top: 1.5rem;
}
.ml-ei-help {
	color: var(--pw-muted-color, #555);
	margin: 0 0 0.75rem;
}
/* Two-column layout: Export on the left, Import on the right. Each
   column is a real <fieldset> with a <legend>; equal flex sizing
   keeps them side by side at the same height. Wraps to a single
   stack on narrow viewports. align-items:stretch is the flex default
   so the two fieldsets fill the same vertical space, which combined
   with margin-top:auto on .ml-ei-actions pushes the buttons to the
   bottom of each column — so the action rows always line up
   horizontally regardless of how much the inputs above consume. */
.ml-ei-cols {
	display: flex;
	flex-wrap: wrap;
	gap: 1rem;
	margin: 0.5rem 0 0;
}
.ml-ei-fieldset {
	position: relative;
	flex: 1 1 280px;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	margin: 1rem 0 0;
	padding: 1.6rem 1.3rem;
	border: 1px solid var(--pw-border-color, #ddd);
	background: var(--pw-blocks-background, #fff);
}
/* Legend sits on the top border, breaking it only where the text
   sits. Absolute positioning + translateY(-50%) puts it centred on
   the border line; the background matches the surrounding so the
   border is hidden behind the text width and only there. UIkit
   normalises legend to display:table by default which suppresses
   the native overlay behaviour, hence the manual positioning. */
.ml-ei-fieldset > legend {
	position: absolute;
	top: 0;
	left: 0.75rem;
	transform: translateY(-50%);
	margin: 0;
	padding: 0 0.6rem;
	background: var(--pw-blocks-background, #fff);
	font-weight: 600;
	font-size: 0.9em;
	color: var(--pw-text-color, #333);
}
/* Action button row pinned to the bottom of its fieldset so all
   columns' buttons sit at the same vertical position. */
.ml-ei-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin-top: auto;
}
/* Inputs (variant select on the left, file picker on the right) read
   as muted-labelled fields so the columns balance visually. */
.ml-export-variant-wrap,
.ml-ei-file {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0 0 0.6rem;
	color: var(--pw-muted-color, #555);
}
.ml-export-variant {
	min-width: 18ch;
}
/* Import form fills the right fieldset so its inner action row can
   still margin-top:auto to the bottom (the form is the level the
   actions live at, not the fieldset). */
.ml-import-form {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	margin: 0;
}
.ml-import-status {
	margin-top: 0.75rem;
	white-space: pre-wrap;
	color: var(--pw-text-color, #444);
	font-family: var(--ml-mono);
	font-size: 0.85em;
}

/* Bookmarks bar — chrome inherited from .WireTabs + .uk-tab classes
   set by the admin theme. Tight: small space above, NONE below, so the
   active tab's red underline butts directly against the filter box. */
.ml-bookmarks-tabs {
	margin-top: 0.35rem;
	margin-bottom: 0;
}
/* Mobile bar: "Bookmarks" / "Collections" are flyout-PARENT tabs whose flyout
   holds the real items one level deeper (same .ml-coll-flyout optics as desktop,
   tap-driven via .ml-flyout-open). Desktop keeps the flat strip. */
@media (max-width: 640px) {
	/* Each category flyout drops below ITS OWN tab (not all at the strip's left).
	   The tab is the positioning context; cap size to the viewport. */
	.ml-bookmarks-tabs > li.ml-coll-has-children {
		position: relative;
	}
	.ml-bookmarks-tabs > li.ml-coll-has-children > .ml-coll-flyout {
		left: 0;
		min-width: 12rem;
		max-width: 86vw;
		max-height: 70vh;
		overflow-y: auto;
	}
	/* A side cascade can't fit a phone, so nested flyouts open inline (accordion),
	   indented under their parent item. width:auto (not the base max-content) so
	   every level fills the flyout's full width — entries stay the same width and
	   all carets line up on the same right edge, with text indented per level. */
	.ml-coll-flyout-item > .ml-coll-flyout {
		position: static;
		top: auto;
		left: auto;
		border: 0;
		box-shadow: none;
		background: none;
		padding-left: 0.9rem;
		width: auto;
		min-width: 0;
		max-width: none;
	}
	/* A nested item's side-pointing caret turns down when expanded inline. (The
	   top-level category carets already point down, so they're left alone.) */
	.ml-coll-flyout-item.ml-flyout-open > a > .ml-coll-tab-caret {
		transform: rotate(90deg);
	}
}
/* Uniform 30 px gap between every tab so the bookmark trash button
   has room to sit in the gap rather than overlapping a neighbour. */
.ml-bookmarks-tabs > li > a {
	margin-right: 30px;
}
/* Per-tab delete button — round 32 × 32 chip with an X icon, sits in
   the right gap of each user bookmark tab. Hover-only reveal; same
   red-on-hover the thumbnail trash uses. */
.ml-bookmarks-tabs > li[data-bookmark-idx],
.ml-bookmarks-tabs > li[data-coll-id] {
	position: relative;
}
/* Collection tabs: a saved set of specific images. The leading icon (set in
   markup) distinguishes them from filter bookmarks; mute it so the name leads. */
.ml-bookmark--collection > .fa {
	opacity: 0.6;
	margin-right: 0.15rem;
}
/* Delete (×) chip — round button in the right gap. Hover-reveal. Deletes a
   filter bookmark, or (no selection) a collection. */
.ml-bookmark-del {
	position: absolute;
	top: 10%;
	right: 0.25rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	padding: 0;
	border: 1px solid var(--pw-border-color, #ccc);
	border-radius: 50%;
	background: var(--pw-inputs-background, #fff);
	color: var(--pw-text-color, #333);
	font-size: 0.8rem;
	line-height: 1;
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.12s ease-out;
}
.ml-bookmarks-tabs > li:hover .ml-bookmark-del,
.ml-bookmark-del:focus-visible {
	opacity: 1;
}
.ml-bookmark-del:hover {
	color: var(--ml-danger);
}
/* While a selection is active you're CURATING, not deleting — hide the × on
   collection tabs. Clicking the tab itself adds/removes the selection; the
   cursor says which. */
.ml-bookmarks-tabs.ml-has-selection li[data-coll-id] .ml-bookmark-del {
	display: none;
}
/* Curate cursors (selection active):
   - a NON-active collection tab → "+" (the click ADDS the selection to it);
   - the ACTIVE collection tab (you're inside it) → "−" (the click REMOVES the
     selection from it). Custom SVG cursors, native copy/pointer as fallback. */
.ml-bookmarks-tabs.ml-has-selection li[data-coll-id] > a {
	cursor: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%232a8a2a'%20stroke='white'%20stroke-width='2'/%3E%3Cpath%20d='M12%207v10M7%2012h10'%20stroke='white'%20stroke-width='2.6'%20stroke-linecap='round'/%3E%3C/svg%3E") 12 12, copy;
}
.ml-bookmarks-tabs.ml-has-selection li[data-coll-id].uk-active > a {
	cursor: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%23c0392b'%20stroke='white'%20stroke-width='2'/%3E%3Cpath%20d='M7%2012h10'%20stroke='white'%20stroke-width='2.6'%20stroke-linecap='round'/%3E%3C/svg%3E") 12 12, pointer;
}
/* Shared (team-wide) tabs are read-only for non-managers: no curate cursor —
   a click just recalls. Managers get the curate cursors back via the
   ml-can-manage-shared root class (the rules above already cover non-shared
   tabs for them). */
.ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared] > a {
	cursor: pointer;
}
.ml-can-manage-shared .ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared] > a {
	cursor: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%232a8a2a'%20stroke='white'%20stroke-width='2'/%3E%3Cpath%20d='M12%207v10M7%2012h10'%20stroke='white'%20stroke-width='2.6'%20stroke-linecap='round'/%3E%3C/svg%3E") 12 12, copy;
}
.ml-can-manage-shared .ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared].uk-active > a {
	cursor: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='10'%20fill='%23c0392b'%20stroke='white'%20stroke-width='2'/%3E%3Cpath%20d='M7%2012h10'%20stroke='white'%20stroke-width='2.6'%20stroke-linecap='round'/%3E%3C/svg%3E") 12 12, pointer;
}
/* Folder model: a parent collection holds its own images and is curatable like
   any leaf, so it keeps the +/− curate cursor — no read-only exception. */
/* Touch devices have no cursor, so the curate +/− affordance is shown as a
   glyph BEFORE the collection name instead. Mirrors the curate-cursor cascade
   above one-for-one: "+" to ADD the selection, "−" to REMOVE it from the
   active tab; only non-manager shared tabs get no glyph. Inherits the tab's
   own colour (no bespoke colour). Selectors and source order match the cursor
   rules so the glyph always agrees with what a click does. */
@media (hover: none) {
	.ml-bookmarks-tabs.ml-has-selection li[data-coll-id] > a::before {
		content: "+";
		display: inline-block;
		flex: 0 0 auto;
		width: 1em;
		margin-right: 0.4rem;
		text-align: center;
		font-size: 1.2em;
		font-weight: 700;
		line-height: 1;
	}
	.ml-bookmarks-tabs.ml-has-selection li[data-coll-id].uk-active > a::before {
		content: "\2212";
	}
	.ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared] > a::before {
		content: "";
	}
	.ml-can-manage-shared .ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared] > a::before {
		content: "+";
		margin-right: 0.45rem;
	}
	.ml-can-manage-shared .ml-bookmarks-tabs.ml-has-selection li[data-coll-id][data-shared].uk-active > a::before {
		content: "\2212";
	}
}

/* Bookmarks are shown in italic to tell them apart from collections (which are
   upright). Targets bookmark tabs AND their flyout items — the <li> carries
   data-bookmark-id either way; the "Show all" tab and collection tabs (which
   carry data-coll-id) are unaffected. */
.ml-bookmarks-tabs li[data-bookmark-id] > a {
	font-style: italic;
}
/* Legacy shared bookmarks were italic via this class; kept only so any
   server-rendered .ml-bookmark--shared still reads consistently. Collections
   stay upright. */
.ml-bookmark--collection.ml-bookmark--shared {
	font-style: normal;
}

/* "Share with the team" toggle in the save dialog. */
.ml-share-toggle {
	display: flex;
	align-items: center;
	gap: 0.35rem;
	margin-top: 0.6rem;
	font-size: 0.95em;
	cursor: pointer;
}
.ml-share-toggle input {
	margin: 0;
}
.ml-share-hint {
	margin-top: 0.2rem;
}

/* Empty / debug */
.ml-empty {
	padding: 2rem;
	text-align: center;
	color: var(--pw-muted-color, #888);
}
.ml-debug dt {
	font-weight: 600;
	margin-top: 0.5rem;
}
.ml-debug pre {
	background: var(--pw-inputs-background, #f5f5f5);
	color: var(--pw-text-color, inherit);
	padding: 0.75rem;
	border: 1px solid var(--pw-border-color, #e5e5e5);
	border-radius: 3px;
	overflow-x: auto;
	max-height: 24rem;
}
