/* ==========================================================================
   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);
}

/* --- 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;
  width: 1.75rem;
  height: 1.75rem;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  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;
}

/* --- 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"],
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);
}


/* --- 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;
}

/* --- Generic content section ---------------------------------------------- */

.section {
  padding: var(--spacing-lg);
  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. */
.card-publish-toggle button {
  all: unset;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  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 dog's
   "Titles\nReg.nr: ..." summary, see kitsunemori.models.dog) 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;
}

/* 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);
}

.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;
}

/* --- Gallery grid / view pages (US-002.03.00) ----------------------------- */

/* Thumbnail grid: columns sized from --gallery-thumb-size (site-wide
   default in tokens.css, optionally overridden per-gallery via
   gallery.txt's ThumbSize field - see kitsunemori/gallery.py). */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--gallery-thumb-size), 1fr));
  gap: var(--spacing-md);
}

.gallery-thumb {
  display: block;
}

.gallery-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-card);
}

/* 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;
}

/* --- Footer ---------------------------------------------------------------- */

.site-footer {
  text-align: center;
  padding: var(--spacing-md);
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

/* --- 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;
}



/* Paragraphs + inline captioned images, rendered from a plain-text
   article body (see kitsunemori/article.py). Constrained to a readable
   line length, same as .section's own max-width. */
.article-body p {
  max-width: 40rem;
  margin: 0 auto var(--spacing-md);
}

.article-image {
  max-width: 40rem;
  margin: var(--spacing-lg) auto;
}

.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;
}

/* --- Dog profile pages (US-006.00.00) --------------------------------------
   Uses the generic .media-object widget classes
   (kitsunemori/widgets/media_object.py) for the image-beside-heading/subtitle
   layout, plus .dog-body for the free-text section below it. */

.dog-body p {
  max-width: 40rem;
  margin: 0 auto var(--spacing-md);
}

/* 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%;
  height: auto;
  border-radius: var(--radius-card);
}

.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;
}





