/* ==========================================================================
   Components — the parts you assemble screens from.

   If a screen needs something that is not here, say so in your pull request
   rather than writing a one-off style in the page. A component added here
   is available to everyone; a style buried in one page is not.
   ========================================================================== */

/* --- panel --------------------------------------------------------------
   The default container. Flat, bordered, no shadow: institutional, not a
   floating card. Never add a coloured stripe to one edge. */
.panel {
  background: var(--surface);
  border: var(--border);
  border-radius: var(--radius);
  padding: var(--space-5);
  margin-bottom: var(--space-4);
}
.panel__title { margin-top: 0; }
.panel--tight { padding: var(--space-4); }

/* --- buttons ------------------------------------------------------------ */
.btn {
  display: inline-block;
  font: inherit; font-size: var(--text-sm); font-weight: 600;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}
.btn--primary { background: var(--blue); color: var(--on-dark); }
.btn--primary:hover { background: var(--blue-dark); color: var(--on-dark); }
.btn--secondary { background: var(--surface); color: var(--indigo); border-color: var(--rule-strong); }
.btn--secondary:hover { background: var(--wash); }
.btn--danger { background: var(--bad); color: var(--on-dark); }

/* A disabled button must still say WHY. Pair it with .hint, never leave a
   dead control with no explanation. */
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--wash); color: var(--ink-muted);
  border-color: var(--rule); cursor: not-allowed;
}

/* --- forms -------------------------------------------------------------- */
.field { margin-bottom: var(--space-4); }
.field > label {
  display: block; font-weight: 600; font-size: var(--text-sm);
  margin-bottom: var(--space-2); color: var(--ink);
}
.input, .select, .textarea {
  width: 100%; font: inherit;
  padding: var(--space-3);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
}
.input:focus, .select:focus, .textarea:focus { border-color: var(--blue); }

/* Helper text. Link it to its input with aria-describedby so a screen reader
   reads it when the field takes focus. */
.hint { font-size: var(--text-sm); color: var(--ink-muted); margin: var(--space-2) 0 0; }

/* A validation failure. Must say what to do, not just that something is wrong. */
.field__error { font-size: var(--text-sm); color: var(--bad); font-weight: 600; margin: var(--space-2) 0 0; }

.formrow { display: flex; gap: var(--space-3); align-items: flex-end; flex-wrap: wrap; }
.formrow .field { flex: 1 1 220px; margin-bottom: 0; }

/* --- tables -------------------------------------------------------------
   For tabular data only, never for layout (SEN106: tables describe
   relationships). Always give a <caption> and scope on every <th>. */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.table caption {
  text-align: left; font-weight: 600; color: var(--ink-muted);
  padding-bottom: var(--space-3);
}
.table th, .table td {
  text-align: left; padding: var(--space-3);
  border-bottom: 1px solid var(--rule); vertical-align: top;
}
.table thead th {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink-muted); border-bottom: 2px solid var(--rule-strong);
  white-space: nowrap;
}
.table tbody tr:nth-child(even) { background: var(--wash); }
.table td.num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.accession { font-family: var(--font-mono); font-size: var(--text-xs); white-space: nowrap; }

/* --- status -------------------------------------------------------------
   Colour is never the only signal (WCAG 1.4.1). Every status carries words,
   which is why there is no icon-only variant here. */
.status { display: inline-block; font-size: var(--text-xs); font-weight: 700;
  padding: 2px var(--space-2); border-radius: var(--radius); border: 1px solid; }
.status--ok   { color: var(--ok);   background: var(--ok-wash);   border-color: var(--ok-line); }
.status--warn { color: var(--warn); background: var(--warn-wash); border-color: var(--warn-line); }
.status--bad  { color: var(--bad);  background: var(--bad-wash);  border-color: var(--bad-line); }
.status--none { color: var(--ink-muted); background: var(--wash); border-color: var(--rule); }

/* A block-level message: an availability line, a refusal, a confirmation. */
.notice { border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--space-4); background: var(--wash); margin-bottom: var(--space-4); }
.notice__head { font-weight: 700; margin: 0 0 var(--space-1); }
.notice__body { margin: 0; font-size: var(--text-sm); color: var(--ink-muted); }
.notice--ok   { background: var(--ok-wash);   border-color: var(--ok-line); }
.notice--ok   .notice__head { color: var(--ok); }
.notice--warn { background: var(--warn-wash); border-color: var(--warn-line); }
.notice--warn .notice__head { color: var(--warn); }
.notice--bad  { background: var(--bad-wash);  border-color: var(--bad-line); }
.notice--bad  .notice__head { color: var(--bad); }

/* --- result list -------------------------------------------------------- */
.results { list-style: none; margin: 0; padding: 0; }
.results > li { border: var(--border); border-radius: var(--radius);
  background: var(--surface); padding: var(--space-5); margin-bottom: var(--space-3); }
.result__title { font-family: var(--font-head); font-size: var(--text-xl);
  margin: 0 0 var(--space-2); }
.result__title a { text-decoration: none; }
.result__title a:hover { text-decoration: underline; }
.result__meta { font-size: var(--text-sm); color: var(--ink-muted);
  display: flex; flex-wrap: wrap; gap: var(--space-4); margin-bottom: var(--space-3); }
.result__actions { display: flex; gap: var(--space-4); margin-top: var(--space-3);
  font-size: var(--text-sm); flex-wrap: wrap; align-items: center; }

/* --- statistics --------------------------------------------------------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-3); margin-bottom: var(--space-5); }
.stat { background: var(--surface); border: var(--border); border-radius: var(--radius);
  padding: var(--space-4); }
.stat__value { font-family: var(--font-head); font-size: var(--text-3xl);
  font-weight: 700; color: var(--indigo); line-height: 1; }
.stat__label { font-size: var(--text-sm); color: var(--ink-muted); margin-top: var(--space-2); }

/* --- misc --------------------------------------------------------------- */
.dl { margin: 0; }
.dl dt { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink-muted); margin-top: var(--space-3); }
.dl dd { margin: 2px 0 0; }

.empty { text-align: center; padding: var(--space-7) var(--space-5); color: var(--ink-muted); }

/* Live region for messages announced after an action.

   Visually hidden on purpose. A sighted reader already sees the outcome --
   the results appear, the panel turns red -- so showing this text as well
   just prints the same sentence twice. A screen reader user gets no such
   visual cue, which is the whole reason the region exists. */
.liveregion {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* A field that should not stretch with its row: a submit button beside an
   input. Prevents the button growing to fill the flex line. */
.field--fixed { flex: 0 0 auto; }

/* --- book covers --------------------------------------------------------
   The call number sits behind the image and shows through when there is no
   cover, which for us is most of the time. Both states fill the same box, so
   a list never reflows as images arrive. */
.cover {
  position: relative;
  flex: none;
  width: 92px;
  aspect-ratio: 2 / 3;
  border-radius: 6px;
  overflow: hidden;
  background: var(--indigo);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-2);
}
.cover__call {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--gold);
  text-align: center;
  line-height: 1.35;
  word-break: break-word;
}
.cover img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.cover.is-loaded img { opacity: 1; }

.result__body { display: flex; gap: var(--space-5); align-items: flex-start; }
.result__text { flex: 1 1 auto; min-width: 0; }

.cover--detail { width: 180px; border-radius: 8px; }

@media (max-width: 560px) {
  .cover { width: 68px; }
  .cover--detail { width: 120px; }
  .result__body { gap: var(--space-4); }
}

/* --- pagination ---------------------------------------------------------
   A <nav> of real links, not buttons: each page of results has its own URL,
   so it can be shared, bookmarked and reached with the back button. */
.pager { margin-top: var(--space-5); }
.pager__list { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; }
.pager__link {
  display: block; min-width: 40px; text-align: center;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm); font-weight: 600;
  color: var(--indigo); text-decoration: none;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface);
}
.pager__link:hover { background: var(--wash); text-decoration: none; }
/* aria-current marks the page you are on for assistive technology; the fill
   is the visual half of the same statement. */
.pager__link[aria-current="page"] {
  background: var(--indigo); color: var(--on-dark); border-color: var(--indigo);
}
.pager__gap { padding: var(--space-2); color: var(--ink-muted); }
.pager__link[aria-disabled="true"] {
  color: var(--ink-muted); background: var(--wash); pointer-events: none;
}
.pager__count { font-size: var(--text-sm); color: var(--ink-muted); margin: var(--space-3) 0 0; }

/* --- loader -------------------------------------------------------------
   A book opening. Shown while a search is in flight, so the wait has
   something to say for itself.

   The element carries role="status" and an accessible name, so a screen
   reader announces that something is loading rather than sitting in silence.
   The animation stops entirely under prefers-reduced-motion: for some
   readers looping movement is not decoration, it is a symptom trigger. */
.loader {
  --book-color: var(--indigo);
  --page-color: var(--gold);

  width: 42px;
  height: 28px;
  position: relative;
  perspective: 120px;
}

.loader::before,
.loader::after {
  content: "";
  position: absolute;
  top: 2px;
  width: 20px;
  height: 24px;
  background: var(--page-color);
  border: 2px solid var(--book-color);
  box-sizing: border-box;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
}

.loader::before {
  left: 1px;
  border-radius: 3px 1px 1px 3px;
  transform-origin: right center;
  animation: open-left 1s ease-in-out infinite alternate;
}

.loader::after {
  right: 1px;
  border-radius: 1px 3px 3px 1px;
  transform-origin: left center;
  animation: open-right 1s ease-in-out infinite alternate;
}

@keyframes open-left {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(38deg); }
}

@keyframes open-right {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(-38deg); }
}

@media (prefers-reduced-motion: reduce) {
  .loader::before,
  .loader::after { animation: none; }
}

/* The loader centred in the space the results will occupy, so the page does
   not jump when they arrive. */
.loading {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-4);
  padding: var(--space-7) var(--space-5);
  background: var(--surface); border: var(--border); border-radius: var(--radius);
}
.loading p { color: var(--ink-muted); font-size: var(--text-sm); margin: 0; }

/* --- error page ---------------------------------------------------------
   A dead end should still offer a way onward. The number is set large because
   it is the one piece of information a reader can act on when reporting the
   problem, not because it is decoration. */
.errorpage { text-align: center; padding: var(--space-7) var(--space-5); max-width: 46rem; margin: 0 auto; }
.errorpage__code {
  font-family: var(--font-head); font-size: 5rem; font-weight: 700;
  color: var(--indigo); line-height: 1; margin: 0 0 var(--space-3);
}
.errorpage h1 { font-size: var(--text-2xl); margin-bottom: var(--space-4); }
/* `.errorpage p` is one element more specific than `.errorpage__code`, so
   without this it would repaint the big number in the muted body colour.
   Naming the element here keeps the two at the same weight. */
.errorpage p.errorpage__code { color: var(--indigo); }
.errorpage p { color: var(--ink-muted); max-width: 46ch; margin-left: auto; margin-right: auto; }
.errorpage__search { margin: var(--space-6) auto var(--space-5); max-width: 30rem; display: flex; gap: var(--space-3); }
.errorpage__search .input { flex: 1 1 auto; }
.errorpage__links { display: flex; gap: var(--space-4); justify-content: center; flex-wrap: wrap; font-size: var(--text-sm); }

/* --- notification offer --------------------------------------------------
   A quiet strip, not a modal. It appears only after the reader has done
   something that makes the offer make sense, and it is dismissible for good.
   A site that begs for notification permission on arrival is the reason
   browsers now block sites that ask twice. */
.pushoffer {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-4);
  background: var(--indigo-wash);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  margin-bottom: var(--space-4);
}
.pushoffer__text { flex: 1 1 260px; min-width: 0; }
.pushoffer__text b { display: block; color: var(--indigo); font-size: var(--text-base); }
.pushoffer__text span { font-size: var(--text-sm); color: var(--ink-muted); }
.pushoffer__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
