/* ==========================================================================
   Site styles — everything here references tokens.css variables instead of
   hard-coded values, so the palette/fonts stay centralized (see
   docs/PROJECT_DETAILS.md -> Frontend Conventions: Design Tokens).
   ========================================================================== */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-background);
  line-height: 1.5;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  color: var(--color-primary);
  margin-top: 0;
}

a {
  color: var(--color-secondary);
}

.admin-shell {
  margin: 0 auto;
}

.admin-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
}

.admin-kicker {
  margin: 0 0 var(--spacing-xs) 0;
  color: var(--color-text-muted);
  font-size: 0.85rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.admin-nav {
  margin: var(--spacing-md) 0;
}

.admin-nav a {
  color: var(--color-secondary);
  font-weight: bold;
}

.dev-quick-login {
  margin: var(--spacing-md) 0;
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-background);
}

.dev-quick-login p {
  margin: 0 0 var(--spacing-xs);
  color: var(--color-text-muted);
  font-weight: bold;
}

.dev-quick-login a {
  display: block;
  margin-top: var(--spacing-xs);
  font-weight: bold;
}

.admin-status,
.admin-logs {
  margin-top: var(--spacing-lg);
}

.admin-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--spacing-sm);
  margin-top: var(--spacing-sm);
}

.admin-status-item {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-background);
}

.admin-status-label {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  font-weight: bold;
}

.admin-status-value {
  font-weight: bold;
  color: var(--color-text);
}

.admin-status-value.admin-success {
  color: var(--color-primary);
}

.admin-status-value.admin-warning {
  color: var(--color-secondary);
}

.admin-status-value.admin-neutral {
  color: var(--color-text-muted);
}

.admin-status-list,
.admin-log-list {
  list-style: none;
  margin: var(--spacing-sm) 0 0;
  padding: 0;
}

.admin-status-tier,
.admin-log-item {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-background);
  margin-top: var(--spacing-xs);
}

.admin-status-tier-name {
  font-weight: bold;
}

.admin-status-tier-meta {
  color: var(--color-text-muted);
}

.admin-log-item {
  display: block;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.admin-empty-state {
  margin-top: var(--spacing-sm);
  color: var(--color-text-muted);
}

/* --- Accessibility: visible keyboard focus state, on-brand -------------- */

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

/* --- Header / nav ------------------------------------------------------- */

.site-header {
  background: var(--color-primary);
  color: var(--color-background);
  padding: var(--spacing-md) var(--spacing-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-header .brand {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  color: var(--color-accent);
  text-decoration: none;
}

.brand-logo {
  height: 3.5rem;
  width: auto;
}

.brand-name {
  font-family: var(--font-heading);
  font-size: 1.5rem;
}

.site-nav a {
  color: var(--color-background);
  text-decoration: none;
  margin-left: var(--spacing-md);
  font-weight: bold;
}

.site-nav a:hover {
  color: var(--color-accent);
}

.site-nav a[aria-current="page"] {
  text-decoration: underline;
  text-decoration-color: var(--color-accent);
  text-decoration-thickness: 2px;
}

/* --- Hero ---------------------------------------------------------------- */

.hero {
  padding: var(--spacing-xl) var(--spacing-lg);
  text-align: center;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.hero h1 {
  font-size: 2.5rem;
}

.hero p {
  color: var(--color-text-muted);
  max-width: 40rem;
  margin: var(--spacing-sm) auto 0;
}

/* Shared with real <button> elements below, so a form's submit button
   looks identical to a .button link (e.g. the admin bar's "+ Nytt
   inlägg") rather than falling back to an unstyled browser default.
   .media-picker-upload-label is a <label> wrapping a hidden file
   input (media_picker.py), not a real <button> - folded into this
   same shared rule so it isn't just plain unstyled label text with no
   visible clickable affordance at all. */
.button,
button,
.media-picker-upload-label {
  display: inline-block;
  margin-top: var(--spacing-md);
  padding: var(--spacing-xs) var(--spacing-md);
  background: var(--color-secondary);
  color: var(--color-background);
  text-decoration: none;
  border: none;
  border-radius: var(--radius-card);
  font: inherit;
  font-weight: bold;
  cursor: pointer;
}

.button:hover,
button:hover,
.media-picker-upload-label:hover {
  background: var(--color-accent);
  color: var(--color-text);
}

/* Shared "small circular icon button" shape - reused by several distinct
   admin affordances (card delete/publish-toggle, widget enable-toggle,
   compose-screen slot move/remove) that would otherwise each redeclare
   the identical box model. Applied via a shared selector list, same
   convention as .button/button above - no shared class added in markup,
   each element keeps its own semantic class name. */
.card-delete,
.card-publish-toggle,
.widget-enable-toggle,
.content-compose-move-up,
.content-compose-move-down,
.content-compose-remove-widget,
.content-compose-remove-item {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  min-width: 1.75rem;
  min-height: 1.75rem;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  overflow: hidden;
  background: var(--color-background);
  color: var(--color-secondary);
  border: 1px solid var(--color-secondary);
  font-size: 1.1rem;
  line-height: 1;
}

.card-delete:hover,
.card-publish-toggle:hover,
.widget-enable-toggle:hover,
.content-compose-move-up:hover,
.content-compose-move-down:hover,
.content-compose-remove-widget:hover,
.content-compose-remove-item:hover {
  background: var(--color-secondary);
  color: var(--color-background);
}

.card-grid-card {
  position: relative;
  padding-top: var(--spacing-lg);
}

.card-grid-card .content-compose-remove-item {
  position: absolute;
  top: var(--spacing-sm);
  right: var(--spacing-sm);
  margin: 0;
}

.card-grid-query-clause {
  position: relative;
  padding-top: var(--spacing-lg);
}

.card-grid-query-clause .content-compose-remove-item {
  position: absolute;
  top: var(--spacing-sm);
  right: var(--spacing-sm);
  margin: 0;
}

/* --- Form fields (US-007.02.02's create/delete admin pages) --------------- */

label {
  display: block;
  margin-top: var(--spacing-sm);
  font-weight: bold;
}

input[type="text"],
input[type="number"],
input[type="password"],
textarea,
select {
  display: block;
  width: 100%;
  max-width: 30rem;
  margin-top: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  font: inherit;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

/* --- Status/validation messages -------------------------------------------
   Shared box treatment for inline validation/status text, grouped by
   semantic color via a shared selector list (same convention as the
   "small circular icon button" shape above) - no shared class added in
   markup, each element keeps its own semantic class name. Previously
   these rendered as plain unstyled text, indistinguishable from
   surrounding body copy. */
.error,
.form-error,
.content-compose-unsupported,
.content-delete-warning-hard {
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-card);
  background: var(--color-secondary);
  color: var(--color-background);
  font-weight: bold;
}

/* Required-field "*" marker (kitsunemori.widgets.admin.required_marker) -
   same error/danger color as .error above, but inline within a label,
   not a standalone block. */
.required-marker {
  color: var(--color-secondary);
  font-weight: bold;
}

.content-delete-warning-hard a {
  color: inherit;
}

.content-delete-warning-hard ul {
  font-weight: normal;
  margin: var(--spacing-xs) 0 0;
}

.form-success {
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-card);
  background: var(--color-primary);
  color: var(--color-background);
  font-weight: bold;
}

/* Bulk-upload's per-file success/failure report (US-010.07.00) - a
   plain list, one line per submitted file, so a partial failure is
   clearly readable rather than a single pass/fail count. */
.bulk-upload-results {
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  list-style: none;
  margin: var(--spacing-sm) 0;
}

/* Bulk-upload live progress (US-010.07.00) - shown by scripts/
   bulk-upload.js while a large batch uploads, hidden otherwise. */
.bulk-upload-progress {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-sm);
}

.bulk-upload-progress-bar {
  flex: 1;
  height: var(--spacing-sm);
}

.bulk-upload-progress-text {
  font-weight: bold;
}

.backup-warning,
.content-compose-warning,
.content-delete-warning {
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-card);
  background: var(--color-accent);
  color: var(--color-text);
  font-weight: bold;
}


/* --- Embedded media picker (US-010.04.00) - a widget field's inline
   "choose an already-archived image" panel, wired by
   scripts/media-picker.js. Admin-only compose-screen tooling - see that
   script's own docstring on why no no-JS fallback is provided here. --- */

.media-picker {
  margin-top: var(--spacing-xs);
}

.media-picker-preview {
  margin-bottom: var(--spacing-xs);
}

.media-picker-preview img {
  display: block;
  max-width: 8rem;
  max-height: 8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.media-picker-preview-empty {
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.media-picker-panel {
  margin-top: var(--spacing-xs);
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  max-width: 30rem;
}

.media-picker-status {
  margin: 0 0 var(--spacing-xs) 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.media-picker-upload-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-xs);
}

.media-picker-upload-label {
  margin: 0;
  font-size: 0.85rem;
  padding: 0.15rem 0.6rem;
}

.media-picker-upload-status {
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.media-picker-upload-details {
  margin-bottom: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
}

.media-picker-upload-details label {
  display: block;
  font-size: 0.85rem;
  margin-top: var(--spacing-xs);
}

.media-picker-upload-filename {
  margin: 0;
  font-size: 0.85rem;
  font-weight: bold;
}

.media-picker-upload-actions {
  display: flex;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-xs);
}

.media-picker-upload-actions button {
  margin: 0;
  font-size: 0.85rem;
  padding: 0.15rem 0.6rem;
}

.media-picker-filters {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  margin-bottom: var(--spacing-xs);
}

.media-picker-type-toggle {
  display: flex;
  gap: var(--spacing-xs);
}

.media-picker-type-btn {
  margin: 0;
  font-size: 0.85rem;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
}

.media-picker-type-btn.active {
  background: var(--color-primary);
  color: var(--color-surface);
  border-color: var(--color-primary);
}

.media-picker-type-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.media-picker-active-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}

.media-picker-tag-chip {
  display: inline-flex;
  align-items: center;
  background: var(--color-primary);
  color: var(--color-surface);
  border-radius: var(--radius-card);
  padding: 0.1rem 0.3rem 0.1rem 0.6rem;
  font-size: 0.8rem;
}

.media-picker-tag-chip button {
  margin: 0;
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0 0.2rem;
}

.media-picker-popular-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}

.media-picker-popular-tag-chip {
  margin: 0;
  background: none;
  border: 1px solid var(--color-accent);
  color: var(--color-text);
  border-radius: var(--radius-card);
  padding: 0.1rem 0.55rem;
  font-size: 0.8rem;
  cursor: pointer;
}

.media-picker-popular-tag-chip:hover {
  background: var(--color-accent);
}

.media-picker-tag-search-wrap {
  position: relative;
  max-width: 14rem;
}

.media-picker-tag-search {
  width: 100%;
  box-sizing: border-box;
  font-size: 0.85rem;
  padding: 0.2rem 0.4rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.media-picker-tag-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 5;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  max-height: 10rem;
  overflow-y: auto;
}

.media-picker-tag-suggestion-item {
  display: block;
  width: 100%;
  text-align: left;
  margin: 0;
  background: none;
  border: none;
  color: var(--color-text);
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.85rem;
}

.media-picker-tag-suggestion-item:hover {
  background: var(--color-background);
}

.media-picker-tag-suggestion-empty {
  padding: 0.25rem 0.5rem;
  font-size: 0.8rem;
  color: var(--color-text-muted);
  font-style: italic;
}

/* --- Generic tag-input autocomplete (scripts/tag-suggest.js) - shared
   by /images/new's own tags field and the embedded media picker's
   upload-details tags field. Same shape as .media-picker-tag-search's
   own suggestion dropdown above, just not scoped to one picker. --- */

.tag-suggest {
  position: relative;
  max-width: 30rem;
}

.tag-suggest-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 5;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  max-height: 10rem;
  overflow-y: auto;
}

.tag-suggest-suggestion-item {
  display: block;
  width: 100%;
  text-align: left;
  margin: 0;
  background: none;
  border: none;
  color: var(--color-text);
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.85rem;
}

.tag-suggest-suggestion-item:hover {
  background: var(--color-background);
}


.media-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
  gap: var(--spacing-xs);
  min-height: 5rem;
}

.media-picker-thumb {
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-card);
  background: none;
  cursor: pointer;
}

.media-picker-thumb:hover {
  border-color: var(--color-accent);
}

.media-picker-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-card);
}

.media-picker-empty {
  grid-column: 1 / -1;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.media-picker-pager {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-xs);
}

.media-picker-pager-label {
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.media-picker-close-btn {
  margin-top: var(--spacing-sm);
}


/* --- Standalone content logo (e.g. below a page's hero) --------------------
   Generic, reusable wherever a page wants to show the kennel logo as its
   own block. */

.content-logo {
  text-align: center;
}

.content-logo-image {
  display: inline-block;
  max-width: 12rem;
  width: 100%;
  height: auto;
}

/* Per-instance size picker (US-010.09.00): the chosen size caps the logo's
   width, overriding the default above. */
.content-logo-image[data-embed-size] {
  max-width: var(--embed-size);
}

/* --- Generic content section ---------------------------------------------- */

.section {
  padding: var(--spacing-lg);
  max-width: var(--content-max-width, 60rem);
  margin: 0 auto;
}

/* Higher specificity than .section alone, so this reliably wins
   regardless of source order: without it, .content-logo's bottom padding
   stacks with the *next* section's top padding, doubling the gap between
   the logo and whatever text follows it. */
.section.content-logo {
  padding-bottom: 0;
}

/* --- Cards ------------------------------------------------------------------ */

/* Columns grow up to a fixed max (not 1fr) so cards stay a sensible, readable
   width instead of stretching edge-to-edge on very large viewports. `auto-fit`
   collapses unused tracks, so the (fixed-width) columns that do exist are
   centered via `justify-content` rather than spread across the full row.
   New rows happen automatically as more cards are added to one `card_grid()`
   call — you don't need to call it multiple times to get more rows.

   `--card-grid-max-columns` (set inline by `card_grid(max_columns=...)`)
   optionally caps how many columns may ever share a row: dividing 100% by
   that number sets a min column width just large enough that only N can
   fit, so auto-fit wraps earlier than it otherwise would. Left unset, it
   defaults to a high number that never meaningfully constrains anything. */
.card-grid {
  --card-grid-max-columns: 12;
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(
      max(200px, (100% - (var(--card-grid-max-columns) - 1) * var(--spacing-md)) / var(--card-grid-max-columns)),
      280px
    )
  );
  justify-content: center;
  gap: var(--spacing-md);
}

.card {
  position: relative;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-elevated);
  padding: var(--spacing-md);
}

/* A logged-in admin's per-card top row - the "Utkast" label (left) and
   the publish-toggle/delete actions (right) share this one full-width
   row, in normal document flow (not overlaid), so every logged-in
   admin's card is pushed down by exactly the same row whether or not
   that particular card is a draft - a card's title always starts at the
   same vertical position regardless of its neighbors' draft state. Only
   ever rendered when there's a draft label and/or actions to show (see
   kitsunemori.widgets.card) - a logged-out visitor's card has no such
   row at all. */
.card-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  min-height: 1.75rem;
  margin-bottom: var(--spacing-sm);
}

/* The two action buttons (delete, publish-toggle) sit in their own
   inner row on the right side of .card-top-row. */
.card-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* .card-delete/.card-publish-toggle's shared box model lives in the
   "small circular icon button" shared rule above. Only what's unique to
   each stays here. */
.card-delete {
  font-weight: bold;
  text-decoration: none;
}

.card-publish-toggle.is-published {
  background: var(--color-secondary);
  color: var(--color-background);
}

/* The <button> itself is just a transparent, borderless hit target
   filling its parent <form>'s already-correctly-shaped circle above -
   `all: unset` strips the browser's default button chrome (padding,
   border, its own font), then only what's needed to fill/center the
   icon glyph is re-declared.

   min-width/min-height: 0 + box-sizing: border-box guard against a
   real cross-browser gotcha `all: unset` alone doesn't reliably clear:
   a <button> (unlike a plain <a>, which .card-delete uses instead and
   is never affected by this) can retain a user-agent-imposed minimum
   content-box size in some engines, which can grow the box past its
   intended 100%/100% square regardless of the explicit width/height
   above - this is the one structural difference between this element
   and .card-delete's own anchor, which is never subject to it. */
.card-publish-toggle button {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  cursor: pointer;
}

/* A draft/unpublished card (kitsunemori.widgets.card's draft=True), only
   ever shown to a logged-in admin who checked "Visa opublicerade" - a
   dashed border + muted background keeps it visually distinct from a
   published card at a glance. */
.card-draft {
  border-style: dashed;
  background: var(--color-background);
}

.draft-label {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--color-secondary);
  color: var(--color-background);
  font-size: 0.75rem;
  font-weight: bold;
  text-transform: uppercase;
}

/* The "Visa opublicerade" checkbox (kitsunemori.widgets.unpublished_toggle),
   shown above a listing's heading alongside the admin bar. */
.unpublished-toggle {
  margin-bottom: var(--spacing-sm);
}

/* A logged-in admin's per-widget top row (e.g.
   kitsunemori.widgets.latest_articles) - the "Inaktiverad" label (left)
   and the widget's own enable/disable action (right) share this one
   full-width row, same shape as .card-top-row/.card-actions above, just
   scoped to a whole page-level widget instead of a single card. */
.widget-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  min-height: 1.75rem;
  margin-bottom: var(--spacing-sm);
}

.widget-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* This widget's own human-readable name (e.g. "Senaste nyheter") - shown
   in both the enabled and disabled state, so a lone toggle icon is never
   the only cue for what it actually controls. */
.widget-name {
  font-size: 0.85rem;
  font-weight: bold;
  color: var(--color-text-muted);
  margin-right: var(--spacing-xs);
}

/* .widget-enable-toggle's shared box model lives in the "small circular
   icon button" shared rule above (near .button/button). Only what's
   unique to it stays here. */
.widget-enable-toggle.is-enabled {
  background: var(--color-secondary);
  color: var(--color-background);
}

.widget-enable-toggle button {
  all: unset;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  cursor: pointer;
}

.widget-disabled-label {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--color-secondary);
  color: var(--color-background);
  font-size: 0.75rem;
  font-weight: bold;
  text-transform: uppercase;
}

/* A page-level widget, whenever a logged-in admin is viewing it (i.e. it
   has its own top row/toggle) - a solid border always outlines the box,
   so it's clear at a glance this whole area is one widget, active or
   not - not just a floating icon. */
.widget-admin {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

/* A currently-disabled page-level widget, revealed to an admin via "Visa
   dolda widgetar" - the border switches to dashed, same visual language
   as .card-draft's dashed border for a draft card, so disabled reads
   differently from active at a glance. There's no content below
   .widget-top-row in this state, so the border ends up wrapping just
   that top row itself. */
.widget-disabled.widget-admin {
  border-style: dashed;
}

.widget-disabled .widget-top-row {
  margin-bottom: 0;
}

/* Respects literal newlines in a card's body text (e.g. a
   "Titles\nReg.nr: ..." summary) as real line breaks, instead of the
   browser default of collapsing them to a space - so a second line
   always starts on its own row regardless of how long the first line
   is. */
.card p {
  white-space: pre-line;
}

.card-thumb {
  display: block;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /* negative top/side margins pull the thumb flush with the card's edges
     (canceling out .card's own padding), so it reads as a "cover" image
     rather than floating inside the padded content area. Only the top
     corners are rounded (matching the card's own radius) since that's the
     only edge that's actually flush with the card's outline. */
  width: calc(100% + var(--spacing-md) * 2);
  max-width: none;
  margin: calc(var(--spacing-md) * -1) calc(var(--spacing-md) * -1) var(--spacing-md);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
}

/* Resets .card-thumb's negative top margin to 0 when a .card-top-row
   (draft label/actions) precedes it, so the image sits below the row
   instead of bleeding up over it - reaches the <img> both bare and
   wrapped in .card-thumb-link (a linked card's actual DOM shape). */
.card-top-row ~ .card-thumb,
.card-top-row ~ .card-thumb-link .card-thumb,
.card-top-row ~ .card-thumb-link {
  margin-top: 0;
}

/* Wraps .card-thumb when the card has an href (clicking the image is a
   natural first instinct - see card()'s docstring). `display: block`
   makes the anchor a plain block box around the image, same as a bare
   .card-thumb img would be, so the negative-margin "flush" trick above
   still behaves identically whether or not the thumb happens to be
   linked. */
.card-thumb-link {
  display: block;
}

/* --- Badges / emblems (e.g. SKK membership + education marks) ------------ */

/* Full image is always shown uncropped (`contain`, no fixed aspect-ratio),
   unlike .card-thumb — these are official marks and any of the four
   provided sizes/orientations should still render correctly whole. */
.badge-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: var(--spacing-lg);
}

.badge {
  max-width: 12rem;
  text-align: center;
}

.badge-image {
  display: block;
  max-width: 100%;
  height: auto;
  object-fit: contain;
  margin: 0 auto var(--spacing-xs);
}

/* Per-instance size (US-010.09.00): one picker per row sets a uniform square
   box for every badge, so marks of different orientations line up evenly.
   object-fit:contain shows each whole mark, letterboxed with whitespace,
   never cropped (these are official marks). --embed-size is set on the row
   and inherited by each badge below. */
.badge-row[data-embed-size] .badge {
  max-width: none;
  width: var(--embed-size);
}

.badge-row[data-embed-size] .badge-image {
  width: 100%;
  height: var(--embed-size);
}

.badge p {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

/* Wraps the image+caption when the badge links out (e.g. to SKK's page
   about that emblem) - `display: block` so the whole badge area, not
   just the text, is one clickable/focusable target. */
.badge-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.badge-link:hover .badge-image,
.badge-link:focus-visible .badge-image {
  opacity: 0.85;
}

/* --- Contact block ------------------------------------------------------- */

.contact-block {
  font-size: 1.1rem;
}

/* --- Media view page (gallery-view classes reused by ContentApp's
   generic media full-view page - see kitsunemori/apps/content_app.py's
   _media_view) --------------------------------------------------------- */

/* Full-size view page: max width from --gallery-view-max-width (same
   default/override pattern as the thumbnail grid above). */
.gallery-view-image {
  display: block;
  max-width: var(--gallery-view-max-width);
  width: 100%;
  height: auto;
  margin: var(--spacing-md) auto;
  border-radius: var(--radius-card);
}

/* Single nav row above the full-size image: <prev> <up/back-to-grid> <next>
   (US-002.03.00 follow-up - previously prev/next sat below the image,
   requiring a scroll to reach them; a fixed 3-column grid keeps the
   middle "up" link centered regardless of whether prev/next are present
   at the first/last image, since empty cells still hold their column). */
.gallery-view-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--spacing-md);
  max-width: var(--gallery-view-max-width);
  margin: 0 auto;
}

.gallery-view-prev {
  justify-self: start;
}

.gallery-view-up {
  justify-self: center;
}

.gallery-view-next {
  justify-self: end;
}

/* Card-grid lightbox overlay (US-002.05.00, scripts/lightbox.js) - a
   progressive enhancement in front of /media/<id>'s own full-view page;
   .gallery-view-image/.gallery-view-nav are reused unchanged inside it. */
.lightbox-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 80%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-md);
  z-index: 1000;
}

.lightbox-dialog {
  position: relative;
  background: var(--color-surface, #fff);
  border-radius: var(--radius-card);
  width: min(90vw, var(--gallery-view-max-width));
  max-height: 90vh;
  overflow-y: auto;
  box-sizing: border-box;
  padding: var(--spacing-lg) var(--spacing-md) var(--spacing-md);
}

.lightbox-dialog:focus {
  outline: none;
}

.lightbox-close {
  position: absolute;
  top: var(--spacing-sm);
  right: var(--spacing-sm);
  border: none;
  background: none;
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
  padding: var(--spacing-xs);
}

.lightbox-title {
  text-align: center;
}

.lightbox-image {
  margin: var(--spacing-sm) auto;
}

/* --- Footer ---------------------------------------------------------------- */

.site-footer {
  text-align: center;
  padding: var(--spacing-md);
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.site-footer-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-xs);
}

.site-footer-brand {
  color: var(--color-primary);
  font-family: var(--font-heading);
  font-weight: bold;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--spacing-sm);
}

.site-footer-link {
  color: var(--color-text-muted);
}

.site-footer-link:hover {
  color: var(--color-primary);
}

/* --- Article body (US-003.01.00) ------------------------------------------- */

/* --- Admin affordances shown directly on public pages (US-007.02.02) -------
   Only ever rendered when a valid admin session is present (see
   kitsunemori.public.app.ArticlesApp._current_user / _render_admin_bar)
   - a logged-out visitor's HTML never contains this element at all, so
   there's no "hide with CSS" concern here, only "make it look intentional
   for the one audience (a logged-in admin) who does see it." */
.admin-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.admin-bar .logged-in-as {
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
}

.admin-bar .logged-in-as:hover,
.admin-bar .logged-in-as:focus-visible {
  text-decoration-color: currentColor;
}

/* .button already has its own margin-top for hero-CTA use; reset it here
   so the button sits centered on the bar's own flex row instead. */
.admin-bar .button {
  margin-top: 0;
}



.article-image {
  max-width: 40rem;
  margin: var(--spacing-lg) auto;
}

/* Per-instance embed size (US-010.09.00): a widget's size picker sets
   data-embed-size on its image/figure; each token maps to a display width
   here via the site-settings variables. An image widget opts in by applying
   var(--embed-size) to whichever property suits its layout (max-width for
   a standalone figure, width for a fixed column, ...). The served file
   tier (thumb/view) is chosen separately in content_app, so a smaller box
   just scales a big-enough file down - never an upscaled blur. */
[data-embed-size="thumb-small"]  { --embed-size: var(--embed-thumb-small, 10rem); }
[data-embed-size="thumb-medium"] { --embed-size: var(--embed-thumb-medium, 15rem); }
[data-embed-size="thumb-large"]  { --embed-size: var(--embed-thumb-large, 22rem); }
[data-embed-size="view-small"]   { --embed-size: var(--embed-view-small, 30rem); }
[data-embed-size="view-medium"]  { --embed-size: var(--embed-view-medium, 42rem); }
[data-embed-size="view-large"]   { --embed-size: var(--embed-view-large, 56rem); }

/* Standalone photo figure: the chosen size caps its width; the margin:auto
   above keeps a narrower photo centered. */
.article-image[data-embed-size] { max-width: var(--embed-size); }

/* Card grid: the chosen size sets each card's target width, so a bigger size
   means bigger (and thus fewer) cards per row. The grid still auto-fits to the
   viewport (collapsing to one full-width column when it's narrower than the
   card), and Max antal kolumner still caps the column count. */
.card-grid[data-embed-size] {
  grid-template-columns: repeat(
    auto-fit,
    minmax(
      min(
        100%,
        max(
          var(--embed-size),
          (100% - (var(--card-grid-max-columns) - 1) * var(--spacing-md)) / var(--card-grid-max-columns)
        )
      ),
      var(--embed-size)
    )
  );
}

.article-image img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
}

.article-image figcaption {
  margin-top: var(--spacing-xs);
  color: var(--color-text-muted);
  font-size: 0.9rem;
  text-align: center;
}

/* Generic media object widget: image beside heading/subtitle, stacking on
   narrow viewports. Reusable beyond dog pages - see the "Media Object" CSS
   pattern (image/media on one side, flexible content on the other). */
.media-object {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-lg);
}

/* side="right" - same markup, just visually reversed. */
.media-object--reverse {
  flex-direction: row-reverse;
}

.media-object-image {
  display: block;
  width: 16rem;
  max-width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-card);
}

/* Per-instance size (US-010.09.00): the chosen size sets the image column's
   width; the fixed 4/3 aspect + object-fit:cover keeps it a tidy rectangle
   regardless of the source photo's orientation (cropping, never letterboxing). */
.media-object-image[data-embed-size] { width: var(--embed-size); }

.media-object-content {
  flex: 1 1 16rem;
}

.media-object-subtitle {
  color: var(--color-text-muted);
  font-size: 1.1rem;
}

.media-object-detail {
  color: var(--color-text-muted);
  font-size: 0.95rem;
}

/* Page-compose screen (kitsunemori.pages.content_compose_page,
   US-011.04.02): one <fieldset> per widget slot, laid out as a fixed
   reorder-buttons sidebar next to that widget's own admin fields, so the
   "\u2191"/"\u2193" buttons are an obvious, always-in-the-same-place control
   rather than plain inline buttons buried among the fields. */
.content-compose-slot {
  margin-bottom: var(--spacing-md);
  padding: var(--spacing-sm);
  border: 1px solid var(--color-secondary);
  border-radius: 4px;
}

.content-compose-slot-layout {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
}

.content-compose-slot-controls {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  flex: 0 0 auto;
}

.content-compose-slot-body {
  flex: 1 1 auto;
  min-width: 0;
}

/* .content-compose-move-up/-down/-remove-widget's shared box model +
   hover live in the "small circular icon button" shared rule above (near
   .button/button) - nothing unique left to declare for any of the three
   here. */

.content-compose-widget-description {
  color: var(--color-text-muted);
  font-size: 0.9rem;
  margin-top: 0;
}

/* Preview screen back-form appears above and below the content. */
.content-preview-back {
  margin-bottom: var(--spacing-md);
}

.content-preview-body {
  border: 1px dashed var(--color-secondary);
  padding: var(--spacing-sm);
}

/* Parent picker: filter <input> above the <select> it filters. */
.content-compose-parent-picker {
  margin-bottom: var(--spacing-md);
}

.content-parent-picker-filter,
.content-parent-picker-select {
  display: block;
  width: 100%;
}

/* Widget-field link picker (US-014.05.00): the free-typed href input
   stays visible, with the filter/select pair shown right below it -
   same always-expanded layout as the parent picker above, just scoped
   per link field instead of once per compose screen. */
.content-link-picker {
  margin-bottom: var(--spacing-md);
}

.content-link-picker-filter,
.content-link-picker-select {
  display: block;
  width: 100%;
}

/* /menu nav-management screen (kitsunemori.pages.nav_page,
   US-011.05.02): one <fieldset> per nav entry, same bordered-box shape
   as .content-compose-slot above, so an entry and the add-form read as
   clearly separate boxes instead of a table whose header doesn't match
   its own rows. */
.nav-menu-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.nav-menu-entry {
  padding: var(--spacing-sm);
  border: 1px solid var(--color-secondary);
  border-radius: 4px;
}

.nav-menu-entry legend {
  max-width: 100%;
  overflow-wrap: break-word;
}

.nav-menu-entry-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm) var(--spacing-md);
}

.nav-menu-entry-fields label {
  flex: 1 1 12rem;
}

.nav-menu-entry-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-xs);
}

.nav-menu-entry-actions button {
  margin-top: 0;
}


/* /settings site-settings screen (kitsunemori.pages.settings_page,
   US-013.03.00): plain single-field form, same label/spacing language
   as the nav-menu add form above. */
.site-settings-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.site-settings-notice {
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--color-surface);
}

.site-settings-hint {
  display: block;
  margin-top: var(--spacing-xs);
  color: var(--color-text-muted);
  font-size: 0.85rem;
  font-weight: normal;
}

.site-settings-group {
  min-width: 0;
  margin: var(--spacing-md) 0 0;
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.site-settings-group legend {
  padding: 0 var(--spacing-xs);
  color: var(--color-primary);
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: bold;
}

.site-settings-group-layout {
  display: grid;
  grid-template-columns: minmax(15rem, 1fr) minmax(16rem, 1fr);
  align-items: start;
  gap: var(--spacing-md);
}

.site-settings-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: var(--spacing-sm);
}

.site-settings-preview {
  min-width: 0;
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.site-settings-color-preview {
  color: var(--color-text);
  background: var(--color-background);
}

.site-settings-color-preview h2,
.site-settings-font-preview h2,
.site-settings-size-preview h2 {
  margin: 0 0 var(--spacing-sm);
  color: var(--color-primary);
  font-family: var(--font-heading);
  font-size: 1.4rem;
}

.site-settings-preview-header {
  margin: calc(-1 * var(--spacing-md)) calc(-1 * var(--spacing-md)) var(--spacing-md);
  padding: var(--spacing-sm) var(--spacing-md);
  color: var(--color-background);
  background: var(--color-primary);
  font-weight: bold;
}

.site-settings-color-preview p {
  color: var(--color-text);
}

.site-settings-color-preview .site-settings-preview-muted {
  color: var(--color-text-muted);
}

.site-settings-color-preview a {
  color: var(--color-secondary);
}

.site-settings-preview-surface {
  margin-top: var(--spacing-sm);
  padding: var(--spacing-sm);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 0.5rem solid var(--color-accent);
}

.site-settings-font-preview {
  color: var(--color-text);
  background: var(--color-surface);
}

.site-settings-font-preview p {
  font-family: var(--font-body);
}

/* The widths being previewed here go up to the full content width, so
   this group stacks (fields above, preview below) instead of splitting
   the row in half like the color/font groups. */
.site-settings-size-layout {
  grid-template-columns: 1fr;
}

.site-settings-preview-column {
  width: calc(var(--content-max-width, 60rem) / 2);
  max-width: 100%;
  padding: calc(var(--spacing-lg) / 2);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}

.site-settings-preview-column > p {
  margin: 0 0 var(--spacing-sm);
}

.site-settings-preview-images {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--spacing-md);
}

.site-settings-size-example {
  flex: 0 0 auto;
  max-width: 100%;
  margin: 0;
  text-align: center;
}

/* Width is the setting being previewed; the height cap only keeps a
   full-width example from filling the screen vertically. */
.site-settings-size-example img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  max-height: 7rem;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.site-settings-size-value {
  white-space: nowrap;
  font-weight: bold;
}

.site-settings-size-example figcaption {
  margin-top: var(--spacing-xs);
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

.site-settings-size-embed_thumb_small {
  width: calc(var(--embed-thumb-small) / 2);
}

.site-settings-size-embed_thumb_medium {
  width: calc(var(--embed-thumb-medium) / 2);
}

.site-settings-size-embed_thumb_large {
  width: calc(var(--embed-thumb-large) / 2);
}

.site-settings-size-embed_view_small {
  width: calc(var(--embed-view-small) / 2);
}

.site-settings-size-embed_view_medium {
  width: calc(var(--embed-view-medium) / 2);
}

.site-settings-size-embed_view_large {
  width: calc(var(--embed-view-large) / 2);
}

.site-settings-size-gallery_view_max_width {
  width: calc(var(--gallery-view-max-width) / 2);
}

@media (max-width: 48rem) {
  .site-settings-group-layout {
    grid-template-columns: 1fr;
  }
}

/* --- Content library browse/filter page (US-010.06.00, slice 1) --- */

.library-toolbar {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.library-toolbar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-md);
}

.library-toolbar-row + .library-toolbar-row {
  margin-top: var(--spacing-sm);
  padding-top: var(--spacing-sm);
  border-top: 1px solid var(--color-border);
}

.library-filter-field {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-size: 0.85rem;
  white-space: nowrap;
}

.library-filter-field label {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.library-filter-hint {
  font-size: 0.78rem;
  color: var(--color-text-muted);
  font-style: italic;
}

.library-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 0.9rem;
}

.library-table th,
.library-table td {
  text-align: left;
  padding: var(--spacing-xs) var(--spacing-sm);
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
  overflow-wrap: break-word;
}

/* Fixed column widths (paired with table-layout: fixed above) so every
   row's columns line up identically regardless of which cells happen
   to have content (e.g. a page row has no thumbnail, an image row has
   no "Redigera" link) - an auto-sized table would otherwise size each
   column per-row, misaligning rows that don't share the same content.

   Namn/Taggar deliberately have no <col> width at all (see
   content_library_page._render_table) - table-layout: fixed shares
   remaining space evenly between any unset column, i.e. "flex to fill
   whatever's left", while every other column below is a short,
   known-format value pinned to a generous fixed rem width. */
.library-col-thumb { width: 3.5rem; }
.library-col-type { width: 4.5rem; }
.library-col-created { width: 13rem; }
.library-col-actions { width: 9rem; }
.library-col-select { width: 2rem; }

.library-cell-select {
  text-align: center;
}

.library-cell-type,
.library-cell-created {
  white-space: nowrap;
}

.library-cell-thumb {
  width: 3.5rem;
  height: 3.5rem;
}

.library-cell-name {
  font-weight: bold;
}

.library-row-thumb {
  width: 2.5rem;
  height: 2.5rem;
  object-fit: cover;
  border-radius: var(--radius-card);
  display: inline-block;
}

.library-row-thumb-missing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-card);
  background: repeating-linear-gradient(
    45deg, var(--color-border), var(--color-border) 4px, var(--color-surface) 4px, var(--color-surface) 8px
  );
  color: var(--color-secondary);
  font-weight: bold;
}

/* .draft-label itself is NOT redeclared here - it already exists (see
   .card-draft's own "Utkast" pill rule above) and this table reuses that
   exact class/style for the same meaning, rather than a second,
   conflicting definition. */
.library-cell-name .draft-label {
  margin-left: var(--spacing-xs);
}

.missing-label {
  display: inline-block;
  margin-left: var(--spacing-xs);
  border-radius: 4px;
  padding: 0.05rem 0.35rem;
  font-size: 0.7rem;
  background: var(--color-secondary);
  color: var(--color-background);
}

.row-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* Fixed-width slot for the page-only "Redigera" link, so the
   publish-toggle/delete icons after it line up at the same x position
   on every row, including image rows that never render it. */
.row-actions-edit {
  display: inline-block;
  min-width: 4.5rem;
}

.pager {
  display: flex;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-sm);
}

.pager a,
.pager span {
  padding: 0.1rem 0.5rem;
  border-radius: 4px;
}

.pager span[aria-current="page"] {
  background: var(--color-primary);
  color: var(--color-background);
}

.library-bulk-form {
  margin: 0;
}

.library-bulk-bar {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
  padding-bottom: var(--spacing-sm);
  border-bottom: 1px solid var(--color-border);
}

.library-bulk-bar-tag-row,
.library-bulk-bar-actions-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-sm);
}

.library-bulk-bar-tag-row .tag-suggest {
  flex: 1;
  min-width: 14rem;
}

/* The generic "button"/".button" rule (near the top of this file) sets
   margin-top: var(--spacing-md), meant for a button sitting below a
   form field it belongs to (its usual context). Here the submit button
   sits *beside* the tags field instead - that inherited top margin
   made align-items: center on .library-bulk-bar center the button's
   margin box rather than its visible content, pushing it visibly
   lower than the input field next to it. */
.library-bulk-bar button {
  margin-top: 0;
}

.bulk-unpublish-submit,
.bulk-delete-submit {
  white-space: nowrap;
}

.bulk-unpublish-result,
.bulk-delete-result {
  margin-top: var(--spacing-sm);
  padding-top: var(--spacing-sm);
  border-top: 1px dashed var(--color-border);
}

.bulk-delete-cascade {
  margin-top: var(--spacing-sm);
}

.bulk-delete-cascade h2 {
  font-size: 1rem;
  margin: 0 0 var(--spacing-xs) 0;
}

.bulk-delete-cascade-tree,
.bulk-delete-cascade-tree ul {
  margin: var(--spacing-xs) 0;
  padding-left: var(--spacing-md);
}

.bulk-delete-cascade-checkbox {
  margin-right: var(--spacing-xs);
}

.tag-merge-tool,
.tag-delete-tool {
  margin-top: var(--spacing-md);
  padding-top: var(--spacing-sm);
  border-top: 1px solid var(--color-border);
}

.tag-merge-tool h2,
.tag-delete-tool h2 {
  font-size: 1rem;
  margin: 0 0 var(--spacing-xs) 0;
}

.tag-merge-form,
.tag-delete-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-md);
}

/* Without this, each <label>'s text and its <select> are plain inline
   content - too narrow to fit on one line, the browser wraps the
   select onto its own line under the label text, while the button
   (a single line) doesn't - producing an uneven, seemingly-random
   vertical alignment across the row. Making each label a small flex
   row of its own keeps label+select together on one line, matching
   the button's own height so align-items: center on the outer row
   actually looks aligned.

   margin-top: 0 here (and on the select below) cancels the generic
   label/select rules' own top margin (meant for a field stacked below
   the one above it, their usual context) - same "sits beside, not
   below" reasoning already applied to .library-bulk-bar button. Left
   in place, only the button had that margin canceled, so the button
   sat flush with the row while both labels/selects still carried
   extra top margin - the row's actual source of the "button floats
   above / feels jumpy against the row" misalignment. */
.tag-merge-form label {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin-top: 0;
  white-space: nowrap;
}

.tag-merge-form label select {
  margin-top: 0;
}

.tag-merge-form button {
  margin-top: 0;
}

.tag-merge-result,
.tag-delete-result {
  margin-top: var(--spacing-sm);
  padding-top: var(--spacing-sm);
  border-top: 1px dashed var(--color-border);
}

.tag-merge-result button,
.tag-delete-result button {
  margin-top: var(--spacing-xs);
  margin-right: var(--spacing-xs);
}
