/* Self-hosted; see fonts/README.md for licenses and how to update. */

/* Masthead. Schoolbell ships latin only. */
@font-face {
  font-family: "Schoolbell";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/schoolbell-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Book titles. */
@font-face {
  font-family: "Ojuju";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/ojuju-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Ojuju";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/ojuju-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
    U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Body and UI. Variable — one file covers the whole 400-700 axis. */
@font-face {
  font-family: "Strichpunkt Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/strichpunkt-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Strichpunkt Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/strichpunkt-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
    U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: light;
  --page-bg: #fbfbfa;
  --ink: #1a1a18;
  --ink-soft: #6b6b66;
  --field-bg: #ffffff;
  --field-border: #dededa;
  --measure: 50%;

  --font-display: "Schoolbell", ui-rounded, cursive;
  --font-title: "Ojuju", ui-sans-serif, system-ui, sans-serif;
  --font-body: "Strichpunkt Sans", ui-sans-serif, system-ui, sans-serif;
}

/* Theme resolution. The dark palette applies both when the OS asks for it
   (unless the toggle has forced light) and when the toggle forces dark. The
   two selectors can't share one block across the media-query boundary, so the
   values are repeated deliberately — keep them in sync. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --page-bg: #16161a;
    --ink: #ececea;
    --ink-soft: #93938d;
    --field-bg: #202027;
    --field-border: #34343d;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --page-bg: #16161a;
  --ink: #ececea;
  --ink-soft: #93938d;
  --field-bg: #202027;
  --field-border: #34343d;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 2.5rem 1rem calc(9rem + env(safe-area-inset-bottom));
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.5;
}

/* Half the viewport on desktop, full bleed on mobile. */
.library {
  width: var(--measure);
  margin-inline: auto;
}

@media (max-width: 48rem) {
  .library {
    width: 100%;
  }
}

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.masthead h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 7vw, 3rem);
  font-weight: 400;
  line-height: 1.1;
}

/* Theme toggle. Fixed to the top-right corner rather than placed in the
   masthead, so it never disturbs the h1 / last-updated baseline row the layout
   test pins. On desktop it floats in the empty margin beside the centred
   column; on mobile the masthead reserves room for it (below). */
.theme-toggle {
  position: fixed;
  z-index: 20;
  top: max(1rem, env(safe-area-inset-top));
  right: max(1rem, env(safe-area-inset-right));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 1px solid var(--field-border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--field-bg) 85%, transparent);
  color: var(--ink);
  cursor: pointer;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 0.25rem 0.75rem color-mix(in srgb, var(--ink) 12%, transparent);
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.theme-toggle:hover {
  border-color: color-mix(in srgb, var(--ink) 35%, var(--field-border));
}

.theme-toggle:active {
  transform: scale(0.92);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Which glyph shows is driven from the resolved theme in CSS, not JS, so it is
   correct on first paint and updates live when the OS theme changes while the
   toggle is still following it. Light shows the sun, dark shows the moon. */
.theme-toggle__icon--moon {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__icon--sun {
    display: none;
  }

  :root:not([data-theme="light"]) .theme-toggle__icon--moon {
    display: block;
  }
}

:root[data-theme="dark"] .theme-toggle__icon--sun {
  display: none;
}

:root[data-theme="dark"] .theme-toggle__icon--moon {
  display: block;
}

@media (prefers-reduced-motion: reduce) {
  .theme-toggle {
    transition: none;
  }
}

/* Controls */

.controls-dock {
  position: fixed;
  z-index: 10;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 5rem 1rem max(1rem, env(safe-area-inset-bottom));
  pointer-events: none;
}

.controls-dock::before,
.controls-dock::after {
  position: absolute;
  inset: 0;
  content: '';
  pointer-events: none;
}

/* backdrop-filter has no gradient value of its own, so a gradient mask fades
   a blurred layer in as the dock approaches the controls. */
.controls-dock::before {
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 58%);
  mask-image: linear-gradient(to bottom, transparent, black 58%);
}

.controls-dock::after {
  background: linear-gradient(
    to bottom,
    transparent,
    color-mix(in srgb, var(--page-bg) 78%, transparent) 3rem,
    var(--page-bg) 5rem
  );
}

.controls {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  width: var(--measure);
  margin-inline: auto;
  pointer-events: auto;
}

/* Desktop keeps the real search input focusable for type-to-search, but moves
   it offscreen so only the filtering controls appear in the dock. */
.controls__search {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

@media (max-width: 48rem) {
  body {
    padding-bottom: calc(12rem + env(safe-area-inset-bottom));
  }

  .masthead {
    display: block;
  }

  /* Full-bleed column here, so keep the title clear of the fixed toggle. */
  .masthead h1 {
    padding-right: 3rem;
  }

  .controls {
    width: 100%;
  }

  .controls__search {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    grid-column: 1 / -1;
  }
}

.controls input,
.controls select {
  width: 100%;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--field-border);
  border-radius: 0;
  background: var(--field-bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 0.9375rem;
  box-shadow: 0 0.25rem 0.75rem
    color-mix(in srgb, var(--ink) 12%, transparent);
}

.controls input:focus-visible,
.controls select:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The shelf: flush, square-edged blocks with no gaps between them. */

.shelf {
  display: flex;
  flex-direction: column;
}

/* --bg and --fg are set per entry by the renderer; --fg is a colour of its own
   hue, solved to sit just above the AA threshold against --bg.

   Do not fade card text with `opacity`. It composites toward the background
   and eats the contrast margin, and computed `color` still reports the
   undimmed value — so it fails silently. Use size and weight for hierarchy. */
.spine {
  padding: 1rem 1.125rem;
  background: var(--bg);
  color: var(--fg);
  /* Hues are hashed, so neighbours can land close enough to blend into one
     another with no gap between them. A hairline keeps the boundary legible
     without reintroducing spacing. */
  border-bottom: 1px solid rgba(0, 0, 0, 0.09);
  border-bottom-color: color-mix(in srgb, var(--fg) 18%, transparent);
}

.spine:last-child {
  border-bottom: none;
}

/* The alternate is an annotation, quieter even than the metadata line. */
.spine__alternate-title {
  margin: 0 0 0.0625rem;
  font-size: 0.625rem;
  font-weight: 400;
  line-height: 1.1;
}

.spine__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

/* Title and creator share this line; the creator is de-emphasised so the
   title still leads. */
.spine__title {
  margin: 0;
  font-family: var(--font-title);
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1.25;
}

/* Smaller and lighter than the note body, so it reads as a subtitle rather
   than as prose. Differentiated by size and weight rather than opacity — see
   the note on .spine above. */
.spine__creator {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 400;
  letter-spacing: 0.01em;
}

.spine__creator::before {
  content: '• ';
}

.spine__status {
  flex-shrink: 0;
  font-size: 0.8125rem;
  font-weight: 500;
}

.spine__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0.125rem 0 0;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.25;
}

.spine__note {
  margin: 0.75rem 0 0;
  font-size: 0.9375rem;
}

/* Status and error messages */

.empty,
.error {
  padding: 1.5rem 0;
  color: var(--ink-soft);
}

.last-updated {
  flex-shrink: 0;
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.8125rem;
  line-height: 1.25;
  text-align: right;
}

.last-updated__change {
  display: block;
  margin-top: 0.125rem;
  font-size: 0.6875rem;
}

@media (max-width: 48rem) {
  .last-updated {
    margin-top: 0.25rem;
    text-align: left;
  }
}

.last-updated[hidden] {
  display: none;
}
