:root {
  --bg: #fafaf9;
  --surface: #ffffff;
  --text: #1c1917;
  --text-muted: #78716c;
  --border: #e7e5e4;
  --accent: #383827;
  --accent-contrast: #ffffff;
  --danger: #b91c1c;
  --header-h: 64px;
  --controls-h: 52px;
  --placeholder-gradient: linear-gradient(135deg, #ece5d8, #d8cebe);
  --page-pad: 24px;
  --grid-gap-x: 36px;
  --grid-gap-y: 36px;
  --grid-gap-mobile: 24px;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1c1917;
    --surface: #292524;
    --text: #f5f5f4;
    --text-muted: #a8a29e;
    --border: #44403c;
    --accent: #f1dfb6;
    --accent-contrast: #052e16;
    --placeholder-gradient: linear-gradient(135deg, #3d372c, #2b271f);
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h1, h2 { margin: 0; }

button {
  font: inherit;
  cursor: pointer;
}

/* Header — Library heading plus every control (tabs, sort, view toggle, add
   book, log out). */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px var(--page-pad) 12px;
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 10;
}

.app-header h1 {
  font-size: 1.3rem;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}

.btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 8px;
  padding: 8px 14px;
  font-size: 0.9rem;
  white-space: nowrap;
}

.header-actions .btn {
  padding: 6px 12px;
  font-size: 0.8rem;
}

.link-action {
  border: none;
  background: transparent;
  padding: 0;
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}

#logout-btn {
  color: var(--text-muted);
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-contrast);
  border-color: var(--accent);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
}

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

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

/* Controls: tabs + sort + view toggle — its own sticky bar just below the
   header. Minimal underline style on tabs/toggle rather than a filled pill. */

.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 15px;
  padding: 8px var(--page-pad) 12px;
  position: sticky;
  top: var(--header-h);
  z-index: 9;
  background: var(--bg);
}

.right-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 20px;
}

/* Below the width where tabs + sort + view-toggle fit on one row, force
   right-controls onto its own full-width row (matches the grid-view 3-column
   breakpoint) with the view-toggle pushed to the far right, across from
   the sort select, instead of the two just hugging together off-center.
   Kept as a single nowrap row rather than letting flex-wrap stack search/
   sort/toggle on top of each other — the search input shrinks to whatever
   space is left instead. */
@media (max-width: 620px) {
  .right-controls {
    width: 100%;
    flex-wrap: nowrap;
    gap: 10px;
  }

  .list-search {
    width: auto;
    min-width: 60px;
    flex: 1 1 auto;
    margin-right: 0;
  }

  #sort-select,
  .view-toggle {
    flex-shrink: 0;
  }
}

.status-tabs {
  overflow-x: auto;
}

.status-tabs, .view-toggle {
  display: flex;
  gap: 14px;
}

.view-toggle {
  gap: 10px;
}

.tab, .view-btn {
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--text-muted);
  padding: 4px 1px;
  font-size: 0.8rem;
  white-space: nowrap;
}

.tab.active, .view-btn.active {
  color: var(--text);
  font-weight: 600;
  border-bottom-color: var(--accent);
}

/* The native <select> arrow is platform chrome, not part of the content box —
   padding-right can't move it (notably in Safari, where it renders flush
   against the edge regardless). appearance:none strips it so we can draw our
   own chevron as a background image instead, with padding controlling its
   distance from the edge. The SVG's stroke color is hardcoded (data URIs
   can't reference CSS custom properties) and swapped for dark mode below. */
#sort-select {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--border);
  background: var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%2378716c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1L5 5L9 1'/%3E%3C/svg%3E") no-repeat right 10px center;
  background-size: 10px 6px;
  color: var(--text);
  border-radius: 10px;
  padding: 6px 24px 6px 9px;
  font-size: 0.8rem;
}

@media (prefers-color-scheme: dark) {
  #sort-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23a8a29e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1L5 5L9 1'/%3E%3C/svg%3E");
  }
}

/* Only shown on the Completed tab (toggled via render.js) — searching
   to-read/reading lists hasn't been worth the UI cost at this collection
   size. Same pill treatment as #sort-select, with a magnifying-glass icon
   standing in for the select's chevron. */
.list-search {
  border: 1px solid var(--border);
  background: var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2378716c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7' cy='7' r='5'/%3E%3Cpath d='M10.6 10.6L14 14'/%3E%3C/svg%3E") no-repeat 9px center;
  background-size: 13px 13px;
  color: var(--text);
  border-radius: 10px;
  padding: 6px 9px 6px 28px;
  font-size: 0.8rem;
  width: 9em;
  margin-right: auto;
}

.list-search::placeholder {
  color: var(--text-muted);
}

.list-search::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

@media (prefers-color-scheme: dark) {
  .list-search {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a8a29e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7' cy='7' r='5'/%3E%3Cpath d='M10.6 10.6L14 14'/%3E%3C/svg%3E");
  }
}

/* Grouping heading — used when sorting by author (grouped by author name), or
   when sorting the Completed tab by publication date / date completed (grouped
   by year). Sticky so the current group stays visible while scrolling.
   Horizontal inset is NOT set here: .grid-view carries its own --page-pad
   inset on the container (shared with the cards), but .list-view keeps its
   container padding at 0 and puts --page-pad on the per-row .list-scroll
   wrapper instead (see wrapForHorizontalScroll for why) — and the heading is
   never wrapped in .list-scroll. So each view grants the inset below,
   whichever side doesn't already provide it, so the heading always lines up
   with the page's edge padding instead of stacking with it. */

.group-heading {
  grid-column: 1 / -1;
  position: sticky;
  top: calc(-1px + var(--header-h) + var(--controls-h));
  z-index: 8;
  background: var(--bg);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 18px 0 8px;
  border-bottom: 1px solid var(--border);

  @media (min-width: 480px) {
    padding-top: 36px;
  }
}


.list-view .group-heading {
  padding-left: var(--page-pad);
  padding-right: var(--page-pad);
}



/* Grid view */

.grid-view {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--grid-gap-y) var(--grid-gap-x);
  padding: 4px var(--page-pad) 32px;
}

/* auto-fill's minmax(120px, 1fr) naturally drops to 2 columns below ~440px,
   which includes most phones (360-428px wide). Force 3 columns with a
   tighter gap instead of letting them shrink to 2 oversized cards. */
@media (max-width: 480px) {
  .grid-view {
    /* minmax(0, 1fr), not bare 1fr: a bare fr track's implicit minimum is
       "auto" (min-content), so a long nowrap author name (.card-author)
       would force that column wider than its equal share, throwing off all
       three columns. minmax(0, ...) replaces that automatic minimum. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--grid-gap-mobile);
  }
}

.book-card {
  display: flex;
  flex-direction: column;
  border: none;
  padding: 0;
  background: transparent;
  text-align: left;
}

.cover-wrap {
  width: 100%;
  aspect-ratio: 2 / 3;
  border-radius: 4px;
  overflow: hidden;
  background: var(--border);
}

.cover-wrap .cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.card-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 2px 0;
}

.card-title {
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-author {
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.completed-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.68rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.check-icon {
  flex-shrink: 0;
}

/* List view — spreadsheet-style: title (cover + title/author·year stacked
   into one cell) takes the remaining width, with rating and completed-date
   grouped 20px apart as a single right-aligned cluster — not separate
   equal-width columns, so the two stay visually paired rather than
   scattered across the row. There's no header row (labels would just
   repeat what's already legible in each cell) and no status column — every
   view is already filtered to a single status via the tabs, so labeling it
   per-row would be redundant. The Completed column only appears on the
   Completed tab (toggled via .show-completed on the container), since that
   date is only ever set for completed books. */

.list-view {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0 32px;
}

/* Each row/header gets its own horizontal-scroll box (see wrapForHorizontalScroll
   in render.js for why it's per-row rather than one shared wrapper). Grid tracks
   still won't shrink below their content's natural minimum, so if the row's
   natural width (most likely on the Completed tab, where the row also carries
   rating and completed-date) exceeds the viewport, this scrolls instead of
   every column getting squeezed unreadably thin. */
.list-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 var(--page-pad);
}

.book-row {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: 8px;
  min-width: fit-content;
  width: 100%;
  border: none;
  background: var(--surface);
  border-radius: 10px;
  padding: 6px 8px;
  text-align: left;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.list-view.show-completed .book-row {
  grid-template-columns: 1fr auto;
}

/* .row-meta wraps everything after the title (rating, completed — only
   populated on the Completed tab). Plain .row-meta stays display:contents
   so an empty one (every tab but Completed) doesn't consume a grid track.
   On the Completed tab it becomes a real flex box instead, grouping rating
   and completed-date into one right-aligned cluster 20px apart, sized to
   its own content as the grid's second (auto) column. */
.row-meta {
  display: contents;
}

.list-view.show-completed .row-meta {
  display: flex;
  align-items: center;
  gap: 20px;
}

.search-result {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 10px;
  padding: 8px;
  text-align: left;
}

.cover-thumb {
  width: 44px;
  height: 66px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}

.book-row .cover-thumb {
  width: 36px;
  height: 54px;
}

.placeholder-cover {
  background: var(--placeholder-gradient);
}

.row-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.row-title-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.row-title-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.row-title {
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.rating-star {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.star-icon {
  flex-shrink: 0;
}

.row-author {
  font-size: 0.8rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.row-rating .rating-star {
  font-size: 0.75rem;
}

.row-completed .completed-badge {
  font-size: 0.75rem;
}

/* Below ~640px the spreadsheet columns get too narrow to read (the cover
   thumbnail alone eats most of an equal share). Switch to a single row:
   title on the left (wrapping instead of truncating, since with the full
   row width available there's no need to clip it), and — on the Completed
   tab, the only one where row-meta actually holds content — rating stacked
   above completed-date on the right, using the space freed up now that
   there's no status badge to share the row with.
   This must come after the base .row-title/.row-author/etc rules above:
   same specificity, so source order decides the winner regardless of the
   media query. */
@media (max-width: 640px) {
  .book-row {
    display: flex;
    align-items: center;
    column-gap: 8px;
    min-width: 0;
  }

  .row-title-cell {
    flex: 1;
    min-width: 0;
  }

  .row-title {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }

  .list-view.show-completed .row-meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    flex: 0 0 auto;
  }
}

.empty-state {
  text-align: center;
  color: var(--text-muted);
  margin-top: 40px;
}

/* Modals */

dialog.modal {
  border: none;
  border-radius: 14px;
  padding: 16px;
  width: min(480px, 92vw);
  max-height: 85vh;
  background: var(--surface);
  color: var(--text);
}

dialog.modal::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

#search-input {
  width: 100%;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 1rem;
}

.hint {
  color: var(--text-muted);
  font-size: 0.85rem;
  min-height: 1.2em;
}

.search-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 55vh;
  overflow-y: auto;
}

.search-result .row-info { flex: 1; }

.result-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.cover-picker {
  width: 100%;
}

#edit-cover-picker {
  margin-bottom: 8px;
}

.cover-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.cover-choice {
  border: 1px solid var(--border);
  background: transparent;
  padding: 0;
  border-radius: 4px;
  overflow: hidden;
  flex-shrink: 0;
}

.cover-choice img {
  display: block;
  width: 44px;
  height: 66px;
  object-fit: cover;
}

.cover-choice-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.cover-choice-none {
  width: 44px;
  height: 66px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  font-size: 0.65rem;
  color: var(--text-muted);
  border-style: dashed;
  text-align: center;
}

/* Edit form */

#edit-form {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.edit-title-row {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-bottom: 8px;
}

.edit-cover-wrap {
  position: relative;
  width: 56px;
  height: 84px;
  border-radius: 6px;
  overflow: hidden;
  flex-shrink: 0;
  background: var(--placeholder-gradient);
}

.edit-cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Hover-to-reveal on pointer devices; touch has no hover concept, so it's
   just always visible there rather than requiring a tap-to-discover step. */
.edit-cover-edit-btn {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: rgba(28, 25, 23, 0.45);
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.edit-cover-wrap:hover .edit-cover-edit-btn,
.edit-cover-edit-btn:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .edit-cover-edit-btn {
    opacity: 1;
    background: rgba(28, 25, 23, 0.3);
  }
}

.edit-title-fields {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.edit-title-input {
  font-weight: 700;
  font-size: 1rem;
}

.edit-author-input {
  color: var(--text-muted);
  font-size: 0.85rem;
}

.synopsis {
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--text);
  max-height: 6.5em;
  overflow-y: auto;
  margin: 0 0 4px;
  padding: 8px 10px;
  background: var(--bg);
  border-radius: 8px;
}

#edit-form label {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 8px;
}

#edit-form select,
#edit-form input,
#edit-form textarea {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
}

#edit-form input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Same native-arrow-hugs-the-edge problem as #sort-select — appearance:none
   plus a custom chevron */
#edit-form select {
  appearance: none;
  -webkit-appearance: none;
  background: var(--bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%2378716c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1L5 5L9 1'/%3E%3C/svg%3E") no-repeat right 10px center;
  background-size: 10px 6px;
  padding-right: 22px;
}

@media (prefers-color-scheme: dark) {
  #edit-form select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23a8a29e' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1L5 5L9 1'/%3E%3C/svg%3E");
  }
}

.edit-actions {
  display: flex;
  justify-content: space-between;
  margin-top: 16px;
}

/* Login page */

.login-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.login-card {
  width: min(320px, 90vw);
  background: var(--surface);
  border-radius: 14px;
  padding: 32px 24px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  text-align: center;
}

.login-card h1 {
  margin-bottom: 24px;
}

.login-card form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}

.login-card input {
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 1rem;
}

.login-card button {
  margin-top: 8px;
  padding: 10px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

.error {
  color: var(--danger);
  font-size: 0.85rem;
}
