:root {
  --bg: #f4f5f1;
  --surface: #ffffff;
  --surface-muted: #f7f8f5;
  --ink: #1c211e;
  --ink-muted: #5b625c;
  --accent: #b4732e;
  --line: #dcded7;
  --danger: #a1453d;
  --success: #4c7a52;
  /* A field an editor has changed but not yet saved (member.js/profile.js's
     own "Datos" forms, via js/formDirty.js) — soft enough not to read as
     an error or a validation warning, distinct enough from --surface to
     spot at a glance which fields are actually pending a save. */
  --field-dirty-bg: #fbf1da;
  /* Interactive links/actions inside content (phone numbers, "Copiar
     NIF", document links) — distinct from --accent, which stays
     reserved for the sidebar/brand. */
  --link: #2f6fed;
}

* {
  box-sizing: border-box;
}

/* The browser's own [hidden] { display: none } rule lives in the
   lowest-priority (user-agent) origin, so any author rule setting
   display on the same element — .btn's display: inline-flex, say —
   silently wins and leaves a "hidden" element visible. This reasserts
   it at author-with-!important, the only thing that reliably beats an
   arbitrary future .whatever { display: ... } rule too. */
[hidden] {
  display: none !important;
}

.impersonation-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 8px 16px;
  background: var(--accent);
  color: #fff;
  font-size: 0.85rem;
  text-align: center;
}

.impersonation-banner .btn {
  background: transparent;
  border-color: #fff;
  color: #fff;
}

.impersonation-banner .btn:hover {
  background: rgba(255, 255, 255, 0.15);
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--ink);
}

/* Every navigation between pages is still a plain, full document load
   (see docs/architecture.md's Frontend section — no SPA, no router, a
   deliberate choice) but Chrome/Edge's cross-document View Transitions
   API smooths the visible swap anyway, for free: opting in with
   @view-transition below makes the browser cross-fade the outgoing and
   incoming page instead of the default instant, sometimes-flickery
   replace — no JS involved, and a browser that doesn't support it
   (Firefox, Safari as of this writing) just keeps today's plain
   navigation, so this is pure progressive enhancement. Naming #sidebar
   and #topbar below goes one step further: since both render the exact
   same markup on every page (js/layout.js's renderShell(), synchronous
   before first paint), the browser treats them as the *same* element
   persisting across the navigation and morphs it in place instead of
   fading it out and back in — which is the part of a hard navigation
   that actually reads as a "flicker", since the rest of the page (the
   sidebar/topbar) never visually changes at all between two
   authenticated pages. */
@view-transition {
  navigation: auto;
}

#sidebar {
  view-transition-name: page-sidebar;
}

#topbar {
  view-transition-name: page-topbar;
}

/* The View Transitions API doesn't honor prefers-reduced-motion on its
   own — it's on the page to turn the animation off, not just shorten
   it, since ::view-transition-group(*)'s default is still a fade/move
   either way. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Shared page shell: an orange sidebar (just nav — the club logo lives
   in the topbar instead, see .topbar-brand, so it isn't repeated here
   too) down the left, everything else — a topbar with the logo/title
   and a greeting/logout, then the page's own content — filling the
   rest. Every page except /login uses this (login has no session yet,
   so a nav full of protected-area links and a "Hola, ..." greeting
   would make no sense there — see login.html's own centered
   form.panel instead). */
.shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 180px;
  flex-shrink: 0;
  background: var(--accent);
  color: #fff;
  padding: 20px 0;
}

/* On a wide screen the header only holds the button that hides the menu (the
   logo is already up in the topbar); the narrow layout below shows the logo too. */
.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 10px 8px;
}

.sidebar-logo {
  display: none;
}

.sidebar-close {
  padding: 0 6px;
  border: none;
  background: none;
  color: #fff;
  font-size: 1.8rem;
  line-height: 1;
  cursor: pointer;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
}

.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
  font-size: 0.92rem;
}

.sidebar-nav a:hover {
  background: rgba(255, 255, 255, 0.12);
}

.sidebar-nav a.active {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  font-weight: 600;
}

/* Thin-line outline icons, one per nav entry — plain inline SVG rather
   than an emoji (no dependency on the browser/OS having a color emoji
   font installed at all, unlike 🏠/👥/🧗/🎒/📚/🙋, which is a real gap on
   a bare-bones Linux desktop) or an icon font/library (nothing to load,
   consistent with how little this app already depends on). stroke:
   currentColor means each one already matches its own link's text
   color — rgba(255,255,255,.85) normally, solid white and bold once
   .active — with no color rule of its own needed here. */
.nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.main-column {
  flex: 1;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 28px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* min-width: 0 overrides a flex item's default auto min-width (its
   content's own min-content size — here, since h1 can wrap, roughly
   the width of its single longest word) with the ordinary "can shrink
   all the way down" floor every non-flex box already has. Without it,
   .topbar's wrap point (see .topbar-user's own comment) landed at an
   inconsistent width depending on exactly where the title's text
   happened to wrap internally, sometimes leaving .topbar-user sharing
   .topbar-brand's own line — off in a half-wrapped state where
   margin-left: auto had much less room to push it into, and it ended
   up rendered near the left instead of the right. */
.topbar-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* The hamburger toggle for .sidebar — hidden here (desktop, where the
   sidebar is always visible) and only shown under the narrow-screen
   media query below. Lives in .topbar-brand, right alongside the logo
   and title, rather than in the sidebar itself — a whole extra
   full-width bar up there just to hold one icon wasted a lot of
   vertical space for nothing else. It's never hidden via [hidden] for
   as long as the menu is open (an earlier version did exactly that),
   but it stays covered by it visually — .sidebar.open sits at a higher
   z-index (see the narrow-screen media query below) and this element
   gets no z-index of its own to poke back above it, so the two never
   fight over the same pixels the way they would if this were an
   ordinary same-stacking-level overlap. The point of leaving it in
   place rather than hiding it is purely to keep it occupying its
   usual slot in .topbar-brand's flex layout: [hidden] used to drop it
   out of that layout entirely, visibly shifting the title left into
   the gap it left behind — being covered instead of removed avoids
   that shift without needing the toggle to actually stay visible. */
.sidebar-toggle {
  display: none;
  padding: 4px 6px;
  border: none;
  background: none;
  color: var(--accent);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.topbar-logo {
  height: 36px;
  width: auto;
}

.topbar h1 {
  font-size: 1.15rem;
  margin: 0;
  color: var(--accent);
}

/* margin-left: auto (not just .topbar's own justify-content:
   space-between) pushes this flush to the right even once .topbar's
   flex-wrap wraps it onto its own second line below .topbar-brand on
   a narrow screen — space-between only has an effect when there are
   two items sharing one line to put space between; alone on a
   wrapped line of its own, it would otherwise sit at that line's
   start (left) instead. flex-shrink: 0 keeps this from being
   compressed below its own natural width first (its default
   flex-shrink: 1 would let it shrink alongside .topbar-brand instead
   of wrapping cleanly) — between this and .topbar-brand's own
   min-width: 0, the wrap either happens or it doesn't, with nothing
   in between where the two sit awkwardly sharing a line. */
.topbar-user {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
  margin-left: auto;
  font-size: 0.9rem;
}

.content {
  padding: 28px;
  max-width: 1120px;
}

/* #feedback (the shared top-of-page status banner every
   authenticated page has, right after <main class="content"> opens —
   login.html's own request-feedback/request-error/code-error sit
   inline below the button they answer instead, on a page short enough
   to never need this) used to just sit in the normal document flow, so
   an action taken further down a long, scrolled page — editing a role,
   filtering a long socios table — could leave it scrolled entirely out
   of view, with nothing on screen to show anything had even happened.
   position: sticky keeps it in its normal place (right below the
   topbar) while the page is scrolled to the top, same as before, but
   pins it to the very top of the viewport once scrolled past — which
   by then is also true of the topbar itself, so nothing already
   visible gets newly covered. Below .sidebar.open's own z-index (100,
   see the narrow-screen media query below) so the mobile menu still
   overlays it rather than the other way around. */
#feedback {
  position: sticky;
  top: 0;
  z-index: 90;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

/* Below this width, .sidebar is collapsed entirely — not just its own
   nav — behind .sidebar-toggle (now up in .topbar-brand), shown only
   once opened via its own .open class (see api.js's
   initSidebarToggle, which also closes it again on a tap/click
   anywhere outside it — the toggle itself sits right underneath the
   open menu, covered by it, see its own comment, so in practice that
   outside tap is what closes it day to day). Opening it overlays the
   current screen from the very top-left corner (position: fixed,
   top/left: 0) at the same width as the desktop sidebar itself, rather
   than pushing .main-column's own content down the page or covering
   the full screen width for just 6 links — covering the topbar
   (toggle, logo, title, greeting) underneath it too, not just the
   page's own content below it, is simply what a fixed top-left overlay
   at a higher z-index does; nothing here carves out an exception for
   the topbar. */
@media (max-width: 800px) {
  .shell {
    flex-direction: column;
  }

  /* Always in the layout, parked off-screen to the left and slid in by
     .open, so opening is an animation instead of a sudden appearance.
     visibility keeps the hidden links out of the tab order and away from
     screen readers; it flips at the end of the slide when closing and at
     the start when opening (hence the delay on the closed state only). */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 180px;
    z-index: 100;
    overflow-y: auto;
    padding-top: 0;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.25s ease, visibility 0s linear 0.25s;
  }

  .sidebar.open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.25s ease;
  }

  /* The club logo on the left and a close "X" on the right, above the
     links, on the menu's own background. Only exists in this narrow
     layout. */
  .sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 10px;
    margin-bottom: 12px;
  }

  .sidebar-logo {
    display: block;
    height: 36px;
    width: auto;
  }

  @media (prefers-reduced-motion: reduce) {
    .sidebar,
    .sidebar.open {
      transition: none;
    }
  }

  .sidebar-toggle {
    display: block;
  }
}

/* The same collapsed layout on a wide screen, when the person hid the menu with
   its own button (api.js's initSidebarToggle puts .sidebar-hidden on .shell and
   remembers it): the menu is parked off-screen and the hamburger shows, as
   below 800px — except that there the hamburger brings the normal layout back
   instead of overlaying the menu. Keep these rules the same as the narrow
   ones above. */
.shell.sidebar-hidden {
  flex-direction: column;
}

.shell.sidebar-hidden .sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 180px;
  z-index: 100;
  overflow-y: auto;
  padding-top: 0;
  transform: translateX(-100%);
  visibility: hidden;
}

.shell.sidebar-hidden .sidebar-toggle {
  display: block;
}

h1 {
  font-size: 1.6rem;
  margin-bottom: 4px;
}

h2 {
  font-size: 1.15rem;
  margin-top: 32px;
}

p.subtitle {
  color: var(--ink-muted);
  margin-top: 0;
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 32px;
}

.section-header h1,
.section-header h2 {
  margin-top: 0;
}

.table-wrap {
  overflow-x: auto;
}

#item-search-field {
  max-width: 22rem;
  margin-bottom: 8px;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 20px;
}

th,
td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 0.92rem;
}

th {
  color: var(--ink-muted);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}

th.sortable {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

th.sortable:hover {
  color: var(--accent);
}

.clickable-rows tr {
  cursor: pointer;
}

.clickable-rows tr:hover {
  background: var(--surface-muted);
}

/* /cuenta's ledger: a row expands to show the transaction's other
   accounts (account.js's toggleEntryDetail). Not .clickable-rows, whose
   tr rules would also catch the expanded detail row itself. */
.entry-row {
  cursor: pointer;
}

.entry-row:hover,
.entry-row:focus-visible {
  background: var(--surface-muted);
}

/* An expanded row and its detail read as one block: same background, and
   no line between them (only the one below the detail remains). */
.entry-row[aria-expanded="true"] td {
  background: var(--surface-muted);
  border-bottom: none;
}

.entry-detail td {
  background: var(--surface-muted);
  padding-top: 0;
  padding-bottom: 12px;
}

.entry-detail-title {
  color: var(--ink-muted);
  font-size: 0.78rem;
  margin-bottom: 4px;
}

.entry-detail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 26rem;
}

.entry-detail-list + .entry-detail-title,
.entry-detail-list + p.entry-detail-title {
  margin-top: 10px;
}

.entry-detail-list li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
  margin-top: 20px;
}

.card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px 18px;
  color: inherit;
  text-decoration: none;
}

.card h3 {
  margin: 0 0 6px;
  font-size: 1rem;
}

.card p {
  margin: 0;
  color: var(--ink-muted);
  font-size: 0.88rem;
}

.card .card-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}

.card select.status-select {
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 0.8rem;
  font-family: inherit;
  background: var(--surface);
  color: var(--ink);
}

.edit-fields:not([hidden]) {
  margin-top: 10px;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: 6px;
}

.edit-fields label {
  font-size: 0.8rem;
  color: var(--ink-muted);
}

.detail-list {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
}

.detail-list .detail-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
}

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  background: #eee;
}

.badge-removable {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  background: #f1e6da;
  color: var(--ink);
  margin: 2px 4px 2px 0;
}

.badge-removable button {
  background: none;
  border: none;
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0 2px;
}

.badge-removable button:hover {
  color: var(--danger);
}

.role-checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  padding: 8px 0;
}

.role-checkbox-group label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.88rem;
  font-weight: normal;
}

.filter-groups {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 4px 0 10px;
}

.filter-group {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
}

.filter-group-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-muted);
  min-width: 100px;
}

.role-add-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
  align-items: center;
}

.role-add-row select,
.role-add-row input {
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 0.8rem;
  font-family: inherit;
  background: var(--surface);
  color: var(--ink);
}

.badge.status-planned {
  background: #e6eef5;
  color: #3e6e8e;
}

.badge.status-completed {
  background: #e8f0e9;
  color: #4c7a52;
}

.badge.status-cancelled {
  background: #f6e9e8;
  color: var(--danger);
}

form.form-panel {
  max-width: 360px;
  margin: 60px auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px;
}

/* 404.html's plain panel for somebody with no session: the same box as the
   sign-in form, for a block that isn't a form. */
.notfound-panel {
  max-width: 480px;
  margin: 60px auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px;
}

form.form-panel label {
  display: block;
  font-size: 0.85rem;
  margin: 14px 0 4px;
}

form.form-panel input {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 0.95rem;
}

form.form-panel button {
  margin-top: 20px;
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 0.95rem;
  cursor: pointer;
}

/* /login's own brand row — the same club logo (images/logo-katiuskas.png)
   every other page's .topbar-logo shows now. */
.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1.3rem;
}

.login-brand-mark {
  height: 32px;
  width: auto;
  flex-shrink: 0;
}

/* Sits below the main submit button as a secondary, alternate way in —
   its own top margin/border keep it visually separate from the
   email/phone form above it, instead of reading like part of the same
   flow (p.subtitle's own margin-top: 0 would otherwise leave it
   flush against the button). */
.telegram-info {
  margin-top: 28px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.error-message {
  background: #f6e9e8;
  color: var(--danger);
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 0.88rem;
  margin-top: 14px;
}

.status-problems {
  background: #f6e9e8;
  color: var(--danger);
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 0.88rem;
}
.status-problems ul { margin: 6px 0 0; padding-left: 20px; }

.success-message {
  background: #e8f0e9;
  color: var(--success);
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 0.88rem;
  margin-top: 14px;
}

button.link-button {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.88rem;
  padding: 4px 8px;
}

/* Generic panel: bordered box used for creation forms and grouped actions. */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 20px 22px;
  margin-top: 16px;
}

.panel h3 {
  margin: 0 0 4px;
  font-size: 1rem;
}

.panel p.hint {
  margin: 0 0 16px;
  color: var(--ink-muted);
  font-size: 0.85rem;
}

/* Bordered card used to wrap a "documents from this folder" section —
   Documentos/Actas/Memorias on index.html, Licencias federativas on
   perfil.html/socio.html — same look regardless of whether what's
   inside is a plain .link-list or the thumbnail grid below, and
   nothing page-specific about the card itself, which is why it lives
   here rather than in dashboard.css. */
.info-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px;
  margin-top: 16px;
  font-size: 0.88rem;
}

/* What opens a card (a list, a paragraph) already has the card's padding above it: its own top
   margin would only add to it. */
.info-card > :first-child {
  margin-top: 0;
}

.link-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.link-list li {
  margin-bottom: 8px;
}

.link-list a {
  color: var(--link);
  font-size: 0.85rem;
  text-decoration: none;
}

.link-list a:hover {
  text-decoration: underline;
}

/* A year filter above one of the .link-list sections above (Actas
   asambleas, Licencias federativas) — the label sits inline with the
   <select>, both separated from the (re-rendered on change) list below
   by the same top margin .link-list already uses. */
.info-card label {
  margin-right: 6px;
  font-size: 0.85rem;
  color: var(--ink-muted);
}

.year-select {
  font: inherit;
  font-size: 0.85rem;
}

/* Licencias federativas' own per-year list (js/licenseFiles.js): a
   small grid of clickable thumbnails instead of a plain .link-list of
   text links, so a license is recognizable at a glance. Each
   thumbnail displays at 140px on its longest side — the file actually
   dropped in at "licencias/.thumbnails/<name>.jpg" alongside a
   license should be about twice that, 280px, so it still looks sharp
   on a high-DPI screen. */
.license-thumbnail-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.license-thumbnail-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 140px;
  text-align: center;
}

.license-thumbnail,
.license-thumbnail-placeholder {
  width: 140px;
  height: 140px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-muted);
}

.license-thumbnail {
  object-fit: contain;
}

/* Shown in place of a thumbnail <img> that failed to load (see
   wireThumbnailFallbacks) — a license with no thumbnail dropped in yet
   whose own file also isn't itself an image (a PDF, say). */
.license-thumbnail-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.6rem;
}

.license-thumbnail-label {
  margin-top: 6px;
  font-size: 0.78rem;
  color: var(--ink-muted);
}

/* Field grid: label-above-input pairs, wrapping into columns on wide screens. */
.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px 16px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Grid/flex items default to min-width: auto — their own content's
     min size, not 0 — so without this a field could refuse to shrink
     past its input's intrinsic width and force the row to overflow
     instead of actually wrapping onto its own line on a narrow screen. */
  min-width: 0;
}

.field.span-2 {
  grid-column: span 2;
}

/* Below this width there's no room for two 200px-minimum columns side
   by side (nombre/apellidos, say) without overflowing — every field
   grid stacks into one column instead, span-2 included, so every field
   just gets its own full-width row. Chosen to comfortably cover down
   to a 320px-wide screen once .content/.panel's own padding is
   subtracted. */
@media (max-width: 480px) {
  .field-grid {
    grid-template-columns: 1fr;
  }

  .field.span-2 {
    grid-column: span 1;
  }
}

.field label {
  font-size: 0.85rem;
}

.field .optional {
  color: var(--ink-muted);
  font-weight: 400;
}

/* One "cantidad + otra cuenta" line of a transaction's dialog
   (account.js's setupEntryLines): a transaction can have as many as the
   person fills in. The amount is the narrow column, the account the wide
   one. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-bottom: 12px; }
.filter-bar .field { margin-bottom: 0; }
.people-count { color: var(--ink-muted); font-size: 0.9rem; margin: 12px 0 6px; }
input[readonly] { background: var(--surface-muted); }
.status-groups h3 { margin: 18px 0 6px; font-size: 1rem; }
.status-groups h3:first-child { margin-top: 0; }
.account-hidden td { opacity: 0.6; }
.load-more { text-align: center; margin: 12px 0; }

.entry-line {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 12px;
  margin-bottom: 8px;
}

@media (max-width: 640px) {
  .entry-line {
    grid-template-columns: 1fr;
  }
}

/* A checkbox next to its own text. Without this, .field input's
   width: 100% below makes the box stretch to the field's full width and
   sit centered above the label instead. */
.field label.checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.field label.checkbox-label input {
  width: auto;
  padding: 0;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
  background: var(--surface);
  color: var(--ink);
}

.field textarea {
  resize: vertical;
  min-height: 64px;
}

/* Applied by js/formDirty.js while a field's value differs from what the
   form was last populated with (a fresh load, a successful save, or
   "Cancelar") — cleared the moment it matches again, including a manual
   edit back to the original value. */
.field input.field-dirty,
.field select.field-dirty,
.field textarea.field-dirty {
  background: var(--field-dirty-bg);
}

/* The controls for whoever manages something (a distribution), kept behind a button. */
.manage-panel {
  margin-top: 10px;
  padding: 10px 14px 14px;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--surface-muted);
}
.manage-panel .hint { margin: 0; }
.manage-panel .panel-actions { margin-top: 10px; }

.panel-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
}

/* Every "create" or "edit" form used to expand a hidden .panel in
   place, on the same page — easy to lose track of, and nothing stopped
   anyone from clicking elsewhere on the page before saving or
   cancelling it. Every one of those is a <dialog class="panel modal">
   now instead (see js/modal.js): a real native dialog, so the rest of
   the page genuinely can't be interacted with while one is open (no
   hand-rolled overlay to keep in sync), Escape already closes it, and
   .panel's own existing rules (background/border/radius/padding,
   h3, p.hint) still apply unchanged since they're a class selector,
   not one scoped to <div>. Only what a plain .panel never needed is
   added here: a maximum width so a short form doesn't stretch edge to
   edge, and the dimmed backdrop over everything behind it — centering
   itself is left to the UA default for an open dialog (position: fixed,
   inset: 0, margin: auto), deliberately not overridden with a
   position of our own: this rule used to set position: relative,
   which — silently, since the UA's own fixed positioning otherwise
   looked the same on screen — took the dialog out of that fixed/
   centered top-layer behavior and back into normal document flow at
   wherever the <dialog> tag happens to sit in the page. That only
   surfaced once js/modal.js's openModal() stopped letting the browser
   scroll the page to bring a mispositioned-back-into-flow dialog into
   view: with nothing left compensating for it, the dialog rendered
   exactly where it sat in the HTML instead of centered in the
   viewport, needing a manual scroll to find it. Still a valid
   containing block for .modal-close's own position: absolute below,
   the same as position: relative was. */
dialog.modal {
  max-width: 560px;
  width: calc(100% - 32px);
  max-height: calc(100% - 64px);
  overflow-y: auto;
  box-shadow: 0 24px 60px rgba(28, 33, 30, 0.35);
}

dialog.modal::backdrop {
  background: rgba(28, 33, 30, 0.55);
}

.modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  padding: 4px;
}

.modal-close:hover {
  color: var(--ink);
}

/* api.js's showError: what went wrong, over everything, to be closed. */
dialog.error-modal {
  margin: auto; /* .panel's own margin-top would keep it at the top */
  max-width: 440px;
  border-top: 4px solid var(--danger);
}

dialog.error-modal h3 {
  color: var(--danger);
  margin-top: 0;
}

dialog.error-modal p {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Buttons: one shared base, three intents, one size variant. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 6px;
  border: 1px solid transparent;
  font-size: 0.88rem;
  font-family: inherit;
  cursor: pointer;
}

/* A link dressed as a button (e.g. the shortcuts on /reparto). */
a.btn {
  text-decoration: none;
}

.btn-primary {
  background: var(--accent);
  color: #fff;
}

.btn-secondary {
  background: var(--surface);
  border-color: var(--line);
  color: var(--ink);
}

.btn-danger {
  background: var(--surface);
  border-color: var(--line);
  color: var(--danger);
}

.btn-small {
  padding: 5px 10px;
  font-size: 0.8rem;
}

.btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.table-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* A money amount in a table (accounting.js's chart of accounts, later
   its own transactions page) — right-aligned and set in tabular
   figures so a column of them lines up on the decimal point instead of
   ragged-left like ordinary text. */
.balance-cell {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Dates and amounts stay on one line however long the description next to them
   is: a table gives the columns of a long text most of the width and would
   otherwise break these in two. */
.balance-cell,
.nowrap {
  white-space: nowrap;
}

.breadcrumbs {
  margin: 16px 0;
  color: var(--ink-muted);
  font-size: 0.9rem;
}

.breadcrumbs a {
  color: inherit;
}

.document-text {
  white-space: pre-wrap;
  font-family: inherit;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 16px;
  margin-top: 16px;
}

.document-embed {
  width: 100%;
  height: 80vh;
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-top: 16px;
}

.document-frame {
  width: 100%;
  height: 80vh;
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-top: 16px;
  background: #fff;
}

.document-image {
  max-width: 100%;
  margin-top: 16px;
  border-radius: 6px;
}

/* An image resource in an activity's memoria (activity.js's own
   loadMemoriaImagePreviews) — unlike .document-image above (a
   dedicated, full-page preview pane with room to scroll), this sits
   inline among a list of memoria entries, so height is capped too —
   max-width alone still lets a tall portrait photo tower over
   everything else in the list. Both together just scale the image down
   to fit, preserving its own aspect ratio, since there's no fixed box
   around it forcing a crop. */
.memoria-image {
  display: block;
  max-width: 100%;
  max-height: 360px;
  margin: 8px 0;
  border-radius: 6px;
}

/* Activities calendar view */

.calendar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 10px 0 4px;
  font-size: 0.8rem;
  color: var(--ink-muted);
}

.calendar-legend-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  margin-right: 5px;
  vertical-align: -1px;
}

.calendar-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 4px;
}

.calendar-year-label {
  font-weight: 600;
  min-width: 3.5em;
  text-align: center;
}

.calendar-legend-non-working {
  background: #f3dcda;
  border: 1px solid var(--danger);
}

.calendar-legend-activity {
  background: var(--accent);
}

.calendar-legend-today {
  background: var(--surface);
  border: 2px solid var(--accent);
}

.calendar-view {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin-top: 12px;
}

.calendar-month {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px;
  background: var(--surface);
}

.calendar-month h3 {
  margin: 0 0 8px;
  font-size: 0.9rem;
  text-transform: capitalize;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}

.calendar-weekday {
  font-size: 0.68rem;
  color: var(--ink-muted);
  text-align: center;
  padding-bottom: 3px;
}

.calendar-day {
  min-height: 40px;
  border-radius: 4px;
  padding: 2px 2px;
  background: var(--surface-muted);
  overflow: hidden;
}

.calendar-day.calendar-day-empty {
  background: transparent;
}

.calendar-day-number {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--ink);
}

.calendar-day.calendar-day-non-working {
  background: #f3dcda;
}

.calendar-day.calendar-day-today {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.calendar-day-activity {
  display: block;
  background: var(--accent);
  color: #fff;
  border-radius: 3px;
  padding: 0 3px;
  margin-top: 1px;
  font-size: 0.6rem;
  line-height: 1.35;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-decoration: none;
}

.calendar-day-activity:hover {
  filter: brightness(1.1);
}

.calendar-day-more {
  display: block;
  font-size: 0.6rem;
  color: var(--ink-muted);
  padding: 0 3px;
}

/* A list inside a table cell with no bullets, e.g. the units somebody asked for. */
.plain-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.plain-list li {
  padding: 2px 0;
}

/* /admin's Roles: who has a role, under its row — a long list (every member has
   the automatic one) scrolls instead of pushing the page down. */
.role-holders {
  max-height: 240px;
  overflow-y: auto;
  margin-bottom: 8px;
}

.role-detail td {
  background: var(--surface-muted);
}
