/*
 * Solar system banner — sits at the top of the Hyde `.content` column,
 * above the page/post content. Settles onto a planet based on the current
 * page's `focus_planet` front matter (see assets/js/main.js).
 */

:root {
  /* Live-updated every frame by main.js to match the 3D scene's fog/void
     color, so any surface using them tracks the scene exactly. */
  --void-bg: #000;
  --void-fg: #f5f5f5;
  /* Hyde's sidebar is a fixed 18rem. Widened by ~10% of the viewport so
     the bio text (now living in the sidebar) has real room to breathe. */
  --sidebar-width: calc(18rem + 10vw);
}

/* True black, not Hyde's default dark gray (#202020) — matches the solar
   banner/void so the seam between sidebar and scene stays invisible. */
.sidebar {
  background-color: #000;
}

/* Hyde anchors the sidebar-about/nav/footer block to the bottom via
   `.sidebar-sticky { position: absolute; bottom: 1rem }`. Align the block
   itself to the top instead, but keep the Fused badge/copyright pinned to
   the bottom of the sidebar (margin-top: auto pushes it down within the
   flex column, past whatever height about/intro/nav end up needing). */
@media (min-width: 48em) {
  .sidebar-sticky {
    position: static;
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  .sidebar-footer {
    margin-top: auto;
  }
}

/* Match the sidebar's real (now wider) width — Hyde's own .content
   margin-left (20rem/22rem) assumed the original 18rem sidebar. */
@media (min-width: 48em) {
  .sidebar {
    width: var(--sidebar-width);
  }
  .content {
    margin-left: var(--sidebar-width);
  }
}

.sidebar-intro {
  margin: 1.5rem 0;
}

.sidebar-intro p {
  margin: 0;
}

/* Below Hyde's own 48em breakpoint the sidebar isn't fixed — it's a plain
   stacked block at the top of the page, so its full desktop content
   (bio paragraph, all nav items spelled out, the Fused badge/copyright)
   would otherwise consume the whole viewport before any real content
   shows. Condense to title + tagline + a tappable menu; intro and footer
   drop entirely rather than just being tucked out of sight. */
/* `.sidebar-nav` is a SIBLING of the <details> below, not nested inside
   it — modern browsers hide non-open <details> content via
   `content-visibility: hidden`, which a plain `display` override on a
   descendant can't undo (the same failure mode the listing's preview
   embeds hit earlier). Keeping nav a plain sibling means its visibility
   is just an ordinary `display` rule, reacting to the details' `[open]`
   state via the sibling combinator — no fighting the UA's own hiding. */
.sidebar-nav {
  display: none;
}

@media (max-width: 47.99em) {
  .sidebar-intro,
  .sidebar-footer {
    display: none;
  }

  .sidebar-menu-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 1.25rem 0;
    color: #fff;
    font-family: "PT Sans", Helvetica, Arial, sans-serif;
    font-size: 0.9rem;
    cursor: pointer;
    list-style: none;
  }

  .sidebar-menu-toggle::-webkit-details-marker {
    display: none;
  }

  .sidebar-menu[open] ~ .sidebar-nav {
    display: block;
    margin-top: 0.5rem;
  }
}

/* At/above 48em the sidebar goes back to being the always-visible fixed
   column — nav is just always shown, and the toggle chrome hidden. */
@media (min-width: 48em) {
  .sidebar-menu-toggle {
    display: none;
  }

  .sidebar-nav {
    display: block;
  }
}

/* Prevents a stray horizontal scrollbar from the 100vw-based breakout
   below (100vw can exceed the visible width by the scrollbar's own size
   in some browsers). Nothing on this site relies on horizontal scroll. */
html,
body {
  overflow-x: hidden;
}

/* Hyde's default 4rem top padding on .content left a gap above the
   banner — removed so it reaches the very top of the viewport too. */
.content {
  padding-top: 0;
}

.solar-banner {
  position: relative;
  width: 100%;
  border-radius: 5px;
  overflow: hidden;
  background: #000; /* matches the sidebar's own background exactly */
  /* Voyager's Pale Blue Dot frame reads as a near-16:9 field rather than
     a cinema-wide strip; retaining that field shape gives the procedural
     ray room to run through the frame. */
  aspect-ratio: 16 / 9;
  margin-bottom: 2.5rem;
}

/* The blog is a listing first: retain the live system as a small orientation
 * strip, rather than making its banner consume the whole initial viewport. */
body.blog-page .solar-banner {
  aspect-ratio: 4 / 1;
}

/* Below 48em main.js never loads (see the energy-saving guard in
   base.html), so the banner would otherwise just be an empty black box
   still reserving its aspect-ratio height — dead space with nothing in
   it. Collapse it outright rather than showing a blank rectangle. */
@media (max-width: 47.99em) {
  .solar-banner {
    display: none;
  }

  /* .content's own padding-top:0 above exists so the banner touches the
     very top of the viewport with no gap — with the banner gone here,
     that same zero padding leaves page content flush against the
     sidebar's bottom edge instead. Restore some breathing room. */
  .content {
    padding-top: 2.5rem;
  }
}

/* A wide screen gets more horizontal context, not a taller scene. Once the
 * sidebar has moved aside, cap the strip and deliberately reduce it again on
 * ultrawide displays so the post list remains the page's visual priority. */
@media (min-width: 48em) {
  body.blog-page .solar-banner {
    aspect-ratio: auto;
    height: clamp(11rem, 16vw, 17rem);
  }

  /* About/contact/individual posts are all prose pages, so their scene may
   * orient the reader but should never claim more than one third of the
   * available vertical space — nor keep growing with viewport width, which
   * is what the unqualified 16/9 aspect-ratio above does on wide screens. */
  body.standard-page .solar-banner,
  body.post-page .solar-banner {
    aspect-ratio: auto;
    height: min(32vh, 17rem);
  }
}

@media (min-width: 110rem) {
  body.blog-page .solar-banner {
    height: 14rem;
  }

  body.standard-page .solar-banner,
  body.post-page .solar-banner {
    height: min(32vh, 14rem);
  }
}

@media (min-width: 150rem) {
  body.blog-page .solar-banner {
    height: 11rem;
  }

  body.standard-page .solar-banner,
  body.post-page .solar-banner {
    height: min(32vh, 11rem);
  }
}

/* .content's left edge already sits exactly at the sidebar's edge
   (margin-left: var(--sidebar-width) above), but the banner still inherits
   1rem of left padding from Poole's `.container` class, and is capped by
   .content's own max-width/margin-right on the right — so by default it
   sits inset on both sides rather than spanning the full right panel.
   Break it out explicitly: cancel the left padding and size it directly
   from the viewport, ignoring .content's own width constraints entirely. */
@media (min-width: 48em) {
  .solar-banner {
    width: calc(100vw - var(--sidebar-width));
    margin-left: -1rem;
    /* Flush on three sides now (top, left against the sidebar, right
       against the viewport) — a rounded corner there just carves a
       visible curved gap into an otherwise straight seam. Square it off. */
    border-radius: 0;
  }
}

/*
 * Home page — the solar system is the hero. Blurb and scene sit inside one
 * unified panel whose background/text color track the scene's own fog
 * color live, so it reads as one continuous surface, not two boxes.
 */

/* This element carries BOTH Hyde's `.content` class (padding-top/bottom
   4rem, margin-right 2-4rem) AND Poole's `.container` class (padding-left/
   right 1rem, margin-left/right auto) — two separate sets of gutters,
   both meant for prose pages, both left visible white borders around the
   home hero. Stripped for the home page specifically so the panel actually
   reaches every edge of the viewport (except the fixed sidebar). */
body.home-page .content {
  max-width: none;
  padding: 0;
  margin-right: 0;
}

.home-hero {
  display: flex;
  height: 100vh;
  margin-bottom: 0;
  background: var(--void-bg);
}

.home-solar {
  position: relative;
  flex: 1 1 auto;
}

@media (max-width: 40em) {
  .home-hero {
    height: auto;
    min-height: 100vh;
  }

  .home-solar {
    aspect-ratio: 1.3 / 1;
  }
}

/* Below 48em the scene never loads (see base.html's energy-saving guard),
   so the hero would just be an empty void — collapse it and fall back to
   the description/blurb as real mobile content instead of nothing. The
   general mobile sidebar rule hides `.sidebar-intro` everywhere else
   (higher specificity here wins it back for the home page specifically). */
@media (max-width: 47.99em) {
  .home-hero {
    display: none;
  }

  body.home-page .sidebar-intro {
    display: block;
  }
}

#scene {
  display: block;
  width: 100%;
  height: 100%;
}

.epoch {
  position: absolute;
  left: 50%;
  bottom: 0.75rem;
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-family: Menlo, Monaco, "Courier New", monospace;
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.55);
  opacity: 1;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

/* main.js (the only thing that ever sets a real value here) doesn't load
   below 48em, so the pill would otherwise just sit frozen on its "—"
   placeholder forever. */
@media (max-width: 47.99em) {
  .epoch {
    display: none;
  }
}

.epoch.is-hidden {
  opacity: 0;
}

.epoch-label {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 0.55rem;
  color: rgba(255, 255, 255, 0.4);
}

.epoch-value {
  color: #fff;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 30em) {
  .solar-banner {
    aspect-ratio: 1.4 / 1;
    border-radius: 0;
    margin-left: -1rem;
    margin-right: -1rem;
    width: calc(100% + 2rem);
  }
}

/*
 * Content typography — serif reading type for prose and titles, quieter
 * small-caps section headings, and the blog's year-grouped listing. Purely
 * additive: sidebar, solar banner and the scene itself are untouched.
 */

:root {
  --content-accent: #a8631b;
}

.page-title,
.post-title,
.post-title a {
  font-family: "Newsreader", Georgia, serif;
  font-weight: 500;
}

.page-title {
  font-weight: 400;
}

/* The opening paragraph of a page reads as a lead-in, set apart from the
   body copy that follows it. */
.page > p:first-of-type {
  font-family: "Newsreader", Georgia, serif;
  font-style: italic;
  font-size: 1.4rem;
  line-height: 1.6;
}

/* :not(.post-title) matters here: blog-listing entries are also `h2`s
   inside a `.post` wrapper (see the year-posts loop in base.html), so an
   unqualified `.post h2` would wrongly catch post titles too, not just
   markdown subheadings written inside a post's own body. */
.page h2:not(.post-title),
.post h2:not(.post-title) {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 2.5rem;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--content-accent);
}

.page h2:not(.post-title)::after,
.post h2:not(.post-title)::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: currentColor;
  opacity: 0.25;
}

.page a,
.post a {
  color: var(--content-accent);
}

.page del,
.post del {
  color: #9a9a9a;
}

/* Admonition blocks (`!!! note "..."`) have no default styling at all —
   without this they'd render as plain, unset-apart paragraphs. */
.page .admonition,
.post .admonition {
  margin: 1.75rem 0;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--content-accent);
  background: #faf6ef;
}

.page .admonition-title,
.post .admonition-title {
  margin: 0 0 0.4rem;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--content-accent);
}

.page .admonition p,
.post .admonition p {
  margin-bottom: 0;
}

.page .admonition p + p,
.post .admonition p + p {
  margin-top: 1rem;
}

.page p,
.post p {
  font-family: "Newsreader", Georgia, serif;
  font-size: 1.1rem;
  line-height: 1.75;
}

/* Blog listing — grouped by year */
.year-group {
  display: grid;
  grid-template-columns: 4rem 1fr;
  gap: 0.5rem 1.75rem;
  padding: 2rem 0;
  border-top: 1px solid #eee;
}

.year-group:first-child {
  border-top: none;
  padding-top: 0;
}

.year-num {
  font-family: "Newsreader", Georgia, serif;
  font-weight: 500;
  font-size: 1.5rem;
  color: #ccc;
  line-height: 1;
}

.year-posts {
  display: flex;
  flex-direction: column;
  gap: 1.4rem;
}

.year-posts .post {
  margin-bottom: 0;
}

.year-posts .post-title {
  margin: 0;
  font-size: 1.15rem;
}

.year-posts .post-date {
  display: block;
  margin-top: 0.2rem;
  margin-bottom: 0;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9a9a9a;
}

.year-posts .post-excerpt {
  margin: 0.5rem 0 0;
  font-family: "Newsreader", Georgia, serif;
  font-style: italic;
  font-size: 0.95rem;
  line-height: 1.5;
  color: #7a7a7a;
  max-width: 40rem;
}

/* Listing preview thumbnails — an image/video post shows a small clickable
   thumbnail via native <details>/<summary>, no JS required; opening it
   reveals the real embed inline, spanning the full row beneath the text,
   without leaving the listing or loading every post's media up front.
   `.post-preview-embed` is a SIBLING of the <details>, not nested inside
   it — `display: contents` on <details> to let a nested embed escape into
   the grid is unreliable across browsers (the box doesn't always dissolve
   the way it does for a plain <div>), so the reveal is done instead with
   `:has()` reacting to the sibling's native `open` attribute. */
.post-has-preview {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  column-gap: 1rem;
}

.post-preview-expand {
  grid-column: 1;
  grid-row: 1;
}

.post-preview-thumb {
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  overflow: hidden;
  border-radius: 4px;
  cursor: pointer;
  list-style: none;
}

.post-preview-thumb::-webkit-details-marker {
  display: none;
}

.post-preview-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.15s ease;
}

.post-preview-thumb:hover img {
  opacity: 0.82;
}

.post-text {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
}

.post-preview-embed {
  display: none;
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: 1.25rem;
}

.post-has-preview:has(.post-preview-expand[open]) .post-preview-embed {
  display: block;
}

/* Link posts — a post whose front matter sets `link:` to an external URL.
   The blog-listing title goes straight to that URL; the post's own
   permalink moves onto the date instead, with the source domain shown
   alongside it as a hint. */
.post-link-hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5em;
  height: 1.5em;
  margin-left: 0.5em;
  border-radius: 50%;
  background: color-mix(in srgb, var(--content-accent) 14%, transparent);
  color: var(--content-accent);
  font-size: 0.62em;
  vertical-align: middle;
}

.year-posts .post-date.post-date-linked {
  display: inline-block;
}

.post-link-domain {
  display: inline-block;
  margin-top: 0.2rem;
  margin-left: 0.4em;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #c7bfae;
}

/*
 * Individual post pages. The wrapper carries both `.post` (shared with
 * blog-listing entries, e.g. for the in-body h2 rule above) and `.post-full`
 * (this page only) — everything below is scoped to the latter so it can't
 * leak into the listing the way an unscoped `.post` rule did before.
 */
.post-full .post-title {
  margin: 0 0 0.6rem;
  font-size: 2.75rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.post-full > .post-date {
  display: block;
  margin: 0 0 2.75rem;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #9a9a9a;
}

/* The opening line of a post reads as a lead-in, same treatment as a
   page's opening paragraph. */
.post-full > p:first-of-type {
  font-family: "Newsreader", Georgia, serif;
  font-style: italic;
  font-size: 1.35rem;
  line-height: 1.6;
}

.post-full p {
  margin-bottom: 1.4rem;
}

.post-full blockquote {
  font-family: "Newsreader", Georgia, serif;
  font-style: italic;
  font-size: 1.1rem;
  border-left-color: var(--content-accent);
  color: #6b6b6b;
}

/* Media embeds — `video:` (YouTube) or `image:` front matter renders the
   actual content inline here, above the outbound CTA below. */
.post-embed {
  margin: 2rem 0 0;
}

.post-embed-video {
  position: relative;
  height: 0;
  padding-top: 56.25%; /* 16:9 */
  overflow: hidden;
}

.post-embed-video iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.post-embed-image img {
  display: block;
  width: 100%;
  margin: 0;
  border-radius: 5px;
}

/* X/Twitter's widgets.js replaces the blockquote with its own iframe once
   it loads — this just centers it at the widget's native width and gives
   the raw blockquote (the fallback shown before/without JS) plain,
   unobtrusive styling rather than Poole's default quote treatment. */
.post-embed-tweet {
  display: flex;
  justify-content: center;
}

.post-embed-tweet blockquote.twitter-tweet {
  margin: 0;
  max-width: 550px;
  width: 100%;
  padding: 0;
  border-left: none;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.9rem;
  color: #515151;
}

/* Listing toggle for a tweet-kind link post — no static thumbnail exists
   without an extra API call, so a small text toggle takes the thumbnail's
   place, expanding the real embed inline via native <details>. */
.post-tweet-toggle {
  display: inline-block;
  margin-top: 0.6rem;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--content-accent);
  cursor: pointer;
  list-style: none;
}

.post-tweet-toggle::-webkit-details-marker {
  display: none;
}

.post-tweet-expand[open] .post-tweet-toggle {
  margin-bottom: 0.75rem;
}

.post-tweet-expand .post-embed-tweet {
  justify-content: flex-start;
}

/* Link-post CTA — the prominent outbound link on a post whose front matter
   sets `link:`, `video:` or `image:`. Any commentary in the post body sits
   above this (and above the embed, for video/image). */
.post-link-cta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  margin: 2rem 0 0;
  padding: 1.1rem 1.4rem;
  border: 1px solid #e5ded0;
}

.post-link-cta:hover {
  border-color: var(--content-accent);
}

.post-link-cta-label {
  font-family: "Newsreader", Georgia, serif;
  font-style: italic;
  font-size: 1.1rem;
  color: #3a362f;
}

.post-link-cta-domain {
  flex: 0 0 auto;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--content-accent);
  white-space: nowrap;
}

/* Previous / next pager */
.post-pager {
  display: flex;
  gap: 2rem;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid #eee;
}

.post-pager-prev,
.post-pager-next {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1 1 0;
  min-width: 0;
}

.post-pager-next {
  text-align: right;
  margin-left: auto;
}

.post-pager-label {
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--content-accent);
}

.post-pager-title {
  font-family: "Newsreader", Georgia, serif;
  font-size: 1.05rem;
  line-height: 1.4;
  color: #3a362f;
}
