
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400&family=Montserrat:wght@500&display=swap');

/* VARIABLES */
/* custom properties for colors and fonts used throughout the site */

:root {
  --co-dark: #6d6866;
  --co-mid: #d1c6c2;
  --co-mid2: #E8E2E0;
  --co-light: #ded7d4;
  --co-pale: #f5efed;
  --co-pale2: #FAF7F6;
  --co-edge: #eceaea;
  --co-border: #d4d2d2;
  --co-fringe: #9a9898;
  --co-notif: #e17373;
  --co-blue: #30A1F3;
  --co-blue2: #97D0F9;
  --co-link: #1C59F7;
  --co-yellow: #F7F452;
  --co-green: #7AE278;
  --co-purple: #BE8FFA;
  --co-beige: #D8C5A0;
  --co-grey: #6D6866;
  --co-warn: #b00;     /* fix-it messages (worksheet slot errors, curtain forms); the old inline fallback, now one token */
  --co-ink: #231f20;   /* the house marks' ink (DOCS/design/marks.md) -- the old icon SVGs' fill */
  --font-main: 'Lato', sans-serif;
  --font-alt: 'Montserrat', sans-serif;
  /* Serif stack for prose-leaning surfaces (delineation synopsis, quote
   * body). System fonts only -- no external font load. Georgia and Iowan
   * Old Style are screen-optimized serifs; the cascade falls through to
   * the generic `serif` keyword if neither is available. */
  --font-serif: Georgia, 'Iowan Old Style', 'Charter', 'Times New Roman', Times, serif;
}

/* RESET */
/* normalize browser defaults and ensure consistent box-sizing */

*, *::before, *::after {
  box-sizing: border-box;
}
* {
  margin: 0;
  padding: 0;
}
html, body {
  height: 100%;
}
body {
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
audio, img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}
input, button, textarea, select {
  font: inherit;
  max-width: 100%;
}
select {
  color: #000;
}

/* Project-wide field default. Mode A: invisible at rest (transparent border
   reserves space, no layout shift on focus); --co-light border on focus.
   Per-context Mode B fields opt in via .field-resting class. */

input[type=text], input[type=email], input[type=date], input[type=number],
input[type=password], textarea, select {
  background: #fff;
  border: 1px solid transparent;
  border-radius: 5px;
}
input[type=text]:hover, input[type=email]:hover, input[type=date]:hover,
input[type=number]:hover, input[type=password]:hover, textarea:hover, select:hover,
input[type=text]:focus, input[type=email]:focus, input[type=date]:focus,
input[type=number]:focus, input[type=password]:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--co-light);
}

/* Mode B: always-field. Opt-in via class on the input. Default shows a
   visible --co-light border at rest; focus darkens to --co-mid. Used for
   fields that always present as fields (chapter quick-add, notes textareas)
   rather than as display-until-active text. */

/* `input.field-resting` ties the Mode A attribute selector on specificity and wins by
   source order; the bare class alone lost to `input[type=text]`, so the opt-in only ever
   worked on textarea/select (DOCS/solutions/2026-09-02-field-resting-opt-in-loses-to-
   the-attribute-selector-on-inputs.md). */
.field-resting, input.field-resting {
  border-color: var(--co-light);
}
.field-resting:focus, input.field-resting:focus {
  border-color: var(--co-mid);
}
p, h1, h2, h3, h4, h5, h6 {
  overflow-wrap: break-word;
}
h1, h2, h3 {
  line-height: 1.25;
}

/* MAIN */
/* Base site typography and layout settings for the entire page */

html {
  font-family: var(--font-main);
  font-size: clamp(12px, 1.5vw, 15px);
  overflow: hidden;
  overscroll-behavior-x: none; /* prevent ios horizontal drag */
  scroll-behavior: smooth;
}
body {
  color: var(--co-dark);
  display: flex;
  flex-direction: column;
}

/* Standard spacing for all heading and text elements */

h1, h2, h3, h4, h5, h6, p, ul, ol {
  margin-bottom: 1rem;
}

/* Heading styles for different levels throughout the site */

h1, .h1 {
  font-size: 2.13333rem;
  font-weight: 500;
  text-transform: uppercase;
}
h2, .h2 {
  font-size: 1.6rem;
  font-weight: 300;
  font-style: italic;
}
h3, .h3 {
  font-size: 1.33rem;
  font-weight: 400;
}
h4, .h4 {
  font-size: 1.0667rem;
  font-weight: 400;
}

/* Standard list indentation */

ul, ol {
  margin-left: 1em;
}

/* Button cursor and disabled state styling */

button {
  cursor: pointer;
}
button[disabled] {
  background-color: #dedede;
  border-color: #bbb;
  color: #bbb;
  cursor: default;
}

/* Body-content link rule. Default: bold, inherits surrounding color, no underline.
   On hover/focus: color shifts to --co-link. Nav, .upstudy-boxes, and .btn-simple-lower
   carry opt-outs below. Links and prose <strong>/<b> now share weight 700 (real bold);
   links stay recognizable by their hover colour (and, in the docs browser, an at-rest
   link colour). */

a {
  /* Experimental (lbk 7/8): links in black at normal weight instead of inherited-colour
     bold — one global rule, easy to revert. */
  color: #000;
  font-weight: normal;
  text-decoration: none;
}
a:hover,
a:focus {
  color: var(--co-link);
}

strong, b,
.ck-content strong, .ck-content b {
  /* Real bold. Was 500 (medium) to differ from links at 700, but 500 barely read as
     bold; 700 is the only clearly-bold weight loaded (Lato ships 300/400/500/700 only,
     so there is no 600 middle ground). Applies to markdown docs and rich-text content. */
  font-weight: 700;
}

/* Dialog/modal window styling for popup forms and dialogs */

dialog {
  background-color: var(--co-dark);
  border: none;
  box-shadow: 0 2px 4px rgba(0,0,0,.25);
  color: #fff;
  align-self: center;
  justify-self: center;
  width: 560px;
  height: 720px;
  max-height: 80vh;
  max-width: 80vw;
  outline: 1px solid #fff;
  outline-offset: -4px;
}
dialog:open {
  display: flex;
  flex-direction: column;
}
dialog header {
  border-bottom-color: #fff!important;
  padding: .5rem;
}
dialog main, dialog footer {
  padding: 1rem;
}
dialog header {
  text-align: center;
  text-transform: uppercase;
}
dialog .choices__item {
  color: #000!important;
}
dialog footer {
  display: flex;
  gap: 10px;
  justify-content: end;
  margin-top: auto;
}
dialog footer button {
  color: inherit!important;
}

/* Link styling in navigation areas */

nav a {
  color: inherit;
}

/* Horizontal rule styling */

hr {
  border-bottom: none;
  color: var(--co-fringe);
}

/* Output element fade-in animation for HTMX responses */

output {
  display: block;
  opacity: 0;
  transition: opacity 1s ease-out;
}
output.htmx-settling {
  opacity: 100;
}

/* ACCESSIBILITY */
/* Skip navigation link  */

#skip-navigation {
  background: #fff;
  font-size: .9em;
  font-weight: bold;
  opacity: 1!important;
  padding: .5em;
  z-index: 10000001;
}

/* Screen reader only content that's hidden visually but available to assistive technology */

.sr-only {
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0);
  height: 1px; width: 1px;
  margin: -1px; padding: 0; border: 0;
}
.sr-only-focusable:active, .sr-only-focusable:focus {
  width: auto;
  height: auto;
  overflow: visible;
  clip: auto;
  z-index: 100;
}

/* List display-settings grid — rows are "things" (Story / Dates / each status),
   columns are modifiers (Show, Group by, Pin to top). Group-by holds the date-divider
   toggle on the Dates row; Pin-to-top sits on the Pinned row. Used in BOTH the workspace
   TOOLS sidebar and the composition popup Meta tab, so it lives here in the global sheet
   (both surfaces load upstudy.css). Shrink-to-content so columns sit close together. */
.tools-settings-grid {
  border-collapse: collapse;
  margin-top: .4rem;
  width: auto;
}
.tools-settings-grid th,
.tools-settings-grid td {
  padding: .15rem .45rem;
  text-align: center;
}
.tools-settings-grid thead th {
  font-size: .78em;
  font-weight: 600;
  letter-spacing: .02em;
  opacity: .7;
}
.tools-settings-grid tbody th[scope="row"] {
  font-weight: 400;
  padding-right: .6rem;
  text-align: left;
  white-space: nowrap;
}
/* Sub-rows that qualify the row above them (Year / Weekday under Dates): indented and
   slightly quieter, so the grid reads as a two-level list rather than five peers. */
.tools-settings-grid__sub th[scope="row"] {
  font-size: .92em;
  opacity: .8;
  padding-left: .9rem;
}
/* "STATUSES" subhead row — a quiet label above the per-status rows. */
.tools-settings-grid__subhead th {
  font-size: .72em;
  font-weight: 600;
  letter-spacing: .04em;
  opacity: .6;
  padding-top: .4rem;
  text-align: left;
  text-transform: uppercase;
}
.tools-settings-grid input[type="checkbox"] {
  margin: 0;
}


/* CLASSES */
/* Collapsible "advanced" section of detail search with expand/collapse button */

.advanced-collapse {
  border: 2px solid #fff;
  border-radius: 3px; /* rounds the corners slightly */
  font-size: 16px;
  margin: 1em 0; /* adds 1em space above and below - 0 left/right */
  text-align: center;
}
.advanced-collapse > button {
  background: transparent;
  color: var(--co-dark);
  display: block;
  font-weight: 400;
  padding: .5em;
  position: relative;
  text-align: center;
  width: 100%;
}
.advanced-collapse > button svg {
  background: none;
  color: inherit;
  position: absolute;
  right: .5em;
}
.advanced-collapse > button.active {
  background: var(--co-grey);
  color: #fff;
}
.advanced-collapse > button.active svg {
  transform: rotate(90deg);
}
.advanced-collapse .collapse {
  background: var(--co-edge);
  padding: .5em;
}
.advanced-collapse .collapse > div {
  border-bottom: 1px solid;
  display: flex;
  gap: .5em;
  padding: .5em 1em;
}
.advanced-collapse .collapse > div:last-child {
  border: none;
}
.advanced-collapse .collapse > div > :first-child {
  text-align: left;
  min-width: 100px;
}

/* Detail-search sidebar: clear/abort control (upper-right) + relocated search submit row.
   The control is dual-state: .detail-search-clear__rest shows at rest, __abort shows while a
   search is in flight (toggled by detail-search-persistence.js). */
.detail-search-header {
  display: flex;
  justify-content: flex-end;
}
.detail-search-clear {
  align-items: center;
  background: transparent;
  border: none;
  color: var(--co-dark);
  cursor: pointer;
  display: inline-flex;
  font-size: 14px;
  gap: .25em;
  padding: .25em .5em;
}
.detail-search-clear:hover,
.detail-search-clear:focus {
  color: var(--co-link);
}
.detail-search-clear[data-state="abort"] {
  color: var(--co-notif);
}
.detail-search-clear .icon {
  height: .9em;
  width: .9em;
}
/* :not([hidden]) so the [hidden] state span stays display:none — a bare `display: inline-flex`
   would override the UA [hidden] rule and show both labels at once. */
.detail-search-clear__rest:not([hidden]),
.detail-search-clear__abort:not([hidden]) {
  align-items: center;
  display: inline-flex;
  gap: .25em;
}
.detail-search-submit-row {
  margin-top: .5em;
  text-align: right;
}

/* Inset box style used for carousel slides and content boxes */

.box-inset {
  background: #fff;
  border-radius: .5rem;
  box-shadow: -.25rem .25rem .5rem rgba(0,0,0,.2) inset;
  line-height: normal;
  margin: .5rem 0;
  position: relative;
  text-align: center;
}
.box-inset p {
  padding: 1.5rem 1rem 0;
}
.box-inset footer {
  border-top: 1px solid var(--co-mid);
  font-size: 10px;
  margin: 0 2rem;
  padding: .5em;
}

/* Navigation dots for carousel/slideshow at bottom of box-inset */

.box-inset .tns-nav {
  position: absolute;
  text-align: center;
  top: 100%;
  width: 100%;
}
.box-inset .tns-nav button {
  background: var(--co-dark);
  border: none;
  border-radius: 50%;
  margin: 0 5px;
  opacity: .5;
  padding: 3px;
}
.box-inset .tns-nav button.tns-nav-active {
  opacity: 1;
}

/* BUTTONS */
/* "Add new" button styling */

.btn-new {
  display: block;
  padding: 1em 0;
}

/* Media button style used for media panel controls */

.btn-media {
  background: none;
  border: 1px solid;
  border-radius: 3px;
  color: #000;
  cursor: pointer;
  line-height: 1.4;
  min-width: 120px;
  padding: .25rem 1rem;
}

/* Plain button with no background or border */

.btn-plain {
  background: none;
  border: none;
  color: inherit;
  line-height: 1;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none; 
  text-decoration: none;
}

.btn-underline:focus,
.btn-underline:hover {
  text-decoration: underline;
}

/* Circular button used for expand/collapse controls */

.btn-circle {
  background: #fff;
  border: none;
  border-radius: 50%;
  color: #000;
  display: inline-flex;
  font-size: 10px;
  align-items: center;
  justify-content: center;
  height: 2em;
  width: 2em;
  padding: 0;
  line-height: 1;
}

/* Sidebar heading with an inline "+" entry-point button. The heading text  */
/* aligns with sibling h2s; the circle button right-aligns within the row.  */
.sidebar-heading-with-add {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.btn-circle.active {
  background: var(--co-grey);
  color: #fff;
  transform: rotate(90deg);
}

/* Simple bordered button used throughout forms */

.btn-simple, .btn-simple-lower {
  background: none;
  border: 1px solid;
  border-radius: 3px;
  color: #000; /* sets the text color to black */
  cursor: pointer;
  font-weight: inherit; /* opt-out from the global a {font-weight:700} when used on anchors */
  padding: .25rem 1rem;
  text-decoration: none;
  text-transform: uppercase;
}
.btn-simple-lower {
  background: #f5f5f5;
  text-transform: lowercase;
}

/* Pill buttons (curtain plan KTD4): the outward-facing pair on the curtain and the
   waiting-list form. Outlined by default, --solid for the primary of a pair. Anchors
   and buttons alike — the site's global a{font-weight:700} is opted out of here. */
.btn-pill, .btn-pill--solid {
  background: none;
  border: 1px solid var(--co-dark);
  border-radius: 999px;
  color: var(--co-dark);
  cursor: pointer;
  display: inline-block;
  font: inherit;
  font-weight: 400;
  line-height: 1.2;
  padding: .55rem 1.5rem;
  text-decoration: none;
  white-space: nowrap;
}
.btn-pill:hover, .btn-pill:focus-visible {
  background: var(--co-pale);
  color: var(--co-dark); /* the global a:hover recolours links; pills keep their own ink */
}
.btn-pill--solid {
  background: var(--co-dark);
  color: #fff;
}
.btn-pill--solid:hover, .btn-pill--solid:focus-visible {
  background: var(--co-ink);
  border-color: var(--co-ink);
}

/* Carousel section wrapper on homepage and study pages */

.carousel-section {
  padding: 60px 0 20px;
}
.carousel-section header {
  align-items: center;
  display: flex;
  gap: .5rem;
  margin-left: min(4vw,60px);
  margin-bottom: 1rem;
}
.carousel-section header h1 {
  margin: 0;
}

/* Horizontal scrolling carousel for study/composition boxes */

.carousel {
  background: linear-gradient(var(--co-edge) 0%, #fbfbfb 80%);  
  margin: 0 -40px;
  overflow-y: auto;
  padding: 40px 0;
  position: relative;
  z-index: 1;
}
.carousel .upstudy-boxes {
  flex-wrap: nowrap;
  margin-left: 70px;
  padding: 0 40px;
  /* Spacing in carousels comes from tiny-slider's gutter (see init_carousel), so
     the base 70px flex gap is zeroed here to avoid doubling the inter-token gap. */
  gap: 0;
}

/* Left/right navigation buttons for carousel */

.carousel-controls {
  align-items: center;
  justify-content: space-between;
  display: flex;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.carousel-controls:focus-visible {
  box-shadow: inset 0 0 2px blue;
}
.carousel-controls button {
  font-size: 1.5rem;
  padding: 2rem;
  z-index: 1;
}
.carousel-controls button svg {
  stroke: rgba(255,255,255,.5);
  stroke-width: 2px;
}
.carousel-controls button:last-child {
  right: 0;
}

/* Detail card form container for small composition detail cards */

.detail > form {
  background: var(--co-edge);
  border-radius: 20px;
  box-shadow: 2px 2px 4px rgba(0,0,0,.25);
  padding: 7px;
}
.detail > form .ck.ck-content.ck-editor__editable {
  min-height: 0;
}
.detail > form .word-count {
  display: none;
}

/* File upload field with drag-and-drop thumbnail preview */

.detail > form .form-field-file {
  background: #fff;
  border-radius: 13px;
  height: 194px;
  position: relative;
  width: 194px;
}
.detail > form .form-field {
  margin-bottom: 4px;
}
.detail > form .form-field:last-of-type {
  margin-bottom: 0;
}
.detail > form .form-field-file.drag-over {
  background: var(--co-blue);
}
.detail > form .form-field-file .form-field-el {
  display: none;
}
.detail > form .form-field-file .file-drop-label {
  align-items: center;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  justify-content: center;
  margin: 0;
  position: relative;
  width: 100%;
  z-index: 1;
}
.detail > form .form-field-file .file-drop-thumb {
  background-position: center;
  background-size: cover;
  border-radius: 13px;
  opacity: .5;
  position: absolute;
  height: 100%;
  top: 0;
  width: 100%;
}

/* Text input and textarea fields in detail card forms */

.detail > form input[type=text],
.detail > form textarea {
  border-radius: 13px; /* deliberate card-form override of project-wide 5px */
  display: block;
  font-size: 13px;
  font-style: italic;
  padding: 4px 8px;
  width: 100%;
}

/* Select dropdown fields in detail card forms */

.detail > form select {
  border-radius: 13px; /* deliberate card-form override of project-wide 5px */
  font-size: 13px;
  font-style: italic;
  padding: 10px 8px;
  width: 100%;
}

/* Specific height settings for title, synopsis, and attribution fields in detail cards */

.detail > form #id_title {
  height: 47px;
}
.detail > form #id_synopsis {
  height: 115px;
}
.detail > form #id_attribution {
  height: 35px;
}

/* Cancel, reset, and submit buttons at bottom of detail card form */

.detail > form .cancel-button,
.detail > form input[type=reset],
.detail > form input[type=submit] {
  background: var(--co-edge);
  border: none;
  border-radius: 0 0 20px 20px;
  color: var(--co-dark);
  cursor: pointer;
  font-weight: 700;
  left: 20px;
  padding-bottom: 2px;
  position: absolute;
  bottom: -24px;
  height: 24px;
  transition: .25s all;
  width: 64px;
  z-index: -1;
}
.detail > form .cancel-button:hover,
.detail > form input[type=reset]:hover,
.detail > form input[type=submit]:hover {
  background: var(--co-blue);
  color: #fff;
}

/* Progress bar shown during form submission */

.detail > form progress {
  display: none;
  width: 100%;
}
.detail > form .htmx-request progress {
  display: block;
}

/* Submit button positioned on right side of detail card form */

.detail > form input[type=submit] {
  left: auto;
  right: 20px;
}

/* Loading indicator and overlay for detail search results */

#detail-search-wrapper {
  position: relative;
}
#detail-search-wrapper.htmx-request {
  opacity: .5;
  pointer-events: none;
}
/* Loading animation for detail search results */
#detail-search-wrapper.htmx-request::before {
  content: ''; /* required for pseudo-elements like ::before */
  display: block;
  position: sticky; /* stays visible while scrolling */
  top: 10%; /* 10% from top of viewport */
  height: 100px;
  margin: -100px auto 0; /* negative top margin pulls it up (no space taken) */
  aspect-ratio: .75; /* width = height x 0.75 = 75px */
  --c: no-repeat linear-gradient(var(--co-dark) 0 0);
  background: 
    var(--c) 0%   100%, /* left bar - bottom position */
    var(--c) 50%  100%, /* center bar - bottom position */
    var(--c) 100% 100%; /* right bar - bottom position */
  background-size: 20% 65%; /* each bar: 20% wide, 65% tall */
  animation: l5 1s infinite linear;
  z-index: 10; /* appears above content */
}
/* animate bars by bouncing up and down */
@keyframes l5 {
  20% {background-position: 0% 50% ,50% 100%,100% 100%} /* left up */
  40% {background-position: 0% 0%  ,50% 50% ,100% 100%} /* left top, center up */
  60% {background-position: 0% 100%,50% 0%  ,100% 50% } /* center top, rght up */
  80% {background-position: 0% 100%,50% 100%,100% 0%  } /* right top */
}

/* Search bar and filters sticky at top of detail search sidebar */

#detail-search {
  padding: 1.5rem 2rem .5rem;
  margin: -1.5rem -1.5rem 0;
  background: var(--co-edge);
  position: sticky;
  top: 0;
  z-index: 1;
}
#detail-search .choices {
  margin: 0;
  text-align: left;
  min-width: 150px;
}
#detail-search .choices__inner {
  min-height: 0;
  padding-top: 4px;
  padding-bottom: 0;
}

/* Statistics display showing counts of active, pinned, etc. in search results */

.detail-search-stats {
  display: inline-flex;
}
#detail-search-stats > span {
  color: #000;
  padding: 0 5px;
}
#detail-search-stats .count {
  background-color: var(--co-beige);
  border-radius: 10px;
  display: inline-flex;
  font-weight: 400;
  height: 18px;
  min-width: 18px;
  text-align: center;
  padding: 0 4px 2px;
  align-items: center;
  justify-content: center;
}
#detail-search-stats .active .count {
  background-color: var(--co-green);
}
#detail-search-stats .pinned .count {
  background-color: var(--co-purple);
}

/* Select all checkbox in detail search */

#detail-search-select-all {
  float: left;
  margin: .25em 0 0 1em;
}

/* Container for detail search results list */

#detail-search-results {
  border: 1px solid var(--co-fringe);
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  margin: .25em .5em;
  padding: .5em 0;
}

/* Pinned items appear first in search results */

#detail-search-stats .pinned,
#detail-search-results .pinned {
  order: -1;
}
#detail-search-results > ul {
  margin: 0!important;
  text-transform: none!important;
}

/* Individual result items in detail search with status indicators */

#detail-search-results > ul > li {
  align-items: center;
  display: grid;
  margin: 2px 0;
  gap: 6px;
  grid-template-columns: 12px 32px 64px 45% 1fr;
  padding: 2px .5rem;
  position: relative;
}
#detail-search-results > ul > li:hover,
#detail-search-results > ul > li:focus-within {
  background-color: var(--co-pale2);
}
#detail-search-results > ul > li:hover > a,
#detail-search-results > ul > li:focus-within > a {
  color: var(--co-link);
}

/* Colored status dot before each search result item */

#detail-search-results > ul > li::before {
  background-color: var(--co-beige);
  border-radius: 50%;
  content: '';
  position: absolute;
  left: -1rem;
  height: 10px;
  width: 10px;
}
#detail-search-results .active > li::before {
  background-color: var(--co-green);
}
#detail-search-results .pinned > li::before {
  background-color: var(--co-purple);
}
#detail-search-results > ul > li a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Action buttons and advanced options sticky at bottom of detail sidebar */

#detail-action {
  position: relative;
}
#detail-action > footer {
  background: var(--co-edge);
  padding: 1rem 1.5rem 80px;
  margin: 0 -1.5rem;
  position: sticky;
  bottom: -60px;
  left: 0;
  right: 0;
}
#detail-action > footer .advanced-collapse {
  box-shadow: 0 2px 2px rgba(0,0,0,.25);
  width: 180px;
}
#detail-action > footer .advanced-collapse > button {
  background: var(--co-edge);
  color: inherit;
  text-align: left;
}
#detail-action > footer .advanced-collapse .collapse {
  background: #fff;
  padding: 4px 2px 2px;
  text-align: left;
}

/* Superfish-style dropdown menu used in detail actions */

.sf-menu {
  list-style: none;
  margin: 0!important;
}
.sf-menu li {
  position: relative;
}
.sf-menu ul {
  background: #fff;
  box-shadow: 0 2px 2px rgba(0,0,0,.25);
  position: absolute;
  bottom: 0;
  left: 97.5%;
  margin: 0!important;
  opacity: 0;
  padding: 4px;
  transition: .25s all;
  transition-delay: .1s;
  visibility: hidden;
  width: 100%;
  z-index: 99;
}
.sf-menu li:hover > ul {
  opacity: 1;
  transition-delay: 0s;
  visibility: visible;
}
.sf-menu li > button, .sf-menu li > span {
  background: #fff;
  border: none;
  color: inherit;
  display: block;
  padding: 0 .5em;
  position: relative;
  text-align: left;
  width: 100%;
}
.sf-menu li:hover > button, .sf-menu li:hover > span {
  background: var(--co-edge);
}

/* Utility classes for inline display */

.d-inline {
  display: inline;
}
.d-flex {
  display: flex;
}

/* Edit button that appears on hover over editable sections */

.edit-button {
  opacity: 0;
  padding: .5rem;
  position: absolute;
  right: 0;
  top: 0;
  transition: .25s opacity;
} 
:hover > .edit-button {
  opacity: 1;
}

/* Form validation error messages */

.errorlist {
  color: #A00;
  list-style-type: none;
  margin-left: 0;
}

/* Float utilities */

.floatl {
  float: left;
}
.floatr {
  float: right;
}

/* Form row layout with flexbox for side-by-side fields */

.form-row {
  align-items: center;
  display: flex;
  gap: .5rem;
  justify-content: space-between;
}
.form-row .form-field {
  flex: 0 1 auto;
}
.form-row .form-field-el {
  display: flex;
  gap: .5rem;
}
.form-row .form-field-el > * {
  width: 100%;
}	

/* Header form row with bottom border */

header.form-row {
  border-bottom: 1px solid;
  margin-bottom: 4px;
}

/* Gap spacing utilities */

.gap-50 {
  gap: .5rem;
}
.gap-100 {
  gap: 1rem;
} 

/* Hover buttons with icons that appear over search results */

.hover-buttons {
  display: flex;
  gap: 1px;
  list-style-type: none;
  margin: 0!important;
  padding-right: 5px;
}
.hover-buttons > li {
  width: 16px;
}
/* Scoped to the direct icon-trigger span (`> li > span`) on purpose: a plain
   descendant `.hover-buttons span` rule also matched spans inside the synopsis
   dropdown (Google Docs paste / CKEditor font styling wrap text runs in
   `<span style>`), painting the full icon sprite at the start of every run.
   See _anchor_preview_list.html's comment about the same failure mode. */
.hover-buttons > li > span {
  background-image: url('../img/search-result-icons.svg');
  background-repeat: no-repeat;
  background-size: auto 16px;
  display: inline-block;
  height: 100%;
  width: 100%;
  vertical-align: middle;
}

/* Individual icon positions in hover buttons sprite */

.hover-buttons span.hb-story {
  background-position: 1px 1px;
}
.hover-buttons span.hb-anchor {
  background-position: -16px 1px;
}
.hover-buttons span.hb-hooks {
  background-position: -34px 1px;
}
.hover-buttons span.hb-image {
  background-position: -51px 1px;
}
.hover-buttons span.hb-audio {
  background-position: -68px 1px;
}
.hover-buttons span.hb-file {
  background-position: -85px 1px;
}
.hover-buttons span.hb-link {
  background-position: -103px 1px;
}
.hover-buttons span.hb-tags {
  background-position: -120px 1px;
}

/* Dropdown panels that appear below hover button icons */

.hover-buttons > li > span + div {
  background: #fff;
  box-shadow: 0 4px 4px rgba(0,0,0,.25);
  border-radius: 5px;
  left: 0;
  opacity: 0;
  padding: 2rem;
  position: absolute;
  /* Rich-text preview renders verbatim: the search results and study-workspace
     detail list live inside `.sidebar nav`, whose `.sidebar nav ul` rule lowercases
     every nested <ul> (including .hover-buttons). Resetting here — a rule that
     directly matches the dropdown beats the inherited lowercase — keeps synopsis,
     anchor, hook and tag previews in their stored casing on every surface. */
  text-transform: none;
  transition: .25s all;
  transition-delay: .1s;
  visibility: hidden;
  width: 100%;
  z-index: 1;
}

/*selects the last child of the div that comes after the span inside the hover-buttons class */
.hover-buttons > li > span + div :last-child {
  margin-bottom: 0; /* removes extra space at the bottom of the last item */
}
.hover-buttons > li:focus-within > span,
.hover-buttons > li:hover > span {
  background-color: #fff;
}
.hover-buttons li:hover div,
.hover-buttons li:focus-within div {
  opacity: 1;
  transition-delay: 0s;
  visibility: visible;
}

/* SVG icon base styling */

.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
}

/* Icon with border */

.icon-border {
  border: 1px solid rgba(0,0,0,.75);
}

/* A house mark (DOCS/design/marks.md): one `currentColor` symbol from upstudy-marks.svg,
   sized by its container -- never a second drawing per size. The <use> inherits `color`,
   so a mark is grey in the nav, fringe on a chip and ink in a heading with no per-mark CSS.
   Containers that want the mark centred in a fixed box (the workspace links) use .mark-box. */
.mark {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -.15em;
  flex-shrink: 0;
}
.mark-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* The view selector (inc/_view_selector.html; popup-header-views 26.08.30): one <details>
   menu that reads the view you're in and lists the thing's other views, ending with Home.
   Lives here, not in popup-form.css, because popups AND page corners render it and pages
   never load popup-form.css. The summary borrows the popup status select's look (border,
   4px radius, .85rem, lowercase); single-class rules only, so nothing from #site-header-nav
   or .sidebar reaches it. Open/close discipline is compositions/js/status-dot-menu.js (data-menu). */
.view-selector {
  display: inline-block;
  font-size: .85rem;
  font-weight: 400;
  line-height: 1.2;
  position: relative;
}
.view-selector > summary {
  align-items: center;
  background: #fff;
  border: 1px solid;
  border-radius: 4px;
  color: inherit;
  cursor: pointer;
  display: inline-flex;
  gap: .3em;
  list-style: none;      /* no native disclosure triangle — the caret is ours */
  padding: .15rem .4rem;
  text-transform: lowercase;
  white-space: nowrap;
}
.view-selector > summary::-webkit-details-marker {
  display: none;
}
.view-selector > summary:focus-visible {
  outline: 2px solid var(--co-purple);
  outline-offset: 1px;
}
.view-selector[open] > summary {
  background: var(--co-pale2);
}
.view-selector-caret {
  font-size: .8em;
  line-height: 1;
}
/* Drops from the summary's RIGHT edge (the control sits in a right-hand corner everywhere it
   renders), above the header it belongs to. */
.view-selector-menu {
  background: #fff;
  border: 1px solid var(--co-light);
  border-radius: .25rem;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
  display: flex;
  flex-direction: column;
  min-width: 8.5rem;
  padding: .25rem;
  position: absolute;
  right: 0;
  top: calc(100% + .25rem);
  z-index: 30;
}
.view-selector-entry {
  align-items: center;
  border-radius: 3px;
  color: inherit;
  display: flex;
  gap: .45rem;
  padding: .3rem .5rem;
  text-decoration: none;
  text-transform: lowercase;
  white-space: nowrap;
}
.view-selector-entry .mark {
  color: var(--co-ink);
}
.view-selector-entry:hover,
.view-selector-entry:focus-visible {
  background: var(--co-pale);
  outline: none;
}
/* The current view: marked (weight + ground), not a link. */
.view-selector-entry.is-current {
  background: var(--co-pale2);
  cursor: default;
  font-weight: 600;
}
.view-selector-home {
  border-top: 1px solid var(--co-edge);
  margin-top: .25rem;
  padding-top: .4rem;
}
.view-selector-home img {
  display: block;
  height: 14px;
  width: auto;
}
/* The instance workspace header and the instance index header lead with the cover (lbk, page
   walk): the cover slot sits LEFT of the identifier line / title / nav, at the person
   workspace's 170px, and the text block takes the rest (basis 0, so it wraps beside the
   cover instead of dropping under it). */
.instance-workspace-header .instance-cover-thumb,
.instance-index-header .instance-cover-thumb {
  height: 170px;
  width: 170px;
}
.instance-workspace-header-main,
.instance-index-header-main {
  flex: 1 1 0;
}

/* The workspace corner (popup-header-views U6): the selector floats into the upper-right of
   a workspace page so the title beside it wraps its own text; a flex host (the person
   header) overrides the float with margin-left:auto + align-self (person-workspace.css). */
.workspace-corner {
  float: right;
  margin: .15rem 0 .5rem 1rem;
}
/* On the study / detail workspaces the selector joins header#meta's set, which reads
   cog · status dot · view · private like the popup's status cluster (lbk, page walk):
   the dot is the read-only .status-dot, and the selector summary + private icon share
   one 20px height. #meta itself stays `align-items: start` for its hover panel. */
#study-workspace #meta .view-selector,
#detail-workspace #meta .view-selector,
#study-workspace #meta > img,
#detail-workspace #meta > img,
#study-workspace #meta > #status,
#detail-workspace #meta > #status {
  align-self: center;
}
#study-workspace #meta > img,
#detail-workspace #meta > img {
  height: 20px;
  width: 20px;
}
/* The meta mark button: the mark drawn at 20px (its 1em), inked like the rest. The hover
 * "meta data" panel opens at top: 28px, so the button — whose height IS the dark tab's —
 * must stand exactly 28px or the tab floats above its window (walk r-8, lbk's screenshots);
 * the side padding gives the mark air before the tab colour's edge. */
#study-workspace #meta .meta-mark-btn,
#detail-workspace #meta .meta-mark-btn {
  box-sizing: border-box;
  color: var(--co-ink);
  font-size: 20px;
  height: 28px;
  line-height: 1;
  padding: 4px 8px;
}
#study-workspace #meta .view-selector > summary,
#detail-workspace #meta .view-selector > summary {
  box-sizing: border-box;
  height: 20px;
  padding: 0 .4rem;
}

/* The summary is mark + caret at every width (lbk, walk r-1 #5 — the word stays in the
   menu); aria-label keeps the accessible name. */
.view-selector-word {
  display: none;
}

/* The status dot + its menu, shared by the to-do row's status picker
   (compositions/list/todo/_status_dot.html) and the popup header's status control
   (core/inc/_status_dot_select.html). Geometry that differs by host — where the menu
   drops from, the dot's size — stays in the host's stylesheet. Open/close discipline is
   compositions/js/status-dot-menu.js (data-menu). */
.status-dot {
  border: 1px solid rgba(0, 0, 0, .15);
  border-radius: 50%;
  display: inline-block;
  flex: 0 0 auto;
  height: .8rem;
  width: .8rem;
}
.status-dot.status-pinned { background: var(--co-purple); }
.status-dot.status-active { background: var(--co-green); }
.status-dot.status-archived { background: var(--co-beige); }
.status-dot-control > summary {
  cursor: pointer;
  list-style: none;    /* drop the native disclosure triangle */
}
.status-dot-control > summary::-webkit-details-marker {
  display: none;
}
.status-dot-menu {
  background: #fff;
  border: 1px solid var(--co-light, #ddd);
  border-radius: .25rem;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
  min-width: 7rem;
  padding: .25rem;
  position: absolute;
  right: 0;
  top: 1.2rem;
  z-index: 5;
}
.status-dot-option {
  align-items: center;
  background: none;
  border: 0;
  cursor: pointer;
  display: flex;
  gap: .4rem;
  padding: .25rem .4rem;
  text-align: left;
  white-space: nowrap;
  width: 100%;
}
.status-dot-option:hover,
.status-dot-option:focus-visible {
  background: var(--co-pale);
  outline: none;
}
.status-dot-option[aria-current="true"] {
  background: var(--co-pale2);
  font-weight: 600;
}

/* Circular icon backgrounds with hover effects */

.icon-circle-border {
  background: none;
  border: 1px solid;
}
[class*=icon-circle] {
  background: #fff;
  border-radius: 50%;
  padding: 0.2em;
  vertical-align: middle;
  transition: .25s all;
}
.icon-circle-border {
  background: none;
  border: 1px solid;
}
:hover > .icon-circle, :hover > .icon-circle-border, :hover > .icon-circle-grey {
  background: var(--co-blue);
  color: #fff;
}
.icon-circle-grey {
  background: var(--co-edge);
}

/* Reusable add (+) button — request via core/templates/inc/_add_button.html. Two
   variants keep + affordances consistent app-wide: --light over light backgrounds,
   --dark over dark. The plus glyph sits in a circle that turns blue on hover/focus. */
.add-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: inherit;
  line-height: 1;
  text-decoration: none;
}
.add-btn .icon-plus {
  font-size: 1.2em;
  border-radius: 50%;
  padding: 0.2em;
  transition: .25s all;
}
.add-btn--light .icon-plus {
  background: #fff;
  border: 1px solid var(--co-edge);
  color: var(--co-dark);
}
.add-btn--dark .icon-plus {
  /* Solid white circle, no border, dark plus — reads cleanly on a dark surface
     (same fill as the light variant, which needs a border only to show on light). */
  background: #fff;
  border: none;
  color: var(--co-dark);
}
.add-btn:hover .icon-plus,
.add-btn:focus .icon-plus {
  background: var(--co-blue);
  border-color: var(--co-blue);
  color: #fff;
}
.add-btn-label {
  /* Small, subtle label — matches the index token's hover "+ cover" affordance. */
  font-size: 0.85rem;
}
/* Sidebar add buttons are compact to fit the dense resources rail. */
.sidebar-add .icon-plus {
  font-size: 1em;
}

/* Add icon for creating new items */

.icon-add {
  border-radius: 50%;
  height: 18px;
  padding: 2px;
  width: 18px; 
}

/* Count badge icon showing numbers */

.icon-count {
  align-items: center;
  background: var(--co-edge);
  border-radius: 50%;
  display: inline-flex;
  font-size: 14px;
  font-weight: 400;
  height: 18px;
  justify-content: center;
  letter-spacing: -1.5%;
  min-width: 18px;
}

/* Icon size adjustments for specific icon types */

.icon-arrow {
  font-size: 1.1em;
}
.icon-cog,
.icon-play,
.icon-plus,
.icon-stop {
  font-size: 1.75em;
}
.icon-x {
  font-size: .8em;
}

/* Margin utility classes */

.m-t-100 {
  margin-top: 1rem;
}
.m-b-100 {
  margin-bottom: 1rem;
}
.m-x-100 {
  margin-left: 1rem;
  margin-right: 1rem;
}	
.m-x-auto {
  margin-left: auto;
  margin-right: auto;
}
.m-y-100 {
  margin-top: 1rem;
  margin-bottom: 1rem;
}

/* Padding utility classes */

.p-t-100 {
  padding-top: 1rem;
}
.p-b-100 {
  padding-bottom: 1rem;
}
.p-x-100 {
  padding-left: 1rem;
  padding-right: 1rem;
}
.p-y-100 {
  padding-top: 1rem;
  padding-bottom: 1rem;
}

/* Sidebar navigation panels (primary and secondary) */

.sidebar {
  background: var(--co-edge);
  box-shadow: 0px 0px .5rem rgba(0,0,0,.35);
  /* Fill the parent #site-main (a flex:1 row inside the full-height column body),
     which is already sized to viewport-minus-header. Using 100% instead of
     calc(100vh - 70px) means the sidebar reaches the bottom regardless of header
     height — the compact internal header (~42px) and the full home header (~70px)
     both work without a per-variant magic number. Mobile resets this to auto. */
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;  
  padding-bottom: 60px;
  transition: .5s all;
  z-index: 100;
}  
/* Direct-child scope (`.sidebar > header`) limits these rules to the top-level */
/* Resources/Tools-Detail-Search header. Without it, the absolute positioning   */
/* cascades into the nested section-card <header> elements and breaks layout.   */
.sidebar > header {
  border-bottom: 1px solid var(--co-fringe);
  position: relative;
  padding: .5rem;
}
.sidebar > header > button {
  font-size: .9rem;
  padding: .5rem;
  position: absolute;
  top: 0;
  transition: .5s all;
  z-index: 1;
}
.sidebar > header h1 {
  display: flex;
  flex: 1;
  font-size: 1rem;
  font-weight: 100;
  line-height: 1;
  margin: 0;
  justify-content: center;
  text-transform: uppercase;
  transition: .5s all;
  white-space: nowrap;
}
.sidebar > header [role=tablist] {
  border-bottom: none;
  margin: -.5rem 0;
}
.sidebar > header [role=tablist] [aria-selected=true] {
  border-color: inherit;
  color: inherit;
}
.sidebar [role=tabpanel] {
  border: none;
  padding: 0;
}

/* Phone tab marks (mission 26.08.30): at phone width the secondary sidebar's rail
   tabs read by mark, not word — Tools + Work Groups take their family marks,
   Pinned the app's purple status dot; Detail Search keeps its word until a mark
   is drawn. The word span stays in the accessibility tree (sr-only, not
   display:none). Desktop: marks hidden, words as before. */
.sidebar [role=tab] > .mark,
.sidebar [role=tab] > .tab-mark-pin,
#sidebar-primary > header h1 > .mark {
  display: none;
}
.sidebar .tab-mark-pin {
  /* the pinned status dot — same 10px --co-purple circle as a pinned search result */
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--co-purple);
}
@media screen and (max-width: 699px) {
  #sidebar-secondary > header [role=tab].tab-marked > .mark,
  #sidebar-secondary > header [role=tab].tab-marked > .tab-mark-pin {
    display: inline-block;
  }
  /* Tap width: the word used to provide it; the bare mark (18px) and the pinned dot
     (10px) are under the 24px WCAG minimum, so the marked rail tab is padded out. */
  #sidebar-secondary > header [role=tab].tab-marked {
    display: inline-block;
    padding: .5rem .75rem;
  }
  #sidebar-secondary > header [role=tab].tab-marked > .mark {
    font-size: 1.2rem;
  }
  /* The primary rail's heading joins the swap (lbk r-3: both rails read by glyph). */
  #sidebar-primary > header h1.tab-marked > .mark {
    display: inline-block;
    font-size: 1.2rem;
  }
  #sidebar-primary > header h1.tab-marked .tab-word,
  #sidebar-secondary > header [role=tab].tab-marked .tab-word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Navigation content within sidebars */

.sidebar nav {
  opacity: 1;
  padding: 1.5rem;
  transition: .5s all .25s;
}
.sidebar nav h2 {
  font-size: 1rem;
  font-style: normal;
  font-weight: 700;
  margin: 1rem 0 0;
  padding-top: .5rem;
  text-transform: uppercase;
}
.sidebar nav ul {
  list-style-type: none;
  margin: .4rem .5rem .4rem .35rem; /* tuck the rail left, narrower (lbk 7/8) */
  text-transform: lowercase;
}
.sidebar nav > div {
  border-top: 1px solid rgba(0,0,0,.1);
  padding: 0 2rem .5rem;
}
.sidebar nav > div h2 {
  padding-top: 0;
}
.sidebar nav .sidebar-callout {
  border-color: rgba(0,0,0,.5);
  margin: 1rem -1.5rem;
  padding-top: 2rem;
}

/* ── Pinned tab tree ─────────────────────────────────────────────────────────
 * Compact, owner-scoped tree of status=pinned items (compositions -> nav-key
 * facets -> instances -> chapters, then studies, then details). Scoped to the
 * panel so the generic `.sidebar nav ul` lowercase + indent rules don't apply
 * and nothing leaks onto other surfaces (incl. base_simple login pages, which
 * never render this panel). Class-based selectors only (no bare `.sidebar
 * header`/`button` descendants that cascade into nested chrome). */
/* Section heading: lowercase Lato Light 18pt (~1.5rem) black, with a 1px divider
 * above and a full line's space separating it from the previous section's last
 * entry. Overrides the inherited `.sidebar nav h2` (uppercase/bold/1rem). */
#side-sec-panel4 .pinned-section-title {
  border-top: 1px solid var(--co-border);
  color: #000;
  font-size: 18pt;
  font-weight: 300;
  letter-spacing: 0;
  margin: 1.5rem 0 .35rem;
  padding-top: .6rem;
  text-transform: lowercase;
}
#side-sec-panel4 .pinned-section:first-child .pinned-section-title {
  margin-top: .35rem;  /* the first head sits just under the tab bar */
}
/* Listings indented under their head. */
#side-sec-panel4 .pinned-tree,
#side-sec-panel4 .pinned-tree ul {
  list-style: none;
  margin: 0;
  padding: 0;
  text-transform: none;  /* override `.sidebar nav ul` lowercase */
}
#side-sec-panel4 .pinned-tree {
  padding-left: 1rem;
}
#side-sec-panel4 .pinned-tree ul {
  padding-left: .85rem;
}
#side-sec-panel4 .pinned-row-main,
#side-sec-panel4 .pinned-row--chapter {
  align-items: center;
  display: flex;
  gap: .35rem;
  padding: .14rem 0;
}
/* Label takes only its content width (so the + sits at its immediate right),
 * shrinking with an ellipsis when the title is too long for the rail. */
#side-sec-panel4 .pinned-row-open,
#side-sec-panel4 .pinned-chapter-label {
  color: var(--co-grey);
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#side-sec-panel4 .pinned-row-open:hover,
#side-sec-panel4 .pinned-row-open:focus-visible {
  text-decoration: underline;
}
#side-sec-panel4 .pinned-row--worksheet > .pinned-row-main > .pinned-row-open {
  font-weight: 700;
}
#side-sec-panel4 .pinned-row .add-btn {
  flex: 0 0 auto;
}
/* Edit (the K mark since walk r-10; was the word "edit") pinned to the far right,
   revealed on row hover/focus; 1rem so the mark stands ~15px in the compact rows. */
#side-sec-panel4 .pinned-row-edit {
  color: var(--co-fringe);
  flex: 0 0 auto;
  font-size: 1rem;
  line-height: 1;
  margin-left: auto;
  opacity: 0;
  transition: opacity .12s;
}
#side-sec-panel4 .pinned-row-main:hover .pinned-row-edit,
#side-sec-panel4 .pinned-row-edit:focus-visible {
  opacity: 1;
}
#side-sec-panel4 .pinned-row-edit:hover {
  color: var(--co-grey);
}
#side-sec-panel4 .pinned-facet-label {
  color: var(--co-fringe);
  font-size: .78rem;
  letter-spacing: .03em;
  text-transform: uppercase;
}
#side-sec-panel4 .pinned-tree-note {
  color: var(--co-fringe);
  font-size: .85rem;
  padding: .5rem 0;
}

/* Text alignment utilities */

.tac {
  text-align: center;
}
.tal {
  text-align: left;
}
.tar {
  text-align: right;
}

/* Choices.js project-wide dropdown rules — apply to every Choices.js
 * dropdown, regardless of which form mounts it.
 *
 * 1. Empty-value placeholder option ("pick a reference", "select a
 *    tag", etc.) doesn't render as a selectable row. Choices.js's
 *    `placeholder: true` config alone needs an explicit `placeholder`
 *    attribute on the <option>, which Django's Select widget doesn't
 *    emit. The empty option still exists for placeholder-text
 *    purposes; it just shouldn't appear as a dropdown choice.
 * 2. Already-selected options (Choices.js puts `.is-selected` on the
 *    dropdown row matching a chosen value) don't appear in the
 *    dropdown. Once chosen, the chip on the input represents the
 *    selection; the dropdown should show what's still pickable, not
 *    duplicate what's already chosen. Holds for both single-select
 *    (chip is the current choice) and multi-select (chips are the
 *    chosen set; dropdown shows the rest).
 *
 *    Note: `[aria-selected="true"]` is NOT the right discriminator —
 *    Choices.js sets that on the keyboard-highlighted row, which is
 *    a different state. The selection state lives on `.is-selected`.
 */
.choices__list--dropdown .choices__item[data-value=""],
.choices__list--dropdown .choices__item.is-selected {
  display: none;
}

/* Suppress the "Press to select" hint Choices.js renders on every dropdown option
   (data-select-text::after, plus a 100px padding reservation for it). Global on
   purpose: it was being stamped out picker-by-picker (template subhead, studies
   segment editor — both now rely on this rule); every dropdown should read as
   plain options. The leading .choices ancestor is load-bearing: upstudy.css loads
   BEFORE choices.min.css in base.html, so at equal specificity the vendor's rules
   win the cascade — and the vendor sets these via TWO selector shapes
   (.choices__list--dropdown … and .choices__list[aria-expanded] …), so both must
   be out-ranked here. */
.choices .choices__list--dropdown .choices__item--selectable[data-select-text],
.choices .choices__list[aria-expanded] .choices__item--selectable[data-select-text] {
  padding-right: 10px;
}
.choices .choices__list--dropdown .choices__item--selectable[data-select-text]::after,
.choices .choices__list[aria-expanded] .choices__item--selectable[data-select-text]::after {
  display: none;
}

/* Tag display styling with arrow graphic */

.tag {
  background: var(--co-mid);
  display: inline-block;
  height: 26px;
  margin: 4px 4px 4px 17px;
  padding: 0 .5rem;
  position: relative;
}
.tag::before {
  background: url('../img/tag-bg.png') no-repeat 0 0 / contain;
  content: '';
  display: block;
  height: 26px;
  width: 13px;
  left: -13px;
  position: absolute;
}

/* Reusable .tag-chip — luggage-tag silhouette via clip-path, taupe bg,
 * punch-hole circle on the left, red × remove button on the right.
 * Generic by design so non-popup surfaces (e.g. Detail's future tags
 * retrofit) can adopt it. Aliased onto #meta-tags-picker's SELECTED
 * chips (`.choices__list--multiple .choices__item--selectable`) — NOT
 * onto dropdown options — so the dropdown rows stay plain-text and only
 * the chosen tags display the tag silhouette.
 *
 * Right edge is a straight vertical (no curve); left edge has the
 * notch + punch-hole.
 */
.tag-chip,
#meta-tags-picker .choices__item[data-item] {
  background: #b8a39d;
  color: #1f1410;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px 4px 22px;
  margin: 0;
  font-size: 0.875rem;
  position: relative;
  /* Override Choices.js's default 20px rounded chip — the luggage-tag
   * silhouette wants a straight right edge to read as a tag, not a pill. */
  border: none;
  border-radius: 0;
  /* Luggage-tag silhouette: pointed left edge (notch) + STRAIGHT right edge. */
  clip-path: polygon(10px 0%, 100% 0%, 100% 100%, 10px 100%, 0% 50%);
}
.tag-chip::before,
#meta-tags-picker .choices__item[data-item]::before {
  /* Punch-hole circle — small white dot near the left edge. */
  content: '';
  position: absolute;
  left: 14px;
  top: 50%;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #ffffff;
  transform: translateY(-50%);
}

/* Red × remove button on selected chips. Choices.js renders the button
 * with its label "Remove item" hidden by `text-indent: -9999px` and a
 * background-image × icon. We keep the text hidden (font-size: 0 collapses
 * the label without disturbing layout) and render our own red × via
 * ::before so the glyph weight + color match the project's typography.
 */
/* Custom × remove button. Choices.js renders <button> with the literal
 * text "Remove item" hidden by text-indent: -9999px and a background-
 * image of an × SVG. We keep the text-indent (don't touch it!) and
 * swap the background-image for a CSS-rendered red × via ::after that
 * sits absolutely inside the button. The button is sized to a visible
 * box so the click target matches the glyph. */
.tag-chip__remove,
.tag-chip .choices__button,
#meta-tags-picker .choices__item[data-item] .choices__button {
  position: relative;
  background-image: none !important;
  background-color: transparent !important;
  border: none !important;
  border-left: none !important;
  cursor: pointer;
  padding: 0 !important;
  margin: 0 0 0 6px !important;
  width: 1.1rem !important;
  height: 1.1rem !important;
  opacity: 1 !important;
  overflow: visible !important;
}
.tag-chip__remove::after,
.tag-chip .choices__button::after,
#meta-tags-picker .choices__item[data-item] .choices__button::after {
  content: '×';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  line-height: 1;
  color: #c0524a;
  font-weight: 700;
  text-indent: 0;
}
.tag-chip__remove:hover::before,
.tag-chip .choices__button:hover::before,
#meta-tags-picker .choices__item[data-item] .choices__button:hover::before {
  color: #8a3530;
}

/* Wrap-flow container for a row of tag chips. */
.tag-chip-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Quote popup Meta-tab tag picker — chips ABOVE the input. Choices.js
 * keeps chips (`.choices__list--multiple`) and the search input
 * (`.choices__input--cloned`) BOTH inside `.choices__inner`. Default
 * DOM order is (chips, input) so plain column-flex gives chips-above-
 * input visually. The dropdown of suggestions then opens naturally
 * below the input, with no chip strip sandwiched between input and
 * dropdown (which read as ambiguous: "are those chips selectable?").
 * Scoped to #meta-tags-picker so other Choices.js multi-selects keep
 * their default inline-chip look.
 */
/* Chip strip sits on the page surface (transparent — lets the meta tab's
 * grey background show through); only the input field is the lighter
 * white surface so it visually reads as the data-entry control. */
#meta-tags-picker .choices__inner {
  display: flex;
  flex-direction: column;
  background-color: transparent;
  padding: 0;
  gap: 10px;
  border: none;
}
#meta-tags-picker .choices__input--cloned {
  background-color: #fff;
  margin: 0;
  padding: 8px;
  order: 2;
  border: 1px solid var(--co-light);
}
#meta-tags-picker .choices__list--multiple {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  background: transparent;
  order: 1;
}
#meta-tags-picker .choices__list--multiple:empty {
  display: none;
}
/* Dropdown options stay plain-text rows (no tag silhouette). Selector
 * keys on [data-choice] which Choices.js puts on dropdown items only —
 * NOT on chips (those carry [data-item]). Earlier [role="listbox"]
 * matched both lists since both are ARIA listboxes. */
#meta-tags-picker .choices__item[data-choice] {
  background: transparent;
  color: inherit;
  clip-path: none;
  padding: 6px 10px;
  font-size: inherit;
  border: none;
  border-radius: 0;
}
#meta-tags-picker .choices__item[data-choice]::before {
  content: none;
}

/* Grid of study/composition tokens on the homepage and index lists.
   Token construct (2026-06-11 redesign): a 210x240 frame holding a 190x190 image
   that, on hover, fades to co-pale and reveals the study/composition synopsis; the
   title sits in the 40px band below the image in both states. The whole token is a
   single link to the workspace (or, for instanced compositions, the instance index);
   a separate `edit` affordance, top-right, opens the entity's popup form. The study
   and composition box partials (_upstudy_box_study / _upstudy_box_composition) share
   this construct, differing only in their link + edit-popup wiring. */

.upstudy-boxes {
  display: flex;
  flex-wrap: wrap;
  gap: 70px;
  margin: 0;
}
.upstudy-boxes > li {
  /* Bare track cell. In the carousel, tiny-slider sizes the <li> (fixedWidth +
     gutter) and renders the gutter as the cell's transparent padding-right — so
     the gap never tints the token. The visible frame is the inner .upstudy-box.
     flex:none stops the padded flex container from shrinking the cell. */
  flex: none;
  /* As a flex item the <li> keeps `display: list-item` in some browsers, so its
     marker still renders — drop it (the box grid is never a bulleted list). */
  list-style: none;
}
/* The visible 210x240 frame. Separate from the <li> track cell so the carousel
   gutter stays outside it (see the li note above). */
.upstudy-box {
  background: var(--co-mid);
  border-radius: 5px;
  width: 210px;
  height: 240px;
  /* Image inset is 10 top/sides; the remaining 40px at the bottom is the title band. */
  padding: 10px 10px 0;
  position: relative;
  overflow: hidden;
  box-sizing: border-box;
}
/* Whole-token link: stacks the 190x190 image over the title band and fills the frame,
   so a click anywhere outside the edit affordance follows it. */
.upstudy-box-open {
  display: flex;
  flex-direction: column;
  height: 100%;
  color: inherit;
  text-decoration: none;
}
.upstudy-boxes h2 {
  color: #000;
  font-size: 1rem;
  font-style: normal;
  font-weight: 500;
  /* Fill and centre the title within the 40px band below the image. */
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: normal;
  margin: 0;
  min-height: 0;
  padding: 0 5px;
  text-align: center;
}
.upstudy-boxes a {
  color: inherit;
  font-weight: inherit;
  text-decoration: none;
}
.upstudy-boxes a:hover,
.upstudy-boxes a:focus {
  color: inherit;
}

/* Image + synopsis overlay — the 190x190 square. */

.upstudy-box figure {
  border-radius: 5px;
  width: 190px;
  height: 190px;
  flex: none;
  overflow: hidden;
  position: relative;
}
.upstudy-box img {
  width: 190px;
  height: 190px;
  object-fit: cover;
  display: block;
}
/* Synopsis overlay: hidden until token hover/focus, then fades the image to co-pale.
   Flex centres the clamped text block vertically; the text itself is left-aligned. */
.upstudy-box figcaption {
  background: var(--co-pale);
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: .5s all;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* Synopsis text padding: 30 top, 15 sides/bottom. */
  padding: 30px 15px 15px;
  box-sizing: border-box;
}
.upstudy-box figcaption .box-synopsis {
  color: #000;
  font-size: 0.95rem;
  line-height: 1.35;
  text-align: left;
  /* Whole-word truncation comes from the template (striptags|truncatewords); this
     line-clamp is the box-fit safety net so an unusually long synopsis can't
     overflow the 190px square. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
  overflow: hidden;
}
.upstudy-box:hover figcaption,
.upstudy-box:focus-within figcaption {
  opacity: 1;
}
/* A cover with a synopsis but no image shows the synopsis at rest (in place of the
   image) instead of an empty image area revealed only on hover. */
.upstudy-box figcaption.figcaption-at-rest {
  opacity: 1;
}

/* Edit affordance: top-right of the image, fades in on token hover/focus, turns
   co-link blue on its own hover. Sits above the synopsis overlay (z-index) so it
   stays clickable, and is a sibling of — not nested in — the whole-token link. */
.upstudy-boxes .upstudy-box-edit {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  opacity: 0;
  color: var(--co-dark);
  /* The K mark (walk r-9; was the lowercase word "edit") at the corner door's size —
     the mark inside is 1em of this. Hover keeps the dark-→-link-blue read. */
  font-size: 1.25rem;
  line-height: 1;
  transition: .25s all;
}
.upstudy-box:hover .upstudy-box-edit,
.upstudy-box:focus-within .upstudy-box-edit {
  opacity: 1;
}
.upstudy-boxes .upstudy-box-edit:hover,
.upstudy-boxes .upstudy-box-edit:focus {
  color: var(--co-link);
}

/* Corner door (workable non-leaf instances): a drill token whose level holds a coarser
   instance (e.g. the 2026 annual on the year grid) carries the workspace (ws) icon in
   the top-right slot — it REPLACES the edit/"+ cover" affordance there (the template
   makes them mutually exclusive; cover management lives inside the workspace header).
   Same position + fade-in-on-hover treatment as the edit affordance it stands in for. */
.upstudy-boxes .upstudy-box-door {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  opacity: 0;
  width: 1.5rem;
  height: 1.25rem;
  font-size: 1.25rem;  /* the workspace mark inside is 1em of this */
  color: var(--co-ink);
  transition: .25s all;
}
.upstudy-box:hover .upstudy-box-door,
.upstudy-box:focus-within .upstudy-box-door {
  opacity: 1;
}
.upstudy-boxes .upstudy-box-door:hover,
.upstudy-boxes .upstudy-box-door:focus {
  opacity: 0.7;
}

/* ---- Composition instance index (years -> months drill) ----
   The index extends base.html, so its chrome lives here (not instances.css, which
   only loads inside the composition workspace). The breadcrumb mirrors the
   instance workspace header's identifier-line; tokens are the year/month chips. */
.instance-index .identifier-line {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.15rem;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.instance-index .identifier-line .identifier-title {
  opacity: 0.7;
}
.instance-index .identifier-line a.identifier-title:hover,
.instance-index .identifier-line a.identifier-title:focus {
  opacity: 1;
  color: var(--co-link);
}
.instance-index .identifier-line .is-current {
  opacity: 1;
}

/* < year > subhead under the h1: prev/next chevrons flank the selected year. */
.instance-index .instance-year-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.25rem 0 1rem;
}
.instance-index .instance-year-label {
  font-weight: 600;
  min-width: 3.5em;
  text-align: center;
}
.instance-index .instance-year-row .btn-plain {
  display: inline-flex;
  align-items: center;
}
.instance-index .instance-year-row .is-disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

/* Study filter overlay (U5): "All | Study | …" scope row above the index grid. */
.instance-index .instance-study-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin: 0.25rem 0 1rem;
  font-size: 0.9em;
}
.instance-index .instance-study-filter-item {
  text-decoration: none;
  color: var(--co-mid, #777);
}
.instance-index .instance-study-filter-item.is-current {
  font-weight: 600;
  color: inherit;
}

/* Year / month tokens: small chips laid out in a wrapping row. */
.instance-tokens {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.instance-token {
  display: inline-block;
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--co-edge);
  border-radius: 0.4rem;
  line-height: 1.2;
}
.instance-token:hover,
.instance-token:focus {
  border-color: var(--co-link);
  color: var(--co-link);
}
.instance-token--unkeyed {
  font-style: italic;
}

/* ---- Cover thumbnail (shared) ----
   The instance workspace header's live cover and the nav-index breadcrumb cover both
   render the same no-frame thumbnail (compositions/instances/_cover_thumb.html). These
   rules live here (not instances.css) so the breadcrumb works on the index pages, which
   extend base.html and never load the workspace-only instances.css. The workspace also
   loads upstudy.css, so its header cover picks these up too; only the remove "x" overlay
   is workspace-specific and stays in instances.css. */
.instance-cover-slot {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
}
.instance-cover-thumb {
  position: relative;
  flex: none;
  display: block;
  width: 120px;
  height: 120px;
  border-radius: 5px;
  overflow: hidden;
}
.instance-cover-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.instance-cover-thumb-label {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 0 0.5rem;
  background: var(--co-mid);
  font-size: 0.8rem;
  text-align: center;
}
/* Synopsis treatment, mirroring the index/home tokens (no frame): over an image it
   fades in on hover; with no image it shows at rest in place of the image. A strip
   along the top is reserved for the hover actions (the remove "x" on the workspace
   cover); the synopsis text is centered in the space below it. */
.instance-cover-thumb-synopsis {
  position: absolute;
  inset: 0;
  background: var(--co-pale);
  color: #000;
  padding: 24px 8px 8px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}
.instance-cover-thumb-synopsis-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  overflow: hidden;
  font-size: 0.72rem;
  line-height: 1.3;
}
.instance-cover-thumb-synopsis.is-hover {
  opacity: 0;
  transition: opacity .4s;
}
.instance-cover-thumb:hover .instance-cover-thumb-synopsis.is-hover,
.instance-cover-thumb:focus-within .instance-cover-thumb-synopsis.is-hover {
  opacity: 1;
}
/* Hover "x" to remove the current cover — tucked into the thumbnail's top-right corner,
   revealed on slot hover (mirrors the index token's edit affordance). Used on both the
   workspace header cover and the nav-index breadcrumb cover. */
.instance-cover-remove-form {
  position: absolute;
  top: 4px;
  right: 4px;
  margin: 0;
  opacity: 0;
  transition: opacity .25s;
  /* Lift above the synopsis overlay (which fills the thumb and paints after this
     form in DOM order) so the "x" stays visible and clickable on hover. */
  z-index: 2;
}
.instance-cover-slot:hover .instance-cover-remove-form,
.instance-cover-slot:focus-within .instance-cover-remove-form {
  opacity: 1;
}
.instance-cover-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--co-edge);
  color: var(--co-dark);
  transition: .25s all;
}
.instance-cover-remove:hover,
.instance-cover-remove:focus {
  background: var(--co-blue);
  border-color: var(--co-blue);
  color: #fff;
}
.instance-cover-remove .icon-x {
  font-size: 0.55em;
}

/* ---- Nav-index breadcrumb cover ----
   Each index level shows, in its upper-right, the cover of the item clicked to reach it:
   the composition token on the years view, the selected year's cover on the months view
   (or a "cover +" add affordance when that level has no cover yet). The header row floats
   the title/identifier block on the left, the cover on the right, with equal breathing
   room above and below the token. */
.instance-index-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  /* The token sits at the top of #site-content (its breathing room above); match it
     below so the token isn't crammed against the token grid that follows. */
  margin-bottom: 20px;
}
.instance-index-header-main {
  min-width: 0;
}
/* The composition title carries a new-instance "+". Keep it in the inline flow (not a
   flex item) so it trails the last word of the heading rather than dropping to its own
   line when the title wraps; a fixed font-size detaches the "+" from the large h1 scale. */
.instance-index-create-add {
  font-size: 1rem;
  vertical-align: middle;
  margin-left: 0.3rem;
}
.instance-cover-slot--breadcrumb {
  align-self: flex-start;
}

/* Edit/delete action buttons that slide in on hover over study/composition boxes */

.upstudy-actions {
  display: flex;
  font-weight: 500;
  gap: 10px;
  opacity: 0;
  padding: 15px 20px;
  position: absolute;
  top: 0;
  right: 0;
  transform: translateX(100%);
  transition: .5s all;
}
.upstudy-actions button, .upstudy-actions a {
  background: var(--co-edge);
  border-radius: 50%;
  box-shadow: 0 0 1rem rgba(0,0,0,.5);
  color: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  height: 25px;
  width: 25px;
  transition: .25s all;
}
.upstudy-actions a:hover,
.upstudy-actions button:hover {
  background: var(--co-blue);
  color: var(--co-edge);
}
li:focus-within .upstudy-actions,
li:focus .upstudy-actions,
li:hover .upstudy-actions {
  opacity: 1;
  transform: translateX(0);
}



/* Vertical alignment utilities */

.va-bottom {
  vertical-align: bottom;
}
.va-middle {
  vertical-align: middle;
}
.va-top {
  vertical-align: top;
}

/* Full width utility */

.w-100 {
  width: 100%;
}

/* Primary sidebar (left side with navigation) */

#sidebar-primary {
  order: -1;
  width: 363px;
}
#sidebar-primary > header > button {
  right: 0;
}
#sidebar-primary > header h1 {
  padding-right: 1rem;
}

/* Resources sidebar — collapsible section card stack.                          */
/* Each .sidebar-section is an independent card with its own open/closed state  */
/* persisted in localStorage (upstudy.sidebar.{name}). The Library card holds   */
/* sub-headings inside its body; Glossary/Studies/Compositions hold flat        */
/* listing slots. See DOCS/brainstorms/2026-05-29-resources-sidebar-tweak.md.   */
.sidebar-sections {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.25rem;
}
#sidebar-primary nav.sidebar-sections {
  /* Override the generic .sidebar nav padding so the gap rule above controls    */
  /* inter-card spacing instead of nav's 1.5rem all-around.                      */
  padding: 1.25rem;
}
/* Per the mockup: the card is a thin co-mid outline with transparent body so   */
/* the sidebar grey shows through; only the section header bar carries the      */
/* white surface. Border-radius rounds the outer corners; the header gets its   */
/* own slightly-smaller radius so the header bg clips against the card border.  */
.sidebar-section {
  background: transparent;
  border: 1px solid var(--co-mid);
  border-radius: 6px;
}
.sidebar-section-header {
  align-items: center;
  background: #fff;
  border-radius: 5px 5px 0 0;
  display: flex;
  gap: .5rem;
  padding: .75rem 1rem;
}
.sidebar-section-closed .sidebar-section-header,
.sidebar-section-empty .sidebar-section-header {
  /* All four corners rounded when no body is visible. */
  border-radius: 5px;
}
.sidebar-section-label {
  font-size: 1rem;
  font-weight: 700;
  text-transform: lowercase;
}
/* The People section's body lists person NICKNAMES — user-authored casing that
   the generic `.sidebar nav ul` lowercase rule must not rewrite (the mounted-
   content bleed of DOCS/solutions/2026-07-01-sidebar-nav-lowercase-bleeds-into-
   mounted-content.md). Pre-empted here even though the body slot ships empty
   (contextual population is deferred wiring shared by all sections). */
.sidebar-section[data-section="people"] .sidebar-section-body,
.sidebar-section[data-section="people"] .sidebar-section-body ul {
  text-transform: none;
}
/* Toggle sits flush right. Closed = thin chevron-right outline (no bg).        */
/* Open  = solid co-dark circle with a white chevron-down.                      */
/* CSS-drawn chevron via ::before so stroke weight is controllable per state.   */
.sidebar-section-toggle {
  align-items: center;
  background: transparent;
  border: none;
  cursor: pointer;
  display: flex;
  height: 22px;
  justify-content: center;
  margin-left: auto;
  padding: 0;
  width: 22px;
}
/* Hide the SVG chevron in the markup; the shared .chevron-line ::before is the visible one. */
.sidebar-section-chevron {
  display: none;
}
/* Shared closed-state disclosure glyph: a discreet 1px diagonal "line chevron" drawn via      */
/* ::before for white-surface toggles (no disc, no ring). Consumers: .sidebar-section-toggle    */
/* (closed caret, below) and the worksheet slot quick-add toggle (compositions worksheet         */
/* extra_styles.css). Each consumer owns its OPEN state, which overrides or suppresses this      */
/* glyph: the sidebar flips it to a white 2px down-caret; the worksheet swaps in a .btn-circle    */
/* .active disc + rotated SVG and removes this glyph. One definition, no drift.                   */
.chevron-line::before {
  border-bottom: 1px solid var(--co-dark);
  border-right: 1px solid var(--co-dark);
  content: "";
  display: inline-block;
  height: 6px;
  margin-right: 2px; /* nudges the visual centre of the > toward the box centre */
  transform: rotate(-45deg);
  width: 6px;
}
.sidebar-section-open .sidebar-section-toggle {
  background: var(--co-dark);
  border-radius: 50%;
}
.sidebar-section-open .sidebar-section-toggle::before {
  border-bottom-width: 2px;
  border-color: #fff;
  border-right-width: 2px;
  height: 5px;
  margin-right: 0;
  margin-bottom: 2px; /* visually centres the v under rotation */
  transform: rotate(45deg);
  width: 5px;
}
/* Sections whose body has no rendered content (empty {% block %}) hide the    */
/* chevron entirely — there's nothing to toggle into view. JS sets this class  */
/* on init based on whether the body has any element children. Library always  */
/* has its sub-section headings inside the body, so it never matches.          */
.sidebar-section-empty .sidebar-section-toggle {
  display: none;
}
.sidebar-section-empty .sidebar-section-body {
  display: none;
}
.sidebar-section-add {
  flex-shrink: 0;
}
/* Find-and-attach affordance (magnifier): understated, sits just left of the chevron   */
/* toggle. Muted by default so it reads as secondary to the (+); darkens on hover.       */
.sidebar-section-find {
  align-items: center;
  color: var(--co-mid);
  cursor: pointer;
  display: flex;
  flex-shrink: 0;
  height: 20px;
  justify-content: center;
  padding: 0;
  width: 20px;
}
.sidebar-section-find:hover,
.sidebar-section-find:focus-visible {
  color: var(--co-dark);
}
.sidebar-section-find-icon {
  height: 14px;
  width: 14px;
}
/* Library sub-headings carry their +/🔍 inline; keep the find glyph aligned with them. */
.sidebar-heading-with-add .sidebar-section-find {
  height: 16px;
  width: 16px;
}
.sidebar-section-body {
  /* The body sits below the header; on close, JS toggles                      */
  /* .sidebar-section-closed which hides it. Padding lives inside each variant  */
  /* (library/glossary/etc.) so an empty body collapses to header-only height.  */
  padding: 1rem;
}
.sidebar-section-closed .sidebar-section-body {
  display: none;
}
/* Contextual body links (a workspace slotting rows into a section — study's
   keyed comps, a person's relations) stack one per line rather than flowing
   inline as a run-on paragraph. Direct children only: the library card's
   nested subsection markup is untouched. */
.sidebar-section-body > a {
  display: block;
  padding: .1rem 0;
}
.sidebar-relation-reading {
  color: var(--co-mid);
}
/* Library card uses sub-headings inside its body. Each subsection has its own */
/* + entry placed right after the label (not flex-pushed to the row's far end),*/
/* and a listing slot below.                                                   */
.sidebar-subsection {
  /* Slight indent so sub-section labels read as nested under the Library card  */
  /* heading — matches the mockup's stagger.                                    */
  padding-left: .5rem;
}
.sidebar-subsection + .sidebar-subsection {
  margin-top: 1.25rem;
}
.sidebar-subsection-heading {
  align-items: center;
  color: var(--co-grey, #555);
  display: flex;
  font-size: .85rem;
  font-weight: 400;
  gap: .5em;
  letter-spacing: .02em;
  margin: 0 0 .5rem;
  text-transform: lowercase;
}
.sidebar-subsection-list {
  list-style: none;
  margin: 0;
  padding-left: 0; /* main items sit flush-left with their sub-heading (lbk 7/8) */
}
.sidebar-subsection-list:empty {
  display: none;
}

/* Refrain portfolio: per-composition group (title link + dark (+) that reveals an inline add  */
/* row), and the inline editor that opens below it (R8). Minimal functional layout only —      */
/* visual polish is deferred to the styleguide pass.                                           */
.detail-comp-heading {
  align-items: center;
  display: flex;
  gap: .5rem;
  justify-content: space-between;
}
.detail-comp-heading .detail-comp-add-toggle {
  flex: 0 0 auto;
}
.detail-comp-add-input {
  box-sizing: border-box;
  width: 100%;
}
.instance-inline-editor:empty {
  display: none;
}
.instance-inline-editing {
  border-top: 1px solid var(--co-line, #ddd);
  margin-top: .5rem;
  padding-top: .5rem;
}
.instance-inline-name {
  align-items: baseline;
  display: flex;
  gap: .5rem;
  justify-content: space-between;
  margin: 0 0 .35rem;
}
.instance-inline-text {
  font-weight: 600;
}
.instance-inline-open {
  font-size: .8rem;
}
.instance-inline-done {
  margin-top: .5rem;
}
/* The keyed-instance inline editor mounts real Detail content — an entry list plus a
   chromeless CKEditor add field — inside the sidebar, where `.sidebar nav ul` forces
   lowercase for the navigation lists. That bleeds into the entry rows AND the field you
   TYPE into, so typed capitals appear to vanish (the data is stored correctly; only the
   display is transformed). This is content, not nav — restore normal casing. Scoped to
   `.sortable-list` so it out-specifies `.sidebar nav ul`; the read-only instance-name
   links in `.sidebar-subsection-list` stay lowercase, matching the other sidebar lists. */
.instance-inline-editor .sortable-list {
  text-transform: none;
}

/* Compositions + popup. Borderless white panel anchored under the + button,    */
/* separated by a soft drop-shadow rather than a drawn border or visible neck.  */
/* The custom neck/border-merge treatment from the mockup is deferred until     */
/* the SVG mockup of the combined shape is available — pure CSS can't merge a   */
/* circle border with a rectangle border cleanly. See session log for context.  */
.sidebar-compositions-add {
  position: relative;
}
.sidebar-compositions-dropdown {
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0,0,0,.18);
  display: none;
  flex-direction: column;
  padding: .35rem 0;
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  width: max-content;
  z-index: 50;
}
.sidebar-compositions-add:focus-within .sidebar-compositions-dropdown {
  display: flex;
}
.sidebar-compositions-dropdown a {
  color: var(--co-grey, #555);
  font-size: .9rem;
  font-weight: 400;
  padding: .25em 1.1em;
  text-align: left;
  text-decoration: none;
  text-transform: lowercase;
}
.sidebar-compositions-dropdown a:hover,
.sidebar-compositions-dropdown a:focus {
  color: #000;
  font-weight: 700;
  text-decoration: none;
}

/* Collapsed state of primary sidebar */

body.sb-pri-closed #sidebar-primary {
  width: 42px;
}
body.sb-pri-closed #sidebar-primary > header {
  border: none;
}
body.sb-pri-closed #sidebar-primary > nav {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0 1.5rem;
  transition: .25s all;
}
body.sb-pri-closed #sidebar-primary > header > button {
  transform: rotate(180deg);
}
body.sb-pri-closed #sidebar-primary > header h1 {
  font-weight: 500;
  padding: .5rem;
  position: relative;
  justify-content: start;
  transform: rotate(90deg);
  transform-origin: 0 100%;
}

/* Secondary sidebar (right side with details/search) */

#sidebar-secondary {
  order: 2;
  width: 566px;
}
#sidebar-secondary > header > button {
  left: 0;
}
#sidebar-secondary > header h1 {
  padding-left: 1rem;
}

/* Collapsed state of secondary sidebar */

body.sb-sec-closed #sidebar-secondary {
  width: 42px;
}
body.sb-sec-closed #sidebar-secondary > header {
  border: none;
}
body.sb-sec-closed #sidebar-secondary > nav {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0 1.5rem;
  transition: .25s all;
}
body.sb-sec-closed #sidebar-secondary > header > button {
  transform: rotate(-180deg);
}
body.sb-sec-closed #sidebar-secondary > header [role=tablist] {
  margin: 0;
}
body.sb-sec-closed #sidebar-secondary > header [role=tab] {
  border-bottom: none;
  line-height: normal;
  padding: 0;
}
body.sb-sec-closed #sidebar-secondary > header [aria-selected=true] {
  color: inherit;
  pointer-events: none;
}
body.sb-sec-closed #sidebar-secondary > header [aria-selected=false] {
  display: none;
}
body.sb-sec-closed #sidebar-secondary > header h1 {
  font-weight: 500;
  padding: .5rem;
  position: relative;
  justify-content: end;
  transform: rotate(-90deg);
  transform-origin: 100% 100%;
}
body.sb-sec-closed #sticky-new-detail {
  display: block;
}

/* Site header with logo, search, and navigation */

#site-header {
  align-items: center;
  background: var(--co-dark);
  color: #fff;
  display: flex;
  flex-wrap: wrap;
  font-size: max(12px,.93333rem);
  gap: 1rem;
  padding: 1.25rem 20px;
}
#site-header-brand {
  color: inherit;
  font-size: 3.3rem;
  font-weight: 300;
  line-height: 1;
  margin-right: auto;
  text-decoration: none;
}
#site-header-brand img {
  display: inline-block;
  vertical-align: bottom;
  max-width: 18%;
  width: 48px;
}

/* Compact internal-page header.
   The rules above are the full-size header, kept for the homepage. Every
   internal full-page app view gets the compact header below — "compact is the
   default", so a new internal page needs no opt-in. Two chrome variants opt
   out via {% block body_class %}: the homepage (`home`, keeps the full header)
   and the stripped auth/simple pages (`simple`, base_simple.html — logo only,
   no nav, left as-is). */
body:not(.home):not(.simple) #site-header {
  padding: .35rem 20px;
}
/* The "new detail" button's tall 1em vertical padding (52px) otherwise sets the
   compact header's height floor. Trim it so the ~30px nav links become the
   tallest element and the bar reads as a slim toolbar (~42px). */
body:not(.home):not(.simple) #site-header .btn-new {
  padding: .2em 0;
}
body:not(.home):not(.simple) #site-header-brand {
  font-size: 1.7rem;
  /* The page title carries the flex spacer instead (see .page-title below);
     fall back to the brand pushing the nav right when a page sets no title. */
  margin-right: 0;
}
body:not(.home):not(.simple) #site-header:not(:has(.page-title)) #site-header-brand {
  margin-right: auto;
}
body:not(.home):not(.simple) #site-header-brand img {
  width: 26px;
}

/* Internal-page title (e.g. "Study Workspace"), Lato Light Italic per the
   mockup. margin-right:auto pushes the search/nav cluster to the right edge,
   replacing the brand's spacer on internal pages. Hidden on the homepage,
   which shows the full header with no title. */
#site-header .page-title {
  color: rgba(255, 255, 255, .7);
  font-family: 'Lato', sans-serif;
  font-size: 1.5rem;
  font-style: italic;
  font-weight: 300;
  line-height: 1;
  margin-right: auto;
}
body.home #site-header .page-title {
  display: none;
}

/* Search box in site header */

#site-header-search {
  background: var(--co-edge);
  border-radius: 1em;
  color: var(--co-dark);
  display: grid;
  grid-template-columns: 1fr 24px;
  gap: 4px;
  max-width: 100%;
  padding: 0 0 0 .5em;
  position: relative;
}
#site-header-search input {
  background: none;
  border: none;
  color: inherit;
  font-family: inherit;
  padding: 0 4px;
  width: 100%;
}
#site-header-search button {
  background: var(--co-mid);
  border: none;
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  height: 24px;
  width: 24px;
  visibility: hidden;
}
#site-header-search:focus-within button {
  visibility: visible;
}

/* Navigation links in site header */

#site-header-nav {
  font-weight: 700;
}
#site-header-nav a {
  padding: .5em;
}

/* User menu dropdown in site header */

#site-header-nav > button {
  padding: .5em;
  position: relative;
}
#site-header-nav > button > div {
  background: var(--co-fringe);
  display: none;
  position: absolute;
  top: -100%;
  right: 0;
  text-align: right;
  width: 150px;
  z-index: 1000;
}
#site-header-nav > button > div a {
  display: block;
}
#site-header-nav > button:focus-within > div {
  display: block;
  top: 100%;
}
/* Compositions dropdown opens on click via the generic :focus-within rule   */
/* above (same as the user menu) — the trigger is now a small + button, so    */
/* hover-to-open would fire whenever the mouse drifts toward the adjacent     */
/* "compositions" index link.                                                 */
.compositions-dropdown {
  text-align: left !important;  /* override parent .right alignment */
  min-width: 0;
}
/* White comp-type panel per the header-nav mockup (the shared dropdown chrome */
/* above is taupe). Slim (sized to content), rounded, soft shadow. Selector    */
/* carries button > child specificity to beat the generic dropdown rule.       */
#site-header-nav > button > .compositions-dropdown {
  background: #fff;
  border-radius: 6px 0 6px 6px;  /* square top-right so it lines up flush under the tail */
  box-shadow: 0 3px 8px rgba(0, 0, 0, .18);
  padding: .4em 0;
  right: 7px;                    /* right edge flush with the tail / + circle (button's right padding) */
  width: max-content;
}
/* Pull the panel up just under the + so the neck stays short (mockup). The     */
/* :focus-within scope outranks the generic open rule's top: 100%.              */
#site-header-nav > button:focus-within > .compositions-dropdown {
  top: 25px;
}
#site-header-nav > button > .compositions-dropdown a {
  color: var(--co-grey);
  font-weight: 400;
  padding: .2em 1.1em;
}
#site-header-nav > button > .compositions-dropdown a:hover,
#site-header-nav > button > .compositions-dropdown a:focus {
  color: #000;
  font-weight: 700;
  text-decoration: none;
}

/* Inline "+" entry-point buttons in the header nav (studies, compositions).  */
/* Reuse the header's bordered-circle + icon (as on "new detail") at a        */
/* smaller size; the compositions one also toggles its dropdown.              */
/* Comp-type head (compositions index) + affordance. Mirrors the instance     */
/* workspace's identifier-line add button (instances.css .instance-add): an     */
/* outlined white circle with a + glyph that turns blue on hover (base          */
/* :hover > .icon-circle), set inline beside the heading via flex + gap.        */
.comp-type-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.comp-type-add {
  cursor: pointer;
}
.comp-type-add .icon-plus {
  font-size: 1rem;
}
.comp-type-add .icon-circle {
  border: 1px solid var(--co-edge);
}

.nav-add-link {
  cursor: pointer;
  padding: .25em;
  vertical-align: middle;
}
/* The + links hold only the circle (no text), so the nav's underline-on-hover */
/* would decorate the whitespace beside the icon. Suppress it (studies + is an  */
/* <a>; the compositions + is a <button>, unaffected).                          */
#site-header-nav a.nav-add-link:hover,
#site-header-nav a.nav-add-link:focus {
  text-decoration: none;
}
.nav-add-link .icon-plus {
  font-size: 1.15em;
}
/* Filled white circle with a dark + glyph (mockup), not the outline ring the  */
/* "new detail" button uses. Wins the blue :hover > .icon-circle rule via      */
/* later source order, so the circles stay white on hover. Raised above the    */
/* connector tail (below) so the rounded top + glyph stay visible.             */
.nav-add-link .icon-circle {
  background: #fff;
  color: var(--co-grey);
  position: relative;
  z-index: 2;
}
/* Connector tail: a same-width white drop from the circle's vertical midpoint */
/* into the open panel, painted behind the circle so the rounded top reads as  */
/* a cap and the straight sides emerge from the midpoint down. Only the        */
/* compositions + (a <button>) has a panel, and it shows only while open.      */
#site-header-nav > button.nav-add-link:focus-within::after {
  content: "";
  position: absolute;
  left: 50%;
  margin-left: -8px;
  top: 50%;
  bottom: 0;
  width: 16px;
  background: #fff;
  z-index: 1;
}

/* Site-wide messages banner (ledger-seeding-surface U7/KTD8) — the site's
   first Django-messages render block, directly under #site-header. Django
   messages are consumed on render, so this is one-time by construction; no
   dismiss control needed. Success = a calm green-tinged neutral; warning
   reuses .overload-note's amber (same "adjust/attend to this" register);
   error added for completeness though nothing emits it yet. */
.site-messages {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.site-message {
  border-left: 3px solid #b9b5ac;
  font-size: .85rem;
  padding: .5rem 1rem;
}
.site-message-success {
  background: #f1f6ef;
  border-left-color: #7fa374;
}
.site-message-warning {
  background: #fbf5e9;
  border-left-color: #c9a24b;
}
.site-message-error {
  background: #faf1ef;
  border-left-color: #b9584f;
}

/* Main content area between sidebars */

#site-main {
  display: flex;
  flex: 1;
  /* min-height:0 overrides the flex default (min-height:auto = min-content), so
     a tall page (e.g. the home landing) can't inflate #site-main past its
     flex-allotted height. Without it, #site-main grows to fit its content and the
     height:100% columns below resolve against that inflated height instead of the
     viewport-minus-header space — which is what let the home columns run far past
     the bottom. With it, #site-main stays at the available height and the columns
     scroll internally (their own overflow-y:auto). */
  min-height: 0;
  margin: 0 auto;
  position: relative;
  width: 100%;
}
#site-content {
  flex: 1;
  /* Fill #site-main (already sized to viewport-minus-header) rather than
     recomputing from a hard-coded header height. With the content and both
     sidebars all at height:100%, none imposes an intrinsic height, so #site-main
     stays at its flex-allotted height and every column reaches the bottom for
     any header size — the compact internal header AND the taller home header.
     (The old calc(100vh - 70px) / 42px override pair assumed fixed header pixels
     and left the home columns overflowing by the header's excess.) Mobile resets
     this to auto where #site-main stacks. */
  height: 100%;
  overflow-y: auto;
  /* Top was 40px to reserve room for action links since moved into the sidebar;
     halved so the content header sits closer to the header bar. */
  padding: 20px 40px 120px;
}

/* Footer at bottom of page with links */

#site-footer {
  background: rgba(255,255,255,.75);
  border-radius: 2em 2em 0 0;
  box-shadow: 0 0 .5rem rgba(0,0,0,.35);
  color: #000;
  display: flex;
  flex-wrap: wrap;
  font-size: max(12px,.93333rem);
  gap: 1em;
  justify-content: space-between;
  position: fixed;
  left: 5%;
  bottom: 0;
  padding: 1em 5%;
  width: 90%;
  z-index: 200;
}
#site-header-nav a,
#site-footer-nav a {
  color: inherit;
  font-weight: inherit;
  text-decoration: none;
}
#site-header-nav a:hover,
#site-header-nav a:focus,
#site-footer-nav a:hover,
#site-footer-nav a:focus {
  color: inherit;
  text-decoration: underline;
}
/* Header add-buttons ("new detail" etc.) sit outside #site-header-nav, so the base */
/* a:hover would tint the label blue (--co-link). Match the nav links instead: keep */
/* the word white and underline it on hover; only its + circle turns blue (via the  */
/* .add-btn:hover .icon-plus rule).                                                  */
#site-header .add-btn:hover,
#site-header .add-btn:focus {
  color: inherit;
}
#site-header .add-btn:hover .add-btn-label,
#site-header .add-btn:focus .add-btn-label {
  text-decoration: underline;
}

/* Floating "add new detail" button shown when secondary sidebar is closed */

#sticky-new-detail {
  display: none;
  position: absolute;
  top: 20px;
  right: 80px;
}

/* Simple content wrapper for login and other simple pages */

#simple-content {
  /* Mirror #site-content's fill-and-scroll: html{overflow:hidden} means pages
     scroll inside their own column, and #simple-content never had that rule —
     invisible while simple pages were short (login), but a tall one (rendered
     docs, the scenario index, /dev/history/) was clipped with no scrollbar.
     (Both the dev-history and docs-browser branches fixed this identically;
     deduped at merge.) */
  flex: 1;
  height: 100%;
  overflow-y: auto;
  padding: 40px;
}
#simple-content form label {
  display: block;
}
#simple-content form input,
#simple-content form button {
  padding: .5rem 1rem;
}

/* The curtain (anonymous `/` and the login door — home/templates/home/curtain.html):
   one centered column, four lines and a slot the forms unfold into. Scoped under
   #simple-content so /dev/ docs pages and the styleguide, which share base_simple,
   do not shift. Nothing here renders on a page without .curtain. */
#simple-content .curtain {
  align-items: center;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  justify-content: center;
  margin: 0 auto;
  max-width: 36rem;
  min-height: 100%;
  text-align: center;
}
#simple-content .curtain-title {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.2;
  margin: 0;
  text-transform: none; /* the global h1 uppercases; the curtain reads "UpStudy" as written */
}
#simple-content .curtain-title-line {
  display: block;
  font-size: 1.2rem;
  font-weight: 300;
}
#simple-content .curtain-figure {
  margin: 0;
  width: 100%;
}
#simple-content .curtain-image {
  display: block;
  height: auto;
  margin: 0 auto;
  max-width: 100%;
  width: 360px;
}
#simple-content .curtain-credit {
  color: var(--co-fringe);
  font-size: .75rem;
  margin-top: .35rem;
}
#simple-content .curtain-tagline {
  font-size: 1.15rem;
  font-weight: 300;
  margin: 0;
}
/* The about page (home/templates/home/about.html — beta: about page mission, 26.09.03):
   the curtain's column width and title register, but prose — left-aligned, top-anchored,
   not vertically centered. The curtain's figure rules serve its image slot unchanged. */
#simple-content .about {
  margin: 0 auto;
  max-width: 36rem;
  padding: 1.5rem 1rem 2rem;
}
#simple-content .about-title {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 1.25rem;
  text-transform: none;
}
#simple-content .about p {
  font-size: 1.05rem;
  font-weight: 300;
  line-height: 1.6;
  margin: 0 0 1rem;
}
/* The picture sits in the upper right with the paragraphs flowing around it (lbk, walk
   issue 1); the article clears the float so the footer clearance still measures the text. */
#simple-content .about-figure {
  float: right;
  margin: .3rem 0 .75rem 1.5rem;
  max-width: 15rem;
  text-align: center;
  width: 42%;
}
#simple-content .about-figure img {
  height: auto;
  width: 100%;
}
#simple-content .about::after {
  clear: both;
  content: "";
  display: block;
}
@media (max-width: 600px) {
  #simple-content .about-figure {
    float: none;
    margin: 0 auto 1.25rem;
    max-width: 20rem;
    width: 100%;
  }
}
/* Simple pages that opt back into the site footer (the curtain, the about page): the
   footer is fixed at the bottom on desktop, so the scrolling column clears it; and the
   brandmark, now a link, lays out as a block holding a block image so the anchor's 3.3rem
   line strut neither grows the slim header past the 40px image nor (as a flex box would)
   collapses the image. */
body.simple:has(#site-footer) #simple-content {
  padding-bottom: 4.5rem;
}
body.simple #site-header-brand,
body.simple #site-header-brand img {
  display: block;
}
body.simple #site-header-brand img {
  max-width: none; /* the base 18% cap is of the containing block — now the 48px anchor, not the header */
  width: 40px;
}
/* The slot: the button row, or whichever form has unfolded into its place. */
#simple-content .curtain-slot {
  display: flex;
  gap: .75rem;
  justify-content: center;
  width: 100%;
}
#simple-content .curtain-slot--form {
  flex-direction: column;
}
#simple-content .curtain-form {
  margin: 0 auto;
  max-width: 26rem;
  text-align: left;
  width: 100%;
}
#simple-content .curtain-form label {
  font-size: .9rem;
  margin: .75rem 0 .2rem;
}
#simple-content .curtain-form input,
#simple-content .curtain-form textarea {
  border: 1px solid var(--co-border);
  border-radius: 3px;
  font: inherit;
  padding: .5rem .75rem;
  width: 100%;
}
#simple-content .curtain-form textarea {
  min-height: 7rem;
  resize: vertical;
}
#simple-content .curtain-form-actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  margin-top: 1.25rem;
}
#simple-content .curtain-form-alert,
#simple-content .curtain-form-error {
  color: var(--co-warn);
  font-size: .85rem;
}
#simple-content .curtain-form-alert {
  margin-bottom: .5rem;
  outline: none;
}
#simple-content .curtain-form-alert p {
  margin: 0 0 .25rem;
}
#simple-content .curtain-form-error {
  display: block;
  margin-top: .2rem;
}
#simple-content .curtain-quiet-link {
  color: var(--co-fringe);
  font-size: .85rem;
  font-weight: 400;
  text-decoration: none;
}
#simple-content .curtain-quiet-link:hover,
#simple-content .curtain-quiet-link:focus-visible {
  text-decoration: underline;
}
#simple-content .curtain-form-foot {
  margin: .5rem 0 0;
  min-height: 1em;
}
/* The waiting-list form's own pieces (beta/templates/beta/): the intro copy, the
   privacy line, the append-ask, and the thanks. Same column, same measure. */
#simple-content .curtain-intro {
  font-size: .95rem;
  line-height: 1.5;
  margin: 0 auto;
  max-width: 30rem;
  text-align: left;
}
#simple-content .curtain-intro p {
  margin: 0 0 .75rem;
}
#simple-content .curtain-privacy {
  color: var(--co-fringe);
  font-size: .8rem;
  line-height: 1.45;
  margin: 1rem auto 0;
  max-width: 26rem;
  text-align: left;
}
#simple-content .curtain-ask {
  margin: 0 auto;
  max-width: 26rem;
  text-align: left;
}
#simple-content .curtain-ask-line {
  margin: 0;
}
/* The thanks state swaps the tagline itself (and takes focus there); no ring on a line of text. */
#simple-content .curtain-tagline:focus {
  outline: none;
}

/* RESPONSIVE */
/* Mobile styles for narrow screens */

@media screen and (max-width: 699px) {
  html {
    font-size: 15px;
    overflow-y: auto;
  }
  input, select, textarea {
    font-size: 16px; /* prevent input focus zoom on iOS */
  }
  .carousel-section header {
    margin-left: 0;
  }
  #site-main {
    flex-direction: column;
    /* Restore the flex default the desktop rule overrides: stacked mobile
       columns size to their content and the page scrolls via html{overflow-y:auto}.
       (Desktop sets min-height:0 to clamp #site-main and scroll columns internally.) */
    min-height: auto;
  }
  .sidebar, #site-content, #simple-content {
    height: auto;
  }
  .sidebar {
    padding-bottom: 0;
  }
  .sidebar nav {
    padding-top: 0!important;
    transition: none!important;
  }
  #sidebar-primary > header, #sidebar-secondary > header {
    border-bottom: 1px solid!important;
  }
  #sidebar-primary > header > button, #sidebar-secondary > header > button {
    padding: 1rem;
    left: auto;
    right: 0;
    rotate: 90deg;
  }
  #sidebar-secondary > header > button {
    rotate: -90deg;
  }
  #sidebar-primary > header h1, #sidebar-secondary > header h1 {
    display: block;
    font-weight: 500;
    padding: .5rem!important;
    transform: none!important;
  }
  #sidebar-primary, #sidebar-secondary {
    order: -1;
    width: 100%!important;
  }
  #detail-search {
    margin-top: .5rem;
  }
  #site-footer {
    border-radius: 0;
    position: static;
    width: 100%;
  }
  #sticky-new-detail {
    right: 40px;
  }
  .upstudy-boxes, .upstudy-details {
    justify-content: center;
  }

  /* Mobile hamburger menu */
  #site-header {
    flex-direction: column;
    align-items: start;
  }
  #mobile-open-button {
    display: block!important;
    font-size: 4em;
    position: absolute;
    right: 20px;
  }
  .mobile-open #mobile-open-button {
    color: var(--co-yellow);
  }
  #site-header-search, #site-header-nav, .sidebar {
    height: 0;
    overflow: hidden;
    position: absolute;
    transition: none;
  }
  .mobile-open #site-header-search, .mobile-open #site-header-nav, .mobile-open .sidebar {
    height: auto;
    overflow: visible;
    position: relative;
  }
  /* The header dropdowns open via :focus-within, which iOS Safari never grants
     a tapped button — so in the hamburger menu the user menu pares down to a
     bare inline "log out" row (no username, no edit profile) and the
     compositions "+" (whose submenu is popup-only) hides entirely. */
  #site-header-nav > button.add-btn,
  #site-header-nav .user-menu-name,
  #site-header-nav .user-menu-profile {
    display: none;
  }
  #site-header-nav > button > div {
    background: none;
    display: block;
    position: static;
    text-align: left;
    width: auto;
  }
}

/* Collapsible sections shown/hidden via JavaScript */

.collapse:not(.active) {
  display: none!important;
}

/* Tab navigation component */

[role="tablist"] {
  border-bottom: 1px solid var(--co-fringe);
  display: flex;
  gap: 2rem;
  justify-content: space-around;
}
[role="tab"] {
  background: none;
  color: #000;
  font-weight: 300;
  padding: .5rem 0;
  border: none;
  border-bottom: 5px solid transparent;
  margin-bottom: -3px;
  text-align: center;
  cursor: pointer;
  text-transform: uppercase;
  z-index: 2;
}
[role="tab"]:focus,
[role="tab"]:hover,
[role="tab"]:active,
[role="tab"][aria-selected="true"] {
  border-color: var(--co-fringe);
}
[role="tab"][aria-selected="true"] {
  border-color: black;
  font-weight: 500;
}

/* Tab panel content areas */

.tab-content {
  height: 100%;
  width: 100%;
  overflow: auto;
}
[role="tabpanel"] {
  display: none;
  height: 100%;
}
[role="tabpanel"].selected {
  display: block;
}

/* Detail workspace three-column grid layout on study detail page */

#detail-workspace {
  display: grid;
  gap: 30px;
  grid-template-columns: min(25%,400px) auto min(25%,400px);
  grid-template-rows:  auto 300px 1fr;
  min-height: 80%;
}
@media (max-width: 768px) {
  #detail-workspace {
    display: flex;
    flex-direction: column;
  }
}

/* Main content and aside panels in detail workspace */

#detail-workspace main, #detail-workspace aside {
  background: #fff;
  border-radius: 5px;
  padding: 1rem;
}
#detail-workspace aside {
  align-self: start;
  background: #fff;
  border: 1px solid var(--co-mid);
  max-height: 100%;
  position: relative;
  width: 100%;
}

/* Snippet lists in workspace sidebars (context, commentary, testimony) */

#detail-workspace .detail-snippets {
  background: var(--co-pale);
  border-radius: 0 0 5px 5px;
  list-style-type: none;
  margin: .5rem -1rem -1rem;
  padding: 1rem;
}
#detail-workspace .detail-snippets li {
  border-radius: 5px;
  margin: 5px 0 10px;
  padding: 2rem 1rem;
  position: relative;
}
#detail-workspace .detail-snippets li:not(:last-child)::after {
  border-bottom: 1px solid var(--co-mid);
  content: "";
  position: absolute;
  bottom: -5px;
  left: 0;
  right: 0;
}
#detail-workspace .detail-snippets li:hover {
  background: var(--co-pale2);
}
#detail-workspace .detail-snippets li :last-child {
  margin-bottom: 0;
}

/* Main content area in center of workspace grid */

#detail-workspace main {
  grid-column: 2;
  grid-row: span 2;
  overflow-x: hidden;
  padding-left: 60px;
  padding-right: 60px;
  position: relative;
}
#detail-workspace main h1 {
  color: #000;
  font-size: 20px;
  font-weight: 400;
  text-transform: none;
}

/* Meta information panel (status, info button) in top-right of workspace */

#detail-workspace #meta {
  align-items: start;
  align-self: start;
  display: flex;
  gap: 10px;
  grid-area: 1 / 3;
  justify-content: end;
  position: relative;
  width: 100%;
}
#detail-workspace #meta #detail-info > span {
  color: #fff;
  display: none;
  float: left;
  padding: 0 10px;
}
#detail-workspace #meta #detail-info > button {
  color: #000;
  float: right;
}

/* Detail info dropdown panel */

#detail-workspace #meta #detail-info > div {
  background: var(--co-pale2);
  border: 1px solid #000;
  border-radius: 0 3px 3px 3px;
  left: 0;
  opacity: 0;
  padding: 1rem;
  position: absolute;
  top: 28px;
  transition: .25s all;
  transition-delay: .1s;
  visibility: hidden;
  width: 100%;
  z-index: 1;
}
#detail-workspace #meta #detail-info > div > div:not(:last-of-type) {
  border-bottom: 1px solid;
  margin-bottom: 1rem;
}
#detail-workspace #meta #detail-info > div > div p:last-child {
  margin-bottom: 0;
}
#detail-workspace #meta #detail-info > div > footer {
  display: flex;
  justify-content: space-between;
  margin-top: 2rem;
}
#detail-workspace #meta #detail-info:hover {
  background: var(--co-dark);
  border: 1px solid #000;
  border-bottom: none;
  border-radius: 3px 3px 0 0;
  width: 100%;
}
#detail-workspace #meta #detail-info:hover > span {
  display: block;
}
#detail-workspace #meta #detail-info:hover > div {
  opacity: 1;
  transition-delay: 0;
  visibility: visible;
}
#detail-workspace #meta #detail-info:hover > button {
  color: #fff;
}

/* Status badge (pinned, active, archived) in workspace */

#detail-workspace .status-pinned {
  background: var(--co-purple);
}
#detail-workspace .status-active {
  background: var(--co-green);
}
#detail-workspace .status-archived {
  background: var(--co-beige);
}

/* Private/public toggle hidden input in workspace form */

#detail-form #form-field-private input {
  display: none;
}

/* Context panel in workspace grid */

#detail-workspace #context {
  grid-area: 1 / 2;
}

/* Commentary panel in bottom-left of workspace grid */

#detail-workspace #commentary {
  grid-area: 3 / 1;
}

/* Testimony panel in bottom-right of workspace grid */

#detail-workspace #testimony {
  grid-area: 3 / 3;
}

/* Primary anchor connection display with expandable list */

#detail-workspace #primary-connection {
  font-size: 14px;
  font-weight: 300;
  margin-bottom: 1rem;
  position: relative;
}
#detail-workspace #primary-connection button {
  left: -25px;
  padding: 4px 4px 4px 8px;
  position: absolute;
  z-index: 1;
}
#detail-workspace #primary-connection #anchors {
  background: var(--co-pale);
  border: 1px solid #000;
  border-radius: 0 0 5px 5px;
  padding: 1rem;
  position: absolute;
  opacity: 0;
  left: 0;
  right: 25px;
  transition: .25s all;
  transition-delay: .1s;
  visibility: hidden;
  z-index: 0;
}
#detail-workspace #primary-connection #anchors div {
  margin-bottom: 1rem;
}
#detail-workspace #primary-connection:hover > button {
  background: var(--co-dark);
  border-radius: 20px 10px 0 20px;
  color: #fff;
  width: 100%;
}
#detail-workspace #primary-connection:hover > button img {
  filter: brightness(0) invert(1);
}
#detail-workspace #primary-connection:hover > span {
  color: #fff;
  position: relative;
  z-index: 2;
}
#detail-workspace #primary-connection:hover #anchors {
  opacity: 1;
  transition-delay: 0;
  visibility: visible;
}

/* Synopsis text in workspace main area */

#detail-workspace #synopsis {
  color: #000;
  font-weight: 300;
  letter-spacing: 8%;
  margin: 2rem 0 4rem;
}

/* Tabbed section below workspace for hooks, anchors, images, etc. */

#detail-workspace-tabs {
  margin: 40px -40px 0;
}
#detail-workspace-tabs nav {
  justify-content: start;
  padding: 0 40px;
}
#detail-workspace-tabs nav [role='tab'] {
  text-transform: none;
}
#detail-workspace-tabs nav .icon-count {
  background: var(--co-light);
}
#detail-workspace-tabs .tab-content {
  background: var(--co-light);
  padding: 40px 40px 120px;
}
#detail-workspace-tabs .tab-content ul {
  margin: 0;
}

/* Individual items in workspace tabs (hooks, anchors, etc.) */

#detail-workspace-tabs .tab-content li {
  border-radius: 5px;
  display: grid;
  gap: 30px;
  grid-template-columns: min(25%,400px) auto min(25%,400px);
  list-style-type: none;
  margin-bottom: 5px;
  padding: 5px;
  transition: .25s background;
}
#detail-workspace-tabs .tab-content li:hover {
  background: var(--co-mid2);
}
@media (max-width: 768px) {
  #detail-workspace-tabs .tab-content li {
    display: flex;
    flex-direction: column;
  }
}
#detail-workspace-tabs .tab-content li > span:first-child img {
  float: left;
}
#detail-workspace-tabs .tab-content li > span:first-child {
  text-align: right;
}
#detail-workspace-tabs .tab-content li > span:first-child .h3,
#detail-workspace-tabs .tab-content li > span:last-child {
  display: block;
  padding-top: 1rem;
}
#detail-workspace-tabs .tab-content li div {
  background: #fff;
  border-radius: 5px;
  padding: 1rem 60px;
  position: relative;
}

/* Form validation messages that appear at top of screen */

#form-messages {
  cursor: pointer; 
  position: fixed; 
  top: 0; 
  left: 50%; 
  opacity: 0;
  transform: translateX(-50%); 
  background: rgba(0,0,0,0.8); 
  color: white; 
  padding: 10px;
  transition: 3s opacity;
}
#form-messages.active {
  opacity: 1;
  transition: none;
}
#form-messages.error {
  background: #a00;
}

/* Main detail form (study/composition add/edit) layout */

#detail-form {
  background: #f5f5f5;
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100vh;
  max-height: -webkit-fill-available;
}

/* Form header with title and metadata fields */

#detail-form header {
  font-weight: 300;
}
#detail-form header .fields {
  padding: 1rem 1rem 1rem 3rem;
}

/* Form input field styling */

#detail-form input {
  padding: .5rem;
}
#detail-form select {
  padding: .5rem;
  width: 100%;
}
#detail-form textarea {
  height: 8rem;
  padding: .5rem;
  width: 100%;
}

/* Private/public toggle in detail form header */

#detail-form #form-field-private input {
  display: none;
}
#detail-form #form-field-private label {
  background: url('/static/core/img/public.svg') no-repeat;
  cursor: pointer;
  display: block;
  height: 25px;
  width: 25px;
  min-width: 25px;
  text-indent: -9999px;
}
#detail-form #form-field-private input:checked + label {
  background-image: url('/static/core/img/private.svg');
}

/* Primary text field of a popup / detail form header. Same display contract */
/* across Entity-extending leaves (Study, Chapter use #id_title) and Minimal- */
/* Anchor leaves (Discipline uses #id_name; future Glossary will use #id_term). */
/* Add per-leaf id here as new forms ship. */

.popup-form #id_title,
.popup-form #id_name,
.popup-form #id_term,
#detail-form #id_title {
  background: none;
  font-size: 125%;
  font-weight: 400;
  margin-bottom: .5rem;
  min-width: 50%;
  width: 100%;
  transition: background 0.2s, border 0.2s;
}
#detail-form #id_title:focus,
.popup-form #id_title:focus,
.popup-form #id_name:focus,
.popup-form #id_term:focus {
  background: #fff;
}

/* Scrollable main content area of detail form */

#detail-form main {
  background: var(--co-edge);
  overflow: auto;
  padding: 1rem;
}

/* Collapsible sections in detail form */

#detail-form-header-dates .collapse {
  border: 1px solid var(--co-light);
  margin: 4px 2rem 4px 0;
  padding: 4px 8px;
  position: relative;
}

/* Mobile responsive adjustments for detail form */

@media (max-width: 580px) {
  #detail-form header .fields {
    padding-left: 1rem;
  }
  #detail-form-header-dates .collapse {
    margin-right: 0;
  }
  #detail-form-header-dates .date-fields tr {
    display: block;
  }
  #detail-form-header-dates .date-fields tr th {
    border-bottom: none!important;
    display: block;
  }
  #detail-form-header-dates .date-fields .form-field-el {
    flex-wrap: wrap;
  }
  #detail-form-header-dates .date-fields thead tr td:last-child {
    border-bottom: none;
    position: absolute;
    top: 0;
    right: 0;
  }
}

/* Date range table in detail form */

#detail-form-header-dates .date-fields {
  border-collapse: collapse;
}
#detail-form-header-dates .date-fields tr {
  position: relative;
}
#detail-form-header-dates .date-fields th {
  padding: 4px;
  text-align: left;
}
#detail-form-header-dates .date-fields td {
  padding: 4px 0;
  text-align: center;
}
#detail-form-header-dates .date-fields .collapse {
  background: #fff;
  right: 1em;
  position: absolute;
  top: 0;
  z-index: 100;
  width: 100px;
}
#detail-form-header-dates tr.border-bottom th,
#detail-form-header-dates tr.border-bottom td {
  border-bottom: 1px solid;
  padding: 2px 4px;
}

/* Title field wrapper in form */

#detail-form #form-field-title {
  margin-left: -.5rem;
}

/* Date validation error alerts */

#detail-form-header-dates .date-fields
#detail-form-header-dates .end-fields,
#detail-form-header-dates .start-fields {
  position: relative;
}
#detail-form-header-dates .date-fields-alert {
  background: url('/static/core/img/alert.svg');
  display: none;
  height: 20px;
  width: 20px;
}
#detail-form-header-dates .date-fields + .date-fields-alert {
  position: absolute;
  bottom: 0;
  right: 0;
}
#detail-form-header-dates.error .date-fields + .date-fields-alert,
#detail-form-header-dates.end-error .end-fields .date-fields-alert,
#detail-form-header-dates.start-error .start-fields .date-fields-alert {
  display: block;
}
#detail-form-header-dates.error .btn-circle,
#detail-form-header-dates.start-error .btn-circle,
#detail-form-header-dates.end-error .btn-circle {
  background-color: #E17373;
  cursor: default;
  pointer-events: none;
}

/* Individual date field inputs in date range */

#detail-form-header-dates .date-fields .form-field-el {
  display: flex;
  gap: 2px;
}
#detail-form-header-dates .date-fields .form-field-el input {
  text-align: center;
}
#detail-form-header-dates .date-fields .form-field-el [name*=date_0] {
  width: 58px;
}
#detail-form-header-dates .date-fields .form-field-el [name*=date_1] {
  width: 58px;
}
#detail-form-header-dates .date-fields .form-field-el [name*=date_2] {
  width: 80px;
}
#detail-form-header-dates .date-fields .form-field-el [name*=date_3] {
  width: 120px;
}
#detail-form-header-dates .date-fields .form-field-el [name*=date_4] {
  opacity: .5;
  width: 60px;
}
#detail-form-header-dates .date-fields button {
  float: right;
}

/* Synopsis field that expands to fill available height */

#detail-form #form-field-synopsis,
#detail-form #form-field-synopsis .form-field-el,
#detail-form #form-field-synopsis .ck-editor-container,
#detail-form #form-field-synopsis .ck-editor-container .ck-editor,
#detail-form #form-field-synopsis .ck-editor-container .ck-source-editing-area,
#detail-form #form-field-synopsis .ck-editor-container .ck-source-editing-area textarea {
  height: 100%;
}
#detail-form #form-field-synopsis .ck-editor-container .ck-editor__main {
  height: calc(100% - 40px);
}

/* CKEditor styling in detail forms */

#detail-form .ck.ck-content.ck-editor__editable {
  font-size: 16px;
  height: 100%;
  min-height: 40px;
}
#detail-form #form-field-attribution .ck-editor__editable {
  height: auto;
  min-height: 0;
}

/* Word count hidden in detail forms */

#detail-form .word-count {
  display: none;
}

/* Footer with metadata fields in detail form */

#detail-form #footer-details {
  background: var(--co-edge);
  display: flex;
  justify-content: space-between;
  padding: 0 1.5rem .5rem;
}

/* Form action buttons at bottom of detail form */

#detail-form #footer-actions {
  border-top: 1px solid var(--co-fringe);
  display: flex;
  gap: 10px;
  justify-content: space-between;
  padding: 1rem;
}
#detail-form #footer-actions button {
  padding: .25rem .5rem;
  vertical-align: middle;
}
#detail-form #footer-actions button path {
  transition: .5s fill ease-out;
}
#detail-form #footer-actions button:hover path:last-child {
  fill: #fff;
}
#detail-form #footer-actions button:hover path:not(:last-child) {
  fill: #30A1F3;
}
#detail-form #footer-actions #submit-button {
  padding: .25rem 1rem;
  margin-left: 1rem;
}

/* Media panel sections (image, audio, file, URL) in detail form */

#detail-form .popup-form [class*='media-panel-'] {
  background: no-repeat left center;
  padding-left: 50px;
  position: relative;
}
#detail-form .popup-form [class*='media-panel-'] input[type=text],
#detail-form .popup-form [class*='media-panel-'] input[type=url] {
  width: 100%;
}
#detail-form .popup-form [class*='media-panel-'] .btn-circle {
  position: absolute;
  top: 1rem;
  right: 0;
}
#detail-form .popup-form [class*='media-panel-'] .collapse {
  display: flex;
  gap: .5rem;
}
#detail-form .popup-form [class*='media-panel-'] .collapse .form-field {
  flex: 1;
}

/* Icons for different media panel types */

#detail-form .popup-form .media-panel-image {
  background-image: url('/static/core/img/US_Media Icons-03.svg');
}
#detail-form .popup-form .media-panel-audio {
  background-image: url('/static/core/img/US_Media Icons-07 1.svg');
}
#detail-form .popup-form .media-panel-file {
  background-image: url('/static/core/img/US_Media Icons-01.svg');
}
#detail-form .popup-form .media-panel-url {
  background-image: url('/static/core/img/US_Media Icons-06 1.svg');
}

/* Media object preview grid in media panels */

#detail-form .popup-form .panel-object {
  display: grid;
  gap: 10px;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
}
#detail-form .popup-form .panel-object :first-child {
  grid-row: -1 / 1;
}

/* Meta panel field spacing */

#detail-form .popup-form .meta-panel .form-field {
  margin-bottom: 1rem;
}


/* Edit mode message in anchor/hook connection fields */
.edit-msg { /* to make the edit form text pop and distinguish it from field syntax text */
  color: #E17373;
}

/* Index filters (Study + Composition indexes) — Tools-tab form */
.index-filters .index-filter-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0 0 0.75rem;
}
.index-filters .index-filter-field label {
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* Section headings: FILTER … and SORT BY sit at the same level so SORT BY reads
   as its own section, not another filter field. Space the second one off. */
.index-filters h2 {
  margin-top: 1.25rem;
}
.index-filters h2:first-child {
  margin-top: 0;
}
/* Status field head: the STATUS label on the left, the pin-to-top opt-out on the
   right of the same line (wraps below if the sidebar is narrow). */
.index-filters .index-filter-field__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 0.5rem;
}
/* Pin-to-top opt-out checkbox (label beside the box; sentence case, not uppercased
   like the field labels — the __head selector outranks `.index-filter-field label`).
   Greyed when the status filter excludes pinned rows. */
.index-filters .index-filter-field__head .index-filter-pin {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75em;
  text-transform: none;
  letter-spacing: normal;
  white-space: nowrap;
  cursor: pointer;
}
.index-filters .index-filter-field__head .index-filter-pin input {
  margin: 0;
}
.index-filters .index-filter-field__head .index-filter-pin--off {
  opacity: 0.45;
  cursor: default;
}

/* Index filters — active-filter chips */
.index-filter-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1rem;
}
.index-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0.6rem;
  border: 1px solid currentColor;
  border-radius: 1rem;
  font-size: 0.85em;
}
.index-filter-chip-remove {
  text-decoration: none;
  font-weight: bold;
  line-height: 1;
}
.index-filter-clear-all {
  font-size: 0.85em;
}
.study-list-empty,
.index-empty {
  font-style: italic;
  opacity: 0.8;
}

/* /dev/ WIP index — the per-worktree surface map (core/templates/dev/surface_map.html).
   Local-only developer surface; styles kept here so the dev pages, which route through
   base.html, pick them up without a second stylesheet. */
.dev-handoff {
  margin-bottom: 2.5em;
}
.dev-handoff-head {
  /* Breathing room above each handoff block so the page is easy to scan. No
     underline here: the head + note read as one group above the rule below. */
  margin-top: 2em;
}
/* Branch tag beside the h2 date on /dev/history/, where entries span branches. Muted +
   monospaced so it reads as metadata, not part of the handoff's title. */
/* Branch + voice tags beside the handoff h2 date share one muted/monospaced
   metadata treatment (voice = person·machine tuple, multi-voice gp). */
.dev-handoff-branch,
.dev-handoff-voice {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.65em;
  font-weight: normal;
  opacity: 0.7;
  margin-left: 0.6em;
}
/* "Handoff history →" escape hatch at the foot of the index and scoped surface map. */
.dev-history-link {
  margin-top: 3em;
}
.dev-handoff-note {
  margin: 0.4em 0 0.8em;
  opacity: 0.85;
}
.dev-handoff-changes {
  border-collapse: collapse;
  width: 100%;
  /* The dividing rule sits *after* the note and *before* the change rows, so the
     header + note above it clearly belong together (the note describes the
     handoff, not the individual rows). */
  border-top: 1px solid #ddd;
}
.dev-handoff-changes td {
  padding: 0.35em 0;
  vertical-align: top;
  border-bottom: 1px solid #f0f0f0;
}
/* Description left: greedy column — it absorbs the slack so the URL column is
   pushed all the way to the right edge. The 1.5em gutter separates the two. */
.dev-change-what {
  width: 100%;
  padding-right: 1.5em;
  white-space: normal;
}
/* Full clickable URL right: sized to the longest URL (no wrap) and right-aligned,
   so every URL's right end is flush with the table's right edge. Monospaced so the
   host:port is obvious at a glance. */
.dev-change-link {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
  white-space: nowrap;
  text-align: right;
}
/* "Glossary added:" / "Design added:" lines under the changes table — the vocabulary and
   UI a handoff introduced (lbk 8.19). Quiet, matching the note's muted register; the label
   is the only emphasized bit so the term list reads as content, not chrome. */
.dev-handoff-added {
  margin: 0.7em 0 0;
  font-size: 0.9em;
  opacity: 0.85;
}
/* Label on its own line, the terms stacked beneath it as a bulleted list, so each item
   is easy to tell from the next (lbk 8.19) rather than running together on one line. */
.dev-handoff-added-label {
  display: block;
  font-weight: 600;
  opacity: 1;
  margin-bottom: 0.15em;
}
.dev-handoff-added-list {
  margin: 0;
  padding-left: 1.4em;
  list-style: disc;
}
.dev-handoff-added-list li {
  margin: 0.15em 0;
}
/* Gameplan breadcrumb at the foot of the /dev/ handoff page — mirrors the footer the
   gameplan docs carry, so this page joins that navigation instead of dead-ending
   (lbk 8.19). Rule above it separates it from the last handoff block. */
.dev-gameplan-footer {
  margin-top: 3.5em;
  padding-top: 1em;
  border-top: 1px solid #ddd;
  font-size: 0.85em;
  opacity: 0.85;
}

/* /dev/ WIP index — the :8000 worktree list (core/templates/dev/index.html). Each row is
   two lines: the identifier + port link on top, liveness/freshness/restart beneath, so the
   link to a session's /dev/ isn't buried in inline stats. */
.dev-wip-list {
  list-style: none;
  padding-left: 0;
}
.dev-wip {
  margin-bottom: 1.3em;
}
.dev-wip-main {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
}
.dev-wip-slug {
  font-weight: 600;
  font-size: 1.1em;
}
.dev-wip-open {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.dev-wip-meta {
  margin-top: 0.15em;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25em 1.1em;
  font-size: 0.85em;
  opacity: 0.85;
}
.dev-wip-down {
  opacity: 0.7;
}
.dev-wip-warn .dev-wip-flag {
  color: #b00;
  opacity: 1;
}
.dev-wip-restart {
  font-size: 0.95em;
}

/* /dev/ docs browser — the scenario index (dev/scenarios.html) and rendered repo
   markdown (dev/doc.html). Same home as the other dev-page styles above. */
.dev-subnav {
  /* Tucks directly under the page h1 as a quiet secondary nav line. */
  margin: -0.4em 0 1.6em;
  font-size: 0.85em;
  opacity: 0.85;
}
.dev-scenarios-intro {
  opacity: 0.85;
  max-width: 46em;
}
.dev-scenario-list {
  list-style: none;
  padding-left: 0;
}
.dev-scenario {
  margin-bottom: 1.2em;
}
.dev-scenario-main {
  display: flex;
  align-items: baseline;
  gap: 0.7em;
}
.dev-scenario-title {
  font-weight: 600;
  font-size: 1.1em;
}
.dev-scenario-captured {
  font-size: 0.85em;
  opacity: 0.7;
}
.dev-scenario-status {
  margin-top: 0.15em;
  font-size: 0.85em;
  opacity: 0.85;
}
/* Rendered markdown (the /dev/ docs browser): a readable, informative baseline that
   matches the app's own palette and type, so a doc read on the live site feels part of
   the tool. All presentation lives here, NOT in the .md files — repo docs stay clean
   markdown (edited in the CLI) and the styling is applied at render. The measure is
   per-block so prose stays at a comfortable width while tables (edge catalogues,
   walkthrough grids) and code expand with the viewport. */
.dev-doc {
  line-height: 1.6;
  color: var(--co-dark);
  padding-bottom: 3rem;
}
.dev-doc > :not(table):not(pre) {
  max-width: 46em;
}
.dev-doc h1,
.dev-doc h2,
.dev-doc h3,
.dev-doc h4 {
  font-family: var(--font-alt);
  font-weight: 500;
  line-height: 1.2;
  /* Warm near-black — the token palette has no step darker than --co-dark (a mid
     grey), too light for heading contrast against the page. */
  color: #3a3634;
}
.dev-doc h1 {
  font-size: 2rem;
  margin: 0 0 1.2rem;
  padding-bottom: 0.4rem;
  border-bottom: 2px solid var(--co-border);
}
.dev-doc h2 {
  font-size: 1.5rem;
  margin: 2.4rem 0 0.9rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--co-edge);
}
.dev-doc h3 {
  font-size: 1.2rem;
  margin: 1.8rem 0 0.6rem;
}
.dev-doc h4 {
  font-size: 1.02rem;
  margin: 1.4rem 0 0.5rem;
}
.dev-doc p,
.dev-doc ul,
.dev-doc ol {
  margin: 0 0 1rem;
}
/* The margin shorthand above zeroes margin-left (the site's global list indent), which
   left nested lists flush with their parents — indent LISTS ONLY via padding-left, at
   every depth (lbk, Instance Keys walk 2026-07-08; review caught the padding briefly
   riding the shared p rule and indenting all prose). */
.dev-doc ul,
.dev-doc ol {
  padding-left: 1.4em;
}
.dev-doc li {
  margin: 0.2rem 0;
}
.dev-doc li > ul,
.dev-doc li > ol {
  margin: 0.2rem 0 0.4rem;
}
.dev-doc a {
  color: var(--co-link);
  text-underline-offset: 2px;
}
.dev-doc hr {
  border: none;
  border-top: 1px solid var(--co-border);
  margin: 2rem 0;
}
.dev-doc pre,
.dev-doc-raw {
  overflow-x: auto;
  background: var(--co-pale);
  border: 1px solid var(--co-edge);
  padding: 0.9em 1em;
  border-radius: 6px;
  line-height: 1.45;
}
.dev-doc code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.88em;
  background: var(--co-mid2);
  padding: 0.08em 0.35em;
  border-radius: 4px;
}
.dev-doc pre code {
  background: none;
  padding: 0;
  font-size: 0.86em;
}
.dev-doc blockquote {
  margin: 1.2rem 0;
  padding: 0.6em 1em;
  border-left: 3px solid var(--co-blue2);
  background: var(--co-pale2);
  border-radius: 0 6px 6px 0;
  color: var(--co-grey);
}
.dev-doc blockquote p:last-child {
  margin-bottom: 0;
}
/* Voice-threaded quotes (walk/manual co-editing): `> claude: …` and `> lbk: …` turns
   render as distinct voice bubbles so a back-and-forth reads as a conversation, not a
   clump. Each turn is a .voice-turn div wrapping its opening paragraph AND any bullet
   list that follows it (core/dev.py:_tag_voice_blockquotes), so structured turns keep
   their structure inside the bubble; the containing quote loses its own chrome when it
   holds turns (:has), letting the bubbles carry it. Claude cool + muted; lbk warm +
   emphatic (the manual contract's "her voice"). */
.dev-doc blockquote:has(.voice-turn) {
  border-left: 0;
  background: transparent;
  padding: 0;
  margin: 0.35rem 0 0.35rem 0.75rem;
}
.dev-doc .voice-turn {
  margin: 0 0 0.35rem;
  padding: 0.4em 0.9em;
  border-radius: 0 6px 6px 0;
}
.dev-doc .voice-turn.voice-claude {
  border-left: 3px solid var(--co-blue2);
  background: var(--co-pale2);
  color: var(--co-grey);
}
.dev-doc .voice-turn.voice-lbk {
  border-left: 3px solid var(--co-orange, #c77f4f);
  background: #faf3ec;
  color: var(--co-dark, #3d3936);
  font-weight: 500;
}
.dev-doc .voice-turn > :last-child {
  margin-bottom: 0;
}
.dev-doc .voice-turn ul,
.dev-doc .voice-turn ol {
  margin: 0.3em 0 0;
  padding-left: 1.3em;
}
.dev-doc .voice-turn li {
  margin: 0.15em 0;
}
.dev-doc table {
  border-collapse: collapse;
  /* Wide tables (edge catalogues, walkthrough grids) take the full viewport width
     (exempt from the prose measure above) and scroll on their own if the columns
     still don't fit. */
  width: 100%;
  display: block;
  overflow-x: auto;
  margin: 0 0 1.2rem;
  font-size: 0.95em;
}
.dev-doc thead th {
  background: var(--co-mid2);
  text-align: left;
  font-family: var(--font-alt);
  font-weight: 500;
}
.dev-doc tbody tr:nth-child(even) td {
  background: var(--co-pale2);
}
/* Keep the Surface column's linked paths on one line so the path reads as a unit. */
.dev-doc td a code {
  white-space: nowrap;
}
.dev-doc th,
.dev-doc td {
  border: 1px solid var(--co-border);
  padding: 0.4em 0.7em;
  vertical-align: top;
}
.dev-doc img {
  max-width: 100%;
}
/* Opt-in borderless table. Markdown gives no border control, so wrap the table:
     <div class="plain-table" markdown="1">
     | ... | ... |
     ...
     </div>
   The table inside stays plain markdown; this drops the grid, header fill, and zebra
   for a clean, lightweight look (short reference or layout-ish tables). Header cells
   keep their weight so columns still read. */
.dev-doc .plain-table table,
.dev-doc .plain-table th,
.dev-doc .plain-table td {
  border: none;
}
.dev-doc .plain-table thead th {
  background: none;
}
.dev-doc .plain-table tbody tr:nth-child(even) td {
  background: none;
}
/* Commentary rows in a plain-table — a row whose first cell is empty carries working
   flags (⚠ drifted / ✗ gone) beneath the term it annotates. These need addressing, so
   they pop: a highlighter wash derived from --co-yellow rather than a new hex. */
.dev-doc .plain-table td:empty + td {
  background: color-mix(in srgb, var(--co-yellow) 28%, white);
  border-left: 4px solid var(--co-yellow);
  padding: 0.45em 0.8em 0.45em 0.7em;
}

/* Docs browser layout: a chapter sidebar beside the article, for manual pages. The
   sidebar is built from DOCS/manual/ (README order) and sticks while the article
   scrolls inside #simple-content. Collapses above the article on narrow screens. */
.doc-layout {
  display: flex;
  align-items: flex-start;
  gap: 2.75rem;
}
.doc-layout .dev-doc {
  flex: 1 1 auto;
  min-width: 0;
}
.doc-sidebar {
  flex: 0 0 13.5rem;
  position: sticky;
  top: 0;
}
.doc-sidebar__home {
  display: block;
  font-family: var(--font-alt);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--co-grey);
  text-decoration: none;
  padding-bottom: 0.6rem;
  margin-bottom: 0.7rem;
  border-bottom: 1px solid var(--co-edge);
}
.doc-sidebar ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.doc-sidebar li a {
  display: block;
  padding: 0.35rem 0.6rem;
  border-radius: 6px;
  color: var(--co-dark);
  text-decoration: none;
  font-size: 0.95rem;
  line-height: 1.35;
}
.doc-sidebar li a:hover {
  background: var(--co-pale);
}
.doc-sidebar a.is-current {
  background: var(--co-mid2);
  color: #3a3634;
  font-weight: 500;
}
@media (max-width: 46rem) {
  .doc-layout {
    flex-direction: column;
    gap: 1.2rem;
  }
  .doc-sidebar {
    position: static;
    flex-basis: auto;
    align-self: stretch;
  }
}

/* =========================================================================
   Focused cover form (details/templates/details/cover_focused_form.html).
   Image / title / synopsis stacked in one column, shown in a small popup
   window. Reuses the shared image widget but presents it as a single large
   drop-zone: the asset icon and the image-title row are hidden so the visible
   form stays image/title/synopsis (a cover is a representation, not a full
   Detail; image_title rides hidden at its default, reachable via the escape
   hatch). Higher specificity than the .image-widget rules in popup-form.css,
   so load order doesn't matter.
   ========================================================================= */
.cover-focused-form {
  /* Flex column filling the scroll area (.tab-content is height:100% of main),
     so the synopsis can stretch down to the footer and grow when the window is
     made taller. No own padding — .tab-content already insets 1rem; this keeps
     the title tight under the header. */
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  height: 100%;
  box-sizing: border-box;
}

/* The popup header layout sets order:3 + margin-top on #form-field-title (it is
   the header's primary row in other forms). In this flex column that would push
   the title below the image and add a gap above it — reset both so the title
   leads in DOM order, flush under the header. */
.popup-form .cover-focused-form #form-field-title {
  order: 0;
  flex: 0 0 auto;
  margin-top: 0;
}

/* Synopsis fills the remaining height down to the footer (and stretches when the
   window is made taller). It is the rich CKEditor field with its toolbar hidden —
   existing styling renders, with no toolbar noise; formatting changes happen in the
   full editor via the gear. The height:100% chain mirrors #detail-form's synopsis. */
.cover-focused-form #form-field-synopsis {
  flex: 1 1 auto;
  min-height: 140px;
}

.cover-focused-form #form-field-synopsis .form-field-el,
.cover-focused-form #form-field-synopsis .ck-editor-container,
.cover-focused-form #form-field-synopsis .ck-editor-container .ck-editor,
.cover-focused-form #form-field-synopsis .ck-editor-container .ck-editor__main {
  height: 100%;
}

/* Hide the toolbar (the "no toolbar noise" ask) — the editable still renders styling. */
.cover-focused-form #form-field-synopsis .ck-editor__top {
  display: none;
}

.cover-focused-form #form-field-synopsis .ck.ck-content.ck-editor__editable {
  height: 100%;
  min-height: 120px;
  overflow-y: auto;
}

/* =========================================================================
   Chromeless inline rich-text editor — the shared "quiet rich field".
   Used wherever an inline quick-edit (list rows, cards, worksheet slots,
   quote panels) needs CKEditor's formatting + keyboard shortcuts without
   toolbar chrome. Pair with config_name="chromeless" (empty toolbar) and
   wrap the field in <div class="chromeless-editor">. Existing bold/italic/
   links still render; Ctrl+B / Ctrl+I work; richer formatting lives in the
   detail popup. Generalizes the .cover-focused-form toolbar-hide above.
   ========================================================================= */
.chromeless-editor .ck-editor__top {
  /* Hide the residual empty toolbar strip left by an empty `toolbar: []`. */
  display: none;
}

.chromeless-editor .ck-word-count {
  /* A quiet inline field shows no word/character count chrome — the WordCount
     plugin is bundled in the build and renders below the editable otherwise. */
  display: none;
}

/* Hide the CKEditor "Powered by" badge. It floats over the bottom-right of a focused
   editable (rendered in a balloon appended to <body>, so it can't be scoped to
   .chromeless-editor) and is most visible on the borderless inline fields. Internal
   personal tool; remove this rule if a CKEditor license ever requires the attribution. */
.ck.ck-powered-by,
.ck-powered-by-balloon {
  display: none !important;
}

/* Resting + focused state: read as an inline field, not a nested card —
   no box border or shadow, ~2 lines min, growing downward with content. */
.chromeless-editor .ck.ck-content.ck-editor__editable,
.chromeless-editor .ck.ck-content.ck-editor__editable.ck-focused {
  min-height: 3.5rem;
  border: none;
  box-shadow: none;
  background: transparent;
  padding-left: 0;
  padding-right: 0;
}

.cover-focused-form .image-widget,
.cover-focused-form .image-widget.is-empty {
  display: block;
  padding: 0;
  position: relative;
}

.cover-focused-form .image-widget-asset-icon,
.cover-focused-form .image-widget-title-row {
  display: none;
}

.cover-focused-form .image-widget-preview {
  width: 100%;
}

/* Empty drop-zone keeps a comfortable click target height. */
.cover-focused-form .image-widget.is-empty .image-widget-preview {
  height: 220px;
}

/* Saved image: full width at its natural height — the field grows down to show
   the whole image (no crop). Block flow so the preview height follows the image
   rather than the shared 90px fixed box. The preview is fed a large (width 960)
   thumbnail so it stays crisp at this width. */
.cover-focused-form .image-widget.is-populated .image-widget-preview {
  display: block;
  height: auto;
}

.cover-focused-form .image-widget-thumb-link {
  height: auto;
}

.cover-focused-form .image-widget-preview img {
  width: 100%;
  height: auto;
  display: block;
}

.cover-focused-form .image-widget-empty-trigger {
  font-size: 14px;
}

/* Saved image: the filename + replace/clear controls are an overlay along the
   bottom of the image, hidden until hover/focus (lbk: "I might want those on
   hover, but not always showing"). Empty state keeps the bare drop-zone — the
   widget's own `hidden` attr on these rows wins until an image is set. */
.cover-focused-form .image-widget-content {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.35rem 0.6rem;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
  opacity: 0;
  transition: opacity 0.15s ease;
}

.cover-focused-form .image-widget:hover .image-widget-content,
.cover-focused-form .image-widget:focus-within .image-widget-content {
  opacity: 1;
}

.cover-focused-form .image-widget-filename,
.cover-focused-form .image-widget-action {
  color: #fff;
}

.cover-focused-form .form-field-el input,
.cover-focused-form .form-field-el textarea {
  width: 100%;
}


/* --- Header: "<usage> for <comp>: <slot>" identity line + full-detail gear --- */
.cover-form-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 1rem 0.3rem;
}

.cover-form-title {
  margin: 0;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cover-form-gear {
  flex: 0 0 auto;
}

.cover-form-gear .icon-cog {
  width: 20px;
  height: 20px;
}

/* --- Footer: Cancel (dirty) / Close (clean) toggles on the Save button's
   disabled state (clean => Save disabled). Default markup state is clean. --- */
.cover-form-close .cover-close-cancel {
  display: none;
}
#footer-actions:has(#submit-button:not(:disabled)) .cover-form-close .cover-close-close {
  display: none;
}
#footer-actions:has(#submit-button:not(:disabled)) .cover-form-close .cover-close-cancel {
  display: inline;
}



/* ---- Attachments: sidebar region + attach-flow popup pages (U7) ------------ */
/* The sidebar list rows reuse .sidebar-subsection-list; these add the header +
   title arrangement and the attach popup-window flow's light layout. */
.attachments-sidebar-list .attachment-row {
  display: block;
  padding: 0.15rem 0;
}
.attachments-sidebar-list .attachment-row-header {
  font-weight: 600;
}
.attachments-sidebar-list .attachment-row-header::after {
  content: ":";
}
.attachments-sidebar-list .attachments-empty {
  color: #777;
  font-style: italic;
}
/* Nested attach-groups (composition→instances, study→chapters): the parent is a header,
   its children indent beneath. */
.attachments-nested {
  list-style: none;
  margin: 0;
  padding: 0;
}
.attachments-nested .attachment-group {
  /* More space BELOW a group than between a header and its own children, so each
     instance reads as belonging to the composition above it, not the one below. */
  padding: 0.1rem 0 0.35rem;
}
.attachment-group-header {
  font-weight: 600;
}
.attachment-group-children {
  list-style: none;
  margin: 0;                 /* sit tight under the parent header */
  padding-left: 0.45rem;     /* a small nesting step — the title itself isn't indented */
  border-left: 1px solid var(--co-mid2);
  line-height: 1.25;         /* child hugs its header (lbk 7/8) */
}
.attachment-group-children .attachment-row {
  padding: 0;
}
/* "instances" vs "attached" subheads inside the compositions section (the only section
   with two association branches). Each subhead row carries its own affordance to its right. */
.sidebar-relation-head {
  align-items: center;
  display: flex;
  gap: 0.35rem;
  margin: 0.5rem 0 0.15rem;
}
/* A subsequent subhead in a section (one that follows a branch/list above it) gets more
   breathing room above, so the branches read as distinct; the first subhead in a section
   stays tight to the section header. (walk 8:30pm #3) */
.sidebar-relation-head:not(:first-child) {
  margin-top: 1rem;
}
.sidebar-relation-label {
  color: var(--co-mid);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
/* The per-branch (+)/find affordance sits compact beside its subhead label. */
.sidebar-relation-head .sidebar-relation-add,
.sidebar-relation-head .sidebar-section-find {
  flex-shrink: 0;
}
.sidebar-relation-head .sidebar-relation-add .icon-plus {
  font-size: 0.9em;
}
/* ASTROLOGIA rail section: planned-but-unbuilt models render as plain text that must
   read as muted WITHOUT hovering (global `a` is black at rest, so color is the only
   at-rest cue distinguishing them from the built, linked entries). --co-fringe sits
   between the black links and the pale --co-mid cluster heads. */
.sidebar-section[data-section="astrologia"] .astro-rail-unbuilt {
  color: var(--co-fringe);
}

/* Comp-type suffix after a composition header ("a little todo [List]"). */
.attachment-group-type {
  color: var(--co-mid);
  font-weight: 400;
}
.attach-flow {
  max-width: 30rem;
  padding: 1rem;
}
.attach-flow-heading {
  margin-top: 0;
}
.attach-flow-form .form-field {
  margin-bottom: 0.75rem;
}
.attach-flow-form input[type="text"],
.attach-flow-form input[type="search"],
.attach-flow .attachments-search-input {
  width: 100%;
  box-sizing: border-box;
}
.attach-flow-hint,
.attach-flow-done {
  color: #555;
}
.attach-flow-back {
  margin-left: 0.75rem;
  font-size: 0.9em;
}
.attach-flow-target-instance {
  display: block;
  margin: 0.6rem 0 0.3rem;
}
.attach-flow .attachments-results,
.attach-flow .attachment-result {
  display: block;
  width: 100%;
  text-align: left;
}
.attach-flow .attachment-result {
  padding: 0.25rem 0.35rem;
  cursor: pointer;
}
.attach-flow .attachment-result:hover,
.attach-flow .attachment-result:focus {
  background: #f0f0f0;
}
.attach-flow .attachment-result-kind,
.attach-flow .attachments-keep-typing {
  color: #777;
  font-size: 0.85em;
}
.attach-flow-inline-form {
  margin: 0;
}
/* Type-first picker (Other Attachments): the list of concrete kinds to attach. */
.attach-kind-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.attach-kind-choice {
  display: block;
  padding: 0.35rem 0.35rem;
  text-decoration: none;
}
.attach-kind-choice:hover,
.attach-kind-choice:focus {
  background: #f0f0f0;
}

/* ---- Detail sidebar: expandable keyed-list instances + find affordance (U8) - */
.detail-comp-instance-line {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.detail-comp-instance-line a {
  flex: 1 1 auto;
  min-width: 0;
}
.detail-comp-expand .icon {
  transition: transform 0.15s ease;
}
.detail-comp-expand.active .icon {
  transform: rotate(90deg);
}
/* The expanded body reuses the list's own container treatment; rein in the width
   and indent so it reads as a nested working surface, not a second page. */
.detail-comp-instance-body {
  margin: 0.25rem 0 0.5rem 0.75rem;
  padding-left: 0.5rem;
  border-left: 2px solid #ddd;
}
.detail-comp-find {
  margin-bottom: 0.35rem;
}

/* Choices.js ships `word-break: break-all` on its dropdown lists and multi-select
   chips, so a long title splits mid-word the moment a picker narrows. Break at
   whitespace instead; overflow-wrap still rescues a single over-long word (lbk,
   Instance Keys walk 2026-07-08). Global on purpose — no picker anywhere wants
   mid-word breaks. The leading `.choices` ancestor out-specifies the vendor rules
   (choices.min.css loads after this sheet in base.html). */
.choices .choices__list--dropdown,
.choices .choices__list[aria-expanded],
.choices .choices__list--multiple .choices__item,
.choices .choices__list--single .choices__item {
  word-break: normal;
  overflow-wrap: break-word;
}


/* ===== Ledger query pages (When / Where) ===== */
/* Glyph⇄title toggle (KTD9): both forms render; html.astro-titles flips
   which span shows. Defaults to glyphs before any stored preference. */
.ledger-query-page .t-title { display: none; }
html.astro-titles .ledger-query-page .t-title { display: inline; }
html.astro-titles .ledger-query-page .t-glyph { display: none; }

.query-page-head {
  align-items: baseline;
  display: flex;
  gap: 1rem;
}
.glyph-toggle {
  background: none;
  border: 1px solid #bbb;
  border-radius: 12px;
  color: #666;
  cursor: pointer;
  font-size: .72rem;
  padding: .06rem .55rem;
}
.glyph-toggle:hover { border-color: #888; color: #333; }

.ledger-query-form { margin: .4rem 0 1rem; }
.ledger-query-form .errorlist {
  color: #8c2f2f;
  font-size: .8rem;
  list-style: none;
  margin: .2rem 0;
  padding: 0;
}

/* --- Sentence row (U6, KTD7) ------------------------------------------------ */
/* Quiet left-accent treatment borrowed from .eventless-note's palette (same   */
/* section, same "the query explains itself" register): bold slots, the       */
/* chevron and Run pinned at the row's end in BOTH the collapsed and open      */
/* states (R1/R2) — Run submits the enclosing <form> directly, no indirection. */
.query-sentence {
  align-items: baseline;
  background: #faf8f2;
  border-left: 3px solid #b9b5ac;
  border-radius: 0 6px 6px 0;
  display: flex;
  flex-wrap: wrap;
  font-size: 1rem;
  gap: .4rem 1rem;
  padding: .5rem .8rem;
}
/* #1 (walk 26.08.16): a shrinkable text column (min-width:0 so it wraps its own
   words) keeps the chevron + Run pinned at the first-line baseline — the upper-
   right corner — instead of the old 16rem floor shoving them onto a second line. */
.query-sentence-text { flex: 1 1 0; min-width: 0; line-height: 1.5; }
/* #1: pin both affordances to the top of the (possibly multi-line) row so they
   hold the upper-right corner together rather than drifting to its vertical middle. */
.query-sentence .query-chevron,
.query-sentence .query-run { align-self: flex-start; }
.query-sentence-text b { font-weight: 600; }
/* .query-library-toggle (walk 8.12 issue #2) shares every rule: the library's
   show/hide chevron is the same standard toggle, just controlling the library
   list instead of the panel (query-sentence.js behavior (k)). */
.query-chevron,
.query-library-toggle {
  align-items: center;   /* center the drawn caret vertically (walk 7.22 #4) */
  align-self: center;
  background: transparent;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  flex: 0 0 auto;
  height: 22px;
  justify-content: center;
  padding: 0;
  width: 22px;
}
/* The shared .chevron-line glyph nudges itself right for its sidebar seat;
   re-center it inside this circular button (walk 7.22 #4): the closed > sits
   optically center when shifted slightly left, the open v slightly up. */
.query-chevron::before,
.query-library-toggle::before { margin-left: -3px; margin-right: 0; }
/* Open state mirrors the sidebar/reading-response chevrons: dark disc, thicker
   white down-caret (the shared .chevron-line ::before supplies the thin closed >). */
.query-chevron[aria-expanded="true"],
.query-library-toggle[aria-expanded="true"] { background: var(--co-dark); }
.query-chevron[aria-expanded="true"]::before,
.query-library-toggle[aria-expanded="true"]::before {
  border-color: #fff;
  border-bottom-width: 2px;
  border-right-width: 2px;
  height: 5px;
  margin-bottom: 0;
  margin-left: 0;
  margin-top: -2px;
  margin-right: 0;
  transform: rotate(45deg);
  width: 5px;
}
.query-run {
  align-self: center;
  background: var(--co-dark);
  border: none;
  border-radius: 4px;
  color: #fff;
  cursor: pointer;
  flex: 0 0 auto;
  font-size: .8rem;
  padding: .28rem .85rem;
}
.query-run:hover { opacity: .88; }

/* --- Panel (open state, KTD2) ----------------------------------------------- */
.query-panel {
  border: 1px dashed #b9b5ac;
  border-radius: 6px;
  display: none;
  font-size: .8rem;
  margin: .5rem 0;
  padding: .6rem .9rem;
}
.query-panel.open { display: block; }
.panel-row {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: .2rem .5rem;
  margin: .4rem 0;
}
.panel-row-label {
  color: #8a8578;
  display: inline-block;
  font-size: .68rem;
  letter-spacing: .06em;
  /* #11: was 6.5rem — far wider than the longest label ("BODIES") needs; the
     tighter gutter is shared with the hanging-indent width below. */
  min-width: 4.25rem;
  text-transform: uppercase;
}
/* #11: the wide pill/zone rows wrapped their content back to the window edge,
   under the label. Float the label into a fixed left gutter so every wrapped
   line starts in the content column instead. Desktop only — below 560px these
   rows already stack the label on its own line above the content. */
@media (min-width: 561px) {
  .panel-row[data-pill-group],
  .panel-row:has(.tz-shorthands) {
    padding-left: 4.25rem;
    position: relative;
  }
  .panel-row[data-pill-group] > .panel-row-label,
  .panel-row:has(.tz-shorthands) > .panel-row-label {
    left: 0;
    min-width: 0;
    position: absolute;
  }
}
.panel-range input[type="text"] {
  background: #faf8f2;
  border: none;
  border-bottom: 1.5px solid #a89f8d;
  font-size: .9rem;
  width: 6.5rem;
}
.panel-range-hint { color: #8a8578; font-size: .75rem; }

/* #2 (walk 26.08.16): the "now" moment-fill — a small quiet action beside the
   date field; fills at + time with the current moment (query-sentence.js). */
.set-now-btn {
  background: #faf9f6;
  border: 1px solid #aaa;
  border-radius: 10px;
  cursor: pointer;
  font-size: .72rem;
  margin-left: .35rem;
  padding: .08rem .6rem;
}
.set-now-btn:hover { background: #efece4; }

/* Date + time share one row (lbk 26.08.16); "now" pinned to the row's right end. */
.panel-moment { align-items: baseline; }
.panel-moment .set-now-btn { margin-left: auto; }
.moment-sub-label {
  color: #8a8578;
  font-size: .68rem;
  letter-spacing: .06em;
  margin-left: .4rem;
  text-transform: uppercase;
}
.panel-range input.moment-time-input { width: 4.5rem; }

/* SPECIFICALLY sub-rows: one per PLACE kind, revealed by JS behavior (c) when
   that kind is checked above (also rendered server-side per the current
   selection, so there's no open-on-load flash). */
.specifically-row {
  display: none;
  margin: .3rem 0 .3rem 6.5rem;
}
.specifically-row.shown { display: block; }
.specifically-label {
  color: #8a8578;
  display: inline-block;
  font-size: .72rem;
  min-width: 4.2rem;
}

/* Degree sub-row clusters (walk 7.22 #5): the number pills, then "of" + signs. */
.degree-cluster { display: block; margin: .15rem 0 .15rem 4.2rem; }
.degree-cluster:first-of-type { display: inline; margin-left: 0; }
.cluster-label { color: #8a8578; font-size: .72rem; margin-right: .2rem; }

/* ZONE input (walk 7.22 #8): same quiet underline as the range inputs.
   Tightened 13rem -> 8rem (walk 8.12 issue #5, placeholder now just "other")
   so the zone row clears the library column; the suggest drop beneath it
   sets its own min-width, so match quality is unaffected. */
.tz-input {
  background: #faf8f2;
  border: none;
  border-bottom: 1.5px solid #a89f8d;
  font-size: .85rem;
  width: 8rem;
}

/* Panel action row: right-aligned as the standard (walk 7.22 12:50 #2). */
.panel-actions { justify-content: flex-end; }
.tz-shorthands { display: inline; }

/* Zone type-to-search (walk 7.22 2:33 #1): a small suggestion drop under the
   input — max ten case-insensitive matches, replacing the native datalist
   (which only offers options that EXTEND the field's current value). */
.tz-entry { display: inline-block; position: relative; }
.tz-suggest {
  background: #fff;
  border: 1px solid #c9c6bf;
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
  display: block;
  left: 0;
  max-height: 14rem;
  overflow-y: auto;
  position: absolute;
  top: calc(100% + 2px);
  min-width: 100%;
  z-index: 30;
}
.tz-suggest[hidden] { display: none; }
.tz-option {
  background: none;
  border: none;
  cursor: pointer;
  display: block;
  font-size: .8rem;
  padding: .25rem .6rem;
  text-align: left;
  width: 100%;
}
.tz-option:hover, .tz-option:focus-visible { background: #efece4; }

/* Update-the-ask (walk 7.22 #7): a quiet secondary action — Run stays the
   one filled button, pinned on the sentence row. */
.update-ask {
  background: #faf9f6;
  border: 1px solid #8a8578;
  border-radius: 4px;
  color: #5c574e;
  cursor: pointer;
  font-size: .78rem;
  padding: .2rem .7rem;
}
.update-ask:hover { background: #efece4; }

/* Bound/decan mode pills (R8) + the picker they swap (JS behavior (d)). */
.mode-row { display: inline-flex; flex-wrap: wrap; gap: .1rem .3rem; margin-right: .6rem; }
.mode-picker { display: none; }
.mode-picker.active { display: inline; }

/* --- Pills: restyled native inputs, no new widgets (KTD2) ------------------- */
/* A visually-hidden checkbox/radio + a pill-styled label — keyboard/screen-    */
/* reader semantics come free from the native input; :checked drives the       */
/* selected look, :focus-visible keeps the pill keyboard-navigable.            */
.pill-input {
  height: 1px;
  opacity: 0;
  overflow: hidden;
  position: absolute;
  width: 1px;
}
.pill,
button.pill {
  background: #faf9f6;
  border: 1px solid #aaa;
  border-radius: 10px;
  cursor: pointer;
  display: inline-block;
  font: inherit;
  font-size: .78rem;
  margin: 0 .15rem .2rem 0;
  padding: .05rem .6rem;
  white-space: nowrap;
}
.pill-input:checked + .pill { background: #e7e3d8; border-color: #8a8578; }
/* JS-driven selected state (any-pills, zone lozenges — walk 7.22 2:33 #2):
   same look as :checked so every pill answers a tap the same way. */
.pill.sel { background: #e7e3d8; border-color: #8a8578; }
.pill.anyp { border-style: dashed; }
.pill.anyp.sel { border-style: solid; }
.pill-input:focus-visible + .pill { outline: 2px solid #5b7ba0; outline-offset: 1px; }

/* --- Query library column (U3/U4, reshaped per walk 8.12 #2/#3/#4/#6) ------ */
/* The opened panel is two columns (table mode only — timeline mode swaps the */
/* whole panel body): form rows left, the library right. The library column   */
/* is a head row (save dialog + title + standard show/hide chevron) over the  */
/* row list; rows show the name only (the ask is a title-attribute tooltip),  */
/* with a pencil + red X trailing. Each row's name IS the load target (a      */
/* plain GET, no JS); pencil/X sit in their own trailing element so a click   */
/* there can never trigger a load — same border/pill vocabulary as the rest   */
/* of the panel, no new palette. */
.panel-columns { align-items: flex-start; display: flex; gap: 1.2rem; }
.panel-main { flex: 1 1 auto; min-width: 0; }
.query-library {
  border-left: 1px solid #e7e3d8;
  flex: 0 0 16rem;
  min-width: 0;
  padding-left: .9rem;
}
.query-library-head { align-items: center; display: flex; flex-wrap: wrap; gap: .2rem .4rem; position: relative; }
.query-library-title {
  color: #8a8578;
  font-size: .68rem;
  letter-spacing: .06em;
  margin-left: auto;
  text-transform: uppercase;
}
/* The save dialog rides the head row (issue #6). Opened (r2 #9), the whole   */
/* details drops to a full-width line UNDER the head — top of the list — while */
/* its summary pill absolutely holds its head-row seat, so "save this ask…"   */
/* never moves and stays the close affordance.                                */
.save-query-dialog { flex: 0 0 auto; }
.save-query-dialog[open] { flex-basis: 100%; order: 3; }
.save-query-dialog[open] > summary { left: 0; position: absolute; top: 0; }
/* r2 #10: the name field takes whatever the row has (longer titles stay      */
/* visible as you type); add/update sit flush right, same edge as the row ×s. */
.save-query-dialog-body {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
  margin: .3rem 0;
}
.save-query-name-input { flex: 1 1 6rem; font-size: .78rem; min-width: 0; padding: .15rem .4rem; }
.save-query-dialog-actions { display: inline-flex; flex: 0 0 auto; gap: .3rem; margin-left: auto; }
.save-query-submit { font-size: .72rem; padding: .15rem .7rem; }
.save-query-error {
  color: #a4442a;
  flex-basis: 100%;
  font-size: .72rem;
  margin: 0;
}
/* r2 #11: past ~45vh the LIST scrolls in place — the head, the save          */
/* affordance, and the fold toggle stay put above it.                         */
.query-library-list { display: none; margin-top: .3rem; max-height: 45vh; overflow-y: auto; }
.query-library-list.open { display: block; }
.saved-query-shelf-list { list-style: none; margin: 0; padding: 0; }
.saved-query-shelf-row {
  align-items: center;
  border-bottom: 1px solid #e7e3d8;
  display: flex;
  gap: .3rem;
  padding: .25rem 0;
}
.saved-query-shelf-row:last-child { border-bottom: none; }
.saved-query-shelf-row.loaded { background: #f2efe6; }
.saved-query-shelf-load {
  color: inherit;
  flex: 1 1 auto;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.saved-query-shelf-load:hover { text-decoration: underline; }
.saved-query-shelf-row-actions { align-items: center; display: flex; flex: 0 0 auto; gap: .2rem; }
/* Pencil + red X (issue #3): quiet trailing icon affordances, never full     */
/* .pill treatment, so the row's own name link stays the dominant target.    */
.saved-query-shelf-rename-toggle {
  background: url("/static/core/img/pencil.svg") center / .8rem no-repeat;
  cursor: pointer;
  display: inline-block;
  height: 1.1rem;
  list-style: none;   /* suppress the native <summary> disclosure marker */
  opacity: .55;
  width: 1.1rem;
}
.saved-query-shelf-rename-toggle::-webkit-details-marker { display: none; }
.saved-query-shelf-rename-toggle:hover { opacity: 1; }
.saved-query-shelf-delete {
  background: none;
  border: none;
  color: #a4442a;
  cursor: pointer;
  font-size: .95rem;
  line-height: 1;
  opacity: .65;
  padding: .1rem .25rem;
}
.saved-query-shelf-delete:hover { opacity: 1; }
/* Editing state (issue #4): while the pencil's <details> is open the name    */
/* display becomes the editable field — the load link, the delete X, and the  */
/* pencil itself all hide, leaving input + save + a TEXT cancel (deliberately */
/* nothing like the X). */
.saved-query-shelf-row:has(.saved-query-shelf-rename[open]) .saved-query-shelf-load { display: none; }
.saved-query-shelf-row:has(.saved-query-shelf-rename[open]) .saved-query-shelf-delete { display: none; }
.saved-query-shelf-rename[open] > .saved-query-shelf-rename-toggle { display: none; }
.saved-query-shelf-rename[open] { flex: 1 1 auto; min-width: 0; }
.saved-query-shelf-row:has(.saved-query-shelf-rename[open]) .saved-query-shelf-row-actions { flex: 1 1 auto; min-width: 0; }
.saved-query-shelf-rename-body {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
}
.saved-query-shelf-rename-input { flex: 1 1 6rem; font-size: .78rem; min-width: 0; padding: .15rem .4rem; }
.saved-query-shelf-rename-submit { font-size: .7rem; padding: .15rem .5rem; }
.saved-query-shelf-rename-cancel {
  background: none;
  border: none;
  color: #8a8578;
  cursor: pointer;
  font-size: .7rem;
  padding: .15rem .2rem;
}
.saved-query-shelf-rename-cancel:hover { color: #4a473e; text-decoration: underline; }
.saved-query-shelf-rename-error {
  color: #a4442a;
  flex-basis: 100%;
  font-size: .72rem;
}
/* The standalone rename/delete <form> elements (when.html, U4) carry only   */
/* hidden inputs -- never visible, but a bare block-level <form> can still   */
/* claim a hair of margin in some browsers; this pins it to zero footprint.  */
.saved-query-shelf-hidden-form { display: none; }
.saved-query-shelf-empty {
  color: #8a8578;
  font-size: .78rem;
  font-style: italic;
  margin: 0 0 .5rem;
}
/* Picked-query freeze (seed B U4, KD16): the frozen badge + reframe link on  */
/* shelf rows, the row-level delete refusal line, and the save dialog's       */
/* frozen note standing in for the hidden update button.                      */
.saved-query-shelf-frozen {
  color: #5b7b96;
  cursor: help;
  font-size: .7rem;
}
.saved-query-shelf-reframe {
  color: #8a8578;
  font-size: .7rem;
  text-decoration: none;
}
.saved-query-shelf-reframe:hover { color: #4a473e; text-decoration: underline; }
.saved-query-shelf-delete-error {
  color: #a4442a;
  display: block;
  flex-basis: 100%;
  font-size: .72rem;
}
.save-query-frozen-note {
  color: #5b7b96;
  cursor: help;
  font-size: .72rem;
  white-space: nowrap;
}

/* Loaded-query name + drift badge (KTD5): rides the always-visible sentence  */
/* row so it survives the panel's post-run collapse; the dead-id note shares  */
/* the row's quiet, muted register rather than an error color — a stale link  */
/* isn't a fault, just something to reissue. */
.loaded-query-chip {
  align-items: baseline;
  display: inline-flex;
  flex: 0 0 auto;
  gap: .3rem;
}
.loaded-query-name {
  background: #e7e3d8;
  border-radius: 10px;
  font-size: .74rem;
  padding: .05rem .6rem;
}
.loaded-query-drift {
  background: #f1e1c6;
  border-radius: 10px;
  color: #7a5a1e;
  font-size: .7rem;
  padding: .05rem .5rem;
}
/* r2 #13: shed the loaded name without loading something else — a quiet ×   */
/* on the chip (query-sentence.js (m) reloads minus `query`).                */
.loaded-query-clear {
  background: none;
  border: none;
  color: #8a8578;
  cursor: pointer;
  font-size: .85rem;
  line-height: 1;
  padding: 0 .1rem;
}
.loaded-query-clear:hover { color: #4a473e; }
.loaded-query-dead {
  color: #8a8578;
  font-size: .78rem;
  font-style: italic;
}

/* Phone-usable panel (~560px, KTD2/U6 verification): each row's label stacks
   above its pills instead of sitting to their left. */
@media (max-width: 560px) {
  /* #1: the sentence stays a wrapping row (not a column stack) so the chevron +
     Run hold the upper-right corner while a long ask line wraps beneath them. */
  .panel-row { align-items: flex-start; flex-direction: column; }
  .panel-row-label { margin-bottom: .15rem; }
  /* The moment row stays a wrapping row (label above): date + time + "now" ride
     one line, "now" still pinned to the right end (lbk 26.08.16). */
  .panel-row.panel-moment { flex-flow: row wrap; align-items: baseline; }
  .panel-moment > .panel-row-label { flex-basis: 100%; }
  /* #10: pill rows read across in wrapping rows (not one-per-line) with the label
     on its own line above; bigger pills + more breathing room for the thumb. */
  .panel-row[data-pill-group] { flex-flow: row wrap; align-items: center; }
  .panel-row[data-pill-group] > .panel-row-label { flex-basis: 100%; }
  .query-panel .pill { font-size: .88rem; padding: .3rem .8rem; margin-bottom: .35rem; }
  /* 10b (walk 26.08.29): "now" sizes up with the pills — it was the one control left small. */
  .set-now-btn { font-size: .88rem; padding: .3rem .8rem; }
  /* Zone follow-ups (walk r-1.5): the shorthand pills + the "other" field wrap as
     one row so "other" rides UTC's row (display:contents lets the pills + input
     share the panel-row's flow), and the field sizes up to match the pills. */
  .panel-row:has(.tz-shorthands) { flex-flow: row wrap; align-items: center; }
  .panel-row:has(.tz-shorthands) > .panel-row-label { flex-basis: 100%; }
  .tz-shorthands { display: contents; }
  /* flex-basis:0 lets "other" ride the tail of UTC's row (it always fits, then
     grows to fill what's left) instead of a fixed width that forces a new line. */
  .tz-entry { flex: 1 1 0; min-width: 3rem; margin-bottom: .35rem; }
  .tz-input { width: 100%; font-size: .88rem; padding: .22rem .5rem; }
  /* The field is narrow (it fills UTC's row tail), so give its match dropdown room
     and anchor it to the right edge so it never runs off-screen. */
  .tz-suggest { min-width: 13rem; left: auto; right: 0; }
  .specifically-row { margin-left: 0; }
  /* The two panel columns stack; the library keeps its head+list shape but   */
  /* spans full width below the form rows (walk 8.12 #2 layout, phone case). */
  .panel-columns { flex-direction: column; }
  .query-library { border-left: none; border-top: 1px solid #e7e3d8; flex-basis: auto; padding-left: 0; padding-top: .5rem; width: 100%; }
}

/* Result tables extend the tools-settings-grid shape with a named class;
   the scroll container keeps wide casts phone-usable (overflow, not squish). */
.results-scroll { overflow-x: auto; }
.ledger-results {
  border-collapse: collapse;
  margin-top: .4rem;
}
.ledger-results th,
.ledger-results td {
  border-bottom: 1px solid #e4e1da;
  padding: .18rem .6rem .18rem 0;
  text-align: left;
  white-space: nowrap;
}
.ledger-results thead th {
  border-bottom: 1px solid #999;
  font-size: .78em;
  font-weight: 600;
  letter-spacing: .02em;
  opacity: .7;
}
.ledger-results tbody th[scope="row"] {
  font-weight: 400;
  padding-right: .6rem;
}
/* #4 (walk 26.08.16): freeze the leading column (body on Where, date on When) so
   the row's identity stays put while a wide cast scrolls sideways. Sticky works
   because .results-scroll is the horizontal scroll container; the cell needs an
   opaque background + a hair of edge separation so scrolled cells pass beneath. */
.ledger-results thead th:first-child,
.ledger-results tbody tr > :first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: #fff;
  box-shadow: 1px 0 0 #e4e1da;
}
/* Match the chart-cast zebra so the frozen cell isn't a white notch on shaded rows. */
.chart-cast tbody tr:nth-child(even) > :first-child { background: #f7f6f2; }
/* 4b (walk 26.08.29): a table opting into two frozen columns (Where: body · position)
   pins column 2 too. Its `left` is column 1's measured width (frozen-columns.js keeps
   --frozen-left-2 current — the body column changes width with the glyph⇄titles toggle),
   and the edge-separation shadow moves out to the new freeze edge. */
table[data-frozen-cols="2"] thead th:nth-child(2),
table[data-frozen-cols="2"] tbody tr > :nth-child(2) {
  position: sticky;
  left: var(--frozen-left-2, 0);
  z-index: 1;
  background: #fff;
  box-shadow: 1px 0 0 #e4e1da;
}
table[data-frozen-cols="2"] thead th:first-child,
table[data-frozen-cols="2"] tbody tr > :first-child { box-shadow: none; }
/* The head row dims itself with opacity:.7, which makes a frozen head cell translucent —
   scrolled header text bled through it (pre-existing for the body head, obvious once two
   cells froze). Frozen head cells go opaque and fake the dimming in color instead
   (each value = the .7-over-white composite of the head's own color/border). */
.ledger-results thead th:first-child,
table[data-frozen-cols="2"] thead th:nth-child(2) {
  opacity: 1;
  color: #9a9694;
  border-bottom-color: #b8b8b8;
  border-left-color: #f2f1ed;
}
table[data-frozen-cols="2"].chart-cast tbody tr:nth-child(even) > :nth-child(2) { background: #f7f6f2; }
/* Bracket rows: the events just outside the range edges (R3) — present but
   visually stepped back, the wireframe's grey-italic treatment. */
.ledger-results .bracket-row td,
.ledger-results .bracket-row th {
  color: #b3afa6;
  font-style: italic;
}
.ledger-results .no-events-row td {
  color: #8a8578;
  font-style: italic;
}
.ledger-query-page .rx { color: #8c2f2f; }
.eventless-note {
  background: #faf8f2;
  border-left: 3px solid #b9b5ac;
  padding: .4rem .7rem;
}
/* Overload guard (7.24): the too-open-ended banner — same quiet left-accent
   shape as .eventless-note, warmed to amber so it reads as "adjust the ask",
   not decoration. */
.overload-note {
  background: #fbf5e9;
  border-left: 3px solid #c9a24b;
  font-size: .85rem;
  margin: .6rem 0;
  padding: .5rem .8rem;
}
.overload-note p { margin: 0 0 .3rem; }
.overload-note ul { margin: 0; padding-left: 1.1rem; }
.overload-note li { color: #6f6a5d; }
/* #3d dropped the "chart cast for …" line (the ask sentence carries the moment);
   .cast-empty is the quiet stand-in when a step lands on a moment with no rows. */
.cast-empty { font-size: .85rem; margin: .6rem 0; opacity: .7; }
/* Time-stepper (#3): arm an interval, then ◀/▶ moves the cast moment by ±1 of it
   and re-casts in place. Arrows hold the two ends; the unit row wraps between. */
.time-stepper {
  align-items: center;
  background: #faf9f6;
  border: 1px solid #d8d3c6;
  border-radius: 8px;
  display: flex;
  /* Arrows hold the two ends (nowrap on the strip); only the unit row wraps,
     inside its own flex item — so it reads ◀ [units…] ▶ at any width. */
  flex-wrap: nowrap;
  gap: .35rem;
  margin: .3rem 0 .7rem;
  padding: .3rem .5rem;
}
.time-stepper .step-units {
  display: inline-flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 0;
  justify-content: center;
}
.time-stepper .step-unit { margin: .1rem .15rem; }
.step-arrow {
  background: #f0eee7;
  border: 1px solid #aaa;
  border-radius: 8px;
  color: #444;
  cursor: pointer;
  flex: 0 0 auto;
  font-size: 1rem;
  line-height: 1;
  min-height: 2rem;
  min-width: 2.2rem;
  padding: .2rem .5rem;
}
.step-arrow:hover { border-color: #888; color: #111; }
.step-arrow:focus-visible { outline: 2px solid #5b7ba0; outline-offset: 1px; }
@media (max-width: 560px) {
  /* Phone (#3): fatter tap targets; arrows keep the ends, units wrap between. */
  .time-stepper { gap: .3rem; padding: .35rem .4rem; }
  .time-stepper .step-unit { font-size: .9rem; margin: .18rem; padding: .32rem .7rem; }
  .step-arrow { font-size: 1.15rem; min-height: 2.5rem; min-width: 2.7rem; }
}
.ledger-pager {
  align-items: center;
  display: flex;
  font-size: .82rem;
  gap: 1rem;
  margin: .6rem 0;
}
.ledger-pager .pager-label { opacity: .7; }

/* Query-polish pass (walk issues 7.21 #3/4/7/8) */
/* Weekday prefix in the When date column — off unless the weekday toggle is on. */
.ledger-query-page .t-weekday { display: none; }
/* Fixed-width when shown (walk 7.22 #3): "M" and "Tu" hold the same box so
   the date digits line up in a clean column. */
html.astro-weekday .ledger-query-page .t-weekday { display: inline-block; width: 2.6ch; }
.weekday-toggle {
  background: none;
  border: 1px solid #bbb;
  border-radius: 12px;
  color: #666;
  cursor: pointer;
  font-size: .72rem;
  padding: .06rem .55rem;
}
.weekday-toggle:hover { border-color: #888; color: #333; }
html.astro-weekday .weekday-toggle { background: #e7e3d8; color: #333; }
/* Glyphs are narrow: center them in the body column (titles stay left-set). */
html:not(.astro-titles) .ledger-results .col-body { text-align: center; }
/* Title mode packs long names tight — give every column more air. */
html.astro-titles .ledger-results th,
html.astro-titles .ledger-results td { padding-right: 1.1rem; }
/* The chart cast's wide column run needs division aids: thin inter-column
   rules + every-other-row shading. */
.chart-cast th,
.chart-cast td { border-left: 1px solid #eceae4; padding-left: .45rem; }
.chart-cast th:first-child,
.chart-cast td:first-child { border-left: none; padding-left: 0; }
.chart-cast tbody tr:nth-child(even) { background: #f7f6f2; }

/* Glyph mode centers the Where cast's columns too (walk tweak 7.21 2:45p) —
   glyphs are narrow everywhere, not just in the body column. */
html:not(.astro-titles) .chart-cast th,
html:not(.astro-titles) .chart-cast td { text-align: center; }

/* ===== Seeding page (ledger-seeding-surface plan, U4) ===================== */
/* Coverage bar (KD4): one relatively-positioned track, three flex segments   */
/* (early tail / headline / late tail) plus an absolutely-positioned hatched   */
/* overlay for the active run, sharing the same left-to-right axis. The       */
/* segments' widths come straight from the view (_seeding_bar_context in      */
/* seeding_views.py) as direct inline percentage widths — the sanctioned      */
/* data-driven exception, same spirit as the timeline lens's --x/--w custom   */
/* props above but plain `style="width: N%"`, not a custom property.          */
.seeding-bar-wrap { margin: .6rem 0 1rem; }
.seeding-bar {
  background: #f2f0e9;
  border-radius: 4px;
  display: flex;
  height: 1.1rem;
  overflow: hidden;
  position: relative;
}
.seeding-bar-seg { height: 100%; }
.seeding-bar-headline { background: #8a8578; }
.seeding-bar-tail { background: #d8d4c8; }
.seeding-bar-active {
  background: repeating-linear-gradient(45deg, rgba(91, 123, 160, .55) 0 4px, rgba(91, 123, 160, .15) 4px 8px);
  bottom: 0;
  position: absolute;
  top: 0;
}
.seeding-bar-caption { color: #6f6a5d; font-size: .82rem; margin: .35rem 0 0; }
.seeding-explainer { color: #6f6a5d; font-size: .88rem; max-width: 46rem; margin: 0 0 1rem; }
.seeding-body-spans { margin: .5rem 0 1rem; font-size: .84rem; color: #6f6a5d; }
.seeding-body-spans summary { cursor: pointer; }
.seeding-body-spans ul { list-style: none; margin: .4rem 0 0; padding: 0; columns: 2; max-width: 34rem; }
.seeding-body-spans li { margin: .1rem 0; }
.seeding-body-name { display: inline-block; min-width: 5.5rem; color: #4c483e; }

.seeding-form { margin: .5rem 0 1rem; }
.seeding-clamp-notes {
  color: #8a6a2f;
  font-size: .8rem;
  list-style: none;
  margin: .2rem 0;
  padding: 0;
}
.seeding-submit-row { align-items: center; }
.seeding-estimate-line { color: #6f6a5d; flex: 1; font-size: .85rem; }

/* Queue / recent-outcomes (U4 static render, U5 polls the same markup). */
.seeding-progress { margin-top: 1.2rem; }
.seeding-progress-heading {
  color: #8a8578;
  font-size: .72rem;
  letter-spacing: .06em;
  margin: 1rem 0 .3rem;
  text-transform: uppercase;
}
.seeding-queue,
.seeding-recent { list-style: none; margin: 0; padding: 0; }
.seeding-run-row {
  align-items: baseline;
  border-bottom: 1px solid #e4e1da;
  display: flex;
  gap: .6rem;
  padding: .3rem 0;
}
.seeding-run-range { font-variant-numeric: tabular-nums; }
.seeding-run-status { color: #8a8578; font-size: .78rem; text-transform: uppercase; }
.seeding-status-running .seeding-run-status { color: #5b7ba0; }
.seeding-status-failed .seeding-run-status { color: #8c2f2f; }
.seeding-status-interrupted .seeding-run-status { color: #c9a24b; }
.seeding-run-pct { color: #6f6a5d; font-size: .78rem; }
.seeding-run-time-left { color: #6f6a5d; font-size: .78rem; }
.seeding-run-error { color: #8c2f2f; font-size: .78rem; }
.seeding-empty-note { color: #8a8578; font-style: italic; }

/* ===== Timeline view (astro-increment-spine U4; band-as-axis) ============ */
/* ONE canvas per view. The increment BAND is the axis (KTD1): bars strata    */
/* lie flush on its faces, split by side, each ribbon named by a thin body-   */
/* colored underbar (the stub color system below); dates/dash/labels are      */
/* occupancy-routed by layout() (far-face when one side draws, own-side when  */
/* both; collision drops flagged per strip); content rows (glyph rows,        */
/* station-label rows) stack outward per side; no station cut-lines — the ℞   */
/* dash lanes + red station dates carry the station story. Positions are     */
/* inline --x/--w custom props set as DATA in astro/_timeline.html; the       */
/* orientation remap is the ONE .tl-h/.tl-v container class — vertical turns  */
/* the same stacked rows into flex COLUMNS, nothing is duplicated (KTD6/7).   */

/* --- Chrome row: orientation left, zoom center, table/timeline toggle right  */
/* (R1/R6/R7) -- reuses the app's own .pill vocabulary (KTD6: plain GET links).*/
.tl-chrome {
  align-items: center;
  display: flex;
  gap: .6rem;
  margin: .5rem 0 .3rem;
}
.tl-chrome-zone { align-items: center; display: flex; flex: 1; flex-wrap: wrap; gap: .3rem; }
.tl-chrome-center { justify-content: center; }
.tl-chrome-right { justify-content: flex-end; }

/* --- Chevron-row timeline label (KTD9/R8): always in the DOM in timeline    */
/* mode (see _query_sentence.html's comment — the chevron's open/closed state */
/* is a live client-side toggle), hidden by CSS until the SAME chevron the    */
/* existing query-sentence.js already flips is actually open -- "unlabeled    */
/* when closed", labeled once opened, no new JS (KTD6). */
.tl-config-label { color: #8a8578; display: none; font-size: .78rem; }
.query-sentence:has(.query-chevron[aria-expanded="true"]) .tl-config-label { display: inline; }
.tl-config-label .paren { font-style: italic; opacity: .75; }

/* --- Chevron panel: align toggle + split-by choices + stratum chips -------- */
/* Color key (walk 7.29 #36): body + element swatches under the scheme       */
/* picker. Swatches inherit the canvas's own vars (body via the shared       */
/* tl-body-<slug> classes, elements via the tint-var scope above), so the    */
/* key always shows exactly what the bars paint — scheme or fallback.        */
.tl-key-item {
  align-items: center;
  color: #5c574e;
  display: inline-flex;
  font-size: .78rem;
  gap: .3rem;
  margin-right: .5rem;
  white-space: nowrap;
}
.tl-key-swatch { border-radius: 2px; display: inline-block; height: 12px; width: 12px; }
.tl-key-body { background: var(--tl-body, #b3afa6); }
.tl-key-signs { color: #8a8578; font-size: .72rem; margin-left: .15rem; }
.tl-split-arrow { color: #b3afa6; margin: 0 .2rem; }
.stratum-chip { border-style: dotted; }
.tl-chip-flip { color: #5c574e; text-decoration: underline dotted; }
.tl-chip-flip:hover { color: #333; }

/* --- Frame: prev/next flank the bar (R6/R7) --------------------------------- */
.tl-hframe, .tl-vframe {
  align-items: center;
  display: flex;
  gap: .5rem;
  margin: .3rem 0 1.6rem;
}
.tl-vframe { flex-direction: column; }
.tl-hframe > .tl { flex: 1; }
.tl-nav { color: #999; font-size: 1.1rem; text-decoration: none; user-select: none; }
.tl-nav:hover { color: #5c574e; }
.tl-nav-2row { font-size: .76rem; line-height: 1.2; text-align: center; }

.tl-span-head {
  color: #3d3a37;
  font-family: var(--font-alt);
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .04em;
  margin: .6rem 0 .3rem;
  text-align: center;
}

.tl,
.tl-key-row,
.cw-tl-astro {
  /* Element tints (walk 7.28 #5): lighter screens of Fire=red, Water=blue,  */
  /* Earth=golden, Air=grey — replaces the first-pass palette. The color-key */
  /* row (walk 7.29 #36) shares the scope so its element swatches read the   */
  /* same vars the canvas paints with — _scheme_styles.html overrides both.  */
  /* .cw-tl-astro (seed B U6/KTD12): the lifeline canvas's astro side reads   */
  /* the SAME vocabulary — same elements, not lookalikes.                     */
  --tl-fire: #e2b3aa;
  --tl-earth: #ddca92;
  --tl-air: #cfd1cd;
  --tl-water: #b7cbdc;
}
.tl { position: relative; }
.tl * { box-sizing: border-box; }

/* Stub body hues (walk 7.28 #5) — placeholders until the real color system   */
/* lands. Set --tl-body, which the underbar always reads and the date/tick    */
/* coloring reads on multi-body asks (the template adds tl-body-<slug> only   */
/* then). Muted-leaning takes on lbk's list so the quiet surface holds.       */
.tl-body-moon { --tl-body: #7d4fb3; }
.tl-body-mercury { --tl-body: #d9740f; }
.tl-body-venus { --tl-body: #2f8f4a; }
.tl-body-sun { --tl-body: #d4a500; }
.tl-body-mars { --tl-body: #c22f2f; }
.tl-body-jupiter { --tl-body: #2f5fb3; }
.tl-body-saturn { --tl-body: #7a5230; }
.tl-body-uranus { --tl-body: #a4adb5; }
.tl-body-neptune { --tl-body: #2b8a85; }
.tl-body-pluto { --tl-body: #1d1b19; }

/* --- The band (R1/KTD1/KTD3): the canvas axis, replacing the old scale     */
/* grid. Its declarations live grouped with the detail timeline's           */
/* .dtl-spine rules (CSS sharing by grouped selectors, no renames — KTD3)    */
/* so the two bands can't drift apart visually: see `.dtl-h .dtl-spine`/     */
/* `.tl-h .tl-band` (~5524) and `.dtl-v .dtl-spine`/`.tl-v .tl-band` (below, */
/* in the "Detail timeline (dtl)" section) for the shared paint/label/nav    */
/* rules. Only the astro-only order/reach/lane rules below live here. */

/* --- .tl-scale survives ONLY for the detail timeline's `dtl-scale tl-scale` */
/* element (details/templates/details/_detail_timeline.html) — astro's OWN   */
/* scale/gridline markup is gone (R1): `.tl-scale-year`, both orientations'  */
/* `.tl-tick-primary`/`.tl-tick-year`, the `.tl-slbl-a`/`-b` tip-label pair,  */
/* and the 48px canvas padding that made room for them are all retired.      */
/* `.tl-slbl` and its hover states stay: dtl's ticks still emit `.tl-slbl`   */
/* labels (kept hidden by `.dtl-h .dtl-scale .tl-slbl`, below). */
.tl-scale { position: absolute; inset: 0; pointer-events: none; }
.tl-scale a { pointer-events: auto; }
.tl-slbl {
  color: #b3afa6;
  display: block;
  font-size: .68rem;
  position: absolute;
  text-decoration: none;
  white-space: nowrap;
}
a.tl-slbl:hover { color: #5c574e; }
a.tl-slbl:hover { text-decoration: underline; }

/* --- The canvas: content rows / date strip / dash lane / bars stack /      */
/* BAND / bars stack / dash lane / date strip / rows (R1/R2/R4/R5/KTD1). */
.tl-canvas { position: relative; }
.tl-crow,
.tl-dates { position: relative; }
.tl-h .tl-crow-glyphs { height: 22px; }
.tl-h .tl-crow-labels { height: 36px; }
/* Date strips grow with their collision-row count (walk 7.29 #32): --dra/  */
/* --drb on .tl-canvas carry each strip's row count from layout(); one row  */
/* (no drops) keeps the original 26px. */
.tl-h .tl-dates-above { height: calc(16px + 10px * var(--dra, 1)); }
.tl-h .tl-dates-below { height: calc(16px + 10px * var(--drb, 1)); }

/* Empty-state collapse (R2's "no strip holds space" extended to the whole   */
/* stack): a stratum-less bars stack, an all-empty date strip, or a dash     */
/* lane with no rx overlays takes no height/width, so the default unsplit    */
/* view doesn't reserve a blank band-facing gutter on its quiet side. NOTE:  */
/* Django's {% for %}/{% endfor %} loops leave whitespace-only text nodes    */
/* around an empty loop, and CSS :empty does NOT treat whitespace text as    */
/* empty — these rules are inert until the affected template loops trim     */
/* that whitespace (this codebase already uses {% spaceless %} elsewhere    */
/* for exactly this, e.g. astro/_query_sentence.html); flagged, not fixed    */
/* here (U4 is a CSS/base.html-only unit — see the U4 handoff report). */
.tl-bars-stack:empty,
.tl-crow:empty { display: none; }
/* Dash lanes deliberately do NOT collapse: the lane always renders (even   */
/* with no ℞ this window) so the tick-reach bridge geometry (+6px h / +7px  */
/* v) is constant — a conditional lane made bridged ticks poke into the     */
/* band whenever the lane vanished (U5 measure round 1). */
.tl-h .tl-dates:empty,
.tl-v .tl-dates:empty { display: none; }

.tl-h .tl-crow > div { height: 100%; left: var(--x); position: absolute; top: 0; }
.tl-h .tl-ing .tl-g { left: 0; position: absolute; top: 2px; transform: translateX(-50%); }
/* Above-zone rows anchor their content to the row's BOTTOM (hugging the      */
/* band); below-zone rows top-anchor. */
.tl-h .tl-crow-above .tl-ing .tl-g,
.tl-h .tl-crow-above .tl-stn-lbl .tl-lbl { bottom: 2px; top: auto; }
.tl-g { font-size: .82rem; white-space: nowrap; }
.tl-h .tl-stn-lbl .tl-lbl {
  left: 0;
  position: absolute;
  text-align: center;
  top: 2px;
  transform: translateX(-50%);
}
.tl-lbl { font-size: .72rem; line-height: 1.3; white-space: nowrap; }
/* The bold "station ℞/D" line stacks above the plain sign+degree line (R4/R5). */
.tl-lbl b { color: #8c2f2f; display: block; font-weight: 600; }
/* walk 7.28 #25: ingress glyph marks tie to the band with a (quieter)       */

/* Date strips: dates hug the band (row 0), collision drops step one row     */
/* further out (walk 7.27 #3), always nearer the band than any label text.   */
.tl-h .tl-datetick { height: 100%; left: var(--x); position: absolute; top: 0; }
.tl-datetick .tl-tick-mark { background: var(--tl-body, #b3afa6); position: absolute; }
.tl-datetick .tl-date {
  color: var(--tl-body, #8a8578);
  font-size: .66rem;
  white-space: nowrap;
}
.tl-h .tl-datetick .tl-date { left: 0; position: absolute; transform: translateX(-50%); }
/* R7: marks extend across the 6px dash lane to touch the bars stack/band    */
/* face rather than stopping at the strip's own edge. */
.tl-h .tl-dates-above .tl-tick-mark { bottom: -6px; height: 11px; left: 0; width: 1px; }
.tl-h .tl-dates-below .tl-tick-mark { height: 11px; left: 0; top: -6px; width: 1px; }
.tl-h .tl-dates-above .tl-date { bottom: 6px; }
.tl-h .tl-dates-below .tl-date { top: 6px; }
/* Multi-level drops (walk 7.29 #32): rows 1–3 step 10px further out, and    */
/* the tick mark extends to reach its dropped date (+6px for the dash lane). */
.tl-h .tl-dates-above .tl-drop-1 .tl-date { bottom: 16px; }
.tl-h .tl-dates-above .tl-drop-2 .tl-date { bottom: 26px; }
.tl-h .tl-dates-above .tl-drop-3 .tl-date { bottom: 36px; }
.tl-h .tl-dates-below .tl-drop-1 .tl-date { top: 16px; }
.tl-h .tl-dates-below .tl-drop-2 .tl-date { top: 26px; }
.tl-h .tl-dates-below .tl-drop-3 .tl-date { top: 36px; }
.tl-h .tl-dates-above .tl-drop-1 .tl-tick-mark,
.tl-h .tl-dates-below .tl-drop-1 .tl-tick-mark { height: 21px; }
.tl-h .tl-dates-above .tl-drop-2 .tl-tick-mark,
.tl-h .tl-dates-below .tl-drop-2 .tl-tick-mark { height: 31px; }
.tl-h .tl-dates-above .tl-drop-3 .tl-tick-mark,
.tl-h .tl-dates-below .tl-drop-3 .tl-tick-mark { height: 41px; }

/* --- Dash lane (R4/R5/KTD5): the ℞ hatch rides the date side, adjacent to  */
/* the band-facing edge of the date strip — never between bars and band.    */
/* Routed by layout() to the same face its station dates use; the lane      */
/* itself is a fixed-size gutter (no content sizes it) between the date     */
/* strip and the bars stack. */
.tl-h .tl-dashlane { height: 6px; position: relative; }
.tl-h .tl-dashlane .tl-rx-hatch {
  background: repeating-linear-gradient(-45deg, rgba(140, 47, 47, .5) 0 3px, transparent 3px 6px);
  height: 5px;
  left: var(--x);
  position: absolute;
  width: var(--w);
}
.tl-h .tl-dashlane-above .tl-rx-hatch { bottom: 0; } /* band sits below this lane */
.tl-h .tl-dashlane-below .tl-rx-hatch { top: 0; } /* band sits above this lane */

/* --- Bars stacks: flush on the band (R2) — the stack element takes no     */
/* height of its own; its ribbon rows size it, and nothing (strip/gutter/   */
/* overlay) separates a stack from the band face it lies against. */
/* 29px = ribbon 26 + underbar 3 — the old 36px reserved a 7px in-row hatch */
/* zone below the underbar; with the hatch relocated to the dash lanes      */
/* (KTD5) that zone is a dead gutter between bars and band, violating R2.  */
.tl-h .tl-ribbon-row { height: 29px; position: relative; }
/* A synthetic context ribbon reserves no hatch/underbar zone — it sits      */
/* flush on the dignity bar below it (walk 7.28 #21; 7.29 #33 closed the     */
/* 1px slack the old 27px row height left between the two). */
.tl-h .tl-row-flush { height: 26px; }
/* walk 7.28 #26 / 7.29 #40: each horizontal seg is a size container, so     */
/* "does the glyph fit" is answered by the slot's REAL pixel width, not a    */
/* server-side percentage guess. A slot 16px+ keeps its glyph in the bar;    */
/* narrower, the glyph hides and the hoisted toplabel (emitted server-side   */
/* for sub-1.5% segs — TOPLABEL_HOIST_PCT) shows instead, centered over the  */
/* slot (#34). Without @container support this degrades to always-in-bar.    */
.tl-seg-toplabel { display: none; }
@container (max-width: 15.99px) {
  .tl-h .tl-seg .tl-g { display: none; }
  .tl-h .tl-seg .tl-seg-toplabel {
    color: #3d3a37;
    display: block;
    font-size: .66rem;
    left: 50%;
    position: absolute;
    top: -13px;
    transform: translateX(-50%);
    white-space: nowrap;
  }
}
/* walk 7.29 #41: a sign ribbon whose dignity row hoists glyphs into it      */
/* (.tl-row-lifted, set by the view) floats its own glyphs to the ribbon's   */
/* top — the hoisted lord glyphs live in its bottom half.                    */
.tl-h .tl-row-lifted .tl-seg { align-items: flex-start; }
.tl-h .tl-row-lifted .tl-g { line-height: 1; }
.tl-h .tl-ribbon,
.cw-tl-h .tl-ribbon { height: 26px; left: 0; position: absolute; right: 0; top: 0; }
.tl-h .tl-seg,
.cw-tl-h .tl-seg {
  align-items: center;
  border-right: 1px solid rgba(255, 255, 255, .75);
  container-type: inline-size;
  display: flex;
  height: 100%;
  justify-content: center;
  left: var(--x);
  position: absolute;
  width: var(--w);
}
/* Fallback before the tints at EQUAL specificity (walk 7.28 #12: an        */
/* orientation-scoped fallback out-specified the single-class tints and     */
/* grayed every bar). */
.tl-seg { background: #d8d4c9; }
.tl-seg-fire { background: var(--tl-fire); }
.tl-seg-earth { background: var(--tl-earth); }
.tl-seg-air { background: var(--tl-air); }
.tl-seg-water { background: var(--tl-water); }
/* Bound/decan bars identify by LORD (walk 7.28 #19): a light screen of the  */
/* lord's stub body hue (the tl-body-* class on the same element), placed    */
/* after the element tints so it wins at equal specificity. */
.tl-seg-lord { background: color-mix(in srgb, var(--tl-body, #b3afa6) 80%, white); color: #fff; }
.tl-seg-lord.tl-body-sun,
.tl-seg-lord.tl-body-uranus { color: #3d3a37; }
.tl-h .tl-seg-in,
.cw-tl-h .tl-seg-in {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 85%);
  mask-image: linear-gradient(90deg, transparent, #000 85%);
}
.tl-h .tl-seg-out,
.cw-tl-h .tl-seg-out {
  -webkit-mask-image: linear-gradient(90deg, #000 15%, transparent);
  mask-image: linear-gradient(90deg, #000 15%, transparent);
}
/* The body underbar hugs the bar (walk 7.28 #13); the ℞ hatch moved out to  */
/* its own dash lane (above) — this is the ONLY paint left under the ribbon. */
.tl-h .tl-underbar { background: var(--tl-body, #d8d4c9); height: 3px; left: var(--x); position: absolute; top: 26px; width: var(--w); }
/* walk 8.5 #7: the body underbar always sits BETWEEN its bars and the band.
   The below face's band is above the stack, so underbar and ribbon swap. */
.tl-h .tl-face-below .tl-underbar { top: 0; }
.tl-h .tl-face-below .tl-ribbon { top: 3px; }
/* A bracket moment outside the ask renders muted — the timeline's version    */
/* of the table's grayed bracket rows (walk 7.28 #30). */
.tl-outside { opacity: .45; }

/* --- Vertical remap (R8, KTD4): the same stacked rows become flex COLUMNS; */
/* every item repositions by top:var(--x) instead of left. KTD4's sides-     */
/* swap: default (above-family) content now reads to the band's RIGHT       */
/* (today's plain source order puts it on the left — the swap mirrors the    */
/* dtl-v flex-order precedent, ~5679); flipped (below-family) content reads  */
/* to its LEFT. Only the region ORDER changes here — every box below keeps   */
/* its own pre-existing sizing/position rules (R8 is template/CSS-level      */
/* only, KTD4). */
.tl-v { width: 100%; }
/* walk 7.28 #28: the flex columns overlay the scale's left/right label      */
/* strips — without this, the label LINKS underneath never receive clicks.   */
.tl-v .tl-canvas { pointer-events: none; }
.tl-v .tl-canvas a { pointer-events: auto; }
.tl-v .tl-canvas { display: flex; flex-direction: row; height: 760px; }
/* Face wrappers (walk 8.5 #3): the two faces are equal-basis flex sides, so
   the band sits centered in the window and STAYS PUT when a split moves
   content across it. below-face renders left of the band, above-face right
   (R8's sides-swap). Inside a face, source order is band-outward bottom-up
   (crows, dates, dash, bars) — row-reverse renders it band-outward in v; the
   above face packs its columns against the band (flex-end under reverse).
   Faces are meaningless boxes in h (plain blocks; their children stack). */
.tl-v .tl-face { display: flex; flex-direction: row-reverse; flex: 1 1 0; min-width: 0; }
.tl-v .tl-face-below { order: 1; }
.tl-v .tl-band { order: 2; }
.tl-v .tl-face-above { order: 3; justify-content: flex-end; }
.tl-v .tl-crow { height: 100%; }
.tl-v .tl-crow-glyphs { width: 84px; }
.tl-v .tl-crow-labels { width: 140px; }
.tl-v .tl-dates { height: 100%; }
/* Vertical strips widen with their collision-row count (walk 7.29 #32);    */
/* one row keeps the original 52px. */
.tl-v .tl-dates-above { width: calc(22px + 30px * var(--dra, 1)); }
.tl-v .tl-dates-below { width: calc(22px + 30px * var(--drb, 1)); }
/* .tl-bars-stack replaces the old .tl-spine as the ribbon-rows' direct       */
/* parent (U3) — carrying its flex-row layout forward so multiple strata     */
/* (a split view) sit side by side as columns rather than stacking block-    */
/* vertically; :not(:empty) keeps the empty-collapse rule above winning at   */
/* equal specificity regardless of source order. Horizontal needs no         */
/* equivalent — ribbon-rows already stack top-to-bottom by default there,    */
/* which IS the desired "stacked ribbons" look. */
.tl-v .tl-bars-stack:not(:empty) { display: flex; flex-direction: row; height: 100%; }
/* +4px = the underbar column only (rw+1..rw+4) — the old +14px reserved    */
/* the in-row hatch zone, retired with KTD5's dash-lane relocation (R2). */
.tl-v .tl-ribbon-row { position: relative; width: calc(var(--rw, 18px) + 4px); }
/* walk 7.29 #33 (vertical): a synthetic context ribbon reserves no          */
/* underbar/hatch zone here either — flush against the next ribbon column. */
.tl-v .tl-row-flush { width: var(--rw, 18px); }
/* walk 7.28 #27: a vertical bar's width carries no time meaning — rows      */
/* whose segment labels are text-length widen to house them. */
.tl-v .tl-row-wide { --rw: 64px; }
.tl-v .tl-ribbon,
.cw-tl-v .tl-ribbon { bottom: 0; left: 0; position: absolute; top: 0; width: var(--rw, 18px); }
.tl-v .tl-seg,
.cw-tl-v .tl-seg {
  align-items: center;
  border-bottom: 1px solid rgba(255, 255, 255, .75);
  border-right: none;
  display: flex;
  height: var(--w);
  justify-content: center;
  left: 0;
  position: absolute;
  top: var(--x);
  width: 100%;
}
.tl-v .tl-seg-in,
.cw-tl-v .tl-seg-in {
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 85%);
  mask-image: linear-gradient(180deg, transparent, #000 85%);
}
.tl-v .tl-seg-out,
.cw-tl-v .tl-seg-out {
  -webkit-mask-image: linear-gradient(180deg, #000 15%, transparent);
  mask-image: linear-gradient(180deg, #000 15%, transparent);
}
.tl-v .tl-underbar { background: var(--tl-body, #d8d4c9); height: var(--w); left: calc(var(--rw, 18px) + 1px); position: absolute; top: var(--x); width: 3px; }
/* walk 8.5 #7 (v): the above face's band is to the stack's LEFT — underbar
   and ribbon swap edges within each ribbon-row column. */
.tl-v .tl-face-above .tl-underbar { left: 0; }
.tl-v .tl-face-above .tl-ribbon { left: 4px; }

/* --- Dash lane, vertical: same fixed 7px gutter, hatch rides the           */
/* band-facing edge of the lane (mirrors the h treatment above). */
.tl-v .tl-dashlane { position: relative; width: 7px; }
.tl-v .tl-dashlane .tl-rx-hatch {
  background: repeating-linear-gradient(0deg, rgba(140, 47, 47, .5) 0 3px, transparent 3px 6px);
  height: var(--w);
  position: absolute;
  top: var(--x);
  width: 5px;
}
.tl-v .tl-dashlane-below .tl-rx-hatch { right: 0; } /* band sits to the right of this lane, post-swap */
.tl-v .tl-dashlane-above .tl-rx-hatch { left: 0; } /* band sits to the left of this lane, post-swap */

.tl-v .tl-datetick { left: 0; position: absolute; top: var(--x); width: 100%; }
.tl-v .tl-datetick .tl-date { position: absolute; top: -.55em; }
/* R7: marks extend across the 7px dash lane to touch the bars stack/band    */
/* face. Post-KTD4-swap anchors (U5 measure round 2): the ABOVE family sits  */
/* RIGHT of the band, so its band-facing edge is LEFT; the BELOW family sits */
/* LEFT, band-facing RIGHT. Every anchor below (marks, dates, drops, crow    */
.tl-v .tl-dates-above .tl-tick-mark { height: 1px; left: -7px; position: absolute; top: 0; width: 14px; }
.tl-v .tl-dates-below .tl-tick-mark { height: 1px; position: absolute; right: -7px; top: 0; width: 14px; }
.tl-v .tl-dates-above .tl-date { left: 8px; }
.tl-v .tl-dates-below .tl-date { right: 8px; text-align: right; }
/* Multi-level drops, vertical (walk 7.29 #32): rows step 30px further from  */
/* the band; the tick mark reaches its dropped date (+7px for the dash lane). */
.tl-v .tl-dates-above .tl-drop-1 .tl-date { left: 38px; }
.tl-v .tl-dates-above .tl-drop-2 .tl-date { left: 68px; }
.tl-v .tl-dates-above .tl-drop-3 .tl-date { left: 98px; }
.tl-v .tl-dates-below .tl-drop-1 .tl-date { right: 38px; }
.tl-v .tl-dates-below .tl-drop-2 .tl-date { right: 68px; }
.tl-v .tl-dates-below .tl-drop-3 .tl-date { right: 98px; }
.tl-v .tl-dates-above .tl-drop-1 .tl-tick-mark,
.tl-v .tl-dates-below .tl-drop-1 .tl-tick-mark { width: 44px; }
.tl-v .tl-dates-above .tl-drop-2 .tl-tick-mark,
.tl-v .tl-dates-below .tl-drop-2 .tl-tick-mark { width: 74px; }
.tl-v .tl-dates-above .tl-drop-3 .tl-tick-mark,
.tl-v .tl-dates-below .tl-drop-3 .tl-tick-mark { width: 104px; }

.tl-v .tl-crow > div { left: 0; position: absolute; top: var(--x); width: 100%; }
.tl-v .tl-crow-above .tl-g,
.tl-v .tl-crow-above .tl-lbl { left: 4px; position: absolute; top: -.6em; }
.tl-v .tl-crow-below .tl-g,
.tl-v .tl-crow-below .tl-lbl { position: absolute; right: 4px; text-align: right; top: -.6em; }
/* Vertical station labels read INLINE ("station ℞ · ♑ 26°") — every label   */
/* gets a full line in this orientation (the wireframe's C treatment). */
.tl-v .tl-lbl b { display: inline; }
.tl-v .tl-lbl b::after { color: #8a8578; content: " · "; font-weight: 400; }

@media (max-width: 560px) {
  .tl-chrome { flex-wrap: wrap; }
  .tl-chrome-zone { flex: 1 1 100%; justify-content: flex-start; }
  .tl-chrome-right { justify-content: flex-start; }
}

/* ===== Detail timeline (dtl) ===== */
/* timeline-puzzling plan (details/timeline.py's chronological point         */
/* puzzler + containment-nested bracket region), rendering the TimelineModel */
/* ranged/vague-date detail, footer/undated shelves). Reuses the shipped     */
/* Timeline view vocabulary above (.tl-chrome/.pill/.tl-span-head/.tl-nav/   */
/* .tl-scale/.tl-slbl/.overload-note, .advanced-collapse/.collapse) wherever */
/* the containment-nested bracket region), so THAT gets its own .dtl-*       */
/* vocabulary. Colors reuse the same                                        */
/* literal ink/muted tones the shipped Timeline view section already uses   */
/* (#3d3a37/#5c574e/#8a8578/#b3afa6/#d9d5cc) -- no separate custom-property  */
/* set exists for them (walk 7.28 #5's --tl-* vars are body-hue-only), and   */
/* deliberately no rust/red accent (that's astro's retrograde-station red,   */
/* not a vague-date color here). */

/* lifeline walk r2 #5: the Timeline composition's zoom clusters (Year/Age,      */
/* Century/Lifespan anchoring pairs) join the chip groups — the bold-current /   */
/* dotted-underline-link selection syntax is ONE convention (lbk: refinements to */
/* it apply everywhere it's used), so the cluster styles here, not in its own    */
/* stylesheet. */
.dtl-labels-chip a, .dtl-thin-chip a, .cw-tl-zoom-cluster a { color: #5c574e; text-decoration: underline dotted; }
.dtl-labels-chip a:hover, .dtl-thin-chip a:hover, .cw-tl-zoom-cluster a:hover { color: #333; }

.dtl { position: relative; font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace; } /* monospace probe (walk § stepping back): fixed advance width makes the server's chars×em estimate exact */
.dtl-above, .dtl-under, .dtl-below, .dtl-feed-lane, .cw-tl-feed-track { font-size: .78rem; } /* ONE em unit for leaders + blocks + the bracket nest — mixed font-size ems made leaders run ~28% past their text (walk #29; bit the bracket region the same way, walk-puzzling #8) — the feed lane (U1, timeline-feed plan) joins the same pin so its ghost/stem children (U2) share it too */
.dtl * { box-sizing: border-box; }
/* The frame's flex child rule keys on .tl (astro's canvas root); without a
   matching rule the .dtl canvas shrinks to content width and every --x
   percentage resolves against a sliver — the whole spine piles up at the
   left (playwright smoke 26.07.31). */
.tl-hframe > .dtl { flex: 1; min-width: 0; }
.tl-vframe > .dtl { flex: 1; min-height: 0; width: 100%; } /* the vframe centers children; without a width the canvas shrink-wraps and the flex sides collapse to zero */
.tl-hframe .tl-nav, .tl-vframe .tl-nav { background: rgba(255, 255, 255, .85); border-radius: 4px; position: relative; z-index: 3; } /* stacked text never buries the nav (walk #18) */

/* (the bracket nest) -- horizontal stacks them top to bottom; .dtl-v remaps */
/* to a flex row (above=left, below=right) below. A fixed min-height stands  */
/* aggregate one) -- roomy enough for the packer's normal stack depths;      */
/* overflow stays visible rather than clipping a genuinely tall stack. */
.dtl-canvas { position: relative; }
.dtl-h .dtl-above { height: var(--above-h, 11rem); min-height: 4rem; overflow: visible; position: relative; }
/* --- Feed lane (U1, timeline-feed plan): the reserved-margin strip        */
/* between the lowest entry row and the spine/increment band — R1's        */
/* install site. Idle it's fully invisible (no background/border);          */
/* timeline-feed.js fills it on hover with a tick comb, the snapped cell     */
/* and a ghost date. 2.6em = 2 rows * LINE_HEIGHT_EM (details/timeline.py's  */
/* ENVELOPE_BLOCK_MARGIN_ROWS), the SAME breathing-room gap                 */
/* `_pack_leaders_h` reserves before its own first block — that's also the  */
/* floor `above_height_em` gets on an empty canvas (R1: the lane must exist  */
/* there too), so the strip's rest-state footprint matches a populated       */
/* canvas's spine-adjacent margin. pointer-events is scoped to the lane's    */
/* own strip only (R3: never intercepts band/chevron/block clicks —         */
/* .dtl-block's z-index:2 already paints over anything of this strip it     */
/* overlaps); every ghost child is pointer-events:none so none of them can  */
/* widen that hit area. crosshair, not the default arrow: the arrow's body  */
/* extends down-right from its hotspot and buried the leader into the band   */
/* (walk 8.7 #3's second half), where a crosshair's thin arms don't. */
/* The lane's own thickness, named ONCE: in v it's a real flex column, so three
   separate rules have to agree on it (the column's basis, the point region's
   basis compensation, and how far the point leaders reach back through it to
   touch the band). Three hand-typed 2.6ems would drift the moment one changed. */
.dtl { --feed-lane-em: 2.6em; }
.dtl-feed-lane { cursor: crosshair; pointer-events: auto; }
/* Horizontal: a full-width strip anchored to .dtl-above's own bottom edge  */
/* (== the spine's top edge), so it never depends on --above-h's value.     */
.dtl-h .dtl-feed-lane { bottom: 0; height: var(--feed-lane-em); left: 0; position: absolute; width: 100%; }
/* Vertical (walk 8.7 #11): the same strip rotated into its own flex column */
/* of the canvas, ordered between the spine and the point region (see the   */
/* .dtl-v order block below). Nothing here is absolute — the flex column IS */
/* the gutter, which is what pushes v's point labels off the band and gives */
/* them the spine-adjacent margin h has had since walk 8.4 #2. */
.dtl-v .dtl-feed-lane { flex: 0 0 var(--feed-lane-em); height: 100%; position: relative; }
/* The three hover children. All pointer-events:none (R3/KTD8) so they can  */
/* never widen the lane's own hit area, and all revealed by ONE .is-hot     */
/* class on the lane (never a raw display:none the JS could forget to       */
/* clear) — pointerleave's hide is a single classList.remove. The comb is   */
/* built once per layout; mousemove only moves the cell and relabels the    */
/* ghost, so the hot path touches two custom properties and one textContent. */
.dtl-feed-ticks, .dtl-feed-cell, .dtl-feed-ghost { display: none; pointer-events: none; position: absolute; }
.dtl-feed-lane.is-hot .dtl-feed-ticks,
.dtl-feed-lane.is-hot .dtl-feed-cell,
.dtl-feed-lane.is-hot .dtl-feed-ghost,
.cw-tl-feed-track.is-hot .dtl-feed-ticks,
.cw-tl-feed-track.is-hot .dtl-feed-cell,
.cw-tl-feed-track.is-hot .dtl-feed-ghost { display: block; }
/* lifeline walk r5 #16: the comp canvas's hover-increment display renders the   */
/* SAME dtl-feed ghost/cell/tick elements inside its add strip                   */
/* (.cw-tl-feed-track, spine-hugging) — fourth member of these groups, the       */
/* r-2 spine no-renames contract.                                                */
.dtl-feed-ticks { inset: 0; }
/* Walk 8.7 #3: the strip reads as SUB-INCREMENTS, not as one rule. Each    */
/* tick is a true boundary of the unit a click snaps to (a year at decade   */
/* zoom, a month at year zoom, …) — scanned out of the snap function itself */
/* by timeline-feed.js, so a tick can never sit where a click wouldn't land. */
.dtl-feed-tick { border-color: #cfc9bc; border-style: solid; border-width: 0; position: absolute; }
.dtl-h .dtl-feed-tick,
.cw-tl-h .dtl-feed-tick { border-left-width: 1px; bottom: 0; height: .4em; left: var(--tx); }
.dtl-v .dtl-feed-tick,
.cw-tl-v .dtl-feed-tick { border-top-width: 1px; left: 0; top: var(--tx); width: .4em; }
/* The snapped cell: the whole sub-increment the click would land in, washed */
/* faintly with its two edges drawn in. It is deliberately WIDER than the    */
/* pointer — that's the fix for "you can't see the leader, your cursor is on */
/* it" (walk 8.7 #3): the cursor sits inside the cell while the cell's edges */
/* and label stay legible around it. z-index lifts the edges over the band's */
/* own background so they visibly land ON the increment band; .dtl-block's   */
/* z-index:2 still paints over the cell, so entry labels stay readable. */
/* Reads clearly AGAINST the comb, which is the hard part — at fine sub-increments the
   cell is only a few pixels wide, so it has to be darker than the ticks rather than
   merely bigger. It also spans further from the band than a tick does, so its edges
   rise clear of the comb, and dips past the band's edge so the landing spot is legible
   below the pointer. */
.dtl-feed-cell { background: rgba(138, 133, 120, .22); z-index: 1; }
.dtl-h .dtl-feed-cell,
.cw-tl-h .dtl-feed-cell {
  border-left: 1px solid rgba(96, 92, 82, .8);
  border-right: 1px solid rgba(96, 92, 82, .8);
  bottom: -.5em;
  height: 1.5em;
  left: var(--cell-x);
  width: var(--cell-w);
}
.dtl-v .dtl-feed-cell,
.cw-tl-v .dtl-feed-cell {
  border-bottom: 1px solid rgba(96, 92, 82, .8);
  border-top: 1px solid rgba(96, 92, 82, .8);
  height: var(--cell-w);
  left: -.5em;
  top: var(--cell-x);
  width: 1.5em;
}
/* The ghost date rides at the CELL's centre rather than at the pointer, so  */
/* at coarse zooms (wide cells) it sits clear of the cursor entirely. The    */
/* shipped muted ink (dtl-spine-label) — a preview, not a committed label —  */
/* and band-hugging (walk 8.6 #4): .55em off the band in h, just past the    */
/* cell's edge in v. */
/* The ghost is wider than the lane, so it spills into the entry region and — before
   walk 8.7 #11's follow-up — spilled UNDERNEATH the entry labels, which carry
   z-index:2: you couldn't read the date you were about to click. It now takes the
   same white panel .dtl-block uses (same padding, so glyphs sit off the edges) one
   layer higher, so it reads over whatever it crosses. Deliberately above blocks
   rather than beside them: this is a transient preview of a click already in
   progress, and it has to be legible for exactly as long as the pointer is there. */
.dtl-feed-ghost {
  background: #fff;
  color: #8a8578;
  padding: .05rem .3rem;
  white-space: nowrap;
  z-index: 3;
}
.dtl-h .dtl-feed-ghost,
.cw-tl-h .dtl-feed-ghost {
  bottom: .55em;
  left: calc(var(--cell-x) + var(--cell-w) / 2);
  transform: translateX(-50%);
}
.dtl-v .dtl-feed-ghost,
.cw-tl-v .dtl-feed-ghost {
  left: 1.3em; /* clear of the cell, which runs to 1em (left:-.5em + 1.5em wide) */
  top: calc(var(--cell-x) + var(--cell-w) / 2);
  transform: translateY(-50%);
}
/* astro-increment-spine U4/KTD3: astro's .tl-band groups with these        */
/* .dtl-spine rules (no renames) so the two bands can't drift apart          */
/* visually. Heights differ per family — astro's strip geometry stays       */
/* px-based (plan assumption) rather than compounding dtl's unpinned 1.6em   */
/* (em-pin learning, 2026-08-04) — everything else astro shares verbatim. */
/* lifeline walk r2 #1: the Timeline composition canvas (.cw-tl-h/.cw-tl-v   */
/* contexts) renders the SAME .dtl-spine element and joins these groups —   */
/* third member, same no-renames contract. */
.dtl-h .dtl-spine,
.cw-tl-h .dtl-spine,
.tl-h .tl-band { background: #e7e3d9; border: 1px solid #d9d5cc; border-radius: 2px; position: relative; }
.dtl-h .dtl-spine,
.cw-tl-h .dtl-spine { height: 1.6em; }
.tl-h .tl-band { height: 26px; }
/* one-timeline-base r-1 (lifeline-base walk #14, 8.30): --x is the slot CENTER —  */
/* every family's builder hands the label its increment's midpoint               */
/* (core.timeline_view.spine_cells) and the label centers on it. Was start-       */
/* anchored (left offset + 3px padding); the composition's r-3 trial divergence   */
/* became the shared rule. */
.dtl-h .dtl-spine-label,
.cw-tl-h .dtl-spine-label,
.tl-h .tl-blbl {
  color: #8a8578;
  font-size: .68rem;
  left: var(--x);
  position: absolute;
  text-decoration: none;
  top: 50%;
  transform: translate(-50%, -50%);
  white-space: nowrap;
}
a.tl-blbl:hover { color: #5c574e; text-decoration: underline; }
/* R3: a folded-year band label (band_ticks' seam flag, astro-derived,      */
/* KTD2) reads darker/bolder — astro's own take on dtl-spine-hot's cue. */
/* No seam styling (walk 8.5 #5): darker/bold is the zoom-scent's vocabulary
   (dtl-spine-hot), so the folded year text is the seam's only cue. The class
   stays in the markup as the seam's semantic hook. */
.dtl-h .dtl-spine .dtl-spine-nav,
.cw-tl-h .dtl-spine .dtl-spine-nav,
.tl-h .tl-band .tl-band-nav { color: #5c574e; font-size: 1rem; position: absolute; text-decoration: none; top: 50%; transform: translateY(-50%); z-index: 3; }
.dtl-h .dtl-spine .dtl-spine-nav-prev,
.cw-tl-h .dtl-spine .dtl-spine-nav-prev,
.tl-h .tl-band .tl-band-nav-prev { left: -1.2em; }
.dtl-h .dtl-spine .dtl-spine-nav-next,
.cw-tl-h .dtl-spine .dtl-spine-nav-next,
.tl-h .tl-band .tl-band-nav-next { right: -1.2em; }
.dtl-v .dtl-spine-nav,
.cw-tl-v .dtl-spine-nav,
.tl-v .tl-band-nav { display: none; } /* v keeps its own column/2-row chevrons */
.dtl-h .dtl-scale .tl-slbl { display: none; } /* increment labels live in the spine now (walk #22); the bottom row at the grid ends stays (walk #12) */
.dtl-v .dtl-spine-label,
.cw-tl-v .dtl-spine-label,
.tl-v .tl-blbl { color: #8a8578; font-size: .68rem; left: 50%; position: absolute; top: var(--x); transform: translate(-50%, -50%); } /* --x = slot center (r-1); was a 2px nudge below the tick */
/* R3 (v): a seam label carrying a folded year wraps under its month,        */
/* centered — the band COLUMN itself never widens to fit it (astro-only;     */
/* dtl's labels never carry a year fold and stay single-line). */
/* pre-line honors the seam label's month\nyear newline (walk 8.5 #4) —
   h collapses the same newline to a space via its nowrap rule. */
.tl-v .tl-blbl { text-align: center; white-space: pre-line; width: 100%; }
/* R14/U6: the zoom-scent cue -- a spine label whose tick conceals at least
   one thinned point (see details/timeline.py's thinned_tick_counts) bolds
   and darkens, independent of orientation (h's --x is a left offset, v's a
   top offset, but the label's OWN ink is orientation-agnostic). */
.dtl-spine-label.dtl-spine-hot { color: #3d3a37; font-weight: 700; }
/* one-timeline-base r-1 (lifeline-base walk #14, flowed back 8.30): every other
   increment slot casts a very faint screen through the content, both orientations,
   inert to the pointer, running all the way to the spine (whose own paint sits over
   it). ONE rule for the three families — the detail view (.dtl-slot-screen, a
   .dtl-canvas child), the When band (.tl-slot-screen, a .tl-canvas child) and the
   Timeline composition (.cw-tl-slot-screen, per topic-lane track / fodder band —
   moved here from timeline/extra_styles.css). --pos/--len come from
   core.timeline_view.spine_cells' start_pct/width_pct. */
.dtl-slot-screen,
.tl-slot-screen,
.cw-tl-slot-screen { background: rgba(138, 133, 120, .06); pointer-events: none; position: absolute; }
.dtl-h .dtl-slot-screen,
.tl-h .tl-slot-screen,
.cw-tl-h .cw-tl-slot-screen { bottom: 0; left: var(--pos); top: 0; width: var(--len); }
.dtl-v .dtl-slot-screen,
.tl-v .tl-slot-screen,
.cw-tl-v .cw-tl-slot-screen { height: var(--len); left: 0; right: 0; top: var(--pos); }
.dtl-h .dtl-below { height: calc(1.5rem + var(--below-rows, 1) * 1.3em + 3em); overflow: visible; position: relative; }
.dtl-h .dtl-under { height: var(--under-h, 0); min-height: 0; overflow: visible; position: relative; }
.dtl-scale { pointer-events: none; }
.dtl-scale a { pointer-events: auto; }

/* --- Leaders: a thin line from the spine to the top of its top block       */
/* (R7), positioned along the time axis by --x; --h is its own length. */
/* A leader is pure decoration — no listener, no hover state, no title — but every
   one of them CROSSES the feed lane on its way to the band (that's the point: it has
   to touch the increment it marks). While they took pointer events, dragging along
   the lane hit a leader instead of the lane on every crossing, which fired the lane's
   pointerleave and blinked the ghost out; in the dense lifespan view the leaders
   carpet the strip so completely that whole spans had no clickable lane left at all
   (walk 8.8 #12 — the 2021–2026 dead zone). Transparent to the pointer, with the
   BLOCKS explicitly re-armed below since pointer-events:none inherits. */
.dtl-leader { pointer-events: none; }
.dtl-h .dtl-leader {
  border-left: 1px solid #8a8578; /* darker than the #e4e1da grid (walk #1) */
  bottom: 0;
  height: var(--h);
  left: var(--x);
  position: absolute;
  width: 0;
}

/* (--bottom, KTD8); the puzzler (U2) pivots inward and truncates (KTD6)    */
/* guaranteed -- in a dense field a lifted block can still cross a          */
/* neighbor's x through a margin gap. When that happens, this panel         */
/* masking behavior, now implicit rather than deliberate) -- a design       */
/* question deliberately left open for the walk, not a guarantee.          */
.dtl-block {
  background: #fff;
  color: #33312c;
  pointer-events: auto; /* re-armed: .dtl-leader (its parent) is pointer-events:none so the lane stays reachable through the leader lines, and that inherits */
  display: block;
  font-size: 1em; /* inherits the region's .78rem — do not compound */
  line-height: 1.3;
  padding: .05rem .3rem;
  position: absolute;
  text-decoration: none;
  width: max-content;
  z-index: 2;
}
.dtl-h .dtl-block { bottom: var(--bottom); }
.dtl-h .dtl-block-right { left: 1px; text-align: left; }
.dtl-h .dtl-block-left { right: 1px; text-align: right; }
/* review #5 backstop: the wrap engines pre-split any single token past
   MAX_LINE_CHARS before this ever renders (details/timeline.py's
   `_hard_split_long_tokens`), but this rule catches anything that still
   arrives wider than its box (e.g. a raw un-wrapped title) rather than
   letting it push past the canvas edge. */
.dtl-line { display: block; overflow-wrap: anywhere; }
.dtl-date { color: #8a8578; display: block; font-size: .66rem; }

/* --- Scale ticks: gridline + click-target label (R9's zoom-in-on-cell),    */
/* the shipped .tl-slbl idiom for the label text itself. */
.dtl-tick { position: absolute; }
.dtl-h .dtl-tick { display: none; } /* grid lines hidden (walk #34); the spine carries the increments */
.dtl-tick .tl-slbl { left: 3px; position: absolute; }
.dtl-h .dtl-tick .tl-slbl { top: 2px; }
.dtl-tick-year .tl-slbl { color: #8a8578; }

/* --- The bracket nest (R9-R12, U4): ONE ranged idiom replaces the old      */
/* bordered-cell bracket / background-bar span split. A bracket is a thin   */
/* bar plus up to two arms running toward the spine (R11); `--depth`        */
/* (1 = nearest the spine, R10's containment nest) drives the bar's own     */
/* distance from the spine AND shares its row grid with the riding label    */
/* ladder below (KTD5 -- 1.3em/row is LINE_HEIGHT_EM, kept in lockstep by   */
/* hand with details/timeline.py so a label search that starts at row       */
/* `depth` lands exactly past its own bar, never on top of it). */
.dtl-h .dtl-bracket {
  background: currentColor;
  border-radius: 2px;
  height: 3px;
  left: var(--x);
  min-width: 3px;
  position: absolute;
  top: calc((var(--depth, 1) - 1) * 1.3em + .5em);
  width: var(--w);
}
.dtl-h .dtl-bracket.dtl-def { color: #2e2b24; } /* KTD11: both endpoints full/timed -- a dark stroke */
.dtl-h .dtl-bracket.dtl-fuzz { color: #aaa49a; } /* any fuzzy endpoint (matrix-brackets always included) -- gray */
.dtl-h .dtl-bracket-arm {
  border-left: 1.5px solid currentColor;
  position: absolute;
  width: 0;
}
.dtl-h .dtl-arm-start { left: 0; bottom: 100%; height: var(--arm-h, 0em); } /* R11: arms FACE the spine (walk #1, 8.4); length computed in details/timeline.py:_arm_em */
.dtl-h .dtl-arm-end { right: 0; bottom: 100%; height: var(--arm-h, 0em); }

/* placed by the SAME point-block ladder (_place_block_h/_v) -- the label   */
/* itself reuses .dtl-block's own look (background/padding/face) so both    */
/* sides of the spine read as one text engine. */
.dtl-h .dtl-bracket-leader {
  border-left: 1px solid #8a8578;
  left: var(--x);
  position: absolute;
  top: calc((var(--depth, 1) - 1) * 1.3em + .5em); /* starts at its bar */
  height: var(--h, 0em); /* exact bar->label length from details/timeline.py */
  width: 0;
}
.dtl-bracket-label { background: #fff; color: #33312c; display: block; font-size: .78rem; line-height: 1.3; padding: 0 .3rem; position: absolute; text-decoration: none; width: max-content; z-index: 2; }
.dtl-bracket-leader { z-index: 3; } /* the leader draws OVER label panels (walk-puzzling #10): a label visibly rides its own line instead of masking it; text glyphs sit inside the padding so the line only ever traces the edge */ /* line metrics pinned to the engine's 1.3em row so the leader ends exactly at the text (walk-puzzling #8) */
.dtl-bracket-label:hover { text-decoration: underline; }
.dtl-h .dtl-bracket-label { left: var(--x); top: var(--bottom); }
.dtl-h .dtl-bracket-label-right { text-align: left; }
.dtl-h .dtl-bracket-label-left { text-align: right; transform: translateX(-100%); }

/* Viewport-edge clip fades (install #1's existing edge-fade behavior,       */
/* reused verbatim -- the same mask-image technique as .tl-seg-in/.tl-seg-out). */
/* A clipped end draws no arm at all (R11, the template omits the element)  */
/* -- the fade here is the bar SHAPE's own cue, same convention as before. */
.dtl-h .dtl-bracket.dtl-clip-start { background: linear-gradient(90deg, transparent, currentColor 30%); }
.dtl-h .dtl-bracket.dtl-clip-end { background: linear-gradient(90deg, currentColor 70%, transparent); }

/* --- Shelves: footer (R3) + undated (R4) -- the app's own quiet-box/       */
/* dashed-note idioms rather than new decoration. */
.dtl-shelf { background: #f3ede6; border: 1px dashed #c9c2b6; border-radius: 4px; font-size: .8rem; margin-top: .9rem; padding: .45rem .7rem; }
.dtl-shelf-tag { color: #5c574e; display: block; font-size: .66rem; letter-spacing: .06em; margin-bottom: .2rem; text-transform: uppercase; }
.dtl-shelf ul { display: flex; flex-wrap: wrap; gap: .2rem 1.4rem; list-style: none; margin: 0; padding: 0; }
.dtl-shelf li { margin: .15rem 0; }
.dtl-shelf a { color: inherit; text-decoration: none; }
.dtl-shelf a:hover { text-decoration: underline; }
.dtl-shelf .dtl-date { color: #8a8578; font-size: .68rem; margin-left: .35rem; }
.dtl-shelf-synopsis { color: #5c574e; display: block; font-family: var(--font-serif); font-size: .78rem; font-style: italic; }
.dtl-shelf-undated, .dtl-shelf-undated .collapse ul { text-align: left; }
.dtl-shelf-note { color: #8a8578; font-size: .72rem; margin: .4rem 0 0; }
/* The undated shelf rides .advanced-collapse (detail_search_results.html's  */
/* "bulk actions" idiom) -- collapsed by default via the shared .collapse    */
/* rule, no dashed-box treatment (it carries no dates to hang a shelf-tag    */
/* style on). */
.dtl-shelf-undated { background: transparent; border-style: dotted; border-color: #d9d5cc; }
.dtl-shelf-undated .dtl-shelf-tag { margin-bottom: 0; }

/* --- Vertical remap (KTD7, mirrors the shipped .tl-v block): above->left,  */
/* a block's cross-axis offset (--bottom) becomes a horizontal distance from */
/* line -- Block.side's own docstring names this template-level remap. */
/* R6/U3's region swap: point leaders (.dtl-above/.dtl-under -- whichever is
   populated; v is single-sided, KTD8b) now render on the CSS side that used
   to host the below-region bracket columns, and .dtl-below (the bracket nest)
   now sits on the side point text used to occupy -- the flex `order` below
   is the ENTIRE swap; every box keeps its own pre-existing sizing/position
   rules verbatim (U4 owns any change to the below-region packers/CSS
   themselves). Structural, not left/right: .dtl-below now precedes
   .dtl-above/.dtl-spine/.dtl-under/.dtl-scale in visual flex order, and the
   balancing spacer (previously first) now trails last. */
.dtl-v .dtl-canvas { display: flex; flex-direction: row; height: 640px; }
.dtl-v .dtl-below { order: 1; }
.dtl-v .dtl-above { order: 2; }
.dtl-v .dtl-spine { order: 3; }
/* Walk 8.7 #11: the feed lane's gutter takes order 4, between the band and
   the point region, pushing .dtl-under/.dtl-scale one place out. */
.dtl-v .dtl-feed-lane { order: 4; }
.dtl-v .dtl-under { order: 5; }
.dtl-v .dtl-scale { order: 6; }
.dtl-v .dtl-canvas { justify-content: center; } /* the two equal-basis side regions below center the spine between them (walk #14); the old empty spacer is gone -- the point region itself is the counterweight */
.dtl-v .dtl-canvas::before { content: none; } /* balances the bracket column so the spine centers under the nav chevron (walk #14) */
.dtl-v .dtl-above { display: block; flex: 0 0 0; height: auto; min-width: 0; position: relative; width: auto; } /* v points are single-side (under, after the swap) -- this box stays collapsed so it can't uncenter the spine */
.dtl-v .dtl-under { flex: 1 1 0; height: 100%; min-width: 0; position: relative; width: auto; }
/* The leader reaches BACK THROUGH the feed lane to touch the band (walk 8.7 #11
   follow-up). Inserting the lane as a flex column moved .dtl-under's left edge a
   gutter's width off the spine, which silently detached every point leader from it —
   the leader has to touch the increment it marks, or the entry reads as floating.
   This is exactly h's arrangement, where the leader runs from the band's edge up
   through the reserved strip and only the BLOCK text is held back by --bottom: the
   line crosses the margin, the label never does. Negative left + a matching width
   increase, both off --feed-lane-em so the reach can't drift from the gutter. */
.dtl-v .dtl-under .dtl-leader {
  border-left: none;
  border-top: 1px solid #8a8578;
  height: 0;
  left: calc(-1 * var(--feed-lane-em));
  position: absolute;
  top: var(--x);
  width: calc(var(--h) + var(--feed-lane-em));
}
/* + --feed-lane-em because blocks position against their LEADER, whose origin just
   moved back to the band (above) so the line could touch the increment. Without this
   compensation `left: 0` lands the label on the band itself, inside the feed lane —
   the line is supposed to cross the gutter, the text never is (h's arrangement, where
   the packer's own ENVELOPE_BLOCK_MARGIN_ROWS does this job; v's lane index starts at
   0, so the gutter has to supply the margin here). Keeps walk #37's contract intact:
   a single-block leader still ends exactly where its text begins. */
.dtl-v .dtl-under .dtl-block { right: auto; left: calc(var(--bottom) + var(--feed-lane-em)); } /* vertical placement belongs to .dtl-block-over/-under — top/bottom:auto here out-specified them and hung right-side text below its line with a phantom underline (walk #43) */
.dtl-v .dtl-block-over { border-bottom: 1px solid #8a8578; bottom: -1px; top: auto; } /* text rides ABOVE the line; the block's own border finishes the line at the text's true edge (walk #15/#37) */
.dtl-v .dtl-block-under { border-top: 1px solid #8a8578; top: 0; bottom: auto; } /* or below it, alternating (walk #15/#37) */
/* astro-increment-spine U4/KTD3: grouped with .tl-v .tl-band so the two    */
/* bands can't drift apart (the h pairing is above, ~5524). lifeline walk   */
/* r2 #1: .cw-tl-v joins here too (third member, no renames). */
.dtl-v .dtl-spine,
.cw-tl-v .dtl-spine,
.tl-v .tl-band { background: #e7e3d9; border: 1px solid #d9d5cc; border-radius: 2px; height: 100%; position: relative; width: 2.4em; }
.dtl-v .dtl-scale { height: 100%; width: 0; }
.dtl-v .dtl-below, .dtl-v .dtl-under { flex: 0 0 min(max(var(--below-em, 24em), 45em), 45%); }
/* Walk 8.7 #11: the point column gives the feed lane's 2.6em gutter back out
   of its OWN basis, so lane + under still sum to exactly .dtl-below's basis.
   Without this the two side regions stop matching and the spine drifts 1.3em
   off centre (walk #14's centering). Compensating here rather than by widening
   .dtl-above (the other candidate) is deliberate: .dtl-above sits on the
   bracket side, and padding it would break "the arms genuinely touch the
   band" (walk 8.4 #7). */
.dtl-v .dtl-under { flex-basis: calc(min(max(var(--below-em, 24em), 45em), 45%) - var(--feed-lane-em)); }
.dtl-v .dtl-below { height: 100%; position: relative; overflow: visible; } /* bars at V_BAR_PITCH_EM lanes + 21em label lanes past them (walk #5, 8.4) */ /* R10 nest depth, axis-swapped (KTD12): one 21em (V_TEXT_LANE_CHARS*EM_PER_CHAR) lane per depth level, shared with the riding-label ladder below */

.dtl-v .dtl-tick { display: none; }
.dtl-v .dtl-tick .tl-slbl { display: none; } /* v increment labels live in the spine (walk #11/#16) */

/* --- The bracket nest, vertical (R9-R12/KTD12): the same nest/arm/label   */
/* engine as h, axes swapped -- --depth now pushes AWAY from the spine     */
/* (which sits at this box's own right edge, R6/U3's region swap) via     */
/* `right`, and the time axis (--x/--w) runs top-to-bottom via `top`/     */
/* `height` instead of `left`/`width`. */
.dtl-v .dtl-bracket {
  background: currentColor;
  border-radius: 2px;
  width: 3px;
  min-height: 3px;
  right: calc((var(--depth, 1) - 1) * 1.4em);
  height: var(--w);
  position: absolute;
  top: var(--x);
}
.dtl-v .dtl-bracket.dtl-def { color: #2e2b24; }
.dtl-v .dtl-bracket.dtl-fuzz { color: #aaa49a; }
.dtl-v .dtl-bracket-arm {
  border-top: 1.5px solid currentColor;
  position: absolute;
  height: 0;
}
.dtl-v .dtl-arm-start { top: 0; left: 100%; width: var(--arm-h, 0em); } /* R11: arms face the spine (right edge of this box) */
.dtl-v .dtl-arm-end { bottom: 0; left: 100%; width: var(--arm-h, 0em); }

.dtl-v .dtl-bracket-leader {
  border-top: 1px solid #8a8578;
  top: var(--x);
  position: absolute;
  right: calc((var(--depth, 1) - 1) * 1.4em); /* starts at its bar (V_BAR_PITCH_EM lanes) */
  width: calc(var(--h, 0em) + .6rem); /* exact bar->label TEXT length + the label's own horizontal padding, so the text's far edge never hangs past the line (walk-puzzling #15) */
  height: 0;
}
.dtl-v .dtl-bracket-label { top: var(--x); right: var(--bottom); max-width: 22em; } /* > the 35-char/21em server wrap, so the overflow-wrap backstop never re-breaks a normal line (walk-puzzling #12) */
.dtl-v .dtl-bracket-label-under { transform: translateY(0); } /* rides BELOW the leader line (walk-puzzling #8/#13) */
.dtl-v .dtl-bracket-label-over { transform: translateY(-100%); } /* rides ABOVE the line -- the preferred phase; NB the v side vocabulary is over/under, not left/right (round-4 correction: the earlier selectors never matched) */

.dtl-v .dtl-bracket.dtl-clip-start { background: linear-gradient(180deg, transparent, currentColor 30%); }
.dtl-v .dtl-bracket.dtl-clip-end { background: linear-gradient(180deg, currentColor 70%, transparent); }

/* PRINT ==================================================================
   The screen layout deliberately scrolls inside fixed-height columns
   (html{overflow:hidden}; #site-content / #simple-content own the scroll),
   which clips printing to the first viewport-worth — a browsed doc printed
   as a single page (walk review 26-08-02). Print un-traps the scroll:
   every wrapper grows to its content and the fixed chrome drops, so the
   full document paginates. */
@media print {
  html, body { display: block; height: auto; overflow: visible; }
  #site-main { display: block; min-height: auto; }
  #site-content, #simple-content { height: auto; overflow: visible; padding: 0; }
  #site-header, #site-footer, #sticky-new-detail, .doc-sidebar { display: none; }
  .doc-layout { display: block; }
}
