/* Layout particular to athelney: the shelf, the copy form, the household list. */

.page-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}
.page-head h1 { margin: 0; }

.field { margin-bottom: 1rem; flex: 1; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; }
.row .field { min-width: 11rem; }
/* A replacement value is a few digits: at full width its box would be a line
   of empty space. As wide as a sum of money, and no wider. */
.field-value { max-width: 18rem; }
.actions { display: flex; gap: 0.6rem; align-items: center; }

.search { margin-bottom: 1.75rem; }
.search-row { display: flex; gap: 0.5rem; align-items: center; }
.search-row input { flex: 1; }
.search .clear { font-size: 0.85rem; color: var(--text-faint); }

/* Whose books to show. A fieldset because the three are one choice, with the
   legend hidden visually but present for a screen reader — the radios alone
   would otherwise be three unlabelled options. */
.scope { border: 0; padding: 0.6rem 0 0; margin: 0; display: flex; gap: 1.1rem; flex-wrap: wrap; }
.scope-option {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.87rem;
  color: var(--text-muted);
  cursor: pointer;
}
.scope-option input { accent-color: var(--accent); margin: 0; }
.scope-option:hover { color: var(--text); }

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

.book-list { list-style: none; padding: 0; margin: 0; }
.book-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
}
.book-list li:last-child { border-bottom: none; }
.book-link { display: block; color: var(--text); }
.book-link:hover { color: var(--accent); text-decoration: none; }
.book-link:hover .book-title { text-decoration: underline; }
.book-title { display: flex; align-items: baseline; gap: 0.45rem; font-size: 1.05rem; }

/* Mine vs somebody else's, before the title. Two hues AND two shapes: colour
   alone would not survive a greyscale print or a red-green eye. */
.owner-icon { flex: none; display: inline-flex; align-items: center; align-self: center; }
.owner-icon.is-mine { color: var(--accent); }
.owner-icon.is-theirs { color: var(--text-faint); }
/* Between the two: a church copy is not yours, but it did not come from a
   stranger either, so it sits nearer the text than a direct share does. */
.owner-icon.is-church { color: var(--text-muted); }
h1 .owner-icon { margin-right: 0.5rem; }
h1 { display: flex; align-items: center; gap: 0.1rem; }
.book-meta { display: block; color: var(--text-muted); font-size: 0.87rem; }

.notes { white-space: pre-wrap; margin-top: 1.25rem; }

.notice {
  color: var(--text-muted);
  font-size: 0.9rem;
  border-left: 3px solid var(--border);
  padding: 0.35rem 0 0.35rem 0.75rem;
  margin: 0.75rem 0 1.25rem;
}
.book-meta.published { color: var(--accent); }

.check { margin-top: 0.5rem; }
.check-label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1rem;
  color: var(--text);
  cursor: pointer;
  margin: 0;
}
.check-label input { accent-color: var(--accent); margin: 0; }
.section { margin-top: 2.25rem; border-top: 1px solid var(--border); padding-top: 1.25rem; }
.danger { margin-top: 2.5rem; }

/* Who a book reaches, and the picker that changes it. */
.share-list { list-style: none; padding: 0; margin: 0 0 1rem; }
.share-list li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--border);
}
.share-list li:last-child { border-bottom: none; }
.share-name { flex: 1; }
.share-list form { margin: 0; }

/* What a share shows, above the picker, as on Add a book's Share with. */
.share-grants { margin: 0 0 0.6rem; }
.share-form { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.share-form select {
  flex: 1;
  min-width: 12rem;
  padding: 0.45rem 0.6rem;
  font: inherit;
  font-size: 1rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.share-form select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

/* --- names of unbounded length -------------------------------------------

   A library's name and a book's title are typed by somebody else and can be
   any length, including one unbroken word. Everywhere one lands, it needs two
   things: permission to shrink inside its flex parent (`min-width: 0`, since
   the default `min-width: auto` is a flex item refusing to go below its
   longest word) and permission to break that word rather than overflow.

   ONE rule, because this is one fact about one kind of content. Order I found
   it in four places at three different sizes of wrong: `.library-name` pushed
   the document to 695px, `.book-title` to 576px, and a book page's `h1` to
   1102px — each of them dragging the whole page sideways on a phone, which is
   the bug the payments table had by another road.

   `anywhere` and not `break-word`: only `anywhere` is counted when the browser
   works out a flex item's minimum width, and that minimum is the whole
   question here. It has a cost, and the block below pays it.

   SCOPED TO THE LIST, not to `.library-name` on its own. That class is also
   the account's name in the nav's account menu, and applying this there let
   the summary shrink below its content: the company's nav went from one line
   to "demo-" / "owner" with the chevron pushed under it. Caught in the browser
   one screenshot after the fix it belongs to. A rule about unbounded content
   has no business on a nav entry whose content is a single account name that
   the nav is sized for. */
.library-list .library-name,
.share-name,
.book-title,
main h1 {
  min-width: 0;
  overflow-wrap: anywhere;
}

.library-list { list-style: none; padding: 0; margin: 0; }
.library-list li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
  /* A flex item will not shrink below its content's minimum width unless it is
     told it may, and the default `min-width: auto` is exactly that refusal. A
     library whose name is one long unbroken word therefore pushed the row —
     and with it the whole document — off the right of a phone, the same bug
     the ledger had by another road. Found by stress-testing this list with a
     57-character name, not by reading it. */
  min-width: 0;
}
.library-list li:last-child { border-bottom: none; }
.library-name { font-weight: 600; }
.library-list .library-name { flex: 1; font-weight: 400; }
/* The admin's roster shows each library's address under its name: names may
   repeat since migration 017, and the address is what tells two apart. Its
   own line, muted, and breakable like the name above it. */
.library-email {
  display: block;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.badge {
  font-size: 0.75rem;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
}
.library-list form { margin: 0; }

/* A whole row is the label, so the library name is part of the hit target and
   not just decoration sitting beside a 13px box. */
.paid-option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
  margin: 0;
  cursor: pointer;
  /* Same refusal as `.library-list li` above, one level in: on a church's own
     page the name sits inside this label, so both have to be allowed to shrink
     or the seat rows push the page sideways. */
  min-width: 0;
}
.paid-option input { accent-color: var(--accent); margin: 0; }
/* On the accounts page the box rides at the end of the row beside the badges,
   rather than taking the row the way it does on a church's own page. */
.paid-option-trailing { flex: 0 0 auto; font-size: 0.75rem; color: var(--text-muted); }
/* The fee box is its own form so it can save itself; without this it would
   claim a flex row of its own beside the Delete form. */
.fee-form { display: inline-flex; align-items: center; gap: 0.5rem; margin: 0; }
/* A reported standing, not a control: it sits where a button would and is
   deliberately quieter than one. */
.standing { flex: 0 0 auto; font-size: 0.8rem; }
.standing.is-paid { color: var(--accent); }
.standing.is-unpaid { color: var(--text-faint); }

@media (max-width: 34rem) {
  .row { flex-direction: column; gap: 0; }
  .page-head { flex-direction: column; align-items: flex-start; }
  /* Daniel: "on mobile (as per screen size threshold) provide no sign up
     button at all". Sign IN stays — the splash's job is still to open for
     somebody who already has a library.

     THIS HIDES SIGNING UP, IT DOES NOT PREVENT IT, and that is not a
     shortcoming of the rule but of what a rule can be. Athelney renders HTML
     with no JavaScript, so a viewport width never reaches the server and
     "screen size" can only ever be CSS. `/signup` stays reachable by address,
     a POST to it still works, and turning the phone sideways brings the link
     back. Daniel has read that and said hiding the link suffices for now.

     The threshold is this block's, not a second one of its own. Two
     nearly-equal numbers in one stylesheet are two numbers that drift. */
  .signup-link { display: none; }
  /* And the reverse: the Scriptorium's Back to the Armarium shows only here.
     Daniel: "only on mobile, when im on armarium forum or scriptorium, there
     also should be a back button which takes us back to pluteum". Pluteus is
     gone and a library lands on the Armarium now, so that is where it goes.
     On a wider screen the nav's Armarium is a click away. */
  .page-back.mobile-only { display: block; }
  /* Tap targets of about 44 px on a phone, measured in the no-breadcrumb order:
     the buttons were 41 px, and a Back link is one line of small text. The
     Back grows its box and gives the height back with negative margins, so it
     stands where it stood. .btn-small (the banner's Pay) keeps its size. */
  .btn:not(.btn-small) { padding-top: 0.55rem; padding-bottom: 0.55rem; }
  .page-back .back-link {
    display: inline-flex; align-items: center;
    min-height: 2.75rem; margin: -0.8rem 0; padding-right: 0.75rem;
  }
  /* And the invitation in the login page's body, which was the same offer in a
     louder place — "No library yet? Sign up — a household shelf is $60 a year,
     a church's is $500." The whole sentence, not the link inside it: the
     remainder with its middle cut out reads worse than either half.

     The login form is untouched. This is the invitation, not the door. */
  .signup-invite { display: none; }

  /* --- a list row stacks rather than squeezing its name ------------------

     THE WRAPPING IS THE SYMPTOM; THE WIDTH IS THE BUG. On /settings at 390px
     "Christ Church Omaha" rendered one or two letters per line while the
     `church` badge and `— contribution not paid` beside it sat comfortably on
     one. A browser only breaks mid-word when the box is narrower than the
     word, so the question was never how it wrapped but why its box was 103px
     tall and 103px wide.

     Measured rather than guessed. In a 342px row: `.standing` is
     `flex: 0 0 auto` at 154px and CANNOT shrink, `.badge` is 60px and has
     nothing to give, two gaps take 24px — so the name was handed the 104px
     left over and told to fit nineteen characters in it.

     And the reason it obeyed instead of overflowing is the rule above:
     `overflow-wrap: anywhere` drops a flex item's minimum width to one
     character, so the name absorbed ALL of the squeeze while two siblings
     absorbed none. That rule arrived in this same order, to stop long names
     pushing the document sideways. It fixed that and caused this.

     So: below this breakpoint the row stacks. The name gets the full width on
     its own line — at which point it wraps at spaces like ordinary prose, and
     `anywhere` only ever fires for a word genuinely wider than the screen —
     and the short things sit underneath it.

     NOT `.row`, which is a form helper for two fields side by side and whose
     `gap: 0` in this same block is right for fields with their own margins and
     wrong for a list row. Same technique, different element; making these
     `.row` would mean either breaking forms or overriding it straight back. */
  .library-list li,
  .share-list li {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
  }
  /* Stop sharing is a list row's button, so .btn-small, and on a phone it is
     still a thing to tap: 44 px, measured in the share-while-adding order,
     where it was 30. The font stays small; only the box grows. A button
     centres its label by itself. */
  .share-list .btn-small { min-height: 2.75rem; }

  /* On a phone the camera control is the width of the phone: it is the thing
     tapped again and again with the other hand holding the book. */
  .photo-camera .btn-camera { flex: 1 1 100%; justify-content: center; }

  /* A shelf's mini-forms are compact where there is room and 44 px to tap on a
     phone, where forty of them are scrolled through with a thumb. */
  .shelf-book input[type=text], .shelf-book input[type=number],
  .shelf-location input { min-height: 2.75rem; }
  /* The × on a shelf photo's thumbnail keeps its 30 px circle and gets a
     44 px reach: the pseudo-element is part of the button, so a tap on it is
     a tap on ×. Only the shelf's, which is the one this order measured. */
  .shelf-photos .photo-drop::after { content: ""; position: absolute; inset: -0.45rem; }
  /* And its two other small things: the photo-library link, and the chips
     that point at the books a refused Save names. */
  .shelf-photos .photo-library { display: inline-flex; align-items: center; min-height: 2.75rem; }
  .shelf-problems .shelf-problem { min-height: 2.75rem; }

  /* The PDF viewer's dialog is the whole screen on a phone rather than a
     sheet on the page, and its page counter gives up its fixed width so the
     title keeps a little room. `[open]` and the extra class are specificity:
     this block comes before the viewer's own rules further down. */
  dialog.viewer[open] {
    width: 100vw; height: 100vh; height: 100dvh; margin: 0; border-radius: 0;
  }
  dialog.viewer .viewer-count { min-width: 0; }
  dialog.viewer .viewer-bar { gap: 0.35rem; }

  /* --- the nav: ☰ at the top left, a person at the far right --------------
     Daniel: "when we pass below threshold for mobile, we get this three
     stipes menu instead", and "name stays far right, maybe on mobile like a
     person in a circle". This block's threshold, not one of its own.

     The ☰ is shown, and the places hidden while closed, only where the
     browser has popovers. One that has not ignores `popover` and would keep a
     ☰ that opens nothing, so there the places simply stay in the row. */
  nav { align-items: center; padding-bottom: 0.5rem; }
  @supports selector(:popover-open) {
    nav .nav-toggle { display: inline-flex; }
    nav .nav-places:not(:popover-open) { display: none; }
  }
  nav .nav-toggle {
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    margin-left: -0.65rem;
    padding: 0;
    background: none;
    border: 0;
    border-radius: var(--radius);
    color: var(--text);
    cursor: pointer;
  }
  nav .nav-toggle:focus-visible,
  nav .nav-close:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
  /* Three stripes drawn from one bar and its two pseudo-elements, and
     aria-hidden: the same trick as the chevron, nothing to read out. */
  nav .stripes,
  nav .stripes::before,
  nav .stripes::after {
    display: block;
    width: 1.3rem;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
  }
  nav .stripes { position: relative; }
  nav .stripes::before,
  nav .stripes::after { content: ""; position: absolute; left: 0; }
  nav .stripes::before { top: -7px; }
  nav .stripes::after { top: 7px; }
  /* The drawer, as sketched: the whole height, from the left, over the page,
     one place a row with a rule between. It is in the top layer while open,
     so it cannot widen the page and scroll it sideways. */
  nav .nav-places:popover-open {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(82vw, 19rem);
    height: 100%;
    max-height: none;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    padding: 0.4rem 0 1rem;
    background: var(--surface);
    color: var(--text);
    border-right: 1px solid var(--border);
    box-shadow: 6px 0 24px rgba(0, 0, 0, 0.18);
    overflow-y: auto;
    font-size: 1.1rem;
  }
  nav .nav-places::backdrop { background: rgba(0, 0, 0, 0.35); }
  nav .nav-places:popover-open .nav-place {
    display: block;
    padding: 0.95rem 1.4rem;
    border-bottom: 1px solid var(--border);
    white-space: normal;
  }
  nav .nav-places:popover-open a.nav-place { color: var(--text); }
  nav .nav-places:popover-open .nav-place[aria-current="page"] { background: var(--body-bg); }
  /* The ✕ sits exactly where the ☰ was, which the drawer now covers, so the
     second tap of the order's "close on a second tap" lands on the same spot. */
  nav .nav-places:popover-open .nav-close {
    display: inline-flex;
    align-self: flex-start;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    margin: 1.1rem 0 0.4rem 0.85rem;
    padding: 0;
    background: none;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
  }
  nav .cross { position: relative; display: block; width: 1.15rem; height: 1.15rem; }
  nav .cross::before,
  nav .cross::after {
    content: "";
    position: absolute;
    left: 0;
    top: calc(50% - 1px);
    width: 100%;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
  }
  nav .cross::before { transform: rotate(45deg); }
  nav .cross::after { transform: rotate(-45deg); }
  /* The account: the same <details>. Its name is kept for a screen reader,
     so it is still the summary's accessible name, but not drawn; the person
     is drawn instead, and the chevron goes. */
  nav .account-menu .account-name {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  nav .account-menu .chevron { display: none; }
  nav .account-menu .account-icon { display: block; }
  nav .account-menu > summary { padding: 0.5rem; margin-right: -0.5rem; border-radius: 50%; }
}

/* A development instance says so, in a fine strip across the very top of
   every page (`views/development-strip`). Daniel: "a fine green strip with
   tiny white text". The light theme's accent in both themes, because the dark
   theme's is a pale green that white cannot be read on; white on #3f6b52 is
   about 6.4:1. Full-bleed: `100vw` and the margin pair take it out of the
   body's centred column to the viewport's edges, the negative top margin takes
   it up through the body's padding, and the bottom margin gives that padding
   back, so the nav sits where it always did. */
.development-strip {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-top: -1.5rem;
  margin-bottom: 1.5rem;
  padding: 0.1rem 0.75rem;
  background: #3f6b52;
  color: #fff;
  font-size: 0.7rem;
  line-height: 1.5;
  text-align: center;
}

/* The owner's "Add an account" on /libraries exists on a development instance
   only (`views/add-account-section`), and its badge says so in the strip's own
   colours, so the form reads as part of what the strip announces. */
.badge-dev {
  background: #3f6b52;
  border-color: #3f6b52;
  color: #fff;
  font-size: 0.7rem;
  font-weight: 400;
  margin-left: 0.4rem;
  vertical-align: middle;
  white-space: nowrap;
}

/* Picking what you are. A radio with a body rather than a label, because each
   choice has a name and a sentence and the whole block should be the hit
   target. */
.choices { border: 0; padding: 0; margin: 0 0 1.5rem; display: block; }
.choice {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin: 0 0 0.75rem;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  font-size: 1rem;
  color: var(--text);
}
.choice:hover { border-color: var(--accent); }
.choice input { accent-color: var(--accent); margin: 0.35rem 0 0; flex: none; }
.choice-body { display: block; }
.choice-label { display: block; font-weight: 600; }
.choice-blurb { display: block; font-size: 0.87rem; color: var(--text-muted); }


/* --- a wide thing scrolls, and the page does not --------------------------

   ONE class for every element too wide to fit a phone. Daniel, looking at the
   payments page on a narrow screen: "make that on mobile, the text stays where
   it is only the table is scrollable on the payments page" — his screenshot
   had the nav, the h1 and every paragraph slid off to the left, the heading
   reading "ents". A wide table with nothing around it drags the whole document
   sideways, and then the one thing on the page you cannot read is the prose
   telling you what you are looking at.

   `max-width: 100%` as well as `overflow-x`, because a block box does not
   shrink below its content without being told to — without it the wrapper is
   as wide as the table and scrolls nothing.

   THE SHADOWS ARE THE AFFORDANCE, and they cost four gradients and no script.
   Two solid ones painted in the page colour are `local`, so they travel with
   the content and cover the shadows when there is nothing more that way; two
   radial ones are `scroll`, so they stay pinned to the edges. The effect is a
   soft dark edge exactly where there is more to see and nowhere else. A region
   that scrolls with no sign that it can is a feature nobody finds — and on a
   touch screen there is no scrollbar to notice. */
.table-scroll {
  max-width: 100%;
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--body-bg) 40%, rgba(0, 0, 0, 0)) left center / 2.5rem 100% no-repeat local,
    linear-gradient(to left, var(--body-bg) 40%, rgba(0, 0, 0, 0)) right center / 2.5rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0)) left center / 0.65rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0)) right center / 0.65rem 100% no-repeat scroll;
}
/* It is focusable (see the handler's `tabindex`), so it needs to say so. */
.table-scroll:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* The front door.

   The splash is the one page centred in the screen, on both axes — Daniel:
   "on the splash page, the big athelny and the text below should be in the
   middle of the screen". Every signed-in page keeps the ordinary top-left
   layout; a front door is not a room.

   `min-height` and not `height`, and this is the whole of surviving a short
   viewport: the box is at least as tall as the screen under the nav, and grows
   past it when the content is taller, so the page scrolls normally instead of
   clipping or sliding under the nav. Checked at laptop height, not only in a
   tall window.

   The subtraction is the nav's own height plus the body's vertical padding —
   deliberately generous rather than exact, because being a little short of
   dead centre is invisible and being a little tall is a scrollbar on a page
   with two lines on it. */
.splash {
  min-height: calc(100vh - 9rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 1rem 0 3rem;
}

/* The splash heading is bigger than every other h1 in the app, and that is the
   whole of its styling: it is the one page where the name of the thing is the
   page rather than a label on it. */
.splash-title { font-size: 3rem; margin-bottom: 0.75rem; }

/* One sentence, and a measure narrow enough to read. It is centred with the
   block: the two five-line paragraphs that used to sit here were left-aligned
   on purpose, because centred ragged text is hard to read at that length — and
   they are gone, so the reason is gone with them. Two lines centre fine. */
.splash-line {
  max-width: 32rem;
  margin: 0;
  font-size: 1.15rem;
  line-height: 1.55;
  color: var(--text-muted);
}

/* Add books: the heading, and the two modes right-aligned in its row. The row
   never stacks (unlike .page-head below 34rem): the heading wraps, and the two
   icons stay on the right. Each is a 44 px tap target. */
.title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.title-row h1 { flex: 1 1 auto; min-width: 0; }
.mode-switch { display: flex; flex: none; gap: 0.3rem; margin-top: 0.2rem; }
.mode-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem;
  color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--radius);
}
.mode-link:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }
.mode-link[aria-current="page"] {
  color: var(--accent); border-color: var(--accent); background: var(--surface);
  box-shadow: inset 0 -3px 0 var(--accent);
}
/* The shelf mode: its photo box, then the list of books a reading found,
   one mini-form each inside one form. */
.shelf-photos .actions { align-items: center; }
.shelf-list > h2 { font-size: 1.1rem; margin: 1.5rem 0 0.4rem; }
.shelf-location { max-width: 32rem; }
.shelf-book { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); padding: 0.4rem 0.8rem 0.3rem; margin: 0.8rem 0 0; min-width: 0; }
.shelf-book legend { font-size: 0.8rem; color: var(--text-muted); padding: 0 0.3rem; }
/* Unsure: the model could only partly read the spine. Said in the legend, so
   it is the first thing read about the book, and in the danger colour at a
   whisper: it asks for a look, it is not an error. */
.shelf-unsure { color: var(--danger); font-style: italic; }
.shelf-book.has-error { border-color: var(--danger); }
.shelf-book > .error { margin: 0.2rem 0 0.5rem; font-size: 0.9rem; }
/* A book not kept is set aside, not hidden: its fields fade and its border
   breaks, so a list with a few unticked reads at a glance. Browsers without
   :has() show every book alike, which is only less telling. */
.shelf-book:has(.shelf-keep input:not(:checked)) { border-style: dashed; background: transparent; }
.shelf-book:has(.shelf-keep input:not(:checked)) > .field,
.shelf-book:has(.shelf-keep input:not(:checked)) > .row { opacity: 0.55; }
.shelf-book .field { margin-bottom: 0.4rem; }
.shelf-more-end { margin-top: 1rem; }
/* Save's refusal over the list: its sentence, then the books it points at as
   chips, each a link to its mini-form. */
.shelf-list > .error { margin: 0.5rem 0 1.25rem; }
.shelf-list > .error p { margin: 0; }
.shelf-problems { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem !important; }
.shelf-problem {
  display: inline-flex; align-items: center; min-height: 2.25rem; padding: 0 0.75rem;
  border: 1px solid var(--danger); border-radius: var(--radius); color: var(--danger);
}
.shelf-problem:hover { background: var(--danger); color: var(--body-bg); text-decoration: none; }
/* Author and year side by side even on a phone, where .row otherwise stacks:
   the list is meant to look compact, a list of small forms. */
.shelf-book .row { flex-direction: row; flex-wrap: nowrap; gap: 0.6rem; }
.shelf-book .row .field { min-width: 0; }
.shelf-book input { padding: 0.3rem 0.5rem; font-size: 0.95rem; }
.shelf-book label { margin-bottom: 0.1rem; }
.shelf-book .shelf-year { flex: 0 0 6rem; min-width: 5rem; }
/* The whole label is the tap target, 44 px tall, as a share row is. */
label.shelf-keep { display: inline-flex; align-items: center; gap: 0.6rem; min-height: 2.75rem; margin: 0; font-size: 0.95rem; color: var(--text); cursor: pointer; }
.shelf-keep input { flex: none; width: 1.15rem; height: 1.15rem; margin: 0; accent-color: var(--accent); }

/* From photos, above the fields on Add books. A box of its own, because it is
   a second form on the page and the Save below does not submit it. */
.from-photos, .share-box {
  margin: 0.5rem 0 1.75rem;
  padding: 0.9rem 1rem 0.2rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.from-photos h2, .share-box h2 { margin: 0 0 0.3rem; font-size: 1.1rem; }
/* One-book mode's box folds (a <details>): the whole summary row is the tap
   target, at least 44 px, with the account menu's drawn chevron turning when
   it opens. The box's own padding moves onto the summary so the row, not only
   the words, is what a thumb hits. Share with, lower on the same page, folds
   the same way and borrows every rule. */
details.from-photos, details.share-box { padding: 0 1rem 0.2rem; }
details.from-photos:not([open]), details.share-box:not([open]) { padding-bottom: 0; }
.from-photos-summary, .share-box-summary, .shelf-share-one-summary {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  min-height: 2.75rem; margin: 0 -1rem; padding: 0.35rem 1rem;
  cursor: pointer; list-style: none;
}
.from-photos-summary::-webkit-details-marker, .share-box-summary::-webkit-details-marker,
.shelf-share-one-summary::-webkit-details-marker { display: none; }
.from-photos-summary::marker, .share-box-summary::marker,
.shelf-share-one-summary::marker { content: ""; }
.from-photos-summary:focus-visible, .share-box-summary:focus-visible,
.shelf-share-one-summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius); }
.from-photos-title, .share-box-title { font-size: 1.1rem; font-weight: 600; }
.from-photos-summary .chevron, .share-box-summary .chevron, .shelf-share-one-summary .chevron {
  flex: none; width: 0.5em; height: 0.5em;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg); transition: transform 0.12s ease;
}
details.from-photos[open] > .from-photos-summary .chevron,
details.share-box[open] > .share-box-summary .chevron,
details.shelf-share-one[open] > .shelf-share-one-summary .chevron { transform: translateY(0.1em) rotate(-135deg); }
details.from-photos[open] > .from-photos-summary,
details.share-box[open] > .share-box-summary { margin-bottom: 0.2rem; }
.from-photos .hint { margin: 0 0 0.8rem; }
.from-photos .notice,
.from-photos .error { margin: 0 0 0.8rem; }

/* Share with, inside the book form below Notes, and its two drafts in the
   shelf mode's preview. The box and its summary are From photos' (above).
   How many are ticked is a quieter part of the summary. */
.share-box { margin: 0.25rem 0 1.25rem; }
.share-count { font-weight: 400; color: var(--text-muted); }
.share-box .hint { margin: 0 0 0.5rem; }
/* The two groups, Churches and Home Libraries: a fieldset each, its legend
   the group's name. */
.share-group { border: 0; margin: 0 0 0.6rem; padding: 0; min-width: 0; }
.share-group legend { font-size: 0.85rem; color: var(--text-muted); padding: 0; margin-bottom: 0.1rem; }
/* One account per row on a phone, columns where there is room. Each row is
   the whole label, 44 px tall, so the name is as good a target as the box. */
.share-choices {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  column-gap: 1rem;
}
label.share-choice {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: 2.75rem; margin: 0;
  font-size: 0.95rem; color: var(--text); cursor: pointer;
}
.share-choice input { flex: none; width: 1.15rem; height: 1.15rem; margin: 0; accent-color: var(--accent); }
.share-choice span { min-width: 0; overflow-wrap: anywhere; }
/* The shelf's two: Share all of these with…, the same box, open, with the
   rule under it; and the per-book one folded small inside each mini-form. */
section.share-box.shelf-share-all { margin: 0.8rem 0 0; padding-bottom: 0.3rem; }
.share-rule { font-size: 0.87rem; color: var(--text-muted); margin: 0.2rem 0 0.6rem; }
.shelf-share-one { margin: 0 0 0.3rem; }
.shelf-share-one-summary { margin: 0; padding: 0.35rem 0; font-size: 0.85rem; color: var(--text-muted); justify-content: flex-start; }
.shelf-share-one .share-choices { grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr)); }
.shelf-share-one label.share-choice { font-size: 0.85rem; }
.photo-form { margin: 0; }
.photo-form .photo-pick { margin-bottom: 0.8rem; }
/* Once the script has taken over, the plain input is still the one that is
   submitted — only out of sight, not `display: none`, so the label that opens
   the phone's photo library can still reach it. */
.photo-form.enhanced .photo-pick {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* `hidden` has to be restated wherever `display` is set, or it loses. */
.photo-camera[hidden], .photo-thumbs[hidden] { display: none; }
.photo-camera {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-bottom: 0.8rem;
}
.btn-camera {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
  padding: 0.75rem 1.4rem;
  font-size: 1.1rem;
  color: var(--body-bg);
}
.photo-camera.full .btn-camera { opacity: 0.45; cursor: default; }
.photo-library {
  margin: 0;
  font-size: 0.9rem;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.photo-thumbs {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin: 0 0 0.9rem;
  padding: 0.45rem 0 0;
}
.photo-thumbs li { position: relative; }
.photo-thumbs img {
  display: block;
  width: 4.75rem;
  height: 4.75rem;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--body-bg);
}
/* Big enough for a thumb, and over the corner so it is plainly about the
   photo it sits on. */
.photo-drop {
  position: absolute;
  top: -0.55rem;
  right: -0.55rem;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.photo-drop:hover { color: var(--danger); border-color: var(--danger); }
.photo-form .actions { flex-wrap: wrap; margin-bottom: 0.8rem; }
.photo-status { font-size: 0.85rem; color: var(--text-muted); }
.btn:disabled { opacity: 0.6; cursor: progress; }

/* PDFs on the shelf, and asking them. */
/* `flex: none`, or a long title squeezes the badge into a column of letters. */
.badge-pdf { flex: none; white-space: nowrap; align-self: center; }
.book-title .badge-pdf { font-size: 0.68rem; letter-spacing: 0.04em; }
h1 .badge-pdf { margin-left: 0.6rem; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }
.page-head .actions { flex-wrap: wrap; }
.ask-link { margin: -0.4rem 0 1.25rem; font-size: 0.95rem; }
.lead { color: var(--text-muted); margin: 0 0 1.25rem; }
.notice.warn { border-left-color: var(--danger); }
.pdf-file { word-break: break-word; }
.busy-note { font-size: 0.85rem; color: var(--text-muted); margin: 0.6rem 0 0; }
.busy-note[hidden] { display: none; }
.ask-form textarea { min-height: 4.5rem; }
.answer { margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1.1rem; }
.answer h2 { margin: 0 0 0.6rem; font-size: 1.15rem; }
.answer h3 { margin: 1.5rem 0 0.4rem; font-size: 0.95rem; color: var(--text-muted); }
.answer .asked { color: var(--text-muted); font-size: 0.9rem; }
.answer p { line-height: 1.55; }
.cite { font-size: 0.8rem; white-space: nowrap; margin-left: 0.15rem; }
.sources { margin: 0; padding-left: 1.4rem; }
.sources li { margin-bottom: 0.35rem; }
.source-pages { color: var(--text-muted); font-size: 0.87rem; }
/* A page number that opens the viewer has to look like a link, small as it is. */
.source-pages a { text-decoration: underline; text-underline-offset: 0.15em; }

/* The PDF viewer. The dialog is the whole screen on a phone, a tall sheet on
   a desktop, and dark around the page either way, so the page reads as paper.
   The plain page without JavaScript is `.viewer-sheet` in the column with
   `.pager` above and below it. */
.page-head .actions .btn + .btn { margin-left: 0.2rem; }
.no-file { color: var(--text-muted); }
/* A <nav>, so it must shed the rule and the spacing the top nav has. */
nav.pager { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin: 0.6rem 0; padding: 0; border: 0; font-size: 0.95rem; }
/* Which PDF this is, and the link back to its page. It sat under a trail
   that named it too, until the breadcrumb went, so it stays modest. */
h1.viewer-heading { font-size: 1.25rem; line-height: 1.3; margin: 0; }
.pager-end { color: var(--text-faint); }
.pager-count { color: var(--text-muted); white-space: nowrap; }
p.viewer-sheet { margin: 0; }
p.viewer-sheet img { display: block; width: 100%; height: auto; background: #fff; border: 1px solid var(--border); }

html.viewer-open { overflow: hidden; }
dialog.viewer {
  padding: 0; border: 0;
  width: min(92vw, 62rem); max-width: none; height: 94vh; max-height: none; margin: auto;
  border-radius: 6px;
  background: #1c1d1f; color: #ecebe6;
}
dialog.viewer[open] { display: flex; flex-direction: column; }
dialog.viewer::backdrop { background: rgba(0, 0, 0, 0.7); }
.viewer-bar { display: flex; align-items: center; gap: 0.45rem; padding: 0.45rem 0.6rem; font-size: 0.9rem; }
.viewer-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viewer-count { white-space: nowrap; min-width: 6.5rem; text-align: center; }
.viewer-bar button {
  min-width: 2.6rem; min-height: 2.6rem; padding: 0 0.5rem;
  background: transparent; color: inherit; font: inherit; font-size: 1.15rem; line-height: 1;
  border: 1px solid rgba(236, 235, 230, 0.3); border-radius: var(--radius); cursor: pointer;
}
.viewer-bar button:hover:not(:disabled) { border-color: rgba(236, 235, 230, 0.7); }
.viewer-bar button:disabled { opacity: 0.3; cursor: default; }
.viewer-bar button:focus-visible { outline: 2px solid #8fc0a3; outline-offset: 1px; }
/* margin: auto centres the page while it fits, and falls to 0 once it is
   bigger than the sheet, so every part of an enlarged page can be scrolled to;
   centring with justify-content would push its left edge out of reach. */
dialog.viewer .viewer-sheet { position: relative; flex: 1; min-height: 0; overflow: auto; display: flex; padding: 0 0.4rem 0.4rem; }
dialog.viewer .viewer-sheet img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; margin: auto; background: #fff; cursor: zoom-in; transition: opacity 0.15s; }
dialog.viewer.zoomed .viewer-sheet img { max-width: none; max-height: none; cursor: zoom-out; }
dialog.viewer.loading .viewer-sheet img { opacity: 0.35; }
.viewer-status { position: absolute; left: 0; right: 0; top: 40%; margin: 0; text-align: center; font-size: 0.9rem; pointer-events: none; }
.viewer-status:empty { display: none; }

/* --- the call log: /dashboard and /calls -------------------------------------

   The company's two pages over the call log. Numbers and small tables, no
   charts (the order: "No charting library for now").

   THE COST COUNTER is four stat tiles in a row, two by two on a phone. Each is
   a label, the figure and a line under it. The figure is semibold in the
   ordinary ink, in proportional figures: `tabular-nums` is for columns, where
   digits have to line up, and a figure on its own looks loose with it. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
  margin: 1.25rem 0 0.5rem;
}
@media (max-width: 34rem) {
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.stat {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.7rem 0.85rem;
  min-width: 0;
}
.stat-label { font-size: 0.82rem; color: var(--text-muted); }
.stat-value { font-size: 1.45rem; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
.stat-note { font-size: 0.8rem; color: var(--text-faint); }

/* The tables. `.ledger` is the payments page's receipt-book table, which went
   with the payments (migration 019) and comes back here as it was: it fills
   the column when there is room and falls back to its minimum inside
   `.table-scroll` when there is not, so a phone scrolls the table and not the
   page. Hairline rules, muted headings, figures in tabular-nums so they line
   up down a column. */
.ledger { width: 100%; border-collapse: collapse; font-size: 0.87rem; }
.ledger th, .ledger td {
  text-align: left;
  padding: 0.45rem 0.6rem 0.45rem 0;
  border-bottom: 1px solid var(--border);
  vertical-align: baseline;
}
.ledger th { color: var(--text-muted); font-weight: 600; font-size: 0.8rem; }
.ledger td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ledger tr.total td { font-weight: 600; }
/* One read's rows under one heading row, so its rounds and retries read as
   one story. The heading is the read; the rows are its attempts. */
.ledger tbody.trace tr.trace-head th {
  padding-top: 1rem;
  color: var(--text);
  font-weight: 600;
  font-size: 0.82rem;
  border-bottom-color: var(--text-faint);
}
.ledger .trace-id, .ledger .error-type { color: var(--text-faint); font-weight: 400; }

.calls-filter { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem; margin: 1rem 0; }
.filter-field { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.82rem; color: var(--text-muted); }
.calls-pager { display: flex; justify-content: space-between; margin: 1rem 0; }

/* /isbns: what Open Library answered, the imprint after the title and authors. */
.isbn-imprint { color: var(--text-muted); }

/* --- where a filled field came from, after a read with the ISBN lookup -------

   Beside the label, small and quiet: *from the photo*, *from Open Library*,
   *guess*. It is a fact about the value, not a heading, so it is the hint's
   size and ink and sits on the label's line. **Not inside the label**: there
   it joined the field's accessible name (*Titlefrom the photo*). The label
   and the mark are two items of one row, `.field-head`, the gap the visible
   space between them; the input names the mark in `aria-describedby`. */
.field-head { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 0.5rem; margin-bottom: 0.2rem; }
.field-head label { margin-bottom: 0; }
.field-source {
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--text-faint);
  white-space: nowrap;
}
.from-photos .ol-down { margin: 0 0 0.8rem; }
/* Try again: the same photos, one tap, under the reader's sentence. */
.try-again { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin: 0 0 1rem; }
.try-again .busy-note { flex-basis: 100%; margin: 0; }
/* /calls has twelve columns, more than the page's column holds at any width,
   so its table scrolls in its box anyway (`.table-scroll`); its cells stay on
   one line rather than breaking `claude-sonnet-5-5` over three. Found in the
   browser. The table is the one after the filter form. */
.calls-filter ~ .table-scroll .ledger td { white-space: nowrap; }

/* --- asking before a switch --------------------------------------------------

   The dialog `busy.js` opens before a feature flag is switched: the question,
   what changes for the whole instance, and the two buttons, Cancel last and
   focused. The app's own surface and ink, not the viewer's dark sheet: it is
   a question on this page, not a room of its own. */
dialog.confirm {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  padding: 1.25rem 1.4rem 1rem;
  width: min(92vw, 30rem);
}
dialog.confirm::backdrop { background: rgba(0, 0, 0, 0.45); }
.confirm-question { font-weight: 600; font-size: 1.05rem; margin: 0 0 0.5rem; }
.confirm-detail { color: var(--text-muted); font-size: 0.92rem; margin: 0 0 1rem; }
dialog.confirm .actions { margin: 0; }
.flag-confirm .actions { margin-top: 1rem; }
