/* Screen-specific layouts: greet, home, partpick, dialog, endscreen. */

/* ─── GREET ─────────────────────────────────────────── */

#greet {
  justify-content: center;
  align-items: center;
  text-align: center;
}

@keyframes greet-reveal {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0);    }
}

/* Underline — enters from above and settles into its final position,
   which is where the input's bottom border used to be. Stays visible
   as the permanent underline of the name field. Background defaults to
   gold but switches to the user-picked colour live: the colour picker
   below sets --user-color via applyUserColor(), and this declaration
   reads it on each paint, so selecting a colour repaints the line
   immediately without re-running the entrance animation. */
.greet-line {
  display: block;
  width: 100%;
  height: 1px;
  background: var(--user-color, var(--gold));
  margin: 0;
  opacity: 0;
  transform: translateY(-160px);
  animation: greet-line-sweep 1.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s forwards;
}

@keyframes greet-line-sweep {
  0%   { transform: translateY(-160px); opacity: 0; }
  12%  { opacity: 1; }
  100% { transform: translateY(0);      opacity: 1; }
}

.greet-inner {
  width: 100%;
  max-width: 400px;
  padding: 0 16px;
}

/* Clip-path reveal applies only to prompt + input — the hint below is
   a sibling and gets a plain opacity fade-in after the sweep settles. */
.greet-reveal-part {
  clip-path: inset(0 0 100% 0);
  animation: greet-reveal 1.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s forwards;
}

.greet-prompt {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 18px;
  color: var(--fg-dim);
  margin-bottom: 48px;
  letter-spacing: 0.02em;
}

.greet-input {
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 400;
  text-align: center;
  padding: 8px 0;
  /* No border-bottom here — the .greet-line sibling sweeps in and
     becomes the permanent underline at the same position/width. */
  color: var(--fg-strong);
  /* Caret hidden until the boot animation ends (same timing as the
     line's landing + hint's start); keeps the cursor from blinking
     while the rest is still resolving. */
  caret-color: transparent;
  background: transparent;
  animation: greet-caret-show 0.3s ease 1.7s forwards;
}

@keyframes greet-caret-show {
  to { caret-color: var(--gold); }
}

/* Colour picker on the greet — fades in at the same moment the caret
   and submit button appear, so the whole "ready to type" state arrives
   together. justify-content: center centres the dots on the screen
   axis (the .color-picker base class is flex). */
.greet-colors {
  margin-top: 28px;
  justify-content: center;
  opacity: 0;
  animation: greet-submit-fade 0.8s ease 1.7s forwards;
}

/* Submit button — replaces the old hint+hintEmpty pair. Always present,
   only clickable when the input is non-empty.
   Boot reveal: same opacity fade as the old hint (so the button arrives
   visually at the same moment the cursor appears). The disabled visual
   uses color/border tokens (see .ghost-btn[disabled]) — not opacity —
   because animation values outrank stylesheet opacity declarations. */
.greet-submit {
  margin-top: 28px;
  opacity: 0;
  animation: greet-submit-fade 0.8s ease 1.7s forwards;
}

@keyframes greet-submit-fade {
  to { opacity: 1; }
}

/* ─── HOME ──────────────────────────────────────────── */
/* 1:1 Reverie — .home-header: index.html:187-195.
   .app-title / .app-subtitle values: index.html:88-108. */

#home {
  justify-content: flex-start;
  align-items: center;
  text-align: center;
  /* Measured from target image (image 2):
     title top at 14.8vh, button bottom at 73.7vh.
     15vh top hits title exactly; 26vh bottom put the button 5vh too
     high empirically, so 21vh.
     2026-05-04: paddings symmetric (17vh / 17vh) for true visual
     centering. The previous asymmetric calc-based approach broke
     down at small viewports (calc clamping, content overflow) and
     produced subtle off-by-x discrepancies. To make the symmetry
     hold cleanly we pulled .home-echo-hint OUT OF FLOW (position:
     absolute below). Without the hint contributing to the flex
     stack, the in-flow content ends with the button, and
     button.bottom sits exactly at inner.bottom = viewport.bottom -
     padding-bottom. Top-gap = padding-top = 17vh, bottom-gap =
     padding-bottom = 17vh, identical at any viewport. Reverie's
     original 15/21 calibration is intentionally abandoned. */
  padding-top: 17vh;
  padding-bottom: 17vh;
}

.home-header {
  /* position: fixed — buttons sit at viewport edges, matching Reverie's
     #home-chrome (position: fixed; inset: 0) even though Fold uses a
     bounded #app container for body content. */
  position: fixed;
  top: 0; left: 0; right: 0;
  padding: var(--space-lg);
  pointer-events: auto;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  z-index: 10;
}
/* Hide home-header when home is not the active screen. */
#home.hidden ~ .home-header,
#home.hidden .home-header {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-normal);
}

.home-header-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.home-inner {
  width: 100%;
  max-width: 400px;
  padding: 0 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
}

.home-title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(42px, 8vw, 72px);
  letter-spacing: 0.18em;
  color: var(--fg);
  line-height: 1;
  flex-shrink: 0;        /* let the crease absorb viewport tightness */
}

.home-subtitle {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(9px, 1.1vw, 11px);
  letter-spacing: 0.35em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-top: 0.6em;
  flex-shrink: 0;
}

/* "Crease" — the fold line with two echoes.
   The name "fold" sits on the central line; the flanking echoes literalize
   the "echoed" in the tagline. Center line carries the weight; echoes are
   ~1/3 opacity, 12px apart — read as resonance, not decoration. */
.home-crease {
  display: flex;
  justify-content: center;
  /* align-items: center so per-echo inline height (e.g. 60%) shrinks
     vertically around the centre instead of from the top. */
  align-items: center;
  /* Gap shrinks on narrow viewports so many echoes still fit without
     horizontal overflow. 4-12px range is enough breathing room for
     the lines to read as separate, even at 4px gap. */
  gap: clamp(4px, 1.4vw, 12px);
  height: clamp(180px, 40vh, 440px);
  margin: auto;
  /* flex-shrink: 1 (overriding the previous 0) so the crease yields
     when total content would overflow the inner container. Title,
     subtitle and button each declare flex-shrink: 0 to ensure only
     the crease absorbs any content/viewport mismatch — keeps the
     symmetric top/bottom gap intact at any viewport size. */
  flex-shrink: 1;
  min-height: 0;
  /* pointer-events: auto so the entire crease band (not just the 1px
     centre line) is clickable to expand. The empty space around the
     line then triggers the unfold too. In expanded state the lines
     handle their own clicks and an empty-band click does nothing. */
  pointer-events: auto;
  max-width: 100vw;
  /* position: relative is the containing-block anchor for
     .home-crease-content (the in-place pinned card, absolute-
     positioned to fill the crease box). */
  position: relative;
}

/* Cursor affordance: the whole crease band reads as clickable while
   the echos are folded in. In expanded state the cursor reverts to
   default for the empty wrap area; the lines keep their own pointer
   cursor (already set on .crease-line.center / .echo). */
.home-crease:not(.expanded) {
  cursor: pointer;
}

.crease-line {
  width: 1px;
  height: 100%;
  /* Default tint via custom property --line-color — set per-line so
     centre can stay in user-color while echos default to ink (visible
     against any user-color, no rainbow). Hover on an echo overrides
     --line-color to its part-color → identity reveals on interaction. */
  background: linear-gradient(
    to bottom,
    transparent 0%,
    var(--line-color, var(--fg-strong)) 50%,
    transparent 100%
  );
  transition: background 0.15s ease;
}

/* Centre line stays pure ink — single load-bearing identifier of the
   "fold" itself, not part-coloured. */
.crease-line.center {
  --line-color: var(--fg-strong);
}

/* Echos default to a near-pure part-colour tint (5% ink mixed in for
   legibility on the lightest palette swatches). Falls back to pure
   ink when --part-color is unset (e.g. parts saved before the colour
   was attached). Tuned in prototypes/echoes-tuner.html (2026-05-05). */
.crease-line.echo {
  --line-color: color-mix(
    in srgb,
    var(--part-color, var(--fg-strong)) 95%,
    var(--fg-strong) 5%
  );
}

.crease-line.center {
  /* Width stays 1px — past iterations of 2px read clunky. Hierarchy
     comes from height: the centre keeps full crease height while
     echos shrink outward (set via inline `height: %` in JS). Click
     opens the Parts manager. */
  opacity: 0.8;
  pointer-events: auto;
  cursor: pointer;
  position: relative;
  transition: opacity var(--t-fast);
}

.crease-line.center::before {
  content: '';
  position: absolute;
  inset: 0 -8px;       /* generous hit-area for the centre */
}

.crease-line.center:hover {
  opacity: 1;
}

/* Echos: dynamic-rendered, one per part. Per-position opacity is
   set by JS via the `--echo-opacity` custom property (log scale on
   dialog count). Default state is HIDDEN (opacity 0, no pointer
   events) — echos only become visible when the crease is in its
   expanded state (.home-crease.expanded), toggled by clicking the
   centre line. This implements the minimal Home: a single line +
   BEGIN DIALOG; click the line to unfold the per-part echos.
   Pseudo-element ::before extends the hit-area horizontally so the 1px
   line is comfortable to click/tap. */
.crease-line.echo {
  opacity: 0;
  /* Height comes from --echo-height (set per-line in JS via a hash-
     deterministic formula). Default 100% so the line still renders
     even if JS hasn't populated the variable yet. The :hover rule
     overrides this to 100% so hovering an echo lifts it to full
     crease height (centred via the parent's align-items: center). */
  height: var(--echo-height, 100%);
  position: relative;
  pointer-events: none;
  cursor: pointer;
  /* 0.5s opacity transition for the unfold/collapse — slower than
     hover/--t-fast so the click-to-expand reads as a deliberate
     fade-in rather than a snap. background change stays at 0.15s
     so the colour shift on hover/pin still feels responsive. filter
     and height also at 0.15s so the hover halo + height-lift glide
     together with the colour shift. */
  transition: opacity 0.5s ease, background 0.15s ease,
              filter 0.15s ease, height 0.15s ease;
}

.crease-line.echo::before {
  content: '';
  position: absolute;
  inset: 0 -5px;       /* extend hit-area 5px on each side */
}

/* Expanded state — echos lift to their per-part log-scaled opacity
   and become interactive. Transition on opacity (above) makes the
   appearance/disappearance smoothly fade. */
.home-crease.expanded .crease-line.echo {
  opacity: var(--echo-opacity, 0.1);
  pointer-events: auto;
  /* Breath / pulse — disabled 2026-05-05. The continuous
     oscillation didn't fit the current visual feel. JS still sets
     --breath-low / --breath-high / --breath-period / --breath-phase
     per line (harmless when the animation isn't running), so this
     is a single-line revert: uncomment the two animation declarations
     plus the @keyframes block + the :hover { animation: none } rule
     below to bring the breath back. */
  /* animation: echo-breath var(--breath-period, 6s) ease-in-out infinite; */
  /* animation-delay: var(--breath-phase, 0s); */
}

/* @keyframes echo-breath {
  0%, 100% { opacity: var(--echo-opacity); }
  25%      { opacity: var(--breath-high); }
  75%      { opacity: var(--breath-low); }
} */

/* Hover/pinned animation pause — only meaningful when the breath
   animation above is enabled. Commented out together with it. */
/* .home-crease.expanded .crease-line.echo:hover,
.home-crease.expanded .crease-line.echo.pinned {
  animation: none;
} */

/* Hover + pinned: line lifts to full opacity + pure part-colour, plus
   a soft halo (drop-shadow in the same part-colour) so the focused
   line POPS without growing thicker. The halo radius (4px) is chosen
   to be felt on a 1px line — perceived presence increases noticeably
   without changing the line's actual stroke width. Pinned (a clicked
   echo whose info stays in the hint area) shares the same visual so
   identity persists when the mouse leaves. */
.home-crease.expanded .crease-line.echo:hover,
.home-crease.expanded .crease-line.echo.pinned {
  --line-color: var(--part-color);
  opacity: 1;
  height: 100%;
  filter: drop-shadow(0 0 4px var(--part-color));
}

/* Home-entry animation — only the centre line fades in. Echos no
   longer animate on entry; they're hidden until the user clicks the
   centre to unfold the crease. Triggered via JS toggling
   `.is-animating` on `.home-crease`.
   fill-mode: backwards — holds the `from` keyframe during the
   pre-animation delay (so the centre starts invisible) but does NOT
   hold the `to` state after the animation ends. Critical: with
   `both` here, the `to` opacity (0.8) lingered on the element even
   after the animation finished, and that animated value beats normal
   author rules in the cascade. That meant the .home-crease.has-pin
   rule (which sets opacity: 0) couldn't hide the centre line in the
   pinned state. With `backwards` the centre's opacity returns to the
   declared style after the animation, so .has-pin can hide it. */
.home-crease.is-animating .crease-line.center {
  animation: crease-fade-in 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.2s backwards;
}

@keyframes crease-fade-in {
  from { opacity: 0;   }
  to   { opacity: 0.8; }
}

.home-begin {
  /* no auto margin — the crease's margin:auto handles vertical distribution.
     flex-shrink: 0 so the button keeps its full height even if the inner
     container is too tight for all content (the crease yields instead). */
  flex-shrink: 0;
}

/* Echo hint — shows the part identity (dot, name, description,
   dialog count) when an echo is hovered or pinned in the expanded
   crease. Position: absolute so the hint is completely out of the
   flex flow — its presence and size cannot shift any other element
   regardless of content. Top-anchored ~32px below the BEGIN button
   (button.bottom sits at viewport.bottom − 17vh; hint.top is
   button.bottom + 32px → top: calc(100% - 17vh + 32px)). The
   hint can grow downward into the bottom-padding area without
   colliding with the button above. */
.home-echo-hint {
  position: absolute;
  top: calc(100% - 17vh + 32px);
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--fg-dim);
  letter-spacing: 0.02em;
  pointer-events: none;
  transition: opacity var(--t-normal);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  max-width: min(500px, 90vw);
  line-height: 1.4;
}

.home-echo-hint:empty {
  opacity: 0;
}

/* Row container holding the dot + name (+ description). Block layout
   keeps the dot as an inline-block element on the first line of text;
   if the line wraps because the name+desc string is long, only the
   text portion wraps — the dot stays anchored to the start of the
   first line. (Using display: flex here would let the dot fall to its
   own line when the text wrapped — the bug we're avoiding.) */
.home-echo-hint-row {
  /* default block layout */
}

.home-echo-hint-count {
  opacity: 0.85;
}

/* In-place pinned card — replaces the echo visualisation in the same
   crease band when an echo is pinned. position: absolute over the
   .home-crease box (which is position: relative). Default state is
   hidden (opacity: 0, pointer-events: none); .home-crease.has-pin
   fades it in and hides the echos. Lays out as a centred column
   with the part identity at top and a wrapped grid of dialog tiles
   below — so all tile content lives WITHIN the crease's clamp height
   instead of overflowing past the BEGIN button. */
.home-crease-content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
  text-align: center;
  /* The wrap container also has overflow: hidden so any tile-row
     overflow inside .home-crease-content-tiles doesn't break the
     band's bounds — tile container handles its own scroll. */
  overflow: hidden;
  box-sizing: border-box;
}

/* When pinned: hide ALL crease lines (the centre + every echo) and
   reveal the content card. Specificity needs to beat the existing
   .home-crease.expanded .crease-line.echo:hover and
   .home-crease.expanded .crease-line.echo.pinned rules (both 0,4,0),
   which would otherwise keep the pinned echo visible at opacity 0.8.
   Selector below carries (0,5,0) to win cleanly. */
.home-crease.expanded.has-pin .crease-line.center,
.home-crease.expanded.has-pin .crease-line.echo,
.home-crease.expanded.has-pin .crease-line.echo.pinned,
.home-crease.expanded.has-pin .crease-line.echo:hover {
  opacity: 0;
  pointer-events: none;
}

/* When pinned the card breaks out of the crease box: it becomes a
   viewport-fixed surface that spans from below the subtitle to just
   above the BEGIN button, scrolls internally, and is visually framed
   so it reads as a distinct surface (not a continuation of the
   crease lines). The Hero pattern still holds — title/subtitle stay
   anchored above, BEGIN stays anchored below, the card sits in the
   middle as the focused content. */
.home-crease.has-pin .home-crease-content {
  opacity: 1;
  pointer-events: auto;

  position: fixed;
  /* Reset the base rule's inset:0 so individual edges below take effect. */
  inset: auto;
  /* Top aligns with the FOLD title's top edge (#home padding-top: 17vh)
     so the card has the maximum vertical space. The card visually
     overlays the title + subtitle while pinned — that's intentional:
     the user is focused on the part, the title is dimmed by the
     overlay. Both reappear when the card is closed. */
  top: 17vh;
  /* Bottom: #home padding-bottom is 17vh; reserve ~70px for the BEGIN
     button height + breathing gap. */
  bottom: calc(17vh + 70px);
  left: 50%;
  transform: translateX(-50%);
  width: min(560px, 92vw);

  /* Visual frame — distinct surface on top of the page bg. */
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);

  /* Stack from top — no longer vertically centred like the small
     in-crease variant. padding-top: 0 because the sticky header
     carries its own padding-top and bg, so it can sit flush with
     the card's top edge. */
  justify-content: flex-start;
  gap: 14px;
  padding: 0 20px 24px;

  /* Internal vertical scroll — the entire card content scrolls when
     it overflows. The tile list no longer needs its own scroll. */
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable;

  /* Above the crease band but below the home-header (z:10) and the
     sidebar overlay. */
  z-index: 5;
}

/* Close button — top-right of the content panel, gives a discoverable
   visual affordance for unpinning. ESC + outside-click also work. */
.home-crease-content-close {
  position: absolute;
  top: 8px;
  right: 10px;
  appearance: none;
  background: none;
  border: none;
  font-family: var(--font-ui);
  font-size: 1.4rem;
  line-height: 1;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 3px;
  transition: color var(--t-fast), background-color var(--t-fast);
}

.home-crease-content-close:hover {
  color: var(--fg-strong);
  background: var(--bg-overlay);
}

/* Header row: emoji + part name + part-color dot; clickable →
   part-detail. Mirrors the bottom hover-hint pattern (emoji + name
   + dot) so identity reads consistently across both states.
   In the pinned card the header sticks to the top of the scroll
   container so the part identity stays visible while desc/count/
   tiles scroll behind it. See .home-crease.has-pin override below. */
.home-crease-content-header {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--fg);
  letter-spacing: 0.02em;
  transition: color var(--t-fast);
  /* Allow long names to wrap inside the header without breaking the
     emoji away from the first line. */
  display: block;
  max-width: 100%;
  line-height: 1.3;
}

/* Sticky header inside the pinned card. Spans card edge-to-edge via
   width: calc(100% + 40px) + negative inline margin (parent padding
   is 20px each side; align-self stretch alone doesn't beat the base
   rule's max-width: 100% inside align-items: center). Sits flush at
   top because the parent dropped its padding-top to 0 — header
   carries its own 14px padding-top, so the part identity reads close
   to the card's upper edge instead of dangling 24px in. */
.home-crease.has-pin .home-crease-content-header {
  position: sticky;
  top: 0;
  width: calc(100% + 40px);
  max-width: none;
  box-sizing: border-box;
  margin: 0 -20px;
  background: var(--bg-surface);
  z-index: 2;
  padding: 14px 36px 14px 20px;
  text-align: center;
  border-bottom: 1px solid var(--border-soft);
}

.home-crease-content-header:hover {
  color: var(--fg-strong);
}

.home-crease-content-emoji {
  /* Emoji stays inline with the first line of the name when it
     wraps. Vertical-align middle pairs it with the name baseline. */
  display: inline-block;
  font-size: 1.3em;
  vertical-align: middle;
  margin-right: 10px;
  position: relative;
  top: -1px;
}

.home-crease-content-desc {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--fg-dim);
  letter-spacing: 0.02em;
  max-width: min(500px, 90vw);
  line-height: 1.4;
}

.home-crease-content-count {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--fg-dim);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Tile column — vertical list, one tile per row. Long titles wrap
   inside their tile instead of pushing the layout horizontally.
   Scrolling is handled by the parent .home-crease-content (whose
   .has-pin override sets overflow-y: auto on the whole card), so the
   tile list itself doesn't need its own scroll. Each tile carries a
   part-color top stripe + click handler routing to the dialog's
   review screen. */
.home-crease-content-tiles {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  width: 100%;
}

.home-echo-mini-tile {
  appearance: none;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.35;
  color: var(--fg);
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-top: 2px solid var(--part-color, var(--border-soft));
  border-radius: 3px;
  padding: 6px 10px;
  cursor: pointer;
  /* Two-cell layout: title flex-grows on the left, date+time meta
     stays right-aligned on the right. align-items: baseline so meta
     sits on the same baseline as the title's first line — title can
     still wrap below. */
  display: flex;
  align-items: baseline;
  gap: 12px;
  text-align: left;
  transition: background-color var(--t-fast), border-color var(--t-fast),
              transform var(--t-fast);
}

.home-echo-mini-tile-title {
  flex: 1;
  min-width: 0;
  /* Wrap long titles instead of pushing the tile wider than the band.
     word-break: break-word as a safety net for unusually long single
     words / URLs / paths so they don't overflow horizontally. */
  white-space: normal;
  word-break: break-word;
}

.home-echo-mini-tile-meta {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.02em;
  color: var(--fg-dim);
  white-space: nowrap;
}

.home-echo-mini-tile:hover {
  background: var(--bg-overlay);
  border-color: var(--fg-muted);
  border-top-color: var(--part-color, var(--fg-muted));
  transform: translateY(-1px);
}

.home-echo-emoji {
  display: inline-block;
  margin-right: 8px;
  vertical-align: middle;
  font-size: 1.05em;
  line-height: 1;
  /* Parent .home-echo-hint sets font-style: italic. Emojis must
     render upright — Apple's emoji font has no italic variant, so
     italic on the emoji glyph leaves it visually leaning compared to
     part-detail / partpick where it's upright. */
  font-style: normal;
}

.home-echo-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  vertical-align: middle;
  margin-left: 6px;
  /* Prevent baseline-drift if the dot ever ends up next to a
     superscript or other vertical-aligned content nearby. */
  position: relative;
  top: -1px;
  /* subtle border in case the part-color is very close to paper */
  box-shadow: 0 0 0 1px var(--border-soft);
}

/* ─── PART PICK ─────────────────────────────────────── */

#partpick,
#part-edit,
#tag-manager {
  justify-content: flex-start;
  align-items: center;
  /* Screen does NOT scroll itself — scrolling moves into .pp-scroll.
     Top and bottom paddings are equal so the screen-top→prompt-top gap
     matches the button-bottom→screen-bottom gap. `max(80px, 10vh)`
     keeps a minimum that clears the fixed back-button header on short
     viewports while scaling with tall ones. */
  padding-top: max(80px, 10vh);
  padding-bottom: max(80px, 10vh);
}

/* Tag-manager wants a wider column than partpick form (380px is too
   narrow for tag rows + meta + delete-icon).  Explicit override of
   the .pp-inner max-width when scoped under #tag-manager. */
#tag-manager .pp-inner {
  max-width: 520px;
}

.pp-header {
  /* position: fixed — same reasoning as .home-header */
  position: fixed;
  top: 0; left: 0; right: 0;
  padding: var(--space-lg);
  pointer-events: auto;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  z-index: 10;
}
#partpick.hidden ~ .pp-header,
#partpick.hidden .pp-header {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-normal);
}

.pp-inner {
  width: 100%;
  max-width: 380px;
  padding: 0 16px;
  /* Fill available height so the begin button sits at the bottom edge
     (= 20vh up from the screen bottom), regardless of content above. */
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Scroll region above the begin button. The begin button below never
   shifts when this region scrolls.
   scrollbar-gutter reserves the bar's width even when content fits, so
   form fields don't get covered by the bar appearing/disappearing.
   The webkit-scrollbar rules give a thin styled bar (matches dialog). */
.pp-scroll {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  min-height: 0;
  scrollbar-gutter: stable;
}

.pp-scroll::-webkit-scrollbar {
  width: 4px;
}

.pp-scroll::-webkit-scrollbar-thumb {
  background: var(--fg-faint);
  border-radius: 2px;
}

.pp-prompt {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  color: var(--fg);
  text-align: center;
  margin-bottom: 32px;
  letter-spacing: 0.02em;
  /* Balance line lengths so multi-line prompts wrap on a sensible
     break (e.g. between name+comma and the question continuation)
     instead of orphaning a single word. Modern browsers (Chrome 114+,
     Firefox 121+, Safari 17.5+); silent fallback to default wrapping
     in older engines. max-width narrows the line so the balance has
     options to choose between. */
  text-wrap: balance;
  max-width: 320px;
  margin-left: auto;
  margin-right: auto;
}

/* Line-with-text divider between CREATE PART (above) and the saved
   parts list (below). */
.pp-divider {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin: 24px 0 16px;
}

.pp-divider::before,
.pp-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}

.pp-parts-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: min(40vh, 420px);
  overflow-y: auto;
  margin-bottom: 28px;
}

/* flex-shrink: 0 is essential — pp-parts-list is flex-column with a
   max-height; without it, browsers squash tiles to fit before the
   scrollbar kicks in, which clips descenders ("g", "y") in part names
   via the tile's overflow:hidden. With shrink:0 each tile renders at
   its natural content height and the list scrolls when needed. */
.pp-part-tile {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 12px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast);
  background: transparent;
  position: relative;
  overflow: hidden;
}

.pp-part-tile:hover {
  background: var(--gold-faint);
  border-color: var(--gold-dim);
}

.pp-part-tile.is-selected {
  background: var(--gold-faint);
  border-color: var(--gold);
}

.pp-part-tile-emoji {
  font-size: 1.4rem;
  line-height: 1;
  flex-shrink: 0;
}

/* Content row: name (flex:1, wraps within its slot) + count (sits at
   the end, never shrinks). DOM order is name-first → count, matching
   visual reading order. align-items: baseline so the count's small
   font sits on the same baseline as the first line of the name. */
.pp-part-tile-content {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.pp-part-tile-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--fg-strong);
  text-align: left;
  line-height: 1.35;
}

.pp-part-tile-count {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-dim);
  white-space: nowrap;
}

.pp-empty-note {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-dim);
  text-align: center;
  padding: 16px 0;
  letter-spacing: 0.02em;
}

/* Type-to-filter hint — appears only while a filter is active.
   Mono font + small size so it reads as a system-level affordance,
   not as part of the part list itself. The pp-filter-text span shows
   the current filter string in --fg-strong; the esc-hint sits to the
   right at lower contrast. */
.pp-filter-hint {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.05em;
  margin: -8px 0 12px;
}
.pp-filter-text {
  color: var(--fg-strong);
}
.pp-filter-esc {
  margin-left: 12px;
  opacity: 0.6;
  font-size: 10px;
  text-transform: lowercase;
}
.pp-filter-empty {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 13px;
  color: var(--fg-dim);
  text-align: center;
  padding: 8px 0 16px;
  letter-spacing: 0.02em;
}

/* CREATE NEW PART — primary affordance on the partpick screen, sits
   above the divider and the existing-parts list. Centered, secondary
   visual weight (text-btn) so the gold BEGIN button below remains the
   primary terminal action. */
.pp-create-btn {
  display: block;
  margin: 0 auto var(--space-md);
}

/* Partpick + Part-Edit primary CTA — centered in the column.
   `width: fit-content` is required because a default-block button would
   stretch full-width and `margin: 0 auto` would then have no effect.
   The top margin keeps breathing space between the scroll region's
   bottom edge and the button. */
#pp-begin {
  display: block;
  margin: var(--space-xl) auto 0;
  width: fit-content;
  flex-shrink: 0;
}

/* Selected-emoji preview right above the emoji picker grid. Reserves
   height even when empty so the layout doesn't jump on first pick. */
.pn-emoji-preview {
  text-align: center;
  font-size: 28px;
  line-height: 1;
  min-height: 40px;
  margin: 12px 0 8px;
}

/* Color row on part-edit is centered — the form is otherwise centered
   in the column; left-aligned dots would feel orphaned. */
#part-edit .color-picker {
  justify-content: center;
}

/* Part-edit name input — transparent bg, centered Cormorant, accent
   underline that doubles as a status indicator: gold when no colour
   is picked yet (the form's "no-pick" state), the picked part-colour
   the moment the user actively picks one (Variant C, 2026-05-05).
   --pe-color is set inline on #part-edit by partedit.js applyPartColor. */
#pe-name {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--pe-color, var(--gold));
  border-radius: 0;
  padding: 8px 0;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 400;
  text-align: center;
  color: var(--fg-strong);
  caret-color: var(--pe-color, var(--gold));
  transition: border-bottom-color var(--t-fast),
              caret-color var(--t-fast);
}

#pe-name:focus {
  border-bottom-color: var(--pe-color, var(--gold));
}

/* ─── DIALOG ────────────────────────────────────────── */

/* Dialog: section is just the centering wrapper around .app-shell.
   The shell holds the entire dialog surface (toolbar + messages + input).
   Vertical padding is generous (48px) so the shell visibly floats above
   the body bg, even on shorter laptop viewports. */
section#dialog.screen {
  align-items: center;
  justify-content: center;
  padding: 48px 16px;
}

/* Review: header / breadcrumb / identity / subtoolbar / divider stay
   full-width above the shell. The shell wraps only the body
   (transcript + notes + both). Section keeps its default top-anchored
   flex column. Bottom padding gives the bordered shell visible
   breathing room from the viewport edge — matches the breathing room
   above the tabs. */
section#review.screen {
  padding: 0 0 24px 0;
}

.app-shell {
  position: relative;
  width: 100%;
  max-width: 640px;
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Dialog uses the shell as the entire surface — bounded width AND height. */
section#dialog .app-shell {
  height: 100%;
  max-height: 900px;
}

/* Review's shell takes the remaining vertical space below the header
   stack and centers horizontally in the otherwise full-width section. */
section#review .app-shell {
  flex: 1;
  margin: 0 auto;
  min-height: 0;
}

@media (max-width: 720px) {
  section#dialog.screen {
    padding: 0;
  }
  .app-shell {
    max-width: none;
    border: none;
    border-radius: 0;
  }
  section#dialog .app-shell {
    max-height: none;
  }
}

.dlg-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-soft);
  flex-shrink: 0;
  gap: 8px;
  /* Stack above the accent stripe so the stripe animates *behind* the
     toolbar/input chrome, never over them. Opaque background (matching
     the dialog surface) so the stripe terminates visually at the
     toolbar's border-bottom — same pattern as .dlg-input-wrap below. */
  position: relative;
  z-index: 3;
  background: var(--bg-surface);
}

.dlg-toolbar button {
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 3px;
  transition: color var(--t-fast), background-color var(--t-fast);
  white-space: nowrap;
  background: transparent;
}

.dlg-toolbar button:hover {
  color: var(--fg);
  background: var(--gold-faint);
}

.dlg-toolbar button .shortcut {
  margin-left: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-muted);
  text-transform: none;
  letter-spacing: 0;
}

.dlg-toolbar-spacer {
  flex: 1;
}

/* ─── Dialog options-strip (disclosure) ───────────────────
   Replaces the former hamburger that opened the sidebar. Triggered
   by .dlg-options-btn (the ☰-glyph in the toolbar) and closed by
   click-outside (handled in dialog.js). Stays open on clicks INSIDE
   the strip so the user can adjust multiple settings without re-
   opening. Three lines: Theme/Fullscreen, Wischer, Audio. */
.dlg-options-btn {
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1;
  padding: 4px 10px;
  min-width: 36px;
  border: 1px solid transparent;
  border-radius: 3px;
  color: var(--fg-dim);
  background: transparent;
  cursor: pointer;
  transition: color var(--t-fast), background-color var(--t-fast), border-color var(--t-fast);
}

.dlg-options-btn:hover {
  color: var(--fg);
  background: var(--gold-faint);
}

.dlg-options-btn.is-open {
  color: var(--fg-strong);
  background: var(--gold-faint);
  border-color: var(--gold-dim);
}

.dlg-options-strip {
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-soft);
  padding: 12px 14px;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 16px;
  row-gap: 10px;
  align-items: center;
  flex-shrink: 0;
  position: relative;
  z-index: 3;
}

.dlg-options-line-label {
  font-family: var(--font-ui);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--fg-dim);
  text-transform: uppercase;
}

.dlg-options-line-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}

/* Sub-control with its own small label — used in the Audio line so
   "Aus / Tief / An" don't lose their context when sitting next to
   each other. */
.dlg-options-mini {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dlg-options-mini-label {
  font-family: var(--font-ui);
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
  text-transform: uppercase;
}

.dlg-message-area {
  flex: 1;
  position: relative;
  overflow: hidden;
}

/* Stripe spans the full dialog height (over toolbar + messages + input),
   inset 4px from the edge so it reads as a clear vertical mark, not a
   border. IWB parity: InnerChildDialog.js:1883/1996/1998.
   Position toggled via `left` only (never `auto` or `right`) so CSS can
   interpolate the slide between sides.
   Speed comes from --stripe-t (defaults to fast); body[data-stripe-anim]
   overrides it for off/slow modes — see further down. */
.dlg-accent-stripe {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 4px;
  width: 3px;
  border-radius: 2px;
  background: var(--gold);
  /* Linear easing so the stripe's sweep paces evenly across the
     dialog width — matches the per-bubble dim wipes which also run
     linear over their own bubble width during each phase. */
  transition: left var(--stripe-t) linear,
              background-color var(--stripe-t) linear,
              opacity var(--stripe-t) linear;
  /* Above the bubbles (which use no z-index, default stacking)
     so the gold stripe stays visible during and after the wipe;
     below the toolbar/input chrome (z:3). */
  z-index: 2;
  pointer-events: none;
}

/* --stripe-t lives on body so both the stripe (uses var() in transition)
   and the veils (use it via calc() in animation-duration / -delay)
   inherit the same value. Pure duration only — --t-fast bakes in an
   easing keyword (`0.15s ease`) which would corrupt every site that
   composes its own easing onto var(--stripe-t). */
body                          { --stripe-t: 0.15s; }
body[data-stripe-anim="off"]  { --stripe-t: 0s;    }
body[data-stripe-anim="slow"] { --stripe-t: 0.6s;  }
body[data-stripe-anim="fast"] { --stripe-t: 0.15s; }

.dlg-accent-stripe.is-right {
  /* 4px gap from the right edge + 3px stripe width = 7px from 100% */
  left: calc(100% - 7px);
}

.dlg-messages {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.dlg-messages::-webkit-scrollbar {
  width: 4px;
}

.dlg-messages::-webkit-scrollbar-thumb {
  background: var(--fg-faint);
  border-radius: 2px;
}

.bubble-wrap {
  display: flex;
  width: 100%;
}

.bubble-wrap.is-right {
  justify-content: flex-end;
}

.bubble-wrap.is-left {
  justify-content: flex-start;
}

.bubble {
  /* 82% — bubbles can grow past the centre stripe like in any
     mainstream messenger. A long active-side bubble may briefly
     read as half-dim/half-bright while the role-switch wipe is
     mid-flight (its right and left halves sit under different
     veils with different clip-path states for one stripe-duration),
     but the lock in dialog.js prevents that mid-flight state from
     being interrupted, so it resolves cleanly into the new steady
     state every time. The visual moment of split is part of the
     wipe, not a glitch. */
  max-width: 82%;
  padding: 6px 10px 4px;
  border: 1px solid var(--bubble-border);
  /* Bubble font: native system UI (IWB parity) — lighter than DM Sans
     so the written voices feel less imposed. Scoped to .bubble and
     .dlg-textarea; the rest of the app stays on --font-ui. */
  font-family: var(--font-bubble);
  font-size: 14px;
  line-height: 1.5;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  position: relative;
  animation: bubbleIn 0.25s ease;
}

.bubble.is-right {
  border-radius: 12px 12px 3px 12px;
}

.bubble.is-left {
  border-radius: 12px 12px 12px 3px;
}

.bubble-text {
  display: inline;
  word-break: break-word;
  /* Multiline-fix: preserve newlines and consecutive spaces.
     Users can paste multi-line text or type Shift+Enter; both render correctly. */
  white-space: pre-wrap;
}

/* Mark-stripe: 3px ink-stripe sitting outside the bubble's outer edge,
   indicates a "marked" message in the review's dialog tab. The stripe
   uses --fg-strong (existing token) so it stays inside Fold's two-color
   palette without introducing a system accent color. The 4px top/bottom
   inset keeps the stripe visually distinct from the bubble's rounded
   corners. */
.bubble.is-marked::before {
  content: '';
  position: absolute;
  top: 4px;
  bottom: 4px;
  width: 3px;
  background: var(--fg-strong);
  border-radius: 2px;
  pointer-events: none;
}
.bubble.is-marked.is-right::before { right: -8px; }
.bubble.is-marked.is-left::before  { left:  -8px; }

/* Gap divider — shown in filter mode between non-adjacent marked
   bubbles. Communicates: "messages were skipped here". Three ASCII
   dots in --fg-dim, no chrome. */
.rv-bubble-gap {
  font-family: var(--font-mono);
  text-align: center;
  letter-spacing: 0.1em;
  color: var(--fg-dim);
  font-size: 14px;
  padding: 6px 0;
  user-select: none;
}

/* Bubble timestamp — 1:1 IWB (InnerChildDialog.js:171). The combination
   of 8px font-size, line-height: 1, and margin-top: 4px lifts the stamp
   into the upper portion of the first text line; that's where the
   "hochgestellt" feel comes from. Color is set per-bubble in JS via
   contrastTextDim() — no extra opacity multiplier so the brightness
   matches IWB. */
.bubble-ts {
  display: inline-block;
  float: right;
  margin: 4px 0 0 8px;
  font-family: var(--font-mono);
  font-size: 8px;
  line-height: 1;
  letter-spacing: 0.04em;
  user-select: none;
}

/* Bubble dimming sits on a per-bubble <span class="bubble-dim-overlay">
   placed inside each .bubble. That replaced an earlier two-veil
   approach (.dlg-veil-left / .dlg-veil-right) which dimmed the
   inactive container half geometrically — fine until a content-fit
   bubble grew past the centre line, at which point the veil split
   it into half-bright/half-dim. Per-bubble overlays follow the
   bubble's own bounds so wide bubbles never read as half-cut.

   Wipe animations are driven from JS via the Web Animations API
   (dialog.js startWipeAnimations) — each overlay gets a delay +
   duration computed from the bubble's bounding rect so its
   clip-path animates only during the slice of time the global
   stripe is actually crossing it. The result: the dim-edge of
   each bubble's overlay stays column-locked to the stripe's x as
   the stripe sweeps. CSS holds only the steady states; everything
   transitional is JS-driven.

   border-radius: inherit so the overlay matches the bubble's
   asymmetric corner-rounding (.bubble.is-right vs .is-left).

   switchRole() in dialog.js holds a lock for the full --stripe-t
   so mid-flight retriggers (Shift-Tab spam) and mid-flight sends
   are silently ignored — the wipe is atomic. */
.bubble-dim-overlay {
  position: absolute;
  inset: 0;
  background: var(--bg-surface);
  opacity: 0.5;
  pointer-events: none;
  border-radius: inherit;
}

/* Steady states (apply before WAA starts and after WAA fill ends).
   inset(0 0 0 0) = overlay fully visible (bubble dim);
   inset(0 100% 0 0) = overlay collapsed to LEFT edge (invisible);
   inset(0 0 0 100%) = overlay collapsed to RIGHT edge (invisible).
   The two "invisible" forms differ in their collapse edge — the
   side they were collapsing toward — so the WAA from-keyframe can
   match the steady-state collapse edge for a seamless handoff. */
.dlg-message-area.active-self .bubble.is-left  .bubble-dim-overlay { clip-path: inset(0 0 0 0);    }
.dlg-message-area.active-self .bubble.is-right .bubble-dim-overlay { clip-path: inset(0 100% 0 0); }
.dlg-message-area.active-part .bubble.is-left  .bubble-dim-overlay { clip-path: inset(0 100% 0 0); }
.dlg-message-area.active-part .bubble.is-right .bubble-dim-overlay { clip-path: inset(0 0 0 0);    }

@keyframes bubbleIn {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.dlg-empty {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 17px;
  color: var(--fg-muted);
  text-align: center;
  pointer-events: none;
  letter-spacing: 0.02em;
  width: 80%;
  /* Above the empty .dlg-messages container (which sits transparent
     in front by source order); below the stripe (z:2). */
  z-index: 1;
}

.dlg-empty p {
  margin: 0;
}

.dlg-empty-title {
  font-weight: 600;
  margin-bottom: var(--space-md) !important;
}

/* Adaptive: keyboard hints by default; touch hint only when the
   primary pointer is coarse (touch screens). */
.dlg-empty-touch { display: none; }

@media (pointer: coarse) {
  .dlg-empty-keys { display: none; }
  .dlg-empty-touch { display: block; }
}

/* Enter-mode adaptive hints inside the keyboard block: only one of
   the two send/newline variants shows, driven by body[data-enter-mode]
   set in main.js + sidebar.js. */
.dlg-empty-keys-newline { display: none; }
body[data-enter-mode="newline"] .dlg-empty-keys-send { display: none; }
body[data-enter-mode="newline"] .dlg-empty-keys-newline { display: block; }

.dlg-input-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 16px 14px;
  border-top: 1px solid var(--border-soft);
  flex-shrink: 0;
  /* Stack above the accent stripe so the stripe animates *behind* the
     input row, never over it. */
  position: relative;
  z-index: 3;
}

.dlg-input-wrap {
  flex: 1;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  /* Opaque (not gold-faint) — looks identical but blocks the accent
     stripe from shining through during its slide. See tokens.css. */
  background: var(--input-bg);
  border: 1px solid var(--gold-dim);
  border-radius: 10px;
  padding: 6px 10px 6px 12px;
  transition: border-color var(--t-fast);
}

.dlg-textarea {
  flex: 1;
  resize: none;
  /* Match the bubble — typed text and the resulting bubble share one
     visual register on the chat surface. */
  font-family: var(--font-bubble);
  font-size: 14px;
  line-height: 1.45;
  max-height: 120px;
  overflow-y: auto;
  padding: 4px 0;
  background: transparent;
  color: var(--fg-strong);
}

.dlg-textarea::-webkit-scrollbar {
  width: 4px;
}

.dlg-textarea::-webkit-scrollbar-thumb {
  background: var(--fg-faint);
  border-radius: 2px;
}

.send-btn {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gold-faint);
  /* color (= SVG currentColor) and border-color are updated per active
     role in dialog.js applyRoleSignals(); the gold here is just a
     pre-render fallback. */
  color: var(--gold);
  border: 1px solid var(--gold-dim);
  transition: background-color var(--t-fast), color var(--t-fast),
              border-color var(--t-fast);
  cursor: default;
}

.send-btn.is-active {
  background: rgba(200, 169, 110, 0.3);
  cursor: pointer;
}

.send-btn svg {
  width: 13px;
  height: 13px;
  stroke: currentColor;
  fill: none;
}

.copy-feedback {
  position: absolute;
  top: 48px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--gold);
  background: var(--bg-surface);
  border: 1px solid var(--gold-dim);
  padding: 6px 12px;
  border-radius: 3px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast);
  letter-spacing: 0.05em;
  z-index: 30;
}

.copy-feedback.show {
  opacity: 1;
}

/* Undo toast — sticky at the bottom of the review shell after a mark
   is removed. Stays in --fg-strong tokens (no new accent color).
   3.5s auto-dismiss; clicking Undo restores the mark. */
.rv-undo-toast {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%) translateY(8px);
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 12px;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg);
  letter-spacing: 0.04em;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast), transform var(--t-fast);
  z-index: 31;
}
.rv-undo-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.rv-undo-toast-btn {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--fg-strong);
  font-family: inherit;
  font-size: inherit;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 0;
  border-bottom: 1px solid var(--fg-dim);
  transition: border-bottom-color var(--t-fast);
}
.rv-undo-toast-btn:hover {
  border-bottom-color: var(--fg-strong);
}


/* ─── SCREEN HEADER (shared by browse, part-detail, part-edit, review) ─────
   Three-slot layout: left stays left, right stays right, center is
   absolutely centered — so showing/hiding right-side actions never
   shifts the center-slot content. */

.screen-header {
  position: relative;
  flex-shrink: 0;
  /* Padding aligned with .home-header (var(--space-lg) = 24px) so a
     chrome-button at the top-right slot of any sub-screen sits at the
     same y-position as home-parts-btn on the home screen — visual
     continuity when navigating from home into Browse / Part-Detail /
     Review. iOS safe-area is added on top. */
  padding: var(--space-lg);
  padding-top: calc(var(--space-lg) + env(safe-area-inset-top, 0px));
  display: flex;
  /* flex-start (not center) — same as .home-header — so left/right slot
     buttons sit at the top of the header, aligned with home-header
     buttons. .header-slot-center is position:absolute so its vertical
     center alignment is preserved independently. */
  align-items: flex-start;
  min-height: 3rem;
  gap: var(--space-sm);
}

.header-slot-left { }

.header-slot-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* Guard for tall center content; still centered at element's midpoint. */
  margin-top: calc(env(safe-area-inset-top, 0px) / 2);
}

.header-slot-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* ─── BREADCRUMB ────────────────────────────────────── */

.breadcrumb {
  flex-shrink: 0;
  padding: 0 var(--space-lg) var(--space-sm);
  font-family: var(--font-ui);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
.breadcrumb::-webkit-scrollbar { display: none; }

.breadcrumb:empty {
  padding: 0;
}

.breadcrumb-segment {
  background: none;
  border: none;
  color: var(--fg-dim);
  font-family: inherit;
  font-size: inherit;
  letter-spacing: inherit;
  padding: 2px 0;
  cursor: pointer;
  transition: color var(--t-fast);
}

.breadcrumb-segment:hover { color: var(--fg); }

.breadcrumb-segment.is-current {
  color: var(--fg);
  cursor: default;
}

.breadcrumb-sep {
  padding: 0 6px;
  color: var(--fg-muted);
}

/* ─── BROWSE (Parts | Dialogs) ──────────────────────── */

#browse {
  justify-content: flex-start;
  padding: 0;
}

/* Section tabs (PARTS | DIALOGS) — the persistent app-level navigation
   that lives in the header-slot-center of every section sub-screen
   (browse, part-detail, review, endscreen). Click navigates to the
   browse list for that tab. Active state derived from current screen. */
.section-tabs {
  width: 220px;
  max-width: 40vw;
}

.browse-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-sm) var(--space-lg) var(--space-lg);
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

.browse-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.browse-list.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-sm);
}

.browse-empty {
  padding: var(--space-xl) var(--space-md);
  text-align: center;
  font-family: var(--font-display);
  font-style: italic;
  color: var(--fg-dim);
  font-size: 0.95rem;
  line-height: 1.5;
}

/* Part tile — row layout (default) */
/* List-mode part-tile uses CSS Grid instead of flex-wrap so the
   meta column stays on the right deterministically — flex-wrap was
   pushing meta to a second row whenever the body wrapped. Three
   columns (emoji | body | meta) plus a tags row spanning all three. */
.part-tile {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: var(--space-md);
  row-gap: 8px;
  padding: var(--space-md);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast);
  background: transparent;
  text-align: left;
  color: inherit;
  font-family: inherit;
  width: 100%;
  position: relative;
  overflow: hidden;
}
.part-tile-emoji  { grid-column: 1; grid-row: 1; align-self: center; }
.part-tile-body   { grid-column: 2; grid-row: 1; }
.part-tile-meta   { grid-column: 3; grid-row: 1; }
.part-tile .tag-mini-chips {
  grid-column: 1 / -1;
  grid-row: 2;
}

.part-tile:hover {
  background: var(--gold-faint);
  border-color: var(--gold-dim);
}

.part-tile-emoji {
  font-size: 1.6rem;
  flex-shrink: 0;
  line-height: 1;
}

.part-tile-body {
  flex: 1;
  min-width: 0;
}

.part-tile-name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--fg-strong);
  line-height: 1.2;
}

.part-tile-desc {
  /* Block so the description always starts on its own line below the
     name, regardless of how short the name is. Without this the span
     flows inline with .part-tile-name and the row reads as one line. */
  display: block;
  font-family: var(--font-ui);
  font-style: italic;
  font-size: 0.75rem;
  color: var(--fg-dim);
  margin-top: 2px;
  line-height: 1.3;
}

/* Tile-meta layout — shared by part-tile-meta and dialog-tile-meta.
   Two rows: time-anchored "main" metric on its own line (date+time
   takes the full width), secondary metrics middot-separated below.
   Items uniform var(--fg-dim) — pro UX pattern (Linear, Notion,
   Apple Mail) keeps row-data calm and lets the toolbar's sort
   dropdown communicate which dimension is active. */
.part-tile-meta,
.dialog-tile-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.03em;
  flex-shrink: 0;
  /* Bottom-anchored in row-mode flex parents (.part-tile is flex
     row, .dialog-tile is block). For .part-tile this puts meta at
     the row's baseline regardless of body height — feels right when
     description wraps across multiple lines. Grid-mode overrides
     align-self via explicit grid-row placement. */
  align-self: flex-end;
}

.tile-meta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  min-width: 0;
  max-width: 100%;
}

.tile-meta-item {
  color: var(--fg-dim);
  position: relative;
}

/* Middot separator between siblings within the same row — direct-
   sibling selector so it never crosses row boundaries. */
.tile-meta-row .tile-meta-item:not(:first-child)::before {
  content: '·';
  margin-right: 10px;
  margin-left: -6px;
  color: var(--fg-muted);
  opacity: 0.6;
  font-weight: 400;
}

/* Grid layout overrides — subgrid so all tiles in the same visual row
   share row tracks (emoji | name | created | meta). If one tile's name
   wraps to two lines, every other tile reserves the same height for its
   name slot, and every tile's timestamp lines up at the same baseline.
   .part-tile-body stays in the DOM (used by row mode) but becomes its
   own nested subgrid here so name + created participate in the parent
   track alignment despite the wrapper. */
.browse-list.grid {
  grid-auto-rows: min-content;
}

.browse-list.grid .part-tile {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: subgrid;
  /* span 5 (was 4) — extra row for the tag-chip strip below meta.
     Without this row, .tag-mini-chips landed in row 4 on top of the
     meta line and overlapped "Last dialog: …". */
  grid-row: span 5;
  justify-items: center;
  align-items: start;
  text-align: center;
  padding: var(--space-md) var(--space-sm);
}

.browse-list.grid .part-tile-emoji {
  grid-row: 1;
  /* 1.5rem (was 2rem) — tile-mode pulls the emoji up smaller so the
     vertical rhythm has room for tags + meta below without crowding. */
  font-size: 1.5rem;
}

.browse-list.grid .part-tile-body {
  /* grid-column: 1 overrides the list-mode .part-tile-body { grid-column: 2 }
     placement. Without this, body lands in an implicit auto-column 2 of the
     single-column tile grid; that column sizes to min-content of the body's
     longest word, which makes the part-name wrap word-by-word. */
  grid-column: 1;
  grid-row: 2 / span 2;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: subgrid;
  width: 100%;
  justify-items: center;
  text-align: center;
}

.browse-list.grid .part-tile-name { font-size: 0.95rem; }

.browse-list.grid .part-tile-desc {
  display: none;
}

.browse-list.grid .part-tile-meta {
  /* Same reason as .part-tile-body — override list-mode grid-column: 3. */
  grid-column: 1;
  grid-row: 4;
  width: 100%;
  align-items: center;
}

/* Tile-mode meta-rows centred to match the tile's centred rhythm
   (justify-items: center on the .part-tile parent only sets the
   meta block, not the rows inside it). */
.browse-list.grid .tile-meta-row {
  justify-content: center;
}

/* Tag chip-strip lives below the meta line — explicit grid-row 5 so
   it doesn't auto-place onto an already-occupied track and overlap
   the meta. Width: 100% so the centred chips cover the full tile. */
.browse-list.grid .part-tile .tag-mini-chips {
  grid-row: 5;
  width: 100%;
}

/* List-mode dialog-tile uses CSS Grid — same rationale as .part-tile.
   Two columns (body | meta) plus a tags row spanning both. */
.dialog-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: var(--space-md);
  row-gap: 8px;
  padding: var(--space-md);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast);
  background: transparent;
  text-align: left;
  color: inherit;
  font-family: inherit;
  width: 100%;
  position: relative;
  overflow: hidden;
}
.dialog-tile-body { grid-column: 1; grid-row: 1; }
.dialog-tile-meta { grid-column: 2; grid-row: 1; }
.dialog-tile .tag-mini-chips {
  grid-column: 1 / -1;
  grid-row: 2;
}

/* Body holds the left column — flex column so title, part-row, and
   preview stack. */
.dialog-tile-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/* Grid mode keeps subgrid alignment across tiles — make the body
   transparent to the grid so its children become direct grid items. */
.browse-list.grid .dialog-tile-body {
  display: contents;
}

.dialog-tile:hover {
  background: var(--gold-faint);
  border-color: var(--gold-dim);
}

/* IW-style dialog tile: {emoji} {title} inline-left, preview below,
   tags row, meta+date row. */
.dialog-tile-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}

.dialog-tile-emoji {
  flex-shrink: 0;
  font-size: 1rem;
  line-height: 1;
}

.dialog-tile-title-text {
  flex: 1;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--fg-strong);
  line-height: 1.2;
  /* Up to 3 lines, then clip with ellipsis. Subgrid-row (in tile mode)
     reserves max(content) height across all tiles in the same row, so
     longer titles still align with shorter ones at the next slot. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-width: 0;
}

.dialog-tile-preview {
  font-family: var(--font-ui);
  font-style: italic;
  font-size: 0.8rem;
  color: var(--fg-dim);
  margin-top: 4px;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Dialog-tile grid mode: subgrid alignment same pattern as part-tile.
   5 rows: part / title / (preview hidden) / tags / meta. Each row's
   height is the largest content across the row, so tiles in the same
   row line up at every slot regardless of part-name or title length. */
.browse-list.grid .dialog-tile {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: subgrid;
  grid-row: span 5;
  justify-items: center;
  align-items: start;
  text-align: center;
  padding: var(--space-md) var(--space-sm);
  /* Default min-width for grid items is `auto` (= max-content) — that's
     why long titles / dates expanded the column past its 140px min and
     the rightmost tiles got clipped by the viewport. min-width: 0 lets
     the tile shrink to the column's actual fr-share. */
  min-width: 0;
}

/* Cascade min-width: 0 down through every intermediate container in
   the dialog-tile tree. Without this, the marquee-inner's
   `width: max-content` and the title-text's -webkit-box block
   propagate their max-content up to .dialog-tile-part / .dialog-tile-title
   (both have default min-width: auto), and from there into the tile.
   Part-tile doesn't need this because it has no max-content children. */
.browse-list.grid .dialog-tile-title,
.browse-list.grid .dialog-tile-part,
.browse-list.grid .dialog-tile-meta,
.browse-list.grid .dialog-tile .tag-mini-chips {
  min-width: 0;
  max-width: 100%;
}

.browse-list.grid .dialog-tile-title {
  grid-row: 1;
  /* Flex-row + justify-content: center centres emoji+text as a pair.
     Earlier `display: block; text-align: center` failed because
     .dialog-tile-title-text is `display: -webkit-box` for the line-
     clamp ellipsis, which is a block-level container and ignores
     text-align on the parent. */
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: var(--space-sm);
}

.browse-list.grid .dialog-tile-title-text {
  /* flex: 1 1 0 (instead of 0 1 auto) — make the title-text take
     exactly its share of the title row, ignoring its intrinsic
     content width. -webkit-box with line-clamp does NOT honour
     flex-shrink against an `auto` flex-basis, so a long title would
     stretch the row and push the column past its grid-track size,
     overflowing the viewport. With basis: 0 the line-clamp wraps
     into multiple lines + ellipsis as intended. */
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}

.browse-list.grid .dialog-tile-part {
  grid-row: 2;
  /* Stretch to full cell width — without this, parent's justify-items:
     center would shrink the row to its intrinsic content width, and
     the marquee detection (wrap.scrollWidth - wrap.clientWidth) would
     always read 0 because wrap fits its content exactly. */
  justify-self: stretch;
  justify-content: center;
  /* In tile mode the name should size to its content (or shrink with
     overflow if too wide), not flex-grow to fill the row — otherwise
     emoji+name would left-align rather than centering as a pair. */
}

.browse-list.grid .dialog-tile-part-name {
  flex: 0 1 auto;
  max-width: 100%;
}

.browse-list.grid .dialog-tile-title-text {
  font-size: 0.95rem;
}

.browse-list.grid .dialog-tile-preview {
  display: none;
}

/* Dialog-tile chip-strip placement — explicit row 4 between preview
   (row 3) and meta (row 5) so it never auto-places on top of either. */
.browse-list.grid .dialog-tile .tag-mini-chips {
  grid-row: 4;
  width: 100%;
}

.browse-list.grid .dialog-tile-meta {
  /* Override list-mode .dialog-tile-meta { grid-column: 2 } so meta
     lands in the single explicit column instead of creating an implicit
     auto-column that squashes the title + tag chips into column 1. */
  grid-column: 1;
  grid-row: 5;
  /* Analogous to .browse-list.grid .part-tile-meta — width:100% +
     align-items:center so the inner .tile-meta-row inherits the
     centred rhythm. Without this the meta-rows left-aligned in the
     cell because the outer flex column has no horizontal alignment
     rule (only the row-children do, via their own justify-content). */
  width: 100%;
  align-items: center;
}

/* Tile-mode (grid) chip-row override — centred (matches tile-mode
   rhythm). Per-chip styling lives in components.css → .tag-mini-chip
   (single source of truth for the tile variant). */
.browse-list.grid .tag-mini-chips {
  justify-content: center;
}

.dialog-tile-meta {
  margin-top: 6px;
}

/* Part identifier row inside dialog-tile (list + tile mode). Smaller
   and dimmer than the title — title owns the prominent slot; the part
   is supporting context. Long names truncate via ellipsis so the
   layout stays clean at the narrower tile-mode width. */
.dialog-tile-part {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: 0.9rem;
  color: var(--fg);
}

.dialog-tile-part-emoji {
  flex-shrink: 0;
  line-height: 1;
  font-size: 0.95rem;
}

.dialog-tile-part-name {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Marquee — reusable wrap + inner pair. Apply via classes
   .marquee-wrap (outer, clips) and .marquee-inner (slides). JS
   helper setupMarquee() in utils.js sets .is-overflowing and the
   --marquee-shift custom property when the inner content is wider
   than the wrap. Idle state stays clean (no animation, no clip).
   Used on dialog-tile-part-name and rv-identity-part-name. */
.marquee-wrap {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
}

.marquee-inner {
  display: inline-block;
  /* width: max-content forces the inner element to its natural content
     width regardless of the wrap's clipped width. Without this, the
     inner shrinks together with the wrap and we never detect overflow. */
  width: max-content;
  max-width: none;
  will-change: transform;
}

.marquee-wrap.is-overflowing {
  text-overflow: clip;
}

.marquee-wrap.is-overflowing .marquee-inner {
  animation: part-name-slide var(--marquee-duration, 5.2s) ease-in-out infinite;
}

@keyframes part-name-slide {
  0%, 15%  { transform: translateX(0); }
  50%, 65% { transform: translateX(var(--marquee-shift, 0)); }
  100%     { transform: translateX(0); }
}

/* Color-stripe via ::after pseudo-element + --part-color custom
   property (set on the tile in JS). List mode: vertical bar at the
   right edge. Tile/grid mode: horizontal bar at the bottom. Tile
   containers carry position: relative + overflow: hidden so stripes
   crop cleanly to the rounded corners. */
.part-tile::after,
.dialog-tile::after,
.pp-part-tile::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 4px;
  background: var(--part-color, transparent);
  pointer-events: none;
}

.browse-list.grid .part-tile::after,
.browse-list.grid .dialog-tile::after {
  top: auto;
  right: 0;
  bottom: 0;
  left: 0;
  width: auto;
  height: 4px;
}

/* ─── REVIEW: part identifier in identity row + container stripe ─── */

.rv-identity-part {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  margin: 4px 0;
  font-family: var(--font-display);
  font-size: 0.95rem;
  color: var(--fg);
}

.rv-identity-part-emoji {
  flex-shrink: 0;
  line-height: 1;
}

/* (No container colour-stripe inside the review screen — when the
   user is reading a dialog, the colour marker lives next to the part
   name in INTRO; the bubbles themselves carry the part colour in
   DIALOG/SPLIT. Reading surfaces stay free of edge chrome.) */

/* ─── PART DETAIL — IW-style profile page ─────────────
   Centered emoji (big), name, description, meta, action buttons,
   mentions list. Reference: IW InnerChildDialog.js:2847–2976. */

#part-detail {
  justify-content: flex-start;
  padding: 0;
}

.pd-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg) var(--space-xl);
  max-width: 500px;
  margin: 0 auto;
  width: 100%;
  text-align: center;
}

.pd-emoji {
  font-size: 36px;
  line-height: 1;
  margin-bottom: var(--space-md);
}

.pd-name-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}

/* Part-Detail name-row stripe — short horizontal bar (32×4) sitting
   inline next to the name, replacing the old color-dot. Element keeps
   its old id (pd-color-dot) for JS compatibility; class is now
   .pd-color-stripe to scope the styling. */
.pd-color-stripe {
  display: inline-block;
  width: 32px;
  height: 4px;
  border-radius: 2px;
  flex-shrink: 0;
}

/* Generic part colour-stripe used inline next to part name+emoji on
   dialog tiles and in the review intro. Two sizes — the small variant
   (sm) is for compact contexts like browse tiles. The default size
   matches .pd-color-stripe so the same visual indicator is used
   consistently across the app. */
.color-stripe {
  display: inline-block;
  width: 24px;
  height: 3px;
  border-radius: 2px;
  flex-shrink: 0;
  vertical-align: middle;
}
.color-stripe.color-stripe-sm {
  width: 18px;
  height: 3px;
}

.pd-name {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--fg-strong);
  line-height: 1.2;
  margin: 0;
}

.pd-description {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  color: var(--fg-dim);
  line-height: 1.5;
  max-width: 400px;
  margin: 0 auto var(--space-md);
}

.pd-description:empty { display: none; }

.pd-created {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-dim);
  margin-bottom: var(--space-lg);
  letter-spacing: 0.03em;
}

/* Primary action — Start dialog stands alone in its own row. */
.pd-actions {
  display: flex;
  gap: var(--space-sm);
  justify-content: center;
  margin-bottom: var(--space-md);
}

/* Secondary actions — Delete stays a text-link, Edit is a neutral
   text-btn (same affordance as the onboarding CREATE NEW PART button).
   Grouped center with a 24px gap so they read as related, not split
   to the screen edges. align-items:center balances the link and the
   button visually since they have different heights. */
.pd-secondary-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 24px;
  margin-bottom: var(--space-xl);
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.pd-link {
  color: var(--fg-dim);
  cursor: pointer;
  transition: color var(--t-fast);
}

.pd-link:hover { color: var(--fg); }

.pd-link.is-danger:hover {
  color: rgba(220, 120, 120, 0.9);
}

.pd-mentions {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-soft);
  text-align: left;
}

.pd-mentions-label {
  font-family: var(--font-ui);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-dim);
  margin-bottom: var(--space-sm);
}

.pd-mentions-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.pd-mentions-empty {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--fg-dim);
  text-align: center;
  padding: var(--space-md);
}

/* ─── INFO (About / Imprint / Privacy as content view) ───────
   Reverie's coll-info-* pattern adapted: simple flex header (back +
   title), then a body with multi-paragraph text rendered from a locale
   key. */

#info {
  justify-content: flex-start;
  align-items: stretch;
  padding: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.info-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  flex-shrink: 0;
}

.info-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.4rem;
  color: var(--fg-strong);
  letter-spacing: 0.04em;
  margin: 0;
}

.info-body {
  padding: 0 var(--space-lg) var(--space-xl);
  max-width: 600px;
  margin: 0 auto;
  width: 100%;
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--fg);
}

.info-body strong {
  font-weight: 600;
  color: var(--fg-strong);
}

.info-body em {
  font-style: italic;
}

/* Headings — Cormorant for visual continuity with paragraphs, weights
   stepped 500/400/400 so they read as scale-by-size, not weight noise. */
.info-body h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.6rem;
  color: var(--fg-strong);
  line-height: 1.3;
  margin: 1.4em 0 0.5em;
  letter-spacing: 0.01em;
}
.info-body h1:first-child { margin-top: 0; }

.info-body h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.25rem;
  color: var(--fg-strong);
  line-height: 1.3;
  margin: 1.2em 0 0.4em;
  letter-spacing: 0.01em;
}

.info-body h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--fg);
  line-height: 1.3;
  margin: 1em 0 0.3em;
  letter-spacing: 0.02em;
  font-style: italic;
}

.info-body ul {
  margin: 0 0 1em 1.2em;
  padding: 0;
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--fg);
}

.info-body li {
  margin-bottom: 0.3em;
}

.info-body hr {
  border: none;
  border-top: 1px solid var(--border-soft);
  margin: 2em auto;
  width: 60%;
}

/* ─── PART EDIT — unified create + edit form ─────────────
   Layout shared with partpick (.pp-inner + .pp-scroll + anchored
   bottom button). Padding/sizing for #part-edit is set via the joint
   rule near the top of this file. */

.pe-field {
  margin-bottom: var(--space-lg);
}

.pe-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-dim);
  margin-bottom: var(--space-sm);
}

.pe-input,
.pe-textarea {
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  color: var(--fg-strong);
  font-family: var(--font-display);
  font-size: 1rem;
  padding: var(--space-xs) var(--space-sm);
  border-radius: 2px;
  outline: none;
  transition: border-color var(--t-fast);
  width: 100%;
}

.pe-textarea {
  resize: vertical;
  font-size: 0.95rem;
  line-height: 1.5;
  min-height: 60px;
}

.pe-input:focus,
.pe-textarea:focus {
  border-color: var(--border-strong);
}

/* Bottom row on #part-edit: Delete-link (left, edit-existing only) +
   Save-button (right, ghost-btn — primary CTA). Anchors at the bottom
   of .pp-inner; .pp-scroll above takes the remaining vertical space. */
.pe-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
  flex-shrink: 0;
}

#pe-save {
  /* width: fit-content keeps the gold button from stretching across
     the row when the delete-link is hidden (new modes). */
  width: fit-content;
}

.pe-delete-link {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-dim);
  cursor: pointer;
  transition: color var(--t-fast);
}

.pe-delete-link:hover {
  color: rgba(220, 120, 120, 0.9);
}

/* Emoji picker (used by #part-edit) */
.emoji-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 4px;
}

.emoji-tile {
  background: var(--control-bg);
  /* Visible subtle border so tile boundaries read even when the tint
     is very faint against the paper background in day mode. */
  border: 1px solid var(--border);
  border-radius: 3px;
  font-size: 1.3rem;
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast);
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.emoji-tile:hover {
  background: var(--control-hover);
}

/* Selected: a stronger control-tone tint instead of gold-faint.
   --control-active inverts per theme: darker in day, lighter in night —
   so the selected tile reads as "pressed in" against either surface.
   Border uses --fg-strong (dark in day / light in night) — same pattern
   as .color-dot.is-sel. */
.emoji-tile.is-sel {
  border-color: var(--fg-strong);
  background: var(--control-active);
}

.emoji-custom-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.emoji-custom-label {
  font-family: var(--font-ui);
  font-size: 0.75rem;
  color: var(--fg-dim);
}

.emoji-custom-input {
  width: 48px;
  text-align: center;
  font-size: 1.3rem;
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: 2px;
  padding: 2px 4px;
  color: var(--fg-strong);
  outline: none;
}

.emoji-custom-input:focus { border-color: var(--border-strong); }

/* ─── REVIEW ────────────────────────────────────────── */

/* #review section centering + padding handled jointly with #dialog at
   the top of the dialog block (search "Dialog and Review live inside"). */

/* Title sits in .rv-identity below the header — bigger than when it
   was crammed into the header-slot-center, since now it has its own
   row and can breathe. */
.rv-title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--fg-strong);
  line-height: 1.2;
  margin: 0;
}

.rv-meta {
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.05em;
  margin-top: 4px;
}

/* Sub-tabs (INTRO | DIALOG | NOTES | SPLIT) live in the rv-subtoolbar
   below the header, where the screen-specific content selector belongs.
   The header itself is reserved for app-level section tabs (PARTS |
   DIALOGS). Width grew with the 4th tab. */
.rv-subtabs {
  width: 320px;
  max-width: 60vw;
  margin-top: 0;
}

/* Sub-toolbar: stacked vertically — sub-tabs on top, contextual
   actions below. Each centered. Never scrolls with content. */
.rv-subtoolbar {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  padding: 0 var(--space-lg) var(--space-sm);
  max-width: 700px;
  width: 100%;
  margin: 0 auto;
}

/* INTRO sub-tab content: the dialog's "cover" — title, part identity,
   meta line, tag chips. Centered, generous breath; fills the shell so
   the title sits comfortably anchored. The other three sub-tabs are
   reading surfaces and stay free of meta chrome. */
.rv-intro {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: var(--space-xl) var(--space-lg);
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

.rv-intro .rv-title {
  font-size: 2rem;
  margin-bottom: var(--space-md);
}

/* Part identity row in INTRO: emoji + full part name + colour dot.
   Inline-block flow so a long part name wraps naturally with the
   surrounding elements; INTRO has space, no marquee. */
.rv-intro .rv-identity-part {
  display: block;
  text-align: center;
  margin: var(--space-sm) 0;
}
.rv-intro .rv-identity-part-emoji {
  display: inline-block;
  vertical-align: middle;
  margin-right: 8px;
}
.rv-intro .rv-identity-part-name {
  display: inline;
  white-space: normal;
}
.rv-identity-part-dot {
  display: inline-block;
  vertical-align: middle;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-left: 10px;
  flex-shrink: 0;
}

/* Meta line — two stacked rows. Row 1: date + optional time-range.
   Row 2: duration + message count. Splitting them gives the meta
   breath now that INTRO has its own surface. */
.rv-intro .rv-meta {
  margin-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
}
.rv-meta-line { display: block; }

/* Tag-mini-chips defaults to flex-basis:100% (sized for row-direction
   parents). Inside .rv-intro the parent is column-direction, so
   100% becomes height — and chips would stretch tall. Reset both flex
   sizing and align-items so chips sit at their natural size. */
.rv-intro .rv-tags {
  flex: 0 0 auto;
  align-items: center;
}

/* Tag chips inside the review identity block — centred (matches
   identity-block's text-align: center), small breath above. Hidden
   when the entry has no tags (review.js renderReviewTags toggles it).
   Pills are one step more present than tile mini-chips: review is a
   reading view, the tags should read as a small but proper label
   strip rather than dimmed metadata. */
.rv-tags {
  justify-content: center;
  margin-top: var(--space-sm);
  gap: 6px;
}

.rv-tags .tag-mini-chip,
.pd-tags .tag-mini-chip {
  font-size: 13px;
  padding: 2px 12px 3px;
  border-radius: 12px;
  color: var(--fg);
  border-color: var(--fg-faint);
}

/* Part-detail tag strip — same register as review (.rv-tags) so the
   two read-only entity views feel like one design family. Sits above
   the primary "Start dialog" action, below the created-date line. */
.pd-tags {
  justify-content: center;
  margin: var(--space-sm) 0 var(--space-md);
  gap: 6px;
}

/* Action group within the rv-subtoolbar — contextual to the active
   sub-tab. Each .rv-action carries data-tab (intro|dialog|notes|split);
   the section[data-subtab] attribute on #review scopes which buttons
   render. Hidden buttons get display:none so the row keeps its natural
   width per tab. */
.rv-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  min-height: 22px;
}
.rv-action { display: none; }
section#review[data-subtab="intro"]  .rv-action[data-tab="intro"],
section#review[data-subtab="dialog"] .rv-action[data-tab="dialog"],
section#review[data-subtab="notes"]  .rv-action[data-tab="notes"],
section#review[data-subtab="split"]  .rv-action[data-tab="split"] {
  display: inline-flex;
}

/* Filter toggle styling — sits in the actions row on the DIALOG tab.
   Mirrors the other text-btn actions but adds a disabled appearance
   when the entry has no marked messages, plus an active state when
   the filter is engaged. */
#rv-filter-toggle[disabled] {
  color: var(--fg-faint);
  cursor: default;
  pointer-events: none;
}
#rv-filter-toggle.is-active {
  color: var(--fg-strong);
}

/* Subtle horizontal rule separating action row from scrollable content.
   Inset so it doesn't run edge-to-edge — matches the .info-body hr feel. */
.rv-divider {
  flex-shrink: 0;
  border: none;
  border-top: 1px solid var(--border-soft);
  width: calc(100% - 2 * var(--space-lg));
  max-width: 700px;
  margin: 0 auto var(--space-md);
}

.rv-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

/* Notes pane — rendered markdown of entry.comment. Same scroll
   container as rv-body, swapped via setTab. Bottom padding matches
   rv-body so all three tabs (Dialog / Notes / Both) end at the same
   visual distance from the shell's bottom border. */
.rv-notes {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg);
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--fg);
}

.rv-notes-empty {
  font-style: italic;
  color: var(--fg-muted);
  text-align: center;
  margin: var(--space-xl) 0;
}

/* Combined view (BOTH): notes on top, dialog on bottom, each its own
   labeled section + scroll container. Together they fill the same
   vertical space as a single view. */
.rv-both {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

/* Each half (Notes + Dialog) gets its own bordered shell. Default
   split is 30:70 (Notes : Dialog) — set on flex-basis so the splitter
   can override it via inline style during drag. */
.rv-both-shell {
  flex-grow: 1;
  flex-shrink: 1;
  min-height: 0;
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
}

.rv-both-shell-notes  { flex-basis: 30%; }
.rv-both-shell-dialog { flex-basis: 70%; }

/* Drag handle between the two shells. Visible bar is 8×96, but the
   element itself extends with vertical padding so the hit area is
   24px tall — comfortable for both mouse and touch. Color uses
   border-strong (vs border-soft elsewhere) to actually be visible. */
.rv-both-splitter {
  flex-shrink: 0;
  align-self: center;
  width: 96px;
  height: 8px;
  margin: 0 auto;
  padding: var(--space-sm) 0;
  /* Background-clip: content-box so padding is transparent and the
     visible bar is just the inner content area. */
  background: var(--border-strong);
  background-clip: content-box;
  border-radius: 4px;
  cursor: row-resize;
  transition: background var(--t-fast);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  position: relative;
  z-index: 5;
  box-sizing: content-box;
}

.rv-both-splitter:hover,
.rv-both-splitter.is-dragging {
  background-color: var(--gold-dim);
}

/* Labels sit above each shell (outside the bordered area), small and
   muted — same UI-font micro-label as elsewhere in the app. */
.rv-both-label {
  flex-shrink: 0;
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0 var(--space-md) var(--space-xs);
}

/* In SPLIT view the outer app-shell goes transparent — each half has
   its own shell. Section gets the data-subtab attribute from review.js
   setSubtab. max-width also goes away so the layout no longer reads
   as a bounded outer card. */
section#review[data-subtab="split"] .app-shell {
  background: transparent;
  border: none;
  border-radius: 0;
  overflow: visible;
  max-width: none;
}

.rv-both-notes {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg);
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.55;
  color: var(--fg);
  min-height: 0;
}

.rv-both-dialog {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 0;
}

/* Inherit the same markdown styling as .rv-notes for the combined view's
   notes pane. */
.rv-both-notes h1, .rv-both-notes h2, .rv-both-notes h3,
.rv-both-notes ul, .rv-both-notes li, .rv-both-notes hr,
.rv-both-notes strong, .rv-both-notes em, .rv-both-notes a,
.rv-both-notes mark { all: revert; }

.rv-both-notes h1 { font-family: var(--font-display); font-weight: 600; font-size: 1.4rem; color: var(--fg-strong); margin: 0.5em 0 0.15em; line-height: 1.25; }
.rv-both-notes h1:first-child { margin-top: 0; }
.rv-both-notes h2 { font-family: var(--font-display); font-weight: 600; font-size: 1.15rem; color: var(--fg-strong); margin: 0.4em 0 0.12em; line-height: 1.25; }
.rv-both-notes h3 { font-family: var(--font-display); font-style: italic; font-weight: 400; font-size: 1.05rem; color: var(--fg); margin: 0.35em 0 0.1em; line-height: 1.25; }
.rv-both-notes ul { margin: 0 0 0 1.4em; padding: 0; }
.rv-both-notes hr { border: none; border-top: 1px solid var(--border-soft); margin: 0.8em auto; width: 50%; }
.rv-both-notes strong { font-weight: 600; color: var(--fg-strong); }
.rv-both-notes em { font-style: italic; }
.rv-both-notes a { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
.rv-both-notes mark { background: var(--gold-faint); color: var(--fg-strong); padding: 0 3px; border-radius: 2px; }

.rv-notes { line-height: 1.55; }
.rv-notes h1 { font-family: var(--font-display); font-weight: 600; font-size: 1.5rem; color: var(--fg-strong); margin: 0.5em 0 0.15em; line-height: 1.25; display: block; }
.rv-notes h1:first-child { margin-top: 0; }
.rv-notes h2 { font-family: var(--font-display); font-weight: 600; font-size: 1.2rem; color: var(--fg-strong); margin: 0.4em 0 0.12em; line-height: 1.25; display: block; }
.rv-notes h3 { font-family: var(--font-display); font-style: italic; font-weight: 400; font-size: 1.05rem; color: var(--fg); margin: 0.35em 0 0.1em; line-height: 1.25; display: block; }
.rv-notes ul { margin: 0 0 0 1.4em; padding: 0; display: block; }
.rv-notes li { display: list-item; }
.rv-notes hr { border: none; border-top: 1px solid var(--border-soft); margin: 0.8em auto; width: 50%; }
.rv-notes strong { font-weight: 600; color: var(--fg-strong); }
.rv-notes em { font-style: italic; }
.rv-notes a { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
.rv-notes a:hover { color: var(--gold-strong, var(--gold)); }
.rv-notes mark { background: var(--gold-faint); color: var(--fg-strong); padding: 0 3px; border-radius: 2px; }

/* ─── END SCREEN ────────────────────────────────────── */

#endscreen {
  /* Full-screen layout, not the previous narrow centered box. The notes
     editor is the primary content and needs the full viewport. */
  justify-content: flex-start;
  align-items: stretch;
  padding: 0;
  overflow: hidden;
}

/* Header bar — back arrow (only in edit mode), title, meta. */
.end-header-bar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  padding-top: calc(var(--space-md) + env(safe-area-inset-top, 0px));
  border-bottom: 1px solid var(--border-soft);
}

.end-header-titles {
  text-align: center;
  flex: 1;
  min-width: 0;
}

.end-title {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--fg-strong);
  line-height: 1.2;
}

.end-meta {
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.05em;
  margin-top: 2px;
}

.end-inner {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: var(--space-md) var(--space-lg);
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
  min-height: 0;
}

.end-field-title {
  flex-shrink: 0;
  margin-bottom: var(--space-md);
}

.end-input {
  font-family: var(--font-display);
  font-size: 22px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-soft);
  background: transparent;
  color: var(--fg-strong);
}

/* Markdown editor: textarea on top, rendered preview below. Stacked
   vertically at every viewport width — same look as the EDIT NOTES
   surface accessed via Review. Each pane scrolls independently so the
   user can keep both visible while writing long notes. */
.end-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.end-editor-label {
  flex-shrink: 0;
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: var(--space-xs);
  /* Allow long inline syntax hint to wrap on narrow viewports without
     overflowing the editor pane. */
  white-space: normal;
}

.end-editor-syntax {
  text-transform: none;
  letter-spacing: 0.02em;
  margin-left: 4px;
}

.end-editor-syntax code {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px 4px;
  margin: 0 2px;
  background: var(--control-bg);
  border-radius: 2px;
  color: var(--fg);
  white-space: nowrap;
}

.end-textarea {
  font-family: var(--font-mono);
  font-size: 14px;
  line-height: 1.6;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: var(--bg-surface);
  color: var(--fg);
  resize: vertical;
  min-height: 200px;
}

.end-textarea:focus {
  outline: none;
  border-color: var(--gold-dim);
}

.end-buttons {
  flex-shrink: 0;
  display: flex;
  gap: 10px;
  margin-top: var(--space-md);
  justify-content: center;
  flex-wrap: wrap;
}

/* End-screen tags field — wraps the tag picker between title input and
   notes editor. Indent matches the title input's content rhythm. */
.end-field-tags {
  margin: 0 auto;
  width: 100%;
  max-width: 700px;
  padding: 0 var(--space-md);
}

/* ─── BROWSE: filter / sort / search toolbar ──────────────
   Two rows: top has search + sort select, bottom has tag-filter chip
   row + AND/OR mode + manage-link. The whole toolbar sits between
   breadcrumb and the lists — same horizontal inset as the lists. */
/* Filter toolbar — Disclosure / Accordion pattern. Header is the
   trigger; the body collapses smoothly. Body height is JS-driven:
   browse.js measures the body's scrollHeight on open and writes it
   to inline max-height on .browse-toolbar-body so the transition
   lerps to exactly the right height (no overshoot, no snap). Box
   border is permanent so it reads as one UI element either way.
   Width-aligned with the tile content area: 700px outer body minus
   2× var(--space-lg) padding = ~652px content width. */
.browse-toolbar {
  display: flex;
  flex-direction: column;
  max-width: calc(700px - 2 * var(--space-lg));
  width: 100%;
  margin: var(--space-sm) auto var(--space-md);
  box-sizing: border-box;
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  background: rgba(58, 53, 45, 0.02);
  transition: background-color var(--t-normal) ease-in-out;
}

.browse-toolbar.is-open {
  background: rgba(58, 53, 45, 0.035);
}

/* Header bar — clickable, contains +/− icon + label + active-dot.
   In the collapsed state the header should read as a quiet trigger,
   not as a primary heading: dim color + 11px so the toolbar doesn't
   shout when there's nothing to interact with. Hover and open state
   bump it up so the user sees they've engaged it. */
.browse-toolbar-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: transparent;
  border: none;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-dim);
  cursor: pointer;
  position: relative;
  text-align: left;
  transition: color var(--t-fast);
}

.browse-toolbar-header:hover {
  color: var(--fg);
}

.browse-toolbar.is-open .browse-toolbar-header {
  color: var(--fg);
}

/* Disclosure glyph — +/− is the most universally understood expand/
   collapse affordance (used by Notion, Bear, accordion patterns).
   Color follows header (dim default, fg on hover/open) via inherit so
   the trigger reads as one unit. */
.filter-toggle-icon {
  display: inline-block;
  width: 14px;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1;
  color: inherit;
  flex-shrink: 0;
}

/* Active-dot — appears when filters are set and the box is closed,
   so the user notices their hidden filter context. Hidden when open. */
.browse-toolbar-header::after {
  content: '';
  position: absolute;
  right: 14px;
  top: 50%;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold);
  transform: translateY(-50%);
  opacity: 0;
  transition: opacity var(--t-fast);
}

.browse-toolbar.has-active:not(.is-open) .browse-toolbar-header::after {
  opacity: 1;
}

/* Body — the part that collapses. Default max-height: 0; browse.js
   sets inline max-height to scrollHeight on open and removes it on
   close. transition runs the lerp. */
.browse-toolbar-body {
  overflow: hidden;
  max-height: 0;
  transition: max-height var(--t-normal) ease-in-out;
}

.browse-toolbar-body-inner {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 0 14px 14px;
  border-top: 1px solid var(--border-soft);
  padding-top: 14px;
}

/* Each labelled section: small uppercase label, then control(s). */
.browse-toolbar-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.browse-toolbar-section-label {
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* fg-dim, not fg-muted — labels need to be readable, not decorative. */
  color: var(--fg-dim);
}

/* Section row — label and a right-side control on the same line
   (e.g. "TAGS" + AND/OR toggle). */
.browse-toolbar-section-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.browse-search {
  width: 100%;
  font-family: var(--font-ui);
  font-size: 14px;
  padding: 8px 12px;
  background: transparent;
  border: 1px solid var(--border-med, var(--fg-faint));
  border-radius: 4px;
  color: var(--fg);
  text-align: center;
  box-sizing: border-box;
}

.browse-search::placeholder {
  /* Browser default placeholder is often almost invisible — explicit
     so it reads as a hint rather than disappearing into the bg. */
  color: var(--fg-muted);
  font-style: italic;
}

.browse-search:focus {
  outline: none;
  border-color: var(--fg-dim);
}

.browse-tag-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  justify-content: center;
}

.browse-andor {
  font-family: var(--font-ui);
  font-size: 11px;
  flex-shrink: 0;
}

/* ─── Action row (Create / Begin + Sort) ──────────────
   3-column grid: empty / primary action / sort. Primary stays centred,
   sort sits flush right. Sort is always visible — sort is a view-
   setting, filter is a scope-setting; UX-conventionally separate. */
.browse-action-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
}

.browse-action-row .pp-create-btn {
  grid-column: 2;
  justify-self: center;
  margin: 0;
}

.browse-sort-wrap {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 4px;
}

.browse-sort {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
}

.browse-sort:hover {
  border-color: var(--fg-dim);
  color: var(--fg);
}

.browse-sort:focus {
  outline: none;
  border-color: var(--fg-dim);
}

/* Direction toggle next to the sort dropdown — small button showing
   the current direction (↓ desc, ↑ asc). Click flips it. */
.browse-sort-dir {
  font-family: var(--font-ui);
  font-size: 12px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
  line-height: 1;
}

.browse-sort-dir:hover {
  border-color: var(--fg-dim);
  color: var(--fg);
}

/* Manage-tags link — small text-only affordance, sits at the right end
   of the filter row to keep the heavy ergonomics out of view. */
.text-btn-link {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-dim);
  background: transparent;
  border: none;
  padding: 4px 6px;
  cursor: pointer;
  letter-spacing: 0.02em;
  border-radius: 3px;
  transition: color var(--t-fast);
}

.text-btn-link:hover {
  color: var(--fg-strong);
}

/* ─── TAG MANAGER ─────────────────────────────────────────
   Layout follows part-edit: pp-header + breadcrumb + pp-inner with
   pp-prompt as Cormorant title. Body lives inside pp-scroll for
   consistent scrolling behaviour with other form-style screens.

   In-screen order is intentional: add-row first (primary affordance),
   hint, sort toggle, list. The user encounters "add" before the
   existing list — same logic as the partpick onboarding. */
.tm-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.tm-add-row {
  margin-bottom: var(--space-xs);
  display: flex;
  justify-content: center;
}

.tm-add-input {
  font-family: var(--font-display);
  font-size: 16px;
  padding: 6px 16px;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: transparent;
  color: var(--fg-strong);
  letter-spacing: 0.01em;
  outline: none;
  width: 260px;
  max-width: 100%;
  text-align: center;
}

.tm-add-input:focus {
  border-color: var(--fg-strong);
}

.tm-add-input::placeholder {
  color: var(--fg-muted);
  font-style: italic;
}

.tm-hint {
  text-align: center;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.02em;
  margin: 0 0 var(--space-sm);
}

.tm-toolbar {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-sm);
}

.tm-sort {
  font-family: var(--font-ui);
  font-size: 11px;
}

.tm-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tm-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 4px 0;
}

.tm-row.is-predef .tm-row-chip {
  cursor: default;
}

.tm-row-chip {
  flex-shrink: 0;
}

.tm-row-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  font-family: var(--font-ui);
  font-size: 10px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
}

.tm-row-usage {
  font-family: var(--font-mono);
}

.tm-row-predef-marker {
  font-family: var(--font-ui);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-muted);
  border: 1px solid var(--border-soft);
  border-radius: 3px;
  padding: 1px 5px;
}

.tm-row-delete {
  background: transparent;
  border: none;
  color: var(--fg-muted);
  font-size: 16px;
  cursor: pointer;
  padding: 0 6px;
  line-height: 1;
  transition: color var(--t-fast);
}

@media (hover: hover) {
  .tm-row-delete:hover {
    color: rgba(220, 120, 120, 0.85);
  }
}

.tm-empty {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--fg-muted);
  text-align: center;
  margin: var(--space-lg) 0;
}

.tm-row-rename {
  text-align: left;
}

/* ─── DATA SCREEN ─────────────────────────────────────────
   Layout mirrors .info-body (centred 600px column under a flex
   header with back-button). R5 adds the diff-renderer styling. */
.data-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 var(--space-lg) var(--space-xl);
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

.data-section-title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--fg-strong);
  letter-spacing: 0.03em;
  margin: 0 0 var(--space-sm);
}

.data-section-hint {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--fg-dim);
  line-height: 1.5;
  margin: 0 0 var(--space-md);
}

.data-file-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: var(--space-md);
}

.data-file-row label {
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--fg-dim);
  text-transform: uppercase;
}

.data-file-row input[type="file"] {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--fg);
}

.data-action-row {
  margin-top: var(--space-md);
  display: flex;
  justify-content: center;
}

.data-action-row .text-btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.data-diff-target {
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-soft);
}

.data-empty {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--fg-muted);
  margin: 0;
}

.data-diff-summary {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--fg);
  line-height: 1.6;
  margin: 0;
}

.data-diff-summary small {
  color: var(--fg-muted);
}

/* Top-bar — primary "this will happen" panel above the section list.
   Heading + summary + apply-button, all centered. The note explains
   the per-item override path so the user reads "this is the plan,
   here's where you'd tweak it" in one breath. */
.data-top-bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  text-align: center;
  padding: var(--space-md) var(--space-lg) var(--space-lg);
  margin-bottom: var(--space-lg);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  background: rgba(58, 53, 45, 0.025);
}

.data-top-heading {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-strong);
}

.data-top-bar .data-summary {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--fg-strong);
  line-height: 1.4;
}

.data-submit-btn {
  align-self: center;
  margin-top: 4px;
}

.data-top-note {
  font-family: var(--font-ui);
  font-size: 12px;
  font-style: italic;
  color: var(--fg-dim);
  margin: 0;
  max-width: 380px;
  line-height: 1.5;
}

/* Prominent bulk-hint at the top of the diff. Replaces the old quiet
   .data-top-note. Visually a callout — gold-faint background + left
   accent border so the user notices it before scanning the section
   counts. Wording explains that defaults are Skip and points the
   user at the bulk-shortcuts inside each section. */
.data-bulk-hint {
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
  background: var(--gold-faint);
  border-left: 3px solid var(--gold);
  border-radius: 3px;
  padding: 10px 14px;
  margin: 4px 0 0;
  max-width: 560px;
}

/* Section — collapsible, header is the trigger, body only renders when
   open. Closed by default so the top-bar gets the user's attention first. */
.data-diff-section {
  margin-bottom: var(--space-md);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  overflow: hidden;
}

.data-section-header {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px;
  width: 100%;
  align-items: baseline;
  padding: 10px 14px;
  background: transparent;
  border: none;
  cursor: pointer;
  font-family: var(--font-ui);
  text-align: left;
  color: var(--fg);
}

.data-section-header:hover {
  background: rgba(58, 53, 45, 0.02);
}

.data-section-header.is-open {
  border-bottom: 1px solid var(--border-soft);
}

.data-section-toggle-icon {
  font-family: var(--font-ui);
  font-size: 1rem;
  width: 14px;
  text-align: center;
  color: var(--fg-dim);
}

.data-section-title {
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--fg-strong);
}

.data-section-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-dim);
  justify-self: end;
  text-align: right;
}

.data-section-summary-status,
.data-section-summary-action {
  display: inline;
}

.data-section-summary-action {
  color: var(--fg);
}

.data-section-body {
  padding: var(--space-md) 14px;
  background: rgba(58, 53, 45, 0.015);
}

/* Bulk-action bar — small text-link style, comma-separated visually. */
.data-bulk-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: baseline;
  margin-bottom: var(--space-sm);
  font-family: var(--font-ui);
  font-size: 11px;
}

.data-bulk-label {
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 10px;
}

.data-bulk-bar .text-btn-link {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-dim);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--border-soft);
}

.data-bulk-bar .text-btn-link:hover {
  color: var(--fg);
}

/* List of items in the diff. */
.data-diff-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.data-diff-item {
  border: 1px solid var(--border-soft);
  border-left-width: 3px;
  border-radius: 4px;
  padding: 8px 12px;
  background: var(--paper-soft, transparent);
}

/* Status border-left color — quick visual sort. */
.data-diff-item.status-new      { border-left-color: var(--gold, #b89968); }
.data-diff-item.status-already  { border-left-color: var(--border-soft); opacity: 0.75; }
.data-diff-item.status-changed  { border-left-color: rgba(200, 150, 60, 0.7); }
.data-diff-item.status-conflict { border-left-color: rgba(200, 90, 90, 0.7); }

/* Status counts inside collapsed section headers. CHANGED reads in
   the gold accent (something to look at). CONFLICT reads in alarm
   red (real attention — usually a duplicate ID situation). NEW and
   ALREADY stay quiet because they're not decisions to revisit. */
.ss-status {
  font-weight: 400;
}
.ss-status-changed {
  color: var(--gold);
  font-weight: 500;
}
.ss-status-conflict {
  color: rgba(190, 70, 70, 0.95);
  font-weight: 600;
}
.ss-status-new {
  color: var(--fg);
}
.ss-status-already {
  color: var(--fg-dim);
}

.data-diff-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-md);
  align-items: center;
}

.data-diff-label {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
}

.data-diff-status {
  font-family: var(--font-ui);
  font-size: 9px;
  letter-spacing: 0.1em;
  /* Was --fg-muted (alpha 0.20) — too faint to read at 9px. fg-dim
     (0.45) gives the label real legibility while staying clearly
     subordinate to the row title. */
  color: var(--fg-dim);
}

/* Per-item action radios — only rendered when there's a real choice. */
.data-diff-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 6px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--fg-dim);
}

.data-diff-action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}

.data-diff-action input[type="radio"] {
  margin: 0;
}

/* CONFLICT reason — short explanation under the row title that tells
   the user WHAT the conflict actually is (semantic match without
   stable-ID match). Empty :empty rule hides the element when the
   item isn't a CONFLICT so it doesn't take up vertical space. */
.data-diff-reason {
  font-family: var(--font-ui);
  font-size: 12px;
  font-style: italic;
  color: var(--fg-dim);
  line-height: 1.4;
  margin-top: 4px;
}

.data-diff-reason:empty {
  display: none;
}

/* Field-level diffs for CHANGED / CONFLICT items.
   Three columns: field-name | Fold value | IWB value. */
.data-diff-fields {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px dashed var(--border-soft);
  padding-top: 6px;
}

.data-diff-fields:empty {
  display: none;
}

.data-field-diff {
  display: grid;
  grid-template-columns: 90px 1fr 1fr;
  gap: 10px;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg-dim);
  align-items: start;
}

.data-field-name {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 9px;
  /* fg-dim (0.45) instead of fg-muted (0.20) — same readability lift
     as .data-diff-status. */
  color: var(--fg-dim);
  padding-top: 1px;
}

.data-field-tag {
  font-size: 8px;
  letter-spacing: 0.08em;
  /* fg-dim instead of fg-muted — the IMPORT/FOLD label needs to read
     at a glance because it's the column key. */
  color: var(--fg-dim);
  text-transform: uppercase;
  margin-right: 4px;
}

.data-field-val {
  color: var(--fg);
}

/* Highlight a value cell that differs from the other side. Bold +
   gold accent so the user can spot at a glance which value is the
   contested one — without the alarm of red. Lives on both columns
   because both values are equally relevant: which to keep depends on
   the user's decision. */
.data-field-val.is-diff {
  font-weight: 500;
  color: var(--fg-strong);
}

.data-field-side.is-diff .data-field-tag {
  color: var(--gold);
}

/* Result-after-action preview for prefs: shows what the value will
   actually be after the import based on the currently selected
   action. Sits below the two-column diff so the user gets a clear
   read on the consequence of their choice. */
.data-field-result {
  grid-column: 1 / -1;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--fg);
  padding-top: 4px;
  border-top: 1px dotted var(--border-soft);
  margin-top: 2px;
}

.data-field-result-label {
  font-size: 8px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-right: 6px;
}

.data-field-result-val {
  font-weight: 500;
  color: var(--fg-strong);
}

/* Submit-note used inside .data-top-bar to flag that R6 is not yet wired. */
.data-submit-note {
  font-family: var(--font-ui);
  font-size: 10px;
  font-style: italic;
  color: var(--fg-muted);
  margin: 0;
}

/* Phase L — All three data sections share the same bordered-card frame. */
.data-section {
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  padding: var(--space-md);
  margin-bottom: var(--space-md);
}

/* Foot-note under each Import trigger button. */
.data-section-foot-note {
  margin: var(--space-sm) 0 0;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  color: var(--fg-dim);
  line-height: 1.3;
}

/* Inline code emphasis for filenames inside hint / foot-note. */
.data-section-hint code,
.data-section-foot-note code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: var(--gold-faint);
  padding: 1px 4px;
  border-radius: 2px;
}

/* Mono summary line shown after a successful export (e.g. "42 dialogs, 3 parts"). */
.data-export-summary {
  margin-top: var(--space-sm);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--fg-dim);
}

/* Inline error for fold-import validation failures. #c44 has no token — intentional. */
.data-fold-error {
  color: #c44;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  margin: var(--space-sm) 0;
}

/* Standalone file label (outside .data-file-row context). */
.data-file-label {
  display: block;
  margin: var(--space-sm) 0;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--fg-dim);
}

/* ─────────── SETTINGS SCREEN ─────────── */

/* Outer container — mirrors .browse-body exactly (flex:1 to take
   remaining height under the info-header, overflow-y for long lists,
   max-width 700px centred). Without flex:1 the body would not scroll
   inside the absolute-positioned .screen flex column. */
.settings-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-sm) var(--space-lg) var(--space-lg);
  max-width: 700px;
  margin: 0 auto;
  width: 100%;
}

/* Each setting row: label left, control right, vertically compact.
   Wraps to label-above-control on narrow viewports (sidebar-width
   sized screens) so the controls never get clipped. */
.setting-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm) var(--space-md);
  padding: 12px 0;
  border-bottom: 1px solid var(--border-soft);
}

.setting-row:last-of-type {
  border-bottom: none;
}

/* Label-block: holds the uppercase label plus an italic hint line
   below it ("12-hour or 24-hour", etc.). The block shrinks to the
   intrinsic content width on wide rows; on narrow rows it wraps to
   its own line above the segmented control via the parent's
   flex-wrap. */
.setting-row-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}

.setting-row-label > label {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  color: var(--fg-dim);
  text-transform: uppercase;
}

.setting-row-hint {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-style: italic;
  color: var(--fg-muted);
  line-height: 1.35;
}

.setting-row .segmented {
  flex: 0 0 auto;
}

/* The "Import & Export data" card at the end of the settings list.
   Visually a bordered card analogous to a .data-section, acting as a
   navigation entry point to #data. */
.settings-data-card {
  display: block;
  margin-top: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  transition: background-color var(--t-fast), border-color var(--t-fast);
}

.settings-data-card:hover {
  background: var(--gold-faint);
  border-color: var(--gold-dim);
}

.settings-data-title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 400;
  color: var(--fg-strong);
}

.settings-data-hint {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--fg-dim);
  line-height: 1.4;
}

/* ─────────── DEV-MODE INDICATOR ─────────── */

/* Dev-mode visual indicator: red bar, top center, always visible when active.
   Values copied verbatim from Reverie (kaleidoscope-data-migration/index.html:497-510). */
#dev-indicator {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 6px);
  left: 50%;
  transform: translateX(-50%);
  width: 40px;
  height: 3px;
  border-radius: 2px;
  background: #c44;
  z-index: 100;
  pointer-events: none;
  opacity: 0.8;
}
#dev-indicator.hidden { display: none; }

