/*
  Books page. Auto-loaded on the `books` template via the `@auto` entry in
  header.php (Kirby resolves that to assets/css/templates/{template}.css).

  The prototype's :root token block and body rule are intentionally absent —
  index.css already defines --color-black/white/grey/light/old-blue,
  --font-family-sans and `body { max-width: 70rem; margin: 0 auto }` with the
  same values. Only the four aliases below are books-specific.
*/

:root {
    --border: var(--color-light);
    --muted: var(--color-grey);
    --accent: var(--color-old-blue);
    --row-hover: var(--color-light);
}

[hidden] { display: none !important; }

/* The prototype relied on a global `* { box-sizing: border-box }` that index.css
   doesn't provide (zoonstyles.css only sets it inside Splide). Scoped here rather
   than made global so the rest of the site keeps its current metrics. */
#books-app, #books-app *,
.books-head, .books-head * { box-sizing: border-box; }

/* Matches `.text h2` in index.css — the size "Tools & Skills" renders at on the
   resume page. index.css resets all headings to font-size: inherit, so this is
   the only rule setting it. */
h1 {
    font-size: 1.25rem;
    margin: 0;
    font-weight: 600;
}

/* Sign-in strip. Sits beside the title so editing is reachable without a
   detour through /panel — same account, same session. */
.books-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
}
.books-auth {
    position: relative;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 0.85rem;
}
.books-auth-who { color: var(--muted); }
.books-auth-btn {
    font: inherit;
    font-size: 0.85rem;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--border);
    background: none;
    color: var(--muted);
    cursor: pointer;
    list-style: none;
}
.books-auth-btn::-webkit-details-marker { display: none; }
.books-auth-btn:hover { color: var(--color-black); border-color: var(--color-black); }
.books-login-form {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 70;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.85rem;
    background: var(--color-white);
    border: 1px solid var(--color-black);
    min-width: 15rem;
}
.books-login-form input {
    font: inherit;
    font-size: 0.85rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--color-black);
    background: var(--color-white);
    color: inherit;
}
.books-login-error {
    margin: 0;
    font-size: 0.8rem;
    color: var(--color-error);
}
.tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
}
.tabs button {
    font: inherit;
    font-size: 0.9rem;
    background: none;
    border: none;
    padding: 0.5rem 0.9rem;
    cursor: pointer;
    color: var(--muted);
}
.tabs button:hover { color: var(--color-black); }
.tabs button.active {
    color: var(--color-black);
    font-weight: 500;
}
.toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-bottom: 1rem;
}
/* Explicit height on both: a bare <select> and <input> resolve different
   intrinsic heights from the same padding, so matching them needs a fixed value. */
.toolbar input[type="search"],
.toolbar select {
    font: inherit;
    height: 2.2rem;
    padding: 0.3rem 0.6rem;
    line-height: normal;
    border: 1px solid var(--color-black);
    background: var(--color-white);
    color: inherit;
}
.toolbar input[type="search"] { min-width: 16rem; }
.toolbar label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.9rem;
    color: var(--muted);
}
.count {
    font-size: 0.85rem;
    color: var(--muted);
    margin-left: auto;
}
/* Sorting for narrow screens, where the table head is hidden. Shown by the
   media query at the bottom of this file; books.js keeps it in sync with the
   column-header state so switching orientation mid-session isn't confusing. */
.sort-bar {
    display: none;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
    font-size: 0.85rem;
    color: var(--muted);
}
.sort-bar select,
.sort-bar button {
    font: inherit;
    font-size: 0.85rem;
    height: 2.2rem;
    border: 1px solid var(--color-black);
    background: var(--color-white);
    color: var(--color-black);
}
.sort-bar select {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.3rem 0.6rem;
    line-height: normal;
}
.sort-bar button {
    flex: none;
    width: 2.2rem;
    cursor: pointer;
}
table {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.9rem;
}

/* Fixed layout is what makes truncation possible: columns keep their width no
   matter how long a title is, so nothing wraps and every row is one line tall.
   The widths live on the header cells because a fixed table takes its column
   widths from the first row — body cells stay width-less, which also keeps them
   out of the way of the narrow layout, where the table becomes a flex list.
   Title is the only auto column, so it absorbs the space that the columns
   hidden on a given tab give back. */
#books-table { table-layout: fixed; }
thead .queue-cell  { width: 3.5rem; }
thead .author-cell { width: 10rem; }
thead .owned-cell  { width: 4rem; }
thead .year-cell   { width: 3.5rem; }
thead .date-cell   { width: 5.5rem; }
thead .status-cell { width: 6.5rem; }
thead .tag-cell    { width: 12rem; }
thead .rating-cell { width: 5.5rem; }

/* Per-tab columns. A tab that can't have a value in a column shouldn't spend
   width on it: nothing in the pile has been read or rated, and Currently
   Reading adds Owned to that. */
#books-app[data-tab="currently-reading"] .date-cell,
#books-app[data-tab="currently-reading"] .rating-cell,
#books-app[data-tab="currently-reading"] .owned-cell,
#books-app[data-tab="to-read"] .date-cell,
#books-app[data-tab="to-read"] .rating-cell { display: none; }

/* Read keeps its status column but not the 392 repetitions of the word "Read":
   the control only surfaces on the row you're pointing at. Removing the column
   outright would make the tab a one-way door — it's the only status editor
   there is, so a book marked read could never be moved back.

   visibility rather than display, so the cell keeps its box and the row doesn't
   reflow as the pointer travels down the table. :focus is in there to keep the
   control reachable by keyboard, where there is no hover to trigger it. */
#books-app[data-tab="read"] .status-select { visibility: hidden; }
#books-app[data-tab="read"] tbody tr:hover .status-select,
#books-app[data-tab="read"] .status-select:focus { visibility: visible; }

/* Truncation. The ellipsis sits on the text rather than the cell so that the
   absolutely-positioned cover preview isn't clipped along with it. Hovering
   lifts the max-width: the text keeps its place in the line and simply spills
   over the columns to its right, on an opaque background — so the full string
   is readable without the row ever changing height. */
.cell-text {
    display: inline-block;
    vertical-align: middle;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.title-cell:hover .cell-text,
.author-cell:hover .cell-text {
    position: relative;
    z-index: 55;
    max-width: 40rem;
    padding: 0.15rem 0.4rem;
    margin: -0.15rem -0.4rem;
    background: var(--color-white);
    /* The border matters: without it, the neighbouring column's text emerging
       from behind the panel reads as a rendering fault rather than as content
       sitting underneath something floating. */
    border: 1px solid var(--border);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
}
/* The author column starts two thirds across, so it needs a tighter ceiling to
   stay inside the page. */
.author-cell:hover .cell-text { max-width: 20rem; }
thead th {
    text-align: left;
    border-bottom: 2px solid var(--color-black);
    padding: 0.5rem 0.6rem;
    white-space: nowrap;
}
thead th.sortable { cursor: pointer; user-select: none; }
thead th.sortable:hover { color: var(--accent); }
thead th.sorted::after { content: " " attr(data-dir); font-size: 0.75em; color: var(--accent); }
tbody td {
    padding: 0.45rem 0.6rem;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    /* A floor, not a fixed height — cells can't be shorter than this, and with
       every cell's content on one line none of them is taller, so the rows come
       out uniform. Tallest content is the queue input at ~35px. */
    height: 2.25rem;
}
tbody tr:hover { background: var(--row-hover); }
tbody tr.hidden { display: none; }

.books-error { color: var(--color-error); }

/* nowrap on the cell, not just the text inside it: the expanded title panel and
   the "+ cover" chip are two inline-blocks, and once the panel is wider than
   the column the cell would break between them and drop the chip onto a second
   line. Keeping them on one line lets the pair overflow to the right together. */
.title-cell { position: relative; white-space: nowrap; }
.cover-preview {
    display: none;
    position: absolute;
    top: 1.5rem;
    left: 0;
    z-index: 50;
    width: 110px;
    height: auto;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
    pointer-events: none;
}
.title-cell:hover .cover-preview { display: block; }

.add-cover-btn {
    display: none;
    font-size: 0.75rem;
    color: var(--muted);
    margin-left: 0.5rem;
    cursor: pointer;
}
/* Revealed on row hover, not title-cell hover: the status control on the same
   row keys off the row, so scanning down any other column would surface that
   one and leave this looking absent. Styled as a chip because it follows the
   title inline — without a surface of its own it would float loose over the
   Year column once an expanded title pushes it out there. */
tbody tr:hover .add-cover-btn {
    display: inline;
    position: relative;
    z-index: 56;
    margin-left: 0.2rem;
    padding: 0.15rem 0.4rem;
    background: var(--color-white);
    border: 1px solid var(--border);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
}
.add-cover-btn:hover { color: var(--accent); }
/* Pinned to the cell rather than left in the flow: it replaces the chip, and
   inline it would sit wherever the expanded title ended — potentially a column
   or two to the right of the book it belongs to. */
.add-cover-input {
    font: inherit;
    font-size: 0.8rem;
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    z-index: 57;
    padding: 0.1rem 0.3rem;
    border: 1px solid var(--color-black);
    background: var(--color-white);
    color: inherit;
    width: 12rem;
}
.cover-error { font-size: 0.75rem; color: var(--color-error); }

/* The column's width is set on the header now (table-layout: fixed), so the
   cell can't grow as tags are added; the overflow on .tags-wrap absorbs the
   excess instead. */
tbody .tag-cell { cursor: text; }
.tags-wrap {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    /* The 3em reserve keeps a patch of empty cell to click on, which is what
       starts an append-mode edit rather than replacing the existing tags. */
    max-width: calc(100% - 3em);
}
/* Same trick as the truncated title: a cell that can't grow shows the rest of
   its content by spilling to the right on hover, so a third tag isn't lost to
   the clip. */
tbody .tag-cell:hover .tags-wrap {
    position: relative;
    z-index: 55;
    width: max-content;
    max-width: 22rem;
    padding: 0.15rem 0.4rem;
    margin: -0.15rem -0.4rem;
    background: var(--color-white);
    border: 1px solid var(--border);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
}
.tag-cell input {
    font: inherit;
    width: 100%;
    padding: 0.2rem 0.3rem;
    border: 1px solid var(--accent);
    background: var(--color-white);
    color: inherit;
}
.tag-suggestions {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 60;
    background: var(--color-white);
    border: 1px solid var(--color-black);
    min-width: 10rem;
    max-width: 16rem;
}
.tag-suggestion {
    padding: 0.25rem 0.5rem;
    font-size: 0.8rem;
    cursor: pointer;
}
.tag-suggestion:hover { background: var(--color-light); }
.tag {
    display: inline-block;
    font-size: 0.75rem;
    color: var(--muted);
    background: var(--color-light);
    padding: 0.05rem 0.5rem;
    /* No bottom margin: .tags-wrap is nowrap, so tags never form a second line,
       and the margin only showed up as 3px of extra height on tagged rows. */
    margin: 0 0.2rem 0 0;
}

/* To Read priority. The column only earns its width while that pile is on
   screen, so it's hidden on the other tabs — books.js mirrors the active tab
   onto #books-app[data-tab] purely so this selector can exist. */
#books-app:not([data-tab="to-read"]) .queue-cell { display: none; }
.queue-input {
    font: inherit;
    font-size: 0.8rem;
    width: 3rem;
    /* Explicit height on both editors: left to their intrinsic size they'd be
       a couple of pixels taller than a text row, and by different amounts on
       different platforms, so the tabs carrying them wouldn't match the rest. */
    height: 1.35rem;
    padding: 0.15rem 0.2rem;
    border: 1px solid transparent;
    background: none;
    color: var(--muted);
    text-align: right;
}
/* Spinners would put two more click targets in every row of a 550-row list. */
.queue-input::-webkit-inner-spin-button,
.queue-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.queue-input { -moz-appearance: textfield; appearance: textfield; }
tbody tr:hover .queue-input { border-color: var(--border); }
.queue-input:focus {
    border-color: var(--accent);
    color: var(--color-black);
    outline: none;
}

.status-select {
    font: inherit;
    font-size: 0.8rem;
    height: 1.35rem;
    padding: 0.1rem 0.2rem;
    border: 1px solid transparent;
    background: none;
    color: var(--muted);
    cursor: pointer;
    max-width: 100%;
}
tbody tr:hover .status-select { border-color: var(--border); }
.status-select:focus { border-color: var(--accent); outline: none; }

/* tbody-scoped: the header cell carries the same class for the width and the
   per-tab hiding, and shouldn't pick up the cell's centring or pointer. */
tbody .owned-cell { cursor: pointer; text-align: center; }
.owned-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1rem;
    height: 1rem;
    border: 1px solid var(--color-light);
    font-size: 0.7rem;
    color: transparent;
}
/* index.css splits #444 into --color-mid-grey; the prototype's
   --color-dark-grey was #444, so use the mid token to match. */
.owned-toggle.on { color: var(--color-mid-grey); }
.owned-cell:hover .owned-toggle:not(.on) { color: var(--muted); }

.stars { display: inline-flex; gap: 1px; cursor: pointer; }
.stars .star { font-size: 0.95rem; color: var(--border); }
/* Filled stars read mid grey at rest and only turn blue under the cursor, so
   the accent marks what you're about to change rather than the whole table. */
.stars .star.on { color: var(--muted); }
.stars:hover .star.on { color: var(--accent); }
.stars.empty .star { color: transparent; }
/* Unrated books hang their placeholder off the cell's hover, not the stars'
   own 18px band. A row whose title or author wraps is two or three lines tall,
   so that band covers barely a quarter of the cell — you'd point at the row,
   hit the padding above or below the stars, and see nothing appear. Hovering
   anywhere in the cell reveals them, at any row height.

   --border (#efefef) was the old reveal colour and is invisible against white.
   The placeholder now shares --muted with a real rating, so it's faded to stay
   distinguishable from one — opacity rather than a lighter hex keeps this on
   the existing tokens. */
.rating-cell:hover .stars.empty .star { color: var(--muted); opacity: 0.45; }
.stars .star.preview { color: var(--accent); }

/* Logged out: the data still renders, but nothing should look clickable. */
#books-app:not([data-editable]) .tag-cell,
#books-app:not([data-editable]) .owned-cell,
#books-app:not([data-editable]) .stars { cursor: default; }
#books-app:not([data-editable]) .rating-cell:hover .stars.empty .star { color: transparent; }

.add-book-toggle-btn {
    font: inherit;
    font-size: 0.85rem;
    padding: 0.35rem 0.7rem;
    border: 2px solid var(--color-black);
    background: none;
    color: var(--color-black);
    cursor: pointer;
}
.add-book-toggle-btn:hover { background: var(--color-black); color: var(--color-white); }
.add-book-form {
    margin: 1rem 0;
    padding: 0.85rem;
    border: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: flex-start;
    max-width: 44rem;
}
.add-book-form input[type="text"],
.add-book-form select {
    font: inherit;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--color-black);
    background: var(--color-white);
    color: inherit;
}
#add-book-query { flex: 1 1 100%; }
.add-book-preview { display: flex; gap: 0.85rem; flex: 1 1 100%; }
.add-book-preview img { width: 60px; height: auto; flex-shrink: 0; }
.add-book-fields { display: flex; flex-direction: column; gap: 0.4rem; flex: 1; }
.add-book-fields label { font-size: 0.85rem; color: var(--muted); display: flex; align-items: center; gap: 0.35rem; }
.add-book-actions { display: flex; gap: 0.5rem; margin-top: 0.2rem; }
.add-book-actions button {
    font: inherit;
    font-size: 0.85rem;
    padding: 0.35rem 0.7rem;
    border: 2px solid var(--color-black);
    cursor: pointer;
    background: none;
    color: var(--color-black);
}
.add-book-actions button:hover { background: var(--color-black); color: var(--color-white); }
.add-book-message { font-size: 0.85rem; color: var(--muted); flex: 1 1 100%; }
.add-book-message.error { color: var(--color-error); }

/* ---------------------------------------------------------------------------
   Touch devices

   Two affordances here are hover-only, which on a touchscreen means either
   dead or — worse — stuck open after a tap. The cover preview is dropped
   outright; `+ cover` becomes permanently visible for editors instead, since
   there's no other way to reach it.
   --------------------------------------------------------------------------- */
@media (hover: none) {
    .title-cell:hover .cover-preview { display: none; }
    tbody tr:hover { background: none; }
    #books-app[data-editable] .add-cover-btn { display: inline; }
    /* Placeholder stars on unrated books are hit-testable but transparent until
       hovered, which on touch means an invisible control occupying real space.
       Kept at --border rather than the hover reveal's --muted: they're on
       permanently here, and five grey stars would read as an actual rating. */
    #books-app[data-editable] .stars.empty .star { color: var(--border); }
    /* Hover-to-reveal can never fire here, so the status control stays out. */
    #books-app[data-tab="read"] .status-select { visibility: visible; }
}

/* ---------------------------------------------------------------------------
   Narrow screens

   The seven columns need roughly 850px before the title and author cells start
   wrapping into unreadable slivers, so below 56rem each row is re-laid-out as a
   compact entry: title, author, then a meta line of rating / owned / year /
   date, with tags clipped to one line underneath. Cells that hold nothing
   collapse (`td:empty`) rather than reserving space — most of the library has
   no rating or read date, so this is what keeps the list tight.

   The markup is untouched; only display roles change. Sorting, filtering and
   inline editing all keep working on the same elements.
   --------------------------------------------------------------------------- */
@media screen and (max-width: 56rem) {
    .books-head { margin-bottom: 1rem; }

    /* Three or four tabs don't fit at 320px — scroll them rather than wrap. */
    .tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
    }
    .tabs::-webkit-scrollbar { display: none; }
    .tabs button {
        padding: 0.5rem 0.6rem;
        white-space: nowrap;
    }

    .toolbar { gap: 0.5rem; margin-bottom: 0.75rem; }
    .toolbar input[type="search"] { flex: 1 1 100%; min-width: 0; }
    .toolbar select { flex: 1 1 8rem; min-width: 0; }

    .sort-bar { display: flex; }

    table { display: block; }
    thead { display: none; }
    tbody { display: block; }

    tbody tr {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        column-gap: 0.55rem;
        row-gap: 0.05rem;
        padding: 0.45rem 0;
        border-bottom: 1px solid var(--border);
    }
    /* Must out-specify the rule above — it's what the filters toggle. */
    tbody tr.hidden { display: none; }
    tbody td {
        display: block;
        padding: 0;
        border: 0;
        /* The wide table's uniform-row floor would apply to every cell here,
           where they're stacked flex items rather than one row of columns —
           each meta value would get its own 36px band. */
        height: auto;
    }
    tbody td:empty { display: none; }

    .title-cell {
        flex: 1 1 100%;
        font-size: 0.95rem;
        line-height: 1.35;
    }
    .author-cell {
        flex: 1 1 100%;
        font-size: 0.8rem;
        color: var(--muted);
    }

    /* On the To Read pile the priority input leads the title instead of taking
       a line of its own, so the entry reads as a numbered list. Basis 0 rather
       than auto: with an auto basis a long title doesn't fit beside the input
       and wraps below it, stranding the input on a line of its own. At 0 the
       title always shares the line and wraps within its own column. */
    #books-app[data-tab="to-read"] .title-cell { flex: 1 1 0; min-width: 0; }
    .queue-input { font-size: 0.75rem; width: 2.5rem; border-color: var(--border); }
    .status-select { font-size: 0.75rem; border-color: var(--border); }
    /* Always shown in the stacked layout: an invisible-but-present control would
       just be a gap in the meta line, and a narrow window on a hover-capable
       device gets the same treatment as a phone. Has to match the specificity of
       the rule that hides it — a bare .status-select would lose to it. */
    #books-app[data-tab="read"] .status-select { visibility: visible; }

    /* Titles wrap here instead of truncating — there's no hover to recover the
       full string on a touchscreen, and the entry already gets its own lines. */
    .title-cell { white-space: normal; }
    .cell-text {
        display: inline;
        max-width: none;
        overflow: visible;
        white-space: normal;
    }
    .title-cell:hover .cell-text,
    .author-cell:hover .cell-text,
    tbody tr:hover .add-cover-btn {
        position: static;
        max-width: none;
        padding: 0;
        margin: 0;
        background: none;
        border: 0;
        box-shadow: none;
    }
    /* Nothing is displaced here — the entry wraps — so the input stays in flow
       and takes the width it can get. */
    .add-cover-input {
        position: static;
        transform: none;
        width: 100%;
    }

    /* Meta line. No separators between the cells: the parentheses and the
       "Read" prefix already mark where one value ends and the next begins.
       Rating is ordered last and pushed right, mirroring its column position
       in the wide table. */
    .rating-cell,
    .owned-cell,
    .year-cell,
    .date-cell {
        font-size: 0.78rem;
        color: var(--muted);
    }
    .owned-cell,
    .year-cell,
    .date-cell,
    .status-cell { order: 1; }
    .rating-cell { order: 2; margin-left: auto; }
    .tag-cell { order: 3; }
    .year-cell:not(:empty)::before { content: "("; }
    .year-cell:not(:empty)::after { content: ")"; }
    .date-cell:not(:empty)::before { content: "Read "; }

    .owned-cell { text-align: left; }
    /* Visitors get a word instead of the editor's toggle box, and only when the
       book is actually owned — an empty checkbox is meaningless if you can't
       click it. The ✕ glyph is the element's text, so it's sized away. */
    #books-app:not([data-editable]) tr:not([data-owned="1"]) .owned-cell { display: none; }
    #books-app:not([data-editable]) .owned-toggle {
        /* inline, not inline-flex: a zero-font-size flex box resolves a baseline
           that sits below the rest of the meta line. */
        display: inline;
        width: auto;
        height: auto;
        border: 0;
        font-size: 0;
    }
    #books-app:not([data-editable]) .owned-toggle::after {
        content: "Owned";
        font-size: 0.78rem;
    }

    .tag-cell { flex: 1 1 100%; }
    .tags-wrap { max-width: 100%; }
    .tags-wrap:empty { display: none; }
    /* Without pills there'd be nothing left to tap to start editing. */
    #books-app[data-editable] .tag-cell:has(.tags-wrap:empty)::after {
        content: "+ tags";
        font-size: 0.7rem;
        color: var(--muted);
    }
    .tag {
        font-size: 0.7rem;
        margin: 0 0.2rem 0 0;
    }

    .add-book-form { padding: 0.7rem; }
    .add-book-form input[type="text"],
    .add-book-form select {
        min-width: 0;
        max-width: 100%;
    }
    .add-book-preview img { width: 48px; }
}
