@import "theme.css?v=7";
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%}
body{background:var(--bg);color:var(--fg);overflow:hidden}
button,input{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:none}
a{color:inherit;text-decoration:none}

/* ---------- header — no bar: controls float directly on the canvas ---------- */
header{
  position:absolute;top:0;left:0;right:0;z-index:30;display:flex;gap:10px;align-items:center;
  padding:12px 16px;background:none;border:none;pointer-events:none;
}
header > *{pointer-events:auto}   /* buttons clickable; the empty bar area is not */
.btn{
  height:38px;border:1px solid var(--line);border-radius:12px;
  background:color-mix(in srgb,var(--card) 78%,transparent);backdrop-filter:blur(8px);
  display:inline-flex;align-items:center;gap:7px;padding:0 12px;flex:0 0 auto;
  white-space:nowrap;font-size:14px;color:inherit;transition:border-color .15s,background .15s;
  box-shadow:var(--shadow);
}
.btn:hover{border-color:var(--muted)}
.btn.icon{padding:0 11px;justify-content:center}
.back .back-label{font-size:14px}
.reset,.theme{width:38px;padding:0;justify-content:center;color:var(--muted)}
.reset:hover,.theme:hover{color:var(--fg)}
.lens{color:var(--muted);gap:6px}
.lens:hover{color:var(--fg)}
.lens .lens-label{font-size:13px}
.taste{color:var(--muted);gap:6px}
.taste:hover{color:var(--fg)}
.taste .taste-label{font-size:13px}
.frontier{color:var(--muted);gap:6px}
.frontier:hover{color:var(--fg)}
.frontier .frontier-label{font-size:13px}
/* pressed/active state for the frontier toggle */
.frontier.on{color:var(--fg);border-color:var(--muted);
  background:color-mix(in srgb,var(--card) 92%,var(--fg) 8%)}

/* ---------- search pill — "find in the sky" ----------
   Same squircle as the buttons; flexes down but never below its icon. The
   count sits inside the pill so the answer ("12") is at the point of input. */
.search{position:relative;display:inline-flex;align-items:center;gap:7px;
  height:38px;flex:0 1 250px;min-width:0;padding:0 9px 0 11px;
  border:1px solid var(--line);border-radius:12px;
  background:color-mix(in srgb,var(--card) 78%,transparent);backdrop-filter:blur(8px);
  box-shadow:var(--shadow);color:var(--muted);transition:border-color .15s,color .15s}
.search:focus-within{border-color:var(--muted);color:var(--fg)}
.search .s-ic{flex:0 0 auto}
.search input{flex:1 1 auto;min-width:0;height:100%;border:none;background:none;
  font-size:14px;color:var(--fg);-webkit-appearance:none;appearance:none}
.search input:focus{outline:none}
.search input::placeholder{color:var(--muted);opacity:.85}
/* the UA's own search clear-× duplicates .s-clear */
.search input::-webkit-search-cancel-button{-webkit-appearance:none;display:none}
.s-count{flex:0 0 auto;font-size:11.5px;letter-spacing:.02em;color:var(--muted);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.s-count.none{color:var(--comet)}      /* zero matches: the map is empty on purpose */
.s-clear{flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  width:20px;height:20px;border-radius:50%;color:var(--muted)}
.s-clear:hover{color:var(--fg);background:var(--chip)}
.s-count[hidden],.s-clear[hidden]{display:none}

/* ---------- "Your taste" panel ---------- */
#tastePanel{position:fixed;inset:0;z-index:70;display:flex;align-items:center;justify-content:center;padding:24px}
#tastePanel[hidden]{display:none}
.tp-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.55)}
.tp-sheet{position:relative;z-index:1;width:min(760px,100%);max-height:calc(100vh - 48px);
  overflow-y:auto;background:var(--card);border:1px solid var(--line);border-radius:16px;
  box-shadow:var(--shadow);padding:34px 36px 40px}
.tp-close{position:absolute;top:14px;right:16px;width:32px;height:32px;border-radius:999px;
  font-size:22px;line-height:1;color:var(--fg);background:color-mix(in srgb,var(--card) 65%,transparent)}
.tp-close:hover{background:var(--chip)}
.tp-body h1{font-size:26px;letter-spacing:-.02em;margin-bottom:4px}
.tp-meta{font-size:12.5px;color:var(--muted);letter-spacing:.03em;margin-bottom:22px}
.tp-summary p{font-size:15px;line-height:1.66;margin-bottom:14px;color:var(--fg)}
.tp-summary .none{color:var(--muted);font-style:italic}
.tp-grid{display:grid;grid-template-columns:1fr 1fr;gap:26px;margin-top:26px;
  padding-top:22px;border-top:1px solid var(--line)}
@media(max-width:620px){.tp-grid{grid-template-columns:1fr}}
.tp-col h2{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:12px}
.tp-sub{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--faint,var(--muted));
  opacity:.7;margin:12px 0 6px}
.tp-chips{display:flex;flex-wrap:wrap;gap:6px}
.tchip{font-size:12px;padding:4px 10px;border-radius:999px;border:1px solid var(--line);
  color:var(--fg);white-space:nowrap}
.tchip.love{border-color:color-mix(in srgb,#5fbf7a 55%,var(--line));
  background:color-mix(in srgb,#5fbf7a 10%,transparent)}
.tchip.cool{border-color:color-mix(in srgb,#c06a6a 45%,var(--line));
  background:color-mix(in srgb,#c06a6a 8%,transparent)}

/* lens legend — value→color key for the active lens, above the magnitude legend */
#lensLegend{position:absolute;left:14px;bottom:52px;z-index:20;display:flex;align-items:center;
  flex-wrap:wrap;gap:9px;max-width:calc(100% - 28px);padding:6px 11px;border-radius:999px;
  background:color-mix(in srgb,var(--card) 62%,transparent);backdrop-filter:blur(8px);
  border:1px solid var(--line);font-size:11px;line-height:1;color:var(--muted);user-select:none}
#lensLegend[hidden]{display:none}
#lensLegend .lg{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
#lensLegend .dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
#lensLegend .sep{width:1px;height:12px;background:var(--line);flex:0 0 auto}
/* push the theme/reset controls to the right now that the title is gone */
.spacer{flex:1 1 auto;pointer-events:none}
header button:focus,header button:focus-visible,header a:focus,header a:focus-visible{outline:none}

/* theme toggle icon swap (same rule set as styles.css) */
.theme svg{display:none}
.theme .ic-auto{display:block}
:root[data-theme="light"] .theme .ic-auto{display:none}
:root[data-theme="light"] .theme .ic-light{display:block}
:root[data-theme="dark"] .theme .ic-auto{display:none}
:root[data-theme="dark"] .theme .ic-dark{display:block}

/* ---------- stage / canvas ---------- */
#stage{position:absolute;inset:0;background:var(--sky);overflow:hidden}
#sky{position:absolute;inset:0;width:100%;height:100%;display:block;
  touch-action:none;cursor:grab}
#sky.dragging{cursor:grabbing}

/* hover tooltip — floats near the cursor; titles WRAP (multi-line) */
#tooltip{position:absolute;z-index:20;pointer-events:none;width:max-content;max-width:240px;
  padding:8px 12px;border-radius:11px;
  background:color-mix(in srgb,var(--card) 92%,transparent);
  border:1px solid var(--line);backdrop-filter:blur(10px);
  box-shadow:var(--shadow);font-size:13px;line-height:1.32;
  transform:translate(-50%,calc(-100% - 14px));white-space:normal;overflow-wrap:anywhere}
#tooltip.below{transform:translate(-50%,14px)}  /* flipped under the cursor near the top edge */
#tooltip[hidden]{display:none}
#tooltip .tt-title{font-weight:650;letter-spacing:-.005em}
#tooltip .tt-author{color:var(--muted);font-size:12px;margin-top:2px}

/* legend — compact "magnitude key", bottom-left. Dots step down in size like the
   stars themselves so the row reads as a brightness scale at a glance. */
#legend{position:absolute;left:14px;bottom:12px;z-index:20;display:flex;align-items:center;
  /* wrap like #lensLegend: the chips are nowrap, so without this the third
     (frontier) chip is clipped by max-width instead of moving to a second line */
  flex-wrap:wrap;border-radius:14px;
  gap:8px 11px;max-width:calc(100% - 28px);padding:6px 11px;
  background:color-mix(in srgb,var(--card) 62%,transparent);backdrop-filter:blur(8px);
  border:1px solid var(--line);font-size:11px;line-height:1;color:var(--muted);
  user-select:none;letter-spacing:.01em}
#legend .lg{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
#legend .dot{display:inline-block;border-radius:50%;flex:0 0 auto}
/* dots track the canvas node tokens, so they read correctly in BOTH themes
   (bright white on dark / dense ink on the light chart). */
#legend .d5{width:11px;height:11px;background:var(--node5);
  box-shadow:0 0 7px 1px color-mix(in srgb,var(--glow) 75%,transparent)}
#legend .d4{width:8px;height:8px;background:var(--node4)}
#legend .d3{width:6px;height:6px;background:var(--node3)}
#legend .d12{width:4px;height:4px;background:var(--node12)}
#legend .comet{width:9px;height:9px;background:transparent;
  border:1.5px solid var(--comet);box-shadow:0 0 0 2px color-mix(in srgb,var(--comet) 22%,transparent)}
#legend .frontier{width:8px;height:8px;background:transparent;
  border:1.3px solid var(--muted);opacity:.85}
#legend .sep{width:1px;height:13px;background:var(--line);flex:0 0 auto}
/* [hidden] must win over the inline-flex/flex display above (explicit display
   otherwise beats the UA hidden rule, leaving a hidden legend chip visible) */
#legend [hidden]{display:none}

/* ---------- dev tuning panel (?dev or press 'd'; hidden by default) ---------- */
#devPanel{position:absolute;top:58px;right:12px;z-index:50;width:240px;max-height:calc(100vh - 80px);
  overflow-y:auto;padding:12px 14px;border-radius:14px;
  background:color-mix(in srgb,var(--card) 88%,transparent);backdrop-filter:blur(10px);
  border:1px solid var(--line);box-shadow:var(--shadow);font-size:12px;color:var(--fg)}
#devPanel[hidden]{display:none}
#devPanel h2{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}
#devPanel .dev-row{margin-bottom:9px}
#devPanel .dev-row label{display:flex;justify-content:space-between;margin-bottom:3px;
  color:var(--muted)}
#devPanel .dev-row label b{color:var(--fg);font-variant-numeric:tabular-nums;font-weight:600}
#devPanel input[type=range]{width:100%;accent-color:var(--star);height:16px}
#devPanel .dev-btns{display:flex;gap:6px;margin-top:10px}
#devPanel .dev-btns button{flex:1;height:30px;border-radius:8px;border:1px solid var(--line);
  background:var(--chip);color:var(--fg);font-size:11.5px}
#devPanel .dev-btns button:hover{border-color:var(--muted)}
#devPanel .dev-hint{margin-top:9px;font-size:10.5px;color:var(--muted);line-height:1.4}

/* loading / error overlays */
.stage-msg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:15;
  color:var(--muted);font-size:15px;text-align:center;padding:20px 26px;border-radius:14px;
  background:color-mix(in srgb,var(--card) 60%,transparent);backdrop-filter:blur(8px);
  max-width:min(440px,80vw);line-height:1.5}
.stage-msg[hidden]{display:none}
.stage-msg.error{color:var(--fg);border:1px solid var(--line)}
.stage-msg.error b{display:block;margin-bottom:6px;color:var(--star)}

/* ---------- modal (shares styles.css structure; atlas owns a self-contained copy
   so it works without loading styles.css) ---------- */
#modal[hidden]{display:none}
#modal{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;padding:24px}
#modal .backdrop{position:absolute;inset:0;background:rgba(0,0,0,.55)}
#modal .sheet{position:relative;z-index:1;width:min(760px,100%);
  max-height:calc(100vh - 48px);
  background:var(--card);border-radius:16px;box-shadow:var(--shadow);overflow:hidden;
  display:grid;grid-template-columns:266px 1fr;grid-template-rows:minmax(0,1fr);align-items:stretch}
#modal .m-nav{position:absolute;top:12px;right:12px;z-index:2;display:flex;gap:6px}
#modal .close{width:32px;height:32px;
  border-radius:999px;background:color-mix(in srgb,var(--card) 65%,transparent);
  backdrop-filter:blur(4px);
  font-size:21px;line-height:1;color:var(--fg);display:flex;align-items:center;justify-content:center}
#modal .close:hover{background:var(--chip)}
/* left column = cover + "nearby" list beneath it (its own scroll if long) */
.m-left{display:flex;flex-direction:column;min-height:0;overflow-y:auto;
  background:color-mix(in srgb,var(--fg) 4%,var(--card));border-right:1px solid var(--line)}
/* cover slot: a tinted placeholder sits behind the <img>; if the cover fails to
   load its onerror hides the <img>, revealing the placeholder underneath. */
.m-cover{position:relative;display:flex;align-items:flex-start;justify-content:center;
  padding:24px 22px;flex:0 0 auto}
.m-cover img{position:relative;z-index:1;max-width:100%;box-shadow:var(--shadow);display:block}
.m-cover .ph{position:absolute;top:28px;left:24px;right:24px;aspect-ratio:2/3;
  background:var(--chip);display:flex;align-items:center;justify-content:center;
  text-align:center;padding:14px;font-weight:600;line-height:1.3}
.m-body{display:flex;flex-direction:column;min-height:0;overflow:hidden}
.m-fixed{flex:0 0 auto;padding:30px 30px 0}
.m-head{margin-bottom:16px}
.m-body h1{font-size:23px;line-height:1.22;margin:6px 0 9px;letter-spacing:-.01em}
.m-facets{display:flex;flex-wrap:wrap;gap:6px}
.m-facets .fpill{font-size:12.5px;line-height:1;padding:5px 11px;border-radius:999px;
  background:transparent;color:var(--fg);border:1px solid var(--line);
  white-space:nowrap}
.m-facets .none{color:var(--muted);font-size:14px}
.m-series{margin-top:9px;font-size:12.5px;color:var(--muted);
  text-transform:uppercase;letter-spacing:.06em}
.m-series .si{color:var(--fg);opacity:.8}
.m-rating{font-size:17px;letter-spacing:2px;color:var(--star);line-height:1}
.m-rating .off{color:var(--line)}
.m-rating.list,.m-rating.frontier{font-size:12px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 11px;
  display:inline-flex;align-items:center;gap:8px}
.m-rating.frontier{border-style:dashed}
/* frontier card explainer (in place of a description we don't have for it) */
.fr-note{color:var(--muted);font-size:13.5px;line-height:1.55;margin:0}
.fr-note b{color:var(--fg);font-weight:600}
/* predicted taste-fit badge on reading-list cards */
.pred{font-size:10.5px;letter-spacing:.03em;padding:2px 8px;border-radius:999px;
  text-transform:none;font-weight:600}
.pred.hi{color:#7ad39a;background:color-mix(in srgb,#7ad39a 16%,transparent)}
.pred.mid{color:#c9b36a;background:color-mix(in srgb,#c9b36a 15%,transparent)}
.pred.lo{color:var(--muted);background:color-mix(in srgb,var(--muted) 14%,transparent)}
.pred.no{color:#c98a8a;background:color-mix(in srgb,#c98a8a 13%,transparent)}
/* cluster line — a button in the LEFT panel (below cover): closes the card and
   zooms to that cluster. Full-width chip so it reads as an actionable row. */
.m-cluster{display:flex;align-items:center;gap:5px;width:calc(100% - 32px);margin:0 16px;
  padding:9px 11px;border-radius:9px;font-size:10.5px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted);text-align:left;line-height:1.3;
  border:1px solid var(--line);transition:border-color .12s,color .12s}
.m-cluster:hover{color:var(--fg);border-color:var(--muted)}
.m-cluster b{color:var(--fg);font-weight:600;letter-spacing:0}
.m-cluster .arw{margin-left:auto;opacity:.6}

/* "Most similar" — one merged list in the LEFT column. Each item carries a
   marker: a filled cluster-hue dot (same cluster) or an arc glyph (bridge to
   another cluster). Clickable to travel to that star. */
.m-sim{flex:0 0 auto;padding:4px 14px 18px;border-top:1px solid var(--line);margin-top:4px}
.m-neigh-h{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  margin:12px 0 7px}
.m-sim-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:6px 7px;border-radius:8px;font-size:12.5px;color:var(--fg);line-height:1.3;
  transition:background .12s}
.m-sim-item:hover{background:color-mix(in srgb,var(--accent) 8%,transparent)}
.m-sim-item .mn-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* markers: filled dot (same cluster) vs arc glyph (cross-cluster bridge) */

/* card open: lighten the DOM backdrop so the canvas's own dim-and-relight of the
   selected star's cluster shows through behind the sheet (spatial context) */
#modal.open .backdrop{background:rgba(0,0,0,.28)}

/* atlas mode: a floating CARD occupying the right half (padded), vertically
   centered. The map's left half stays visible and the focused star is panned to
   center there. Click-outside (the transparent backdrop) closes it. */
/* docked: the whole overlay is click-through EXCEPT the sheet, so the map stays
   fully interactive (hover, wheel-zoom, tooltips) with a card open. Click-outside
   is handled by a document listener (see closeModal wiring), not the backdrop. */
#modal.docked{justify-content:flex-end;align-items:center;padding:64px 22px 22px 0;
  pointer-events:none}
#modal.docked .backdrop{background:transparent;pointer-events:none}
#modal.docked .sheet{pointer-events:auto;
  width:min(50vw,760px);max-width:calc(100vw - 44px);
  max-height:calc(100vh - 86px);height:auto;
  border-radius:16px;border:1px solid var(--line);
  box-shadow:var(--shadow);grid-template-columns:210px 1fr}
@media (max-width:720px){
  /* narrow screens: the card becomes the normal full sheet */
  #modal.docked{justify-content:center;padding:0}
  #modal.docked .sheet{width:100%;height:100dvh;max-height:100dvh;border-radius:0}
}
.m-desc{flex:1 1 auto;min-height:0;overflow-y:auto;
  padding:18px 30px 30px;font-size:14.5px;line-height:1.62;
  --fade-top:0px;--fade-bot:0px;
  -webkit-mask-image:linear-gradient(to bottom,
    transparent 0,#000 var(--fade-top),
    #000 calc(100% - var(--fade-bot)),transparent 100%);
  mask-image:linear-gradient(to bottom,
    transparent 0,#000 var(--fade-top),
    #000 calc(100% - var(--fade-bot)),transparent 100%)}
.m-desc p{margin-bottom:11px}
.m-desc p:last-child{margin-bottom:0}
.m-desc b{font-weight:650}
.m-desc i{font-style:italic}
.m-desc ul,.m-desc ol{margin:0 0 11px 1.2em}
.m-desc li{margin-bottom:4px}
.m-desc .none{font-style:italic;color:var(--muted)}
.m-desc .m-rec{margin-top:18px;padding:14px 16px;border-radius:8px;
  background:color-mix(in srgb,var(--accent) 5%,var(--card));
  border:1px solid var(--line)}
.m-desc .m-rec b{display:block;margin-bottom:8px;
  font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.m-desc .m-rec .tlink{line-height:1.9}
.mtitle-link{color:inherit;text-decoration:none}
.mtitle-link:hover{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}
.tlink{color:inherit;text-decoration:underline;text-decoration-color:var(--line);
  text-decoration-thickness:1px;text-underline-offset:2px;transition:text-decoration-color .12s}
.tlink:hover{text-decoration-color:currentColor}
/* a description title that also exists as a star on the map: marked with a ✦ so
   the reader knows hovering it lights that star. Click still goes to Amazon. */
.tlink-star{text-decoration-color:color-mix(in srgb,var(--hilite) 45%,transparent)}
.tlink-star::after{content:" ✦";font-size:.8em;color:var(--muted);opacity:.7}
.tlink-star:hover::after{color:var(--hilite);opacity:1}

/* ---------- mobile ----------
   The header floats over the canvas and cannot wrap (a second row would eat
   map), so narrow widths shed button labels in tiers instead. Every tier keeps
   all six controls on screen: overflow here is not a scrollbar, it puts the
   reset/theme buttons off the edge where nothing can reach them. */
/* ≤560: taste + frontier go icon-only and the search pill collapses to its
   magnifier (worst-case row ≈ 431px; the lens label is kept because it is the
   only readout of which lens is active — "Difficulty" is the widest at 101px
   and sets that budget). Tapping the magnifier expands search over the whole
   bar — the other controls hide, so the input always gets full width. */
@media (max-width:560px){
  .taste .taste-label,.frontier .frontier-label{display:none}
  .taste,.frontier{width:38px;padding:0;justify-content:center}
  .search{flex:0 0 38px;width:38px;padding:0;justify-content:center;cursor:pointer}
  .search input,.search .s-count,.search .s-clear{display:none}
  header.searching .search{flex:1 1 auto;width:auto;padding:0 9px 0 11px;
    justify-content:flex-start;cursor:auto}
  header.searching .search input{display:block}
  /* :not([hidden]) so the hidden attribute still wins over these show-rules */
  header.searching .search .s-count:not([hidden]){display:block}
  header.searching .search .s-clear:not([hidden]){display:flex}
  /* the spacer goes too, or it splits the bar with the field */
  header.searching > *:not(.search){display:none}
  /* collapsed but still filtering: the magnifier carries the pressed look of
     the frontier toggle, so an active search is legible with the field shut —
     that is what lets blur always collapse and give the other controls back. */
  .search.has-query{color:var(--fg);border-color:var(--muted);
    background:color-mix(in srgb,var(--card) 92%,var(--fg) 8%)}
}
/* ≤480: the lens name goes too, and Reset leaves the bar — six 38px icons
   (≈300px, fits 320). Reset is the one control with a gesture equivalent
   (pinch out), so it is the right thing to shed for the search field. */
@media (max-width:480px){
  header{gap:8px;padding:10px 12px}
  .lens .lens-label{display:none}
  .lens{width:38px;padding:0;justify-content:center}
  .reset{display:none}
}
@media (max-width:640px){
  .back .back-label{display:none}
  /* label gone -> square it up, so back matches the 38px reset/theme buttons */
  .back{width:38px;padding:0;justify-content:center}
  .atlas-title{font-size:13px;letter-spacing:.1em}
  #legend{font-size:11px;gap:5px 10px;padding:7px 10px}
  #modal{padding:0}
  #modal .sheet{display:flex;flex-direction:column;
    grid-template-columns:none;grid-template-rows:none;
    width:100%;height:100%;max-height:100dvh;border-radius:0;overflow-y:auto;
    -webkit-overflow-scrolling:touch}
  #modal .m-cover{display:none}
  #modal .m-nav{position:fixed;top:12px;right:12px}
  #modal .close{width:36px;height:36px;background:var(--chip);backdrop-filter:none}
  .m-body{flex:1 1 auto;display:block;overflow:visible;min-height:0}
  .m-fixed{padding:52px 20px 0}
  .m-body h1{font-size:20px}
  .m-desc{overflow:visible;min-height:0;padding:14px 20px 36px}
}
