/*
 * Project-specific CSS only. Tabler covers typography, colour, spacing and components; the rules
 * below exist because Tabler has no bottom-navigation component for the mobile shell.
 */

/* Ticket 116: Tabler's secondary gray (#6b7280) falls under 4.5:1 on the light surfaces, so the
   secondary text (dates, descriptions, subtitles, group titles) uses a darker gray: 5.9:1 on white,
   5.5:1 on the page background. Tabler itself is not modified; only its tokens are redefined. */
:root,
[data-bs-theme=light] {
  --tblr-secondary: #5c6474;
  --tblr-secondary-rgb: 92, 100, 116;
  --tblr-muted: #5c6474;
  --tblr-muted-rgb: 92, 100, 116;
}

/* Ticket 116: Tabler paints the light badges with the colour itself: 2:1 for yellow ("Pendiente"),
   2.5:1 for green ("Activa"), 4:1 for red. A darker shade of the same colour keeps the cue and reads
   at 4.9:1 or more on its light background. The avatars with initials or icons use the same pairs. */
:is(.badge, .avatar).bg-yellow-lt,
:is(.badge, .avatar).bg-warning-lt {
  color: #8a5a00 !important;
}

:is(.badge, .avatar).bg-green-lt,
:is(.badge, .avatar).bg-success-lt {
  color: #1d7a2e !important;
}

:is(.badge, .avatar).bg-red-lt {
  color: #b42727 !important;
}

:is(.badge, .avatar).bg-orange-lt {
  color: #a84300 !important;
}

:is(.badge, .avatar).bg-azure-lt,
:is(.badge, .avatar).bg-info-lt {
  color: #1f6aa8 !important;
}

:is(.badge, .avatar).bg-blue-lt,
:is(.badge, .avatar).bg-primary-lt {
  color: #0757a3 !important;
}

:is(.badge, .avatar).bg-purple-lt {
  color: #8a2ea0 !important;
}

:is(.badge, .avatar).bg-teal-lt {
  color: #07725a !important;
}

/* Ticket 287 (QA 2026-10-18, ui H-06): the green outline buttons of the rows ("Completar", "Registrar realización",
   "Comprado", "Resuelto") read at 2.7:1 with Tabler's green. They take the green of the badges for themselves: 5.4:1 on
   white for their text and border, and for the light text of their fill when hovered or pressed. Badges, alerts and
   icons keep Tabler's green. */
.btn-outline-success {
  --tblr-success: #1d7a2e;
  --tblr-success-rgb: 29, 122, 46;
}

/* Ticket 287 (ui H-06): Tabler fixes a lighter blue on .btn-link (4.1:1 on white); "Ver más" of a card, "Volver a la
   lista" and "Ver archivados" of Compras and "Cancelar" of Reportar take the link colour of the app (5:1). */
.btn-link {
  color: var(--tblr-link-color);
}

/* Ticket 287 (ui H-11): an avatar keeps its size beside a long text; in a flex row Tabler's .avatar shrank (Mi perfil
   with a name of 200 letters left it ≈22 px wide). */
.avatar {
  flex-shrink: 0;
}

/* Ticket 287 (ui H-08): "Saltar al contenido", the first Tab of a screen with the sidebar (lg up), hidden until it has
   the focus (visually-hidden-focusable). With the focus it floats at the top left, over the sidebar, instead of pushing
   the screen down. */
.app-skip-link:focus {
  position: fixed !important;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1090;
  padding: 0.5rem 1rem;
  border-radius: var(--tblr-border-radius);
  background: var(--tblr-bg-surface);
  color: var(--tblr-link-color);
  font-weight: 500;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

/* Ticket 116: Tabler hides the radios of a segmented control with display:none, which takes them
   out of the keyboard order. They stay reachable (arrows, Tab) and the focused segment is visible. */
.app-segmented .nav-link-input {
  display: block;
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.app-segmented .nav-link-input:focus-visible + .nav-link {
  outline: 2px solid var(--tblr-primary);
  outline-offset: 1px;
}

.app-segmented {
  position: relative;
  max-width: 100%;
}

/* Review of PR #131: a long name can't push its segment past the viewport (Tabler keeps the labels
   on one line); it is cut with an ellipsis and the full name stays in the title. */
.app-segmented .nav-link {
  display: block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: calc(var(--tblr-nav-height) - 2 * var(--tblr-nav-padding) - 2px);
}

.app-bottom-nav {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  /* Ticket 032: pin the bar to the viewport width. With only `inset-inline: 0` the browser can
     resolve the shrink-to-fit width beyond 100vw (measured 471 px at a 360 px viewport on
     Finances), which pushes the last item off-screen and creates page-level horizontal overflow. */
  width: 100%;
  max-width: 100vw;
  min-width: 0;
  z-index: 1030;
  display: flex;
  justify-content: space-around;
  align-items: stretch;
  background: var(--tblr-bg-surface);
  border-top: 1px solid var(--tblr-border-color);
  padding-bottom: env(safe-area-inset-bottom);
}

.app-bottom-nav-link {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  /* Comfortable touch target on phones. */
  min-height: 3.5rem;
  padding: 0.375rem 0.25rem;
  color: var(--tblr-secondary);
  text-decoration: none;
  font-size: 0.6875rem;
  line-height: 1;
}

.app-bottom-nav-link i {
  font-size: 1.375rem;
}

.app-bottom-nav-link:hover,
.app-bottom-nav-link:focus-visible {
  color: var(--tblr-primary);
}

.app-bottom-nav-link:focus-visible {
  outline: 2px solid var(--tblr-primary);
  outline-offset: -2px;
}

.app-bottom-nav-link.active {
  color: var(--tblr-primary);
  font-weight: 600;
}

/* Ticket 039: the center action is raised and unmistakably primary while every control keeps an
   equal-width slot, including at 320 px. */
.app-bottom-nav-capture {
  position: relative;
  transform: translateY(-0.5rem);
  color: var(--tblr-primary);
  font-weight: 600;
}

.app-bottom-nav-capture-icon {
  display: grid;
  width: 2.75rem;
  height: 2.75rem;
  place-items: center;
  border: 0.25rem solid var(--tblr-bg-surface);
  border-radius: 50%;
  background: var(--tblr-primary);
  color: var(--tblr-primary-fg);
  box-shadow: var(--tblr-box-shadow);
}

.app-bottom-nav-capture .app-bottom-nav-capture-icon i {
  font-size: 1.5rem;
}

.app-bottom-nav-capture:hover .app-bottom-nav-capture-icon,
.app-bottom-nav-capture:focus-visible .app-bottom-nav-capture-icon {
  filter: brightness(0.9);
  color: var(--tblr-primary-fg);
}

/* Shared account entry point: the same partial is used in the desktop sidebar and mobile header. */
.app-account-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.5rem;
  border-radius: var(--tblr-border-radius);
  color: var(--tblr-body-color);
  text-decoration: none;
}

.app-account-link:hover {
  background: var(--tblr-bg-surface-secondary);
  color: var(--tblr-body-color);
}

.app-account-copy {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.app-account-name {
  overflow: hidden;
  font-weight: 600;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-account-hint {
  color: var(--tblr-secondary);
  font-size: 0.75rem;
}

.app-account-chevron {
  flex: 0 0 auto;
  color: var(--tblr-secondary);
}

/* Ticket 108: Mi perfil and the settings reached from it are the account link's branch. */
.app-account-link.active,
.app-account-link.active:hover {
  background: var(--tblr-bg-surface-secondary);
}

.app-account-link.active .app-account-name,
.app-account-link.active .app-account-chevron {
  color: var(--tblr-primary);
}

/* Link back to the page a settings page is reached from (ticket 108). */
.app-page-back {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-bottom: 0.25rem;
  font-size: 0.875rem;
  text-decoration: none;
}

/* Group headings inside the shared destination list (sidebar, ticket 028). */
.app-nav-group-title {
  padding: 0.75rem 0.75rem 0.25rem;
  /* Ticket 116: 12 px instead of 11 px, with the darker secondary gray. */
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.25rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--tblr-secondary);
  list-style: none;
}

.app-nav-group-title:first-child {
  padding-top: 0.25rem;
}

.navbar-nav .app-nav-group-title + .nav-item .nav-link {
  padding-top: 0.25rem;
}

/* Desktop sidebar: destinations scroll independently on short screens while brand, identity and
   logout remain pinned (ticket 028). The aside is fixed-height, so the whole chain below it must
   be constrained for the inner list to scroll instead of overflowing the viewport. */
@media (min-width: 992px) {
  .navbar-vertical .container-fluid {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
  }

  .navbar-vertical .navbar-collapse.show {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
  }

  .app-sidebar-scroll {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    overflow-y: auto;
    overflow-x: hidden;
  }
}

/* Keep page content clear of the fixed bottom navigation. */
@media (max-width: 991.98px) {
  .page-body {
    padding-bottom: calc(4.5rem + env(safe-area-inset-bottom));
  }
}

/*
 * Ticket 032: guarantee a visible focus indicator on every interactive control. The browser sweep
 * found buttons that match :focus-visible without any outline or shadow, so keyboard users could
 * not see where they were. Inputs and selects already show Tabler's focus styling; this rule
 * covers buttons, links and focusable scroll regions.
 */
button:focus-visible,
a:focus-visible,
[tabindex="0"]:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible {
  outline: 2px solid var(--tblr-primary);
  outline-offset: 2px;
}

/* Tabler sizes its empty-state icon for inline SVG; the icon webfont needs an explicit size. */
.empty-icon .ti {
  font-size: 3rem;
  color: var(--tblr-secondary);
}

/*
 * Shared validation summary (ticket 027). The container always exists so unobtrusive client
 * validation can fill it, but it is never visible —nor announced— without a real error.
 * Pages render it with: <div class="alert alert-danger app-validation-summary" role="alert"
 * asp-validation-scope="Input"></div> (see FormValidationSummaryTagHelper).
 */
.app-validation-summary.validation-summary-valid {
  display: none;
}

.app-validation-summary.validation-summary-errors {
  margin-bottom: 0.75rem;
}

.app-validation-summary.validation-summary-errors ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.app-validation-summary.validation-summary-errors li + li {
  margin-top: 0.125rem;
}

/* Ticket 131: a button that takes the full width on a phone measures its text from md up. */
@media (min-width: 768px) {
  .w-md-auto {
    width: auto !important;
  }
}

/*
 * Compact purchase research list (ticket 030). The same option markup is a comparison table on
 * desktop and stacks as prioritized cards on phones, so no horizontal page overflow occurs. Ticket 163:
 * the label of each cell (data-label) is only for the stacked cards; on desktop the header says it.
 */
@media (max-width: 767.98px) {
  .app-purchase-options thead {
    display: none;
  }

  .app-purchase-options,
  .app-purchase-options tbody,
  .app-purchase-options tr,
  .app-purchase-options td {
    display: block;
    width: 100%;
  }

  .app-purchase-options tr {
    border: 1px solid var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
    margin-bottom: 0.5rem;
    padding: 0.5rem;
  }

  .app-purchase-options tr.table-success {
    border-color: var(--tblr-success);
  }

  .app-purchase-options td {
    border: 0;
    padding: 0.25rem 0;
  }

  .app-purchase-options td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.6875rem;
    line-height: 1.25rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tblr-secondary);
  }
}

/*
 * Compact financial overviews (ticket 031). Summary flex rows need to shrink below their
 * content width so long Spanish descriptions wrap instead of overflowing the page.
 */
.min-width-0 {
  min-width: 0;
}

/*
 * Ticket 031: installment schedules stack as labeled cards on phones (same pattern as the
 * purchase options table from 030) so the schedule never forces page-level horizontal overflow.
 * The cell labels only show there (ticket 163).
 */
@media (max-width: 767.98px) {
  .app-stacked-table thead {
    display: none;
  }

  .app-stacked-table,
  .app-stacked-table tbody,
  .app-stacked-table tr,
  .app-stacked-table td {
    display: block;
    width: 100%;
  }

  .app-stacked-table tr {
    border: 1px solid var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
    margin-bottom: 0.5rem;
    padding: 0.5rem;
  }

  .app-stacked-table td {
    border: 0;
    padding: 0.25rem 0;
  }

  .app-stacked-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.6875rem;
    line-height: 1.25rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tblr-secondary);
  }

  /* Ticket 166: a movement of a statement (and of its import preview) is a compact card: the
     description on its own line, then date and amount, then installment and status, two per row. */
  .app-statement-movements.app-stacked-table tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 0.75rem;
  }

  /* Ticket 166: a statement's data (file, movements, totals) are long, so they take the whole width.
     Ticket 170.6: the trash can "Borrar" is next to the title, as in every card, and "Ver movimientos"
     goes below the data, at the right, so the title keeps the width of the card. */
  .app-card-row.app-statement-row {
    grid-template-areas:
      "title icons"
      "meta meta"
      "primary primary";
  }

  .app-statement-movements.app-stacked-table td.app-statement-movement-description {
    grid-column: 1 / -1;
    order: -1;
    overflow-wrap: anywhere;
  }
}

/* Ticket 162: the expenses of Finanzas are cards (.app-card-list) on every viewport; the table of 156 is gone. */

/*
 * Ticket 032: Tabler cards are flex columns, so a scroll-region table that is a direct card child
 * grows to its content width instead of clipping. Reset the flex minimum so the region actually
 * scrolls (Finances expenses; applies to any future direct-child scroll region).
 */
.card > .table-responsive {
  min-width: 0;
}

/*
 * Ticket 032: practical 44 px touch targets on phones. Ordinary controls (buttons, selects, inputs)
 * get a 2.75rem minimum height below tablet; icon-only buttons also get a 2.75rem minimum width so
 * the whole target —not just the glyph— is comfortably tappable. The `-sm` variants used by the bulk
 * grids keep 2.5rem (40 px): the rows carry their own visible label per column, the layout stays
 * scannable and they remain keyboard-accessible; this is the documented compact exception.
 */
@media (max-width: 767.98px) {
  .btn {
    min-height: 2.75rem;
  }

  .btn-icon {
    min-width: 2.75rem;
  }

  .form-control,
  .form-select {
    min-height: 2.75rem;
  }

  .btn-sm,
  .form-control-sm,
  .form-select-sm {
    min-height: 2.5rem;
  }

}

/*
 * Ticket 184: with JavaScript (site-head.js marks <html class="js"> before the first paint) a filter that
 * auto-submits does not need its apply button; hiding it here, not in site.js on `load`, keeps it from
 * showing for an instant. Without JavaScript the button stays as the fallback.
 */
.js form[data-autosubmit-on-change] .app-filter-apply {
  display: none !important;
}

/*
 * Ticket 032: shared collapsible filters (FiltersPanelTagHelper). Phones collapse the filter form
 * behind a labeled toggle; from tablet up the toggle disappears and the body is always visible.
 */
.app-filters-toggle {
  margin-bottom: 0.5rem;
}

.app-filters-toggle .app-filters-chevron {
  transition: transform 0.15s ease-in-out;
}

.app-filters-toggle[aria-expanded="true"] .app-filters-chevron {
  transform: rotate(180deg);
}

.app-filters-active {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.5rem;
  margin-bottom: 0.5rem;
  font-size: 0.875rem;
  color: var(--tblr-secondary);
}

.app-filters-active .app-filters-clear {
  white-space: nowrap;
}

@media (max-width: 767.98px) {
  .app-filters-body {
    margin-bottom: 0.5rem;
  }
}

@media (min-width: 768px) {
  .app-filters-toggle {
    display: none;
  }

  /* The body is authored to be permanently visible on wide screens regardless of the phone state. */
  .app-filters-body {
    display: block !important;
  }
}

/*
 * Ticket 032: Inbox-style rows keep their primary text usable on phones. The row wraps so the badge
 * stays inline with the text while the action cluster takes its own full-width line, and the text
 * column is allowed to shrink so long notes wrap instead of fragmenting.
 */
@media (max-width: 767.98px) {
  .app-row-with-actions {
    flex-wrap: wrap;
  }

  .app-row-with-actions .app-row-main {
    flex: 1 1 100%;
    min-width: 0;
  }

  .app-row-with-actions .app-row-actions {
    flex: 1 1 100%;
    justify-content: flex-start;
  }
}

/*
 * Ticket 151: the row action of Entrada reads whole ("Convertir…", not a clipped "Convertir..."). A
 * .btn is a flex box, so the space between its icon and its text collapses; the gap brings it back.
 * On a phone the actions already take their own line below the note's text (ticket 032).
 */
[data-note-list] .app-row-primary {
  gap: 0.375rem;
}

/* Ticket 151: the note a window of Entrada converts or edits, at the top of its body: its text in
   three lines at most, the rest with "…", and its date. Not part of the shared window (ticket 149). */
.app-inbox-sheet-note {
  flex: none;
  margin-bottom: 1rem;
  padding: 0.5rem 0.75rem;
  border-inline-start: 3px solid var(--tblr-primary);
  border-radius: var(--tblr-border-radius, 0.25rem);
  background: var(--tblr-bg-surface-secondary);
}

.app-inbox-sheet-note-text {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  white-space: pre-line;
  overflow-wrap: anywhere;
  font-weight: var(--tblr-font-weight-medium, 500);
}

/* Ticket 038: the browser-only dictation control is revealed by site.js after support detection. */
.app-voice-controls[hidden] {
  display: none !important;
}

/* Ticket 054: the label counts down ("Detener (3)"), so the width and digits stay stable; on phones
   the button is the main control while dictating and keeps a large touch target. */
.app-voice-button {
  min-width: 8rem;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 767.98px) {
  .app-voice-button {
    min-height: 2.75rem;
  }
}

.app-voice-button.app-voice-listening {
  color: var(--tblr-danger);
  border-color: var(--tblr-danger);
  background: var(--tblr-danger-lt);
}

.app-voice-button.app-voice-listening .ti-microphone {
  animation: app-voice-pulse 1.2s ease-in-out infinite;
}

.app-voice-status {
  min-width: 0;
}

@keyframes app-voice-pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.18);
    opacity: 0.65;
  }
}

@media (prefers-reduced-motion: reduce) {
  .app-voice-button.app-voice-listening .ti-microphone {
    animation: none;
  }
}

/*
 * Ticket 040: the activity trail groups every entry of one operation inside a connected block.
 * Ticket 166: each entry takes two lines at most, on a phone too: the time, the verb, the entity and the
 * «description» (cut with "…") above; the origin and the actor below, with "Ver datos registrados" at
 * the right. Its data, when open, take the whole width below.
 */
.app-activity-group {
  border-inline-start: 3px solid var(--tblr-border-color);
}

.app-activity-entries {
  margin-inline-start: 0.75rem;
  display: grid;
  gap: 0.375rem;
}

.app-activity-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 0.5rem;
  min-width: 0;
}

.app-activity-entry-main {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  min-width: 0;
  white-space: nowrap;
}

.app-activity-entry-main > * {
  flex-shrink: 0;
}

/* The description gives way first; a long entity name only when there is no description left. */
.app-activity-entry-main > .app-activity-entity,
.app-activity-entry-main > .app-activity-description {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-activity-entry-main > .app-activity-entity {
  flex-shrink: 1;
}

.app-activity-entry-main > .app-activity-description {
  flex-shrink: 1000;
}

.app-activity-entry-meta {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  white-space: nowrap;
}

.app-activity-actor {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-activity-json {
  justify-self: end;
}

.app-activity-json[open] {
  grid-column: 1 / -1;
  justify-self: stretch;
}

.app-activity-json[open] > summary {
  text-align: end;
}

/* "Ver N más" of a group of more than three entries; it says "Ver menos" while open. */
.app-activity-more {
  margin-inline-start: 0.75rem;
}

.app-activity-more > summary {
  cursor: pointer;
  color: var(--tblr-primary);
  padding-block: 0.25rem;
}

.app-activity-more > .app-activity-entries {
  margin-inline-start: 0;
  margin-top: 0.25rem;
}

.app-activity-more-open,
.app-activity-more[open] .app-activity-more-closed {
  display: none;
}

.app-activity-more[open] .app-activity-more-open {
  display: inline;
}

.app-activity-json summary {
  cursor: pointer;
  user-select: none;
}

/* Ticket 045: explicit dark text. Tabler tints pre content light, which is unreadable on
   this secondary-surface block (white on light gray). */
.app-activity-json-block {
  padding: 0.5rem;
  border-radius: var(--tblr-border-radius);
  background: var(--tblr-bg-surface-secondary);
  color: var(--tblr-body-color);
  font-size: 0.75rem;
  overflow-x: auto;
  max-height: 16rem;
  overflow-y: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin-inline-end: 0.5rem;
}

/* Provenance affordances ("Generó: …", "Desde nota") sit inside metadata lines. Ticket 171: they stay whole
   while they fit; one that does not fit goes down to the next line, and one longer than the line breaks
   inside it instead of pushing the card past the edge of the screen. */
.app-provenance-link {
  display: inline-block;
  max-width: 100%;
  vertical-align: top;
}

/* Ticket 040: the row a provenance focus link points at is highlighted without edit mode. */
.app-focus-highlight {
  border-inline-start: 3px solid var(--tblr-primary);
  background: var(--tblr-active-bg, var(--tblr-bg-surface-secondary));
  animation: app-focus-fade 1.6s ease-out 1;
}

@keyframes app-focus-fade {
  0% {
    background: var(--tblr-primary-lt);
  }

  100% {
    background: var(--tblr-bg-surface);
  }
}

@media (prefers-reduced-motion: reduce) {
  .app-focus-highlight {
    animation: none;
  }
}

/*
 * Sin marca en el shell: la navegacion arranca directo en el contenido.
 * overflow-x clip evita el scroll horizontal sin romper el sticky del shell.
 */
html,
body {
  overflow-x: clip;
}

.page-wrapper {
  min-width: 0;
}

.app-sidebar-capture {
  padding-inline: 0.75rem;
}

/*
 * Ticket 150: the page header. On a phone it is one row (back arrow, title, primary action and
 * "⋯"); the pretitle and the description are hidden there (d-none d-md-block) and the subtitle
 * (the baby's age, 124) is a small line below. From md up it reads as before: back link, pretitle,
 * title with the actions on its right, subtitle and description. Hoy and Tareas use these same rules.
 */
.page-header {
  margin-block: 0 0.75rem;
}

.page-header.app-page-header {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "back title actions"
    "subtitle subtitle subtitle";
  align-items: center;
  min-height: 0;
}

.app-page-header > .app-page-back { grid-area: back; }
.app-page-header > .app-page-header-pretitle { grid-area: pretitle; }
.app-page-header > .page-title { grid-area: title; }
.app-page-header > .app-page-header-actions { grid-area: actions; }
.app-page-header > [data-page-subtitle] { grid-area: subtitle; }
.app-page-header > .app-page-header-description { grid-area: description; }

.page-header .page-pretitle {
  margin-bottom: 0.125rem;
  font-size: 0.6875rem;
}

.page-header .page-title {
  font-size: 1.25rem;
  line-height: 1.3;
  margin-bottom: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.page-header .text-secondary.mt-1 {
  margin-top: 0.25rem !important;
  font-size: 0.875rem;
}

.app-page-header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-inline-start: 0.5rem;
}

.app-page-header-actions .btn {
  white-space: nowrap;
}

@media (max-width: 767.98px) {
  /* At most 12 px above the header on a phone: the page-body padding only (Tabler adds 24 + 24),
     and 12 px between the header and the content. */
  .page-body {
    margin-top: 0;
  }

  .page-wrapper .page-header {
    margin-block: 0 0.75rem;
  }

  /* The arrow alone is the 44 px target; its text stays as the accessible name. */
  .app-page-header > .app-page-back {
    justify-content: center;
    min-width: 2.75rem;
    min-height: 2.75rem;
    margin: 0 0.25rem 0 -0.625rem;
    font-size: 1.25rem;
  }

  .app-page-back-text {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }

  .app-page-header-actions .btn {
    min-height: 2.75rem;
  }

  .app-page-header-actions .app-row-menu-toggle {
    min-width: 2.75rem;
  }
}

@media (min-width: 768px) {
  .page-header.app-page-header {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "back ."
      "pretitle ."
      "title actions"
      "subtitle ."
      "description description";
  }

  .app-page-header > .app-page-back {
    justify-self: start;
  }
}

.page-body {
  padding-top: 0.75rem;
}

/*
 * Ticket 071: the Content-Security-Policy allows no inline style attributes, so sizes that used
 * to be written inline live here.
 */
/* Ticket 166: only from md; on a phone the window's body is what scrolls (no scroll inside a scroll). */
@media (min-width: 768px) {
  .app-statement-preview-scroll {
    max-height: 22rem;
    overflow-y: auto;
  }
}

/*
 * Backlog 077: the shell notice with a link to what was created and/or "Deshacer". Text and
 * actions share a line when they fit and wrap below it otherwise; every action is a 44 px target
 * and nothing overflows at 375 px.
 */
.app-shell-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

.app-shell-notice-text {
  flex: 1 1 12rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.app-shell-notice-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.app-shell-notice-action {
  min-height: 44px;
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  font-weight: 600;
}

/*
 * Ticket 163 (docs/UI_GUIDE.md, "Aviso de la acción"): after saving, the list scrolls to the row
 * (#anchor, data-focus-target), which left the notice at the top of the page out of view. The notice
 * floats instead: on a phone and a tablet over the bottom bar, at full width minus the gutters; on a
 * desktop at the bottom right. Its ✕ (Bootstrap's alert dismiss) takes it away, and the page gets
 * room below its last row, as tall as the notice, so that nothing stays under it.
 */
[data-shell-notice] {
  position: fixed;
  z-index: 1030;
  inset-inline: 0.75rem;
  /* The bottom bar measures about 4.4rem with "Capturar"; the notice floats a little above it. */
  bottom: calc(5.25rem + env(safe-area-inset-bottom));
  margin: 0;
  align-items: center;
  /* Tabler's alert background is translucent: over the list it must be opaque. */
  background-color: var(--tblr-bg-surface, #fff);
  background-image: linear-gradient(var(--tblr-alert-bg), var(--tblr-alert-bg));
  box-shadow: var(--tblr-box-shadow-dropdown, 0 1rem 2rem rgba(0, 0, 0, 0.15));
}

[data-shell-notice] > :first-child {
  flex: 1 1 auto;
  min-width: 0;
}

.app-shell-notice-close {
  flex: 0 0 auto;
  width: 2.75rem;
  height: 2.75rem;
  margin: -0.5rem -0.5rem -0.5rem 0;
  padding: 0;
  background-size: 0.75rem;
}

/* site.js measures the notice (--app-shell-notice-height): a notice that wraps to several rows (the
   one-tap options of /Baby) still leaves its last row above it. */
body:has([data-shell-notice]) .page-body {
  padding-bottom: calc(5.25rem + var(--app-shell-notice-height, 4.75rem) + 1rem + env(safe-area-inset-bottom));
}

@media (min-width: 992px) {
  [data-shell-notice] {
    inset-inline: auto 1.5rem;
    bottom: 1.5rem;
    width: min(32rem, calc(100vw - 3rem));
  }

  body:has([data-shell-notice]) .page-body {
    padding-bottom: calc(1.5rem + var(--app-shell-notice-height, 4.5rem) + 1rem);
  }
}

/* Ticket 273: a list window the page opens after saving one of its items carries the notice inside it
   (form-sheet.js). Below lg, where there is no bottom bar in the window, it floats at the bottom of the
   window and the list leaves room for it below its last row; on a desktop it stays at the bottom right,
   beside the modal. */
@media (max-width: 991.98px) {
  .app-sheet [data-shell-notice] {
    bottom: calc(0.75rem + env(safe-area-inset-bottom));
  }

  .app-sheet:has([data-shell-notice]) .app-sheet-body {
    padding-bottom: calc(var(--app-shell-notice-height, 4.75rem) + 1.5rem);
  }
}

/* Ticket 163: the title of the page takes the focus when the trigger of a closed window cannot
   (form-sheet.js); it is not a control, so it gets no focus ring. */
.page-title[tabindex="-1"]:focus {
  outline: none;
}

/* Backlog 100: "Ver todo el historial" of a recurring expense is a 44 px target in every viewport. */
.app-history-link {
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
}

/*
 * Ticket 114: Hoy lays out its two columns with a grid (since ticket 144 without the "Bebé" card,
 * which lives only in /Baby).
 */
.app-today-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "main" "side";
  column-gap: 1.5rem;
}

.app-today-main { grid-area: main; margin-bottom: 1rem; }
.app-today-side { grid-area: side; }

@media (min-width: 992px) {
  .app-today-grid {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    grid-template-areas: "main side";
    align-items: start;
  }

  .app-today-main { margin-bottom: 0; }
}

/*
 * Ticket 128: below lg both columns stack, so they let their blocks into one flex column where the
 * empty blocks of either column go after every block with items. From lg each
 * column already renders its empty blocks last.
 */
@media (max-width: 991.98px) {
  .app-today-grid {
    display: flex;
    flex-direction: column;
  }

  .app-today-main,
  .app-today-side { display: contents; }

  .app-today-block { order: 1; }
  .app-today-block.app-today-empty { order: 2; }
}

/* Ticket 128: an empty block of Hoy is one compact line: its name and why it is empty. */
.app-today-empty > .card-body {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
}

.app-today-empty-icon {
  display: inline-grid;
  flex: none;
  width: 2rem;
  height: 2rem;
  place-items: center;
  border-radius: 50%;
  background: var(--tblr-bg-surface-secondary);
}

/* Ticket 128: "Ver los N", the link of a block to its whole list; a 44 px target. */
.app-today-more {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}

/* Ticket 114: one-tap baby buttons, 44 px targets in two columns; several babies are set apart. */
.app-baby-tap {
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-width: 0;
  white-space: normal; /* "Toma · <long name>" wraps instead of being cut. */
  text-align: center;
}

/* Ticket 153 (docs/UI_GUIDE.md, "Jerarquía"): the one-tap buttons are quick actions, an outline
   button with a large icon over its text, so several babies do not show six primary buttons. */
.app-quick-action {
  flex-direction: column;
  gap: 0.25rem;
  min-height: 3.5rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  line-height: 1.2;
}

.app-quick-action > .ti {
  font-size: 1.5rem;
  width: auto;
  height: auto;
  margin: 0;
}

/* Ticket 153: the sections of /Baby, the second card: a compact grid of links (icon over the name),
   three per row on a phone. Ticket 172: shared by the groups of /Sections (_SectionButtons), with a
   pending number as a badge in the corner. */
.app-sections {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: 0.25rem;
}

.app-section {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  min-height: 3.25rem;
  padding: 0.375rem 0.25rem;
  border-radius: var(--tblr-border-radius);
  color: var(--tblr-body-color);
  font-size: 0.8125rem;
  text-align: center;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.app-section > .ti {
  font-size: 1.25rem;
  color: var(--tblr-primary);
}

.app-section:hover,
.app-section:focus-visible {
  background: var(--tblr-bg-surface-secondary);
  text-decoration: none;
}

.app-section-count {
  position: absolute;
  top: 0.125rem;
  right: 0.25rem;
  min-width: 1.25rem;
}

/* Ticket 172: the flows of /Sections, one row each (icon, name and what is pending, number). */
.app-section-flow {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.75rem;
}

.app-section-flow > .ti {
  font-size: 1.25rem;
  color: var(--tblr-primary);
}

/* Ticket 164 (docs/UI_GUIDE.md, "Tarjetita de estado"): Comida, Sueño and Pañal of a baby as three small
   cards in one row, each with its elapsed time as a big number (188, no "hace") and a short detail; they wrap, never cut. */
.app-status-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
}

.app-status-card {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
  padding: 0.5rem;
  border: 1px solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius);
  background: var(--tblr-bg-surface-secondary);
  color: var(--tblr-body-color);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.app-status-card:hover,
.app-status-card:focus-visible {
  border-color: var(--tblr-primary);
  text-decoration: none;
}

.app-status-card-label {
  color: var(--tblr-secondary);
  font-size: 0.75rem;
}

/* Ticket 188: the elapsed time is the number, big; the label above stays small. */
.app-status-card-headline {
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.app-status-card-detail {
  color: var(--tblr-secondary);
  font-size: 0.75rem;
  line-height: 1.25;
}

.app-status-card-empty .app-status-card-headline {
  color: var(--tblr-secondary);
  font-weight: 400;
}

.app-baby-status-baby + .app-baby-status-baby,
.app-baby-status-questions {
  border-top: 1px solid var(--tblr-border-color);
  margin-top: 0.75rem;
  padding-top: 0.75rem;
}

/* Ticket 115: on /Baby the pending questions are a card of their own, without the separator. */
.app-baby-card > .card-body > .app-baby-status-questions {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

/* Ticket 123: upcoming appointments of the baby, a big date next to the title; several babies are set apart. */
.app-baby-appointments-baby + .app-baby-appointments-baby {
  border-top: 1px solid var(--tblr-border-color);
  margin-top: 0.75rem;
  padding-top: 0.75rem;
}

.app-baby-appointment-date {
  flex: 0 0 3rem;
  line-height: 1.1;
}

.app-baby-appointment-day {
  font-size: 1.5rem;
  font-weight: 700;
}

/* Ticket 122: week charts drawn on the server as SVG. Colours live here (the CSP forbids inline
   styles); the stacking order (BabyWeekChart.StackOrder) was checked for colour-blind separation:
   blue "Pis", aqua "Seco" (120), orange "Caca", violet "Pis y caca", then greys for "Otro" and
   "Sin tipo"; magenta for a type added later. Text keeps text colours, never the series colour. */
.app-chart {
  display: block;
  width: 100%;
  height: auto;
  max-height: 15rem;
}

.app-chart-baseline { stroke: #c3c2b7; stroke-width: 1; }
.app-chart-value { fill: var(--tblr-body-color); font-size: 12px; font-weight: 600; }
.app-chart-label { fill: #52514e; font-size: 12px; }
.app-chart-label-small { font-size: 11px; }

.app-chart-feeding,
.app-chart-diaper-wet { fill: #2a78d6; background-color: #2a78d6; }
.app-chart-diaper-dirty { fill: #eb6834; background-color: #eb6834; }
.app-chart-diaper-both { fill: #4a3aa7; background-color: #4a3aa7; }
.app-chart-diaper-dry { fill: #1baf7a; background-color: #1baf7a; }
.app-chart-diaper-extra { fill: #e87ba4; background-color: #e87ba4; }
.app-chart-diaper-other { fill: #898781; background-color: #898781; }
.app-chart-diaper-unspecified { fill: #c3c2b7; background-color: #c3c2b7; }

.app-chart-legend { color: #52514e; }

.app-chart-swatch {
  display: inline-block;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 2px;
  margin-right: 0.25rem;
  vertical-align: -0.0625rem;
}

/* Ticket 122: the average beside a card title wraps under it on a phone instead of squeezing the title. */
.card-header:has(> .app-card-aside) {
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
}

.card-header > .app-card-aside { margin-left: auto; }

/*
 * Ticket 127: each item of Hoy is a link that opens its detail dialog (a native <dialog>, see
 * js/today-items.js). The link covers the whole row and keeps the row's text colors.
 */
.app-today-item {
  padding: 0;
}

.app-today-item-link {
  display: block;
  padding: var(--tblr-list-group-item-padding-y, 0.75rem) var(--tblr-list-group-item-padding-x, 1rem);
  color: inherit;
  text-decoration: none;
}

.app-today-item-link.d-flex {
  display: flex;
}

/*
 * Ticket 175 (docs/UI_GUIDE.md, "Íconos por tipo"): every item of Hoy has the icon of its kind to the left
 * (UI/TodayItemIcons.cs). The icon keeps its size and the text takes the rest of the row, wrapping a long title
 * instead of pushing it out of the card.
 */
.app-today-item-icon {
  flex-shrink: 0;
}

.app-today-item-link.d-flex > div,
.app-today-footer-link > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.app-today-item-link:hover,
.app-today-item-link:focus-visible {
  color: inherit;
  text-decoration: none;
  background: var(--tblr-bg-surface-secondary);
}

/* Ticket 292: the text of a note of Hoy is inside its item's link, which cannot hold a button: its "Ver más" goes
   below the link, aligned with the text after the icon. */
.app-today-item > .app-expandable-text-toggle {
  margin-inline-start: calc(var(--tblr-list-group-item-padding-x, 1rem) + 1.5rem);
  margin-block: -0.75rem 0.25rem;
}

.app-today-footer-link {
  padding: 0.25rem 0;
  min-height: 2.75rem;
  display: flex;
  align-items: center;
}

.app-today-footer-link:hover,
.app-today-footer-link:focus-visible {
  background: transparent;
  text-decoration: underline;
}

/*
 * Ticket 149 (docs/UI_GUIDE.md, "Formulario en ventana"): the shared form window, a native <dialog>
 * (UI/FormSheetTagHelper.cs, js/form-sheet.js). The dialogs of Hoy (ticket 127) use the same box,
 * header and backdrop; their content (details, steps, actions) keeps its own rules below.
 *
 * Desktop (576 px up): a centered modal of 34rem (48rem with asp-size="wide").
 */
.app-sheet,
.app-today-dialog {
  width: min(34rem, calc(100vw - 2rem));
  max-width: none;
  max-height: calc(100dvh - 2rem);
  padding: 0;
  border: 0;
  border-radius: var(--tblr-border-radius-lg, 0.5rem);
  background: var(--tblr-bg-surface);
  color: var(--tblr-body-color);
  box-shadow: var(--tblr-box-shadow-dropdown, 0 1rem 3rem rgba(0, 0, 0, 0.25));
}

.app-sheet.app-sheet-wide {
  width: min(48rem, calc(100vw - 2rem));
}

.app-today-dialog {
  overflow-y: auto;
}

.app-sheet::backdrop,
.app-today-dialog::backdrop {
  background: rgba(24, 36, 51, 0.5);
}

/* Three zones: the header stays, the body scrolls and the footer (data-sheet-actions) stays at the
   bottom of the body, also with a short form. */
.app-sheet[open] {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Without JavaScript the server's <dialog open> is not modal: fixed over the page, in the same place. */
.app-sheet[open]:not(:modal) {
  position: fixed;
  inset: 0;
  z-index: 1060;
  margin: auto;
  height: fit-content;
}

/* The page behind a modal window does not scroll. */
:root:has(.app-sheet:modal, .app-today-dialog:modal) {
  overflow: hidden;
}

.app-sheet-header,
.app-today-dialog-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

.app-sheet-header {
  flex: none;
  padding: 1rem 1.25rem 0.75rem;
  border-bottom: var(--tblr-border-width, 1px) solid var(--tblr-border-color);
}

.app-today-dialog-header {
  margin-bottom: 1rem;
}

.app-sheet-title,
.app-today-dialog-title {
  margin: 0;
  font-size: 1.125rem;
  overflow-wrap: anywhere;
}

.app-sheet-header .btn-close,
.app-today-dialog-header .btn-close {
  flex: none;
  min-width: 2.75rem;
  min-height: 2.75rem;
  margin: -0.75rem -0.75rem 0 0;
}

.app-sheet-header .btn-close {
  margin-bottom: -0.75rem;
}

.app-sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1rem 1.25rem 0;
}

.app-sheet-body > form {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

.app-sheet [data-sheet-actions] {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
  margin: auto -1.25rem 0;
  padding: 0.75rem 1.25rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
  border-top: var(--tblr-border-width, 1px) solid var(--tblr-border-color);
  background: var(--tblr-bg-surface);
}

.app-sheet [data-sheet-actions] .btn {
  min-height: 2.75rem;
}

/* Desktop: right after the fields there is no empty band before the footer. */
.app-sheet-body > form > .app-validation-summary:last-of-type {
  margin-bottom: 1rem;
}

.app-today-dialog-content {
  padding: 1.25rem;
}

.app-today-dialog-details {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.375rem 1rem;
  margin-bottom: 1.25rem;
}

.app-today-dialog-details dt {
  font-weight: var(--tblr-font-weight-medium, 500);
  color: var(--tblr-secondary);
}

.app-today-dialog-details dd {
  margin: 0;
}

.app-today-dialog-text {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.app-today-dialog-question {
  font-weight: var(--tblr-font-weight-medium, 500);
  overflow-wrap: anywhere;
}

.app-today-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

.app-today-dialog-actions .btn {
  min-height: 2.75rem;
}

/* Phone: a dialog of Hoy is a panel that rises from the bottom, at full width, rounded only at the
   top and as tall as its content. Its buttons stack at full width and the main one (the last) sits
   at the bottom, under the thumb. (Form windows are full screen with a top bar: below.) */
@media (max-width: 575.98px) {
  .app-today-dialog {
    width: 100%;
    max-height: calc(100dvh - 1rem);
    margin: auto 0 0;
    border-radius: var(--tblr-border-radius-lg, 0.5rem) var(--tblr-border-radius-lg, 0.5rem) 0 0;
  }

  .app-today-dialog-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .app-today-dialog-actions .btn {
    width: 100%;
  }

  .app-today-dialog-content {
    padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
  }

  .app-today-dialog-details {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.125rem;
  }

  .app-today-dialog-details dd + dt {
    margin-top: 0.5rem;
  }
}

/*
 * Ticket 149.1, the rule for every form window since 149.2 (Pablo, 2026-10-11; docs/UI_GUIDE.md):
 * below md a window (its header is .app-sheet-top-bar) is a full-screen dialog with a fixed bar on
 * top: the X ("Cancelar") at the left, the title, and the form's own primary button at the right.
 * There is no footer: the body with the fields takes the rest and scrolls, so the keyboard never
 * hides "Guardar". The primary stays inside the <form> (it submits without JavaScript): the footer
 * (data-sheet-actions) dissolves (display: contents) and the primary is positioned against the
 * dialog, which is outside the scrolling body, so it sits in the bar; the footer's "Cancelar" is
 * hidden (the X is the same link). Another action of the footer (the "Volver" of a step, review of
 * PR #250) stays at the end of the body, under the fields.
 * The height is the page's (dvh shrinks with the keyboard thanks to interactive-widget=resizes-content
 * in _Layout) or, where the browser ignores it, the visible part that form-sheet.js measures.
 * From md up the window is the modal of ticket 149, with its footer.
 */
@media (max-width: 767.98px) {
  .app-sheet,
  .app-sheet.app-sheet-wide,
  .app-sheet[open],
  .app-sheet[open]:not(:modal) {
    inset: var(--app-sheet-visible-top, 0px) 0 auto;
    width: 100%;
    max-width: none;
    height: var(--app-sheet-visible-height, 100dvh);
    max-height: var(--app-sheet-visible-height, 100dvh);
    margin: 0;
    border-radius: 0;
  }

  .app-sheet-header.app-sheet-top-bar {
    align-items: center;
    gap: 0.25rem;
    min-height: calc(3.5rem + env(safe-area-inset-top, 0px));
    /* The right side is the primary's (positioned over the bar, below). Ticket 149.2: room for a
       primary as long as "Crear mantenimiento" without "…". */
    padding: env(safe-area-inset-top, 0px) 10.75rem 0 0.25rem;
  }

  /* Ticket 287 (finanzas H-12): a window without a primary (a list window like "Cuotas mes a mes") does not keep the
     right side of the bar for one, so its title is whole. */
  .app-sheet:not(:has([data-sheet-actions] .btn-primary)) > .app-sheet-header.app-sheet-top-bar {
    padding-right: 0.75rem;
  }

  .app-sheet-header.app-sheet-top-bar .btn-close {
    order: -1;
    margin: 0;
  }

  .app-sheet-top-bar .app-sheet-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: normal;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .app-sheet .app-sheet-body {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .app-sheet [data-sheet-actions] {
    display: contents;
  }

  .app-sheet [data-sheet-actions] [data-sheet-close] {
    display: none;
  }

  .app-sheet [data-sheet-actions] .btn-primary {
    position: absolute;
    top: calc(env(safe-area-inset-top, 0px) + 0.375rem);
    right: 0.75rem;
    z-index: 1;
    width: auto;
    max-width: 10rem;
    padding-inline: 0.75rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .app-sheet [data-sheet-actions] .btn-primary > .ti {
    display: none;
  }

  /* A secondary step action ("Volver") under the fields, not in the bar. */
  .app-sheet [data-sheet-actions] .btn:not(.btn-primary) {
    align-self: flex-start;
    margin: 0.25rem 0 1rem;
  }
}

/*
 * Ticket 149.1: with interactive-widget=resizes-content the keyboard shrinks the page, so the bottom
 * navigation would ride on top of the keyboard while typing. On a touch screen it hides while a field
 * that opens the keyboard has the focus, as it did when the keyboard covered it.
 */
@media (max-width: 991.98px) and (pointer: coarse) {
  body:has(:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="submit"], [type="button"], [type="reset"], [type="hidden"], [type="date"], [type="time"], [type="datetime-local"], [type="month"], [type="week"]), textarea, [contenteditable="true"]):focus) .app-bottom-nav {
    display: none;
  }
}

/*
 * Ticket 132: the bulk loads of the baby pages. The form is a size container: where it is wide
 * (the panel alone at full width on a desktop) the rows are a grid under a header of column titles
 * and the label of each field is only for screen readers; where it is narrow (a phone, a tablet, or
 * the panel beside the open form) each row is a box with a visible label per field, two short fields
 * per line. Each page names its columns in --app-bulk-columns.
 */
.app-bulk {
  container-type: inline-size;
}

.app-bulk-feedings { --app-bulk-columns: minmax(11.5rem, 1.6fr) minmax(0, 1fr) minmax(0, 1fr) minmax(4.5rem, 0.6fr) minmax(4.5rem, 0.6fr) minmax(0, 1.4fr) 5.5rem; }
.app-bulk-diapers { --app-bulk-columns: minmax(11.5rem, 1fr) minmax(0, 1fr) minmax(0, 2fr) 5.5rem; }
.app-bulk-measurements { --app-bulk-columns: minmax(9rem, 1.2fr) repeat(3, minmax(0, 1fr)) 5.5rem; }
.app-bulk-notes { --app-bulk-columns: minmax(11.5rem, 1fr) minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) 5.5rem; }
.app-bulk-sleeps { --app-bulk-columns: minmax(11.5rem, 1fr) minmax(11.5rem, 1fr) minmax(0, 1.5fr) 5.5rem; }

.app-bulk-head {
  display: none;
}

.app-bulk-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* A label that wraps keeps the two fields of a line aligned at their inputs. */
  align-items: end;
  gap: 0.5rem;
  padding: 0.75rem;
  margin-bottom: 0.5rem;
  border: var(--tblr-border-width, 1px) solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius);
}

.app-bulk-row .app-bulk-label {
  margin-bottom: 0.25rem;
}

.app-bulk-wide,
.app-bulk-owner,
.app-bulk-remove {
  grid-column: 1 / -1;
}

.app-bulk-remove {
  justify-self: end;
}

.app-bulk-remove:has(> [hidden]) {
  display: none;
}

@container (min-width: 52rem) {
  .app-bulk-head {
    display: grid;
    grid-template-columns: var(--app-bulk-columns);
    gap: 0.5rem;
    margin-bottom: 0.25rem;
    font-size: 0.875rem;
    font-weight: var(--tblr-font-weight-medium, 500);
    color: var(--tblr-secondary);
  }

  .app-bulk-row {
    grid-template-columns: var(--app-bulk-columns);
    align-items: center;
    padding: 0;
    border: 0;
  }

  .app-bulk-wide,
  .app-bulk-remove {
    grid-column: auto;
  }

  .app-bulk-remove,
  .app-bulk-remove:has(> [hidden]) {
    display: block;
  }

  .app-bulk-row .app-bulk-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

/* Ticket 091.2: the currency selector next to an amount keeps the width of its symbol ("$" / "US$"). */
.input-group > .app-currency-select {
  flex: 0 0 auto;
  width: auto;
}

/* Ticket 146: the login introduces the app with its icon, name and tagline. */
/* Tabler's `.layout-fluid [class*=" container-"]` widens .container-tight to 100%, so the login and
   the other centered screens (access denied, errors) spread across a desktop screen. */
.layout-fluid .container-tight {
  max-width: 30rem;
}

.app-login-icon {
  border-radius: 1rem;
  box-shadow: var(--tblr-shadow-card, 0 1px 2px rgba(0, 0, 0, .08));
}

.app-login-name {
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1.1;
  margin-bottom: .5rem;
}

/*
 * Ticket 149.2 (Pablo, 2026-10-11, docs/UI_GUIDE.md "Filas de lista"): "me cuesta distinguir dónde
 * termina una tarea y dónde empieza la siguiente". Every list separates its rows with a marked line
 * (gray-300 instead of Tabler's gray-200), and a list of cards (.app-card-list, first in Tareas;
 * ticket 162 takes it to the other lists) shows each item as a card with a visible border and space
 * between cards.
 */
:root,
[data-bs-theme=light] {
  --app-row-border: #c9ced6;
}

.list-group {
  --tblr-list-group-border-color: var(--app-row-border);
}

.app-card-list {
  gap: 0.5rem;
  padding: 0.75rem;
}

.app-card-list > .list-group-item {
  padding: 0;
  border: 0;
  background: transparent;
}

/* The focus of a link (040) marks the card, not the space around it. */
.app-card-list > .list-group-item.app-focus-highlight {
  border-inline-start: 0;
  background: transparent;
  box-shadow: none;
  animation: none;
}

.app-card-list > .app-focus-highlight > .app-card-row {
  border-color: var(--tblr-primary);
  box-shadow: inset 3px 0 0 var(--tblr-primary);
}

/*
 * A card is horizontal. Phone: the title with the icon actions (pencil, trash can) on the first line,
 * the data and the row's primary action ("Completar") on the second. From md: title and data at the
 * left, the primary and the icons at the right, centered on both lines.
 */
.app-card-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title icons"
    "meta primary";
  align-items: center;
  gap: 0.125rem 0.5rem;
  padding: 0.375rem 0.375rem 0.5rem 0.875rem;
  border: var(--tblr-border-width, 1px) solid var(--app-row-border);
  border-radius: var(--tblr-border-radius-lg, 0.5rem);
  background: var(--tblr-bg-surface);
}

/* Ticket 171 (docs/UI_GUIDE.md "Filas de lista", "Texto largo"): nothing long leaves its card. A long
   word, URL or title breaks inside it (the buttons keep their nowrap), every part of the grid can shrink,
   and a badge (a quantity, a tag) breaks too. The description keeps its one line with "…". */
.app-card-row {
  overflow-wrap: anywhere;
}

.app-card-row > * {
  min-width: 0;
}

.app-card-row .badge {
  max-width: 100%;
  white-space: normal;
  text-align: start;
}

.app-card-row-title {
  grid-area: title;
  min-width: 0;
}

.app-card-row-heading {
  overflow-wrap: anywhere;
}

/* The description is a hint of one line; the whole text is in the edit window. */
.app-card-row-description {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Ticket 174: "Ver más" unfolds a cut description in the card, with its line breaks (site.js shows the
   button only when the text is cut). The button is a reading control, not an action of the item. */
.app-card-row-description.app-card-row-description-expanded {
  overflow: visible;
  text-overflow: clip;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Ticket 292: a note of Entrada (the text of its card) and of "Entrada pendiente" in Hoy goes in three lines at most,
   with "…" and its line breaks, like the note at the top of the windows of Entrada (151). "Ver más" unfolds it as the
   description of 174; site.js measures the cut by its height. */
.app-text-clamp {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.app-text-clamp.app-card-row-description-expanded {
  display: block;
  overflow: visible;
  -webkit-line-clamp: unset;
  line-clamp: none;
}

.app-expandable-text-toggle {
  padding: 0;
  border: 0;
  min-height: 0;
  text-decoration: none;
}

@media (max-width: 767.98px) {
  .app-expandable-text-toggle {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
  }
}

/* With "Ver más" the card grows downwards when it unfolds: the pencil and the trash can (and, from md,
   the primary) go at the top from the start, so unfolding does not move them. */
.app-card-row:has([data-expandable-text-toggle]:not([hidden])) > .app-card-row-icons {
  align-self: start;
}

@media (min-width: 768px) {
  .app-card-row:has([data-expandable-text-toggle]:not([hidden])) > .app-card-row-primary {
    align-self: start;
  }
}

.app-card-row-meta {
  grid-area: meta;
  min-width: 0;
}

.app-card-row-chip {
  margin-inline-end: 0.375rem;
}

.app-card-row-primary {
  grid-area: primary;
  justify-self: end;
}

.app-card-row-primary .btn {
  min-height: 2.75rem;
  gap: 0.375rem;
  white-space: nowrap;
}

.app-card-row-icons {
  grid-area: icons;
  display: flex;
  justify-self: end;
}

.app-card-row-icons .app-row-icon {
  width: 2.75rem;
  height: 2.75rem;
  font-size: 1.25rem;
}

.app-card-row-confirm {
  grid-column: 1 / -1;
  margin-top: 0.5rem;
}

/* Ticket 162: what a card unfolds (the detail of a purchase, the payments of a recurring expense)
   takes the card's whole width, under its two lines. */
.app-card-row-detail {
  grid-column: 1 / -1;
}

/* Ticket 162: the "⋯" that follows the pencil and the trash can is a touch target of 44 px too. */
.app-card-row-icons .app-row-menu-toggle {
  width: 2.75rem;
  height: 2.75rem;
}

@media (min-width: 768px) {
  .app-card-row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "title primary icons"
      "meta primary icons";
    column-gap: 0.75rem;
    padding: 0.5rem 0.5rem 0.5rem 1rem;
  }
}

/* The exceptional state (a completed or cancelled task) is a muted card, still readable: its text
   keeps the secondary gray at 5:1 or more, and its chip says the state. The default state (pending)
   carries no chip. */
.app-card-row-muted {
  border-color: var(--tblr-border-color);
  background: var(--tblr-gray-100, #f3f4f6);
}

/* Specific enough to win over the skin of Tareas (reference-ui.css), which colors .fw-medium. */
.app-card-list > .list-group-item > .app-card-row-muted .app-card-row-heading {
  color: var(--tblr-secondary);
}

/* Ticket 159: the answer boxes of a pediatric consultation start at two lines and grow with their text. */
.app-visit-answer {
  field-sizing: content;
  min-height: 3.75rem;
  max-height: 40vh;
}

/* Ticket 159: the counters of the consultation context stay compact, two per row. */
.app-visit-datagrid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * Ticket 169: the one-tap chips of the quick window of /Baby (js/baby-quick-entry.js). Each chip is a
 * toggle button (aria-pressed) of 44 px; the pressed one is filled with the primary color. They wrap
 * in rows, never scroll sideways. The note popup is a dialog of Hoy (127) with a textarea.
 */
.app-chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.app-chip-group > .app-chip {
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding-inline: 0.875rem;
  border-radius: 999px;
}

.app-chip-group > .app-chip[aria-pressed="true"] {
  color: var(--tblr-primary-fg, #fff);
  background-color: var(--tblr-primary);
  border-color: var(--tblr-primary);
}

/* On a touch screen the hover sticks after a tap: a released chip must not keep looking pressed. */
@media (hover: none) {
  .app-chip-group > .app-chip[aria-pressed="false"]:hover {
    color: var(--tblr-primary);
    background-color: transparent;
  }
}

/*
 * Ticket 280: "Fechas rápidas" (<app-date-presets>, js/date-presets.js), the chips above a date input. Without
 * JavaScript they would not respond, so they are not shown (site-head.js puts the js class on <html>, 184). The
 * concrete day ("19/10") reads as a secondary part of the chip.
 */
html:not(.js) .app-date-presets {
  display: none;
}

/* A .btn is a flex box, which drops the space between the name and the day: the gap puts it back. */
.app-date-presets > .app-chip {
  gap: 0.3em;
  padding-inline: 0.75rem;
}

.app-date-presets .app-date-preset-day {
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

.app-quick-note-button {
  min-height: 2.75rem;
}

.app-quick-note-preview {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/*
 * Ticket 186: "Más detalles" of the one form of a feeding or a diaper change: a <details> of the same
 * form (no JavaScript needed to open it) whose summary reads as a link with a 44 px touch target. The
 * detail chips ("Otro" of a diaper change) show only while it is open, or when pressed.
 */
.app-record-details > summary {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  color: var(--tblr-link-color, var(--tblr-primary));
  cursor: pointer;
}

.app-record-details-body {
  padding-top: 0.5rem;
}

.app-chip-group > .app-chip[data-quick-detail-chip]:not([aria-pressed="true"]) {
  display: none;
}

form:has(details[data-record-details][open]) .app-chip-group > .app-chip[data-quick-detail-chip] {
  display: inline-flex;
}

/*
 * Ticket 173: "Agregar hora" of the task forms. A task is due on a day; the time is one tap away, in a
 * <details> (no JavaScript). The summary reads as a link and keeps a comfortable touch target.
 */
.app-task-due-time > summary {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  cursor: pointer;
  color: var(--tblr-primary);
}

/*
 * Ticket 185: the read-only detail of an item (_ItemDetailSheet). The title of a card is a link that opens it
 * (app-card-row-link; the card is found with :has, so its markup keeps its classes);
 * its ::after covers the whole card, so tapping anywhere outside the buttons opens the detail, and the card's own
 * controls stay above it. The detail is a list of label and value, with the long texts keeping their line breaks.
 */
.app-card-row:has(.app-card-row-link),
.timeline-event-card:has(.app-card-row-link) {
  /* Ticket 185.1: a memory of Recuerdos is a card of the timeline, not an app-card-row. */
  position: relative;
}

.app-card-row-link {
  color: inherit;
  text-decoration: none;
}

.app-card-row-link:focus-visible {
  color: var(--tblr-primary);
  text-decoration: underline;
}

/* On a touch screen the hover sticks after a tap: only a real pointer underlines the title. */
@media (hover: hover) {
  .app-card-row-link:hover {
    color: var(--tblr-primary);
    text-decoration: underline;
  }
}

.app-card-row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.app-card-row:has(.app-card-row-link) :is(a, button, summary, select, input, textarea, label):not(.app-card-row-link),
.timeline-event-card:has(.app-card-row-link) :is(a, button, summary, select, input, textarea, label):not(.app-card-row-link) {
  /* Only the controls themselves: a wrapper with z-index would trap the "⋯" menu under the next card. */
  position: relative;
  z-index: 1;
}

.app-detail-fields {
  margin: 0 0 1rem;
}

.app-detail-field {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--app-row-border, var(--tblr-border-color));
}

.app-detail-field:last-child {
  border-bottom: 0;
}

.app-detail-field > dt {
  font-weight: 500;
  font-size: 0.8125rem;
  color: var(--tblr-secondary);
  margin-bottom: 0.125rem;
}

.app-detail-field > dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Ticket 274: the month summary of Finanzas: label and amount on one line, in its window and in its card. */
.app-summary-fields .app-detail-field {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
}

.app-summary-fields .app-detail-field > dt {
  margin-bottom: 0;
}

.app-summary-fields .app-detail-field > dd {
  text-align: right;
  white-space: nowrap;
}

.app-summary-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  color: var(--tblr-secondary);
}

.app-summary-line + .app-summary-line {
  margin-top: 0.25rem;
}

.app-detail-multiline {
  white-space: pre-line;
}

.app-detail-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.app-detail-actions .btn {
  min-height: 2.75rem;
}

.app-detail-actions .btn,
.app-sheet [data-detail-edit] {
  gap: 0.375rem;
}

/*
 * Ticket 189: the simple window of a sleep (_SleepWindow). Each end is a row with its label and a big time,
 * and under it the steps of minutes (−15 −5 +5 +15), four buttons of 44 px that share the width on a phone.
 * The duration under both follows them. "Cambiar día u hora" and "Agregar nota" are <details> (no JavaScript).
 */
.app-sleep-window {
  margin-bottom: 1rem;
}

.app-sleep-end {
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--app-row-border, var(--tblr-border-color));
}

.app-sleep-end-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.app-sleep-end-label {
  font-weight: 500;
}

.app-sleep-time {
  font-size: 1.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.app-sleep-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
}

.app-sleep-steps[hidden],
.app-sleep-wake-now[hidden] {
  display: none;
}

.app-sleep-step,
.app-sleep-wake-now {
  min-height: 2.75rem;
  font-variant-numeric: tabular-nums;
}

.app-sleep-wake-now {
  width: 100%;
}

.app-sleep-duration {
  margin: 0.75rem 0 0;
  font-size: 1.125rem;
  font-weight: 500;
}

.app-sleep-exact > summary {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  cursor: pointer;
  color: var(--tblr-primary);
}

.app-sleep-note > summary {
  list-style: none;
}

.app-sleep-note > summary::-webkit-details-marker {
  display: none;
}

/* Ticket 272: Bootstrap's ".card-link + .card-link { margin-left }" is meant for text links inside a card; on Hoy
   the two link cards (Buscar and Recientes) are whole cards, so the second one was pushed to the right. */
a.card.card-link + a.card.card-link {
  margin-left: 0;
}
