/* ==========================================================================
   3DOptix redesign — SINGLE BLOG POST page-unique CSS.

   Depends on: dxr-tokens.css -> dxr-components.css -> dxr-shell.css
   Enqueued CONDITIONALLY from single-post.php (handle 'dxr-page-post',
   dependency 'dxr-shell'), so it ships on single posts only and can never
   reorder the shared layer.

   Geometry is the Academy article's, from README §6: body minmax(0,1fr) + a
   348px sidebar at a 72px gap, collapsing at the site's one named breakpoint.
   The two long-form surfaces are the same layout problem and deliberately share
   one answer; only the blog-specific furniture below (§3's featured figure, tag
   chips, prev/next pair and comment container) is new.

   THIS FILE IS THE POST'S CHROME, NOT ITS BODY.
   The body — the client's existing, editor-authored, sometimes Elementor-wrapped
   HTML — is styled by assets/css/dxr-prose.css and by nothing here. There is not
   one selector in this file that reaches inside .dxr-prose, and there must never
   be: the prose layer is an arithmetic-checked specificity ladder (see its
   header), and a stray rule from a page stylesheet landing in the middle of it
   is exactly how ~50 posts get silently unstyled. If something about the body
   needs changing, change dxr-prose.css.

   RULES FOR THIS FILE:
   - SCOPE. Every selector is scoped to .dxr-page-post, the wrapper the template
     emits inside the shell's <main>. No bare element selectors, ever.
   - No new colour. Every value resolves from the token layer; the only raw
     numbers are geometry, and each is named in §0 rather than repeated below.
   - The spectrum gradient appears ZERO times in this file. The header eyebrow
     uses --dxr-spectrum-eyebrow through the shared .dxr-eyebrow-bar component;
     dxr-tokens.css §1.4 documents that five-stop gradient as a different asset
     from the seven-stop --dxr-spectrum and explicitly outside the two-placement
     rule. Site-wide budget unchanged: nav 2px + footer 3px.
   - ONE media query, at --dxr-nav-break (900px), the site's only named
     breakpoint. See §2.
   - No icon set: every arrow and separator in this file is a typographic mark
     printed by the template.

   Contents
     §0  Page-local custom properties
     §1  Header band — breadcrumb, eyebrow, title, meta
     §2  Two-column body — the grid and its collapse
     §3  Article column — figure, pagination, tags, end bar, prev/next, comments
     §4  Sidebar shell
     §5  Sidebar (a) — related posts
     §6  Sidebar (b) — dark CTA
   ========================================================================== */


/* ==========================================================================
   §0 Page-local custom properties

   Prototype values with no entry in the token layer. Declared once, used by
   name below, so a design change is a one-line edit and every literal in this
   file is traceable.
   ========================================================================== */
.dxr-page-post{
  --dxr-post-head-top:34px;                    /* == the article header rhythm */
  --dxr-post-head-bot:clamp(27px,3.3vw,44px);
  --dxr-post-body-top:clamp(35px,4.1vw,56px);
  --dxr-post-aside-w:348px;                    /* README §6 — see §2 */
  --dxr-post-sticky-top:104px;                 /* == --dxr-nav-h (76) + 28 */
  --dxr-post-card-pad:22px;
  --dxr-post-radius-chip:6px;                  /* tag pills, §3 */
  --dxr-post-block-gap:34px;                   /* article column block rhythm */
}


/* ==========================================================================
   §1 Header band

   .dxr-section supplies the hairline (--rule-b) and the container; the rhythm
   is the article's own, much tighter than --dxr-section-y, so it is overridden
   here. The two-class selector out-ranks .dxr-section (0,1,0) without
   !important.

   A post header is a label for what follows, not a hero: no blueprint, no glow,
   no lede. Deliberately NOT .dxr-hero.
   ========================================================================== */
.dxr-page-post .dxr-post__head{
  background:var(--dxr-surface);
  padding:var(--dxr-post-head-top) 0 var(--dxr-post-head-bot);
}

/* --- breadcrumb ----------------------------------------------------------
   The SECOND copy of this block; dxr-page-article.css §1 carries the first and
   its comment explains why: nothing in dxr-components.css is a breadcrumb, the
   mono eyebrow roles are all uppercase/tracked and a path is neither, and the
   two page stylesheets were written in parallel so each keeps its own
   page-scoped copy rather than racing to define a shared one.

   NOW THAT THERE ARE TWO IDENTICAL COPIES, THE PATTERN IS CONFIRMED. Promoting
   these five rules into dxr-components.css and deleting both copies is a
   copy-paste and belongs to whoever owns the shared layer — it is not done here
   because this file must not edit a shared stylesheet that other surfaces are
   being built against in the same batch. Flagged, not fixed.

   flex-wrap is load-bearing, not defensive: the third crumb is the post title,
   and blog titles run considerably longer than Academy ones. It wraps rather
   than truncates — a truncated breadcrumb tells the reader less than a two-line
   one.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-crumbs{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:10px;
  margin:0 0 28px;
  font:400 12px/1.4 var(--dxr-font-mono);       /* mono: a path is measured content */
  color:var(--dxr-text-subtle);
}
.dxr-page-post .dxr-crumbs a{
  color:var(--dxr-text-subtle);
  transition:color .15s ease;
}
.dxr-page-post .dxr-crumbs a:hover{color:var(--dxr-primary)}
.dxr-page-post .dxr-crumbs__sep{color:var(--dxr-text-disabled)}
.dxr-page-post .dxr-crumbs__current{color:var(--dxr-ink)}

/* --- title row -----------------------------------------------------------
   align-items:flex-end pins the CTA's baseline to the meta line's. flex-wrap
   drops the button under the copy before it can squeeze the heading — the
   button is white-space:nowrap, so without the wrap a long title and a long
   label fight for the same row.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__head-row{
  display:flex;
  align-items:flex-end;
  justify-content:space-between;
  gap:40px;
  flex-wrap:wrap;
}
.dxr-page-post .dxr-post__head-copy{
  min-width:0;   /* lets a long unbroken title shrink instead of overflowing */
}

/* Local deltas from the shared .dxr-eyebrow-bar (26px margin, 44px rule,
   .13em) — 16px, 40px, .12em, matching the article header. Everything else is
   inherited from the component. */
.dxr-page-post .dxr-post__eyebrow{margin-bottom:16px}
.dxr-page-post .dxr-post__eyebrow .dxr-eyebrow-bar__rule{width:40px}
.dxr-page-post .dxr-post__eyebrow .dxr-eyebrow-bar__label{letter-spacing:.12em}

/* The type role is .dxr-h1--compact (shared). Only the box is local — and the
   element may be an <h1> or a <p> depending on whether the stored content
   already carries an <h1>, which is why margin is declared here rather than
   relied on from the reset. */
.dxr-page-post .dxr-post__title{
  margin:0;
  overflow-wrap:break-word;
}

/* Meta line. Mono, because a byline and a date are measured content — the same
   split home.php's index cards use, so a post and its card agree. Wraps, since
   a long display name plus a long-format date exceeds a narrow column. */
.dxr-page-post .dxr-post__meta{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:8px;
  margin-top:18px;
  font:400 12px/1.5 var(--dxr-font-mono);
  color:var(--dxr-text-subtle);
}
.dxr-page-post .dxr-post__meta-author{color:var(--dxr-body)}
.dxr-page-post .dxr-post__meta-sep{color:var(--dxr-text-disabled)}

/* The CTA never shrinks below its label — it wraps to its own row instead. */
.dxr-page-post .dxr-post__cta{flex-shrink:0}


/* ==========================================================================
   §2 Two-column body — README §6

   minmax(0,1fr) ON THE BODY COLUMN IS LOAD-BEARING. A grid track's default
   min-width is auto, i.e. its content's min-content size. Legacy post bodies
   carry tables with inline pixel widths, images with large intrinsic width
   attributes and iframes with inline heights; under `1fr` alone any one of them
   would widen the track and push the sidebar off the container. dxr-prose §12
   scrolls the tables and §10 caps the images; this is the outer guarantee that
   makes those work.

   align-items:start is what makes the sidebar's position:sticky function at
   all — the default `stretch` gives the aside the full row height, leaving it
   nothing to slide within.
   ========================================================================== */
.dxr-page-post .dxr-post__body{
  background:var(--dxr-surface);
  padding:var(--dxr-post-body-top) 0 var(--dxr-section-y);
}
.dxr-page-post .dxr-post__grid{
  display:grid;
  grid-template-columns:minmax(0,1fr) var(--dxr-post-aside-w);
  gap:var(--dxr-gap-col-xwide);                             /* 72px */
  align-items:start;
}
.dxr-page-post .dxr-post__main{min-width:0}

/* --- the collapse --------------------------------------------------------
   ONE breakpoint, at the site's only named one.

   At a 901px viewport the gutter is 4vw ~= 36px, leaving 829px of container.
   Minus the 72px gap and the 348px sidebar the body column still holds 409px,
   comfortably above the 340px floor the prototype chose. Below 900px the
   sidebar stacks under the article, un-sticks, and reads as an appendix; the
   dark CTA card (§6) becomes the end-of-article CTA. Do not introduce a second
   breakpoint.
   ------------------------------------------------------------------------ */
@media (max-width:900px){                                   /* == --dxr-nav-break */
  .dxr-page-post .dxr-post__grid{
    grid-template-columns:minmax(0,1fr);
    gap:40px;
  }
  .dxr-page-post .dxr-post__aside{
    position:static;
    top:auto;
  }
}


/* ==========================================================================
   §3 Article column

   Everything between the figure and the end bar is .dxr-prose and belongs to
   dxr-prose.css.
   ========================================================================== */

/* --- featured image ------------------------------------------------------
   OUTSIDE .dxr-prose by construction: it is post metadata the template renders,
   not content the editor wrote, so prose §10's figure treatment does not reach
   it and the frame is declared here.

   aspect-ratio is deliberately NOT set. Featured images across the library are
   not all 16:9, and cropping a diagram to fit a box loses information the post
   is about. height:auto keeps whatever ratio the file has; max-width:100% keeps
   it inside the minmax(0,1fr) track.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__figure{
  margin:0 0 var(--dxr-post-block-gap);
  border:1px solid var(--dxr-border);
  border-radius:var(--dxr-radius-panel);
  overflow:hidden;
  background:var(--dxr-surface-raised);   /* shows while the image decodes */
}
.dxr-page-post .dxr-post__figure-img{
  display:block;
  width:100%;
  height:auto;
  border-radius:0;   /* the frame owns the corners */
}

/* --- wp_link_pages() -----------------------------------------------------
   For a multi-page post. Absent from the design, but the parent template called
   it and dropping the styling would leave bare, reset-stripped number links if
   an editor ever splits a post. Mono, because page numbers are measured
   content. 44px is the touch floor, matching the pagers on the list pages.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__pages{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:8px;
  margin-top:var(--dxr-post-block-gap);
  font:500 13px/1 var(--dxr-font-mono);
  color:var(--dxr-body);
}
.dxr-page-post .dxr-post__pages a,
.dxr-page-post .dxr-post__pages > span{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:44px;
  height:44px;
  padding:0 10px;
  border:1px solid var(--dxr-border);
  border-radius:var(--dxr-radius-md);
  background:var(--dxr-surface);
  color:inherit;
  transition:border-color .15s ease,color .15s ease;
}
.dxr-page-post .dxr-post__pages a:hover{
  border-color:var(--dxr-primary);
  color:var(--dxr-primary);
}
.dxr-page-post .dxr-post__pages > span{
  background:var(--dxr-ink);
  border-color:var(--dxr-ink);
  color:var(--dxr-on-dark);
}

/* --- tags ----------------------------------------------------------------
   LIVE on this post type, unlike the Academy article where the same block is
   insurance. the_tags() emits plain anchors, which the reset leaves as bare
   coloured text; the chip treatment is what makes them read as a set of
   filters. The label is mono (a taxonomy is measured), the chips are sans (a
   tag name is prose).
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__tags{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:8px;
  margin-top:var(--dxr-post-block-gap);
}
.dxr-page-post .dxr-post__tags-label{
  font:500 10px/1 var(--dxr-font-mono);
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--dxr-text-subtle);
  margin-right:4px;
}
.dxr-page-post .dxr-post__tags a{
  padding:7px 12px;
  border:1px solid var(--dxr-border);
  border-radius:var(--dxr-post-radius-chip);
  font:600 12px/1 var(--dxr-font-sans);
  color:var(--dxr-body);
  transition:border-color .15s ease,color .15s ease;
}
.dxr-page-post .dxr-post__tags a:hover{
  border-color:var(--dxr-primary);
  color:var(--dxr-primary);
}

/* --- end-of-article bar --------------------------------------------------
   Sits INSIDE <article>, after .dxr-prose and outside it. There is no
   full-width closing band on this screen: the header CTA, the dark sidebar card
   and the prev/next pair carry the closing load.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__foot{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:24px;
  flex-wrap:wrap;
  margin-top:var(--dxr-post-block-gap);
  padding-top:26px;
  border-top:1px solid var(--dxr-border);
}
.dxr-page-post .dxr-post__back{
  display:inline-flex;
  align-items:center;
  gap:8px;
  font:600 14px/1 var(--dxr-font-sans);
  color:var(--dxr-primary);
  transition:color .15s ease;
}
.dxr-page-post .dxr-post__back:hover{color:var(--dxr-primary-hover)}
.dxr-page-post .dxr-post__foot-meta{
  font:400 12px/1.4 var(--dxr-font-mono);       /* a date is measured content */
  color:var(--dxr-text-subtle);
}

/* --- previous / next -----------------------------------------------------
   FLEX, NOT A TWO-COLUMN GRID, and that is the whole point: either half can be
   absent (the first and last post in a category each get exactly one
   neighbour). Under a fixed two-column grid a lone "Next" would sit in the left
   cell reading as a "Previous". `margin-left:auto` on the next card pushes it
   to the right edge whether or not a previous card precedes it.

   `flex:1 1 240px` uses the system's tuned 240px minimum. Below that the two
   cards wrap onto separate rows rather than compressing into unreadable
   columns. min-width:0 lets a long title ellipsize instead of widening the row.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__adjacent{
  display:flex;
  flex-wrap:wrap;
  gap:16px;
  margin-top:20px;
}
.dxr-page-post .dxr-post__adjacent-link{
  flex:1 1 240px;
  min-width:0;
  display:flex;
  flex-direction:column;
  gap:8px;
  /* 44px touch floor is exceeded by the two-line content; padding keeps the
     target comfortably above it even for a one-word title. */
  padding:16px 18px;
  border:1px solid var(--dxr-border);
  border-radius:var(--dxr-radius-panel);
  background:var(--dxr-surface);
  transition:border-color .15s ease;
}
.dxr-page-post .dxr-post__adjacent-link--next{
  margin-left:auto;
  text-align:right;
}
.dxr-page-post .dxr-post__adjacent-link:hover{border-color:var(--dxr-primary)}
.dxr-page-post .dxr-post__adjacent-label{
  font:500 10px/1 var(--dxr-font-mono);
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--dxr-text-subtle);
}
.dxr-page-post .dxr-post__adjacent-title{
  font:600 14px/1.45 var(--dxr-font-sans);
  color:var(--dxr-ink);
  overflow-wrap:break-word;
}
.dxr-page-post .dxr-post__adjacent-link:hover .dxr-post__adjacent-title{color:var(--dxr-primary)}

/* --- comments ------------------------------------------------------------
   INSURANCE, and labelled as such: comments appear to be closed across the
   current library, but 'post' supports them and the parent template called
   comments_template(), so turning them on must not produce reset-stripped
   markup.

   This container is NOT .dxr-prose — that layer would put disc bullets and a
   22px indent on .comment-list, which is a marker-less list by design. Instead
   the two reset rules that actually hurt a comment thread are put back, scoped
   to this container only: paragraph margins and link underlines. Everything
   else core emits is left as core emits it.
   ------------------------------------------------------------------------ */
.dxr-page-post .dxr-post__comments{
  margin-top:var(--dxr-section-y);
  padding-top:30px;
  border-top:1px solid var(--dxr-border);
  font:400 15px/1.7 var(--dxr-font-sans);
  color:var(--dxr-body);
}
.dxr-page-post .dxr-post__comments p{margin:0 0 16px}
.dxr-page-post .dxr-post__comments p:last-child{margin-bottom:0}
.dxr-page-post .dxr-post__comments a{
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:2px;
}
.dxr-page-post .dxr-post__comments h2,
.dxr-page-post .dxr-post__comments h3{
  margin:0 0 18px;
  font:800 clamp(22px,2.5vw,28px)/1.25 var(--dxr-font-sans);
  letter-spacing:-.028em;
  color:var(--dxr-ink);
}
.dxr-page-post .dxr-post__comments li{margin:0 0 20px}
/* Form controls at >= 16px so iOS does not auto-zoom on focus. */
.dxr-page-post .dxr-post__comments input,
.dxr-page-post .dxr-post__comments textarea{
  width:100%;
  max-width:100%;
  min-height:44px;
  padding:12px 14px;
  border:1px solid var(--dxr-border-strong);
  border-radius:var(--dxr-radius-md);
  background:var(--dxr-surface);
  font:400 16px/1.5 var(--dxr-font-sans);
  color:var(--dxr-ink);
}
.dxr-page-post .dxr-post__comments input[type="checkbox"],
.dxr-page-post .dxr-post__comments input[type="radio"]{
  width:auto;
  min-height:0;
}
.dxr-page-post .dxr-post__comments input[type="submit"]{
  width:auto;
  min-height:0;
  height:48px;
  padding:0 24px;
  border:0;
  background:var(--dxr-primary);
  color:var(--dxr-on-dark);
  font:700 14px/1 var(--dxr-font-sans);
  cursor:pointer;
}


/* ==========================================================================
   §4 Sidebar shell

   position:sticky, not fixed: the aside must stop scrolling when the article
   column runs out, which fixed cannot do. It needs three things and all three
   are here or in §2 — a sticky element, an `align-items:start` grid parent
   (§2), and no overflow:hidden anywhere on the ancestor chain (which is why
   that section is a plain .dxr-section and NOT .dxr-section--overlay).

   A sidebar taller than the viewport simply scrolls with the page; there is no
   inner scroller, because a nested scroll region inside a sticky panel is a
   trap on a trackpad. The two cards total ~380px, well under any laptop
   viewport.
   ========================================================================== */
.dxr-page-post .dxr-post__aside{
  position:sticky;
  top:var(--dxr-post-sticky-top);
  display:flex;
  flex-direction:column;
  gap:var(--dxr-gap-panels);                    /* 20px */
  min-width:0;
}


/* ==========================================================================
   §5 Sidebar (a) — related posts

   The label reuses the shared .dxr-label--strip role; only its margin is local.

   The rows are real posts (see the query in single-post.php) and the card is
   suppressed entirely when there are none, so this never renders as an empty
   frame or with invented titles.
   ========================================================================== */
.dxr-page-post .dxr-post-related{
  padding:var(--dxr-post-card-pad);
  border:1px solid var(--dxr-border);
  border-radius:var(--dxr-radius-panel);
  background:var(--dxr-surface);
}
.dxr-page-post .dxr-post-related__label{margin-bottom:16px}
.dxr-page-post .dxr-post-related__list{
  display:flex;
  flex-direction:column;
  gap:14px;
}
.dxr-page-post .dxr-post-related__link{
  padding-bottom:14px;
  border-bottom:1px solid var(--dxr-border-inner);
  font:600 14px/1.45 var(--dxr-font-sans);
  color:var(--dxr-ink);
  transition:color .15s ease;
}
/* Last row loses the rule and the padding — the "all posts" link below closes
   the card instead. */
.dxr-page-post .dxr-post-related__link:last-child{
  padding-bottom:0;
  border-bottom:0;
}
.dxr-page-post .dxr-post-related__link:hover{color:var(--dxr-primary)}
/* The shared .dxr-cta-link is 14px; 13px here matches the sidebar scale. */
.dxr-page-post .dxr-post-related__all{
  margin-top:16px;
  font-size:13px;
}


/* ==========================================================================
   §6 Sidebar (b) — dark CTA card

   .dxr-on-dark is on the element in the template, which is what gives the
   shared .dxr-cta-link its --dxr-cyan-light colour and white hover, so nothing
   needs restating here.

   The border is the light hairline, transcribed from the Academy card so the
   sidebar cards share one frame and the stack reads as a set, one of which
   happens to be filled dark. dxr-page-article.css §7 flags the same value for
   sign-off; if that review lands on --dxr-border-on-dark, this file must change
   with it. The two are deliberately identical so the decision is made once.
   ========================================================================== */
.dxr-page-post .dxr-post-aside-cta{
  position:relative;
  overflow:hidden;
  padding:var(--dxr-post-card-pad);
  border:1px solid var(--dxr-border);            /* see the note above */
  border-radius:var(--dxr-radius-panel);
  background:var(--dxr-ink-deep);
}
.dxr-page-post .dxr-post-aside-cta__title{
  margin-bottom:8px;
  font:700 15px/1.4 var(--dxr-font-sans);
  color:var(--dxr-on-dark);
}
.dxr-page-post .dxr-post-aside-cta__text{
  margin:0 0 16px;
  font:400 13px/1.6 var(--dxr-font-sans);
  color:var(--dxr-on-dark-muted);
}
.dxr-page-post .dxr-post-aside-cta .dxr-cta-link{font-size:13px}

/* TWO STACKED CTA LINKS, AND WHY THE GAP IS 34px AND NOT 14px.
   .dxr-cta-link is inline-flex, so the second needs `display:flex` to take its
   own row at all. The margin is the part that matters: the shared component
   carries `.dxr-cta-link::after{position:absolute;inset:-16px 0}`, a 16px
   invisible extension of the hit area above AND below each link. Two links
   14px apart would therefore have 32px of expanded target separated by 14px of
   gap — the two tap zones OVERLAP, and the later one in the DOM wins, so
   tapping just above "Open the design studio" silently fires the wrong link on
   touch. 34px clears 16 + 16 with 2px to spare, which is the smallest gap at
   which each link owns its own target. Do not reduce it.
   This is the only place on the site where two .dxr-cta-link elements stack. */
.dxr-page-post .dxr-post-aside-cta__second{
  display:flex;
  margin-top:34px;
}
