/* diff-view.css — the comparison lens's one stylesheet.
 *
 * Loaded by the host, never injected by JS:
 *   <link rel="stylesheet" href="/prism/components/diff-view/diff-view.css">
 *
 * RULES THIS FILE OBEYS (docs/COMPONENT-CONTRACT.md §4)
 *
 * - Wrapped in `@layer prism`, so an app's own unlayered styles win an override
 *   without a specificity war and without !important.
 * - Semantic tokens only — never a ramp step, never a bare hex — and every
 *   var() carries a literal fallback so the component still renders if a host
 *   forgot tokens.css.
 * - Every selector is a `.prism-diff-view*` class. No bare element selectors.
 * - No document-level selector reaches into the subtree, so Shadow DOM stays
 *   available as a later opt-in. Density arrives by custom-property
 *   inheritance, which crosses a shadow boundary; a descendant selector would
 *   not.
 *
 * Feature classes live here too, even though features are optional files: one
 * stylesheet is the contract, and the unused rules cost nothing.
 *
 * SIZING. The component fills a height you give it. Either set one on the
 * element from your own CSS (unlayered, so it wins), or override the custom
 * property:  .my-panel .prism-diff-view { --prism-diff-view-height: 70vh; }
 */

@layer prism {
  /* ================================================================ *
   * Shell
   * ================================================================ */

  .prism-diff-view {
    display: flex;
    flex-direction: column;
    min-width: 0;
    height: var(--prism-diff-view-height, 420px);
    border: 1px solid var(--prism-border, rgba(11, 11, 11, 0.1));
    border-radius: var(--prism-radius-lg, 8px);
    background: var(--prism-surface-1, #fcfcfb);
    color: var(--prism-ink-primary, #0b0b0b);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    font-size: var(--prism-text-md, 13px);
    overflow: hidden;
  }

  .prism-diff-view:focus-visible {
    outline: var(--prism-focus-ring-width, 2px) solid var(--prism-focus-ring, #2a78d6);
    outline-offset: var(--prism-focus-ring-offset, 1px);
  }

  /* ================================================================ *
   * Chrome — labels and change navigation
   * ================================================================ */

  .prism-diff-view__chrome {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--prism-space-3, 8px);
    padding: var(--prism-space-2, 4px) var(--prism-space-3, 8px);
    background: var(--prism-surface-2, #f0efec);
    border-bottom: 1px solid var(--prism-border-strong, #c3c2b7);
  }

  .prism-diff-view__labels {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    gap: var(--prism-space-3, 8px);
  }

  /* Each label cell is exactly half the width, so the boundary between them is
     the boundary between the panes. The change counter and its buttons ride
     inside the last cell rather than beside the row — see paintChrome(). */
  .prism-diff-view__label {
    display: flex;
    flex: 1 1 50%;
    align-items: center;
    gap: var(--prism-space-2, 4px);
    min-width: 0;
    font-size: var(--prism-text-sm, 12px);
    font-weight: var(--prism-weight-medium, 500);
    color: var(--prism-ink-secondary, #52514e);
  }

  .prism-diff-view__label-text {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .prism-diff-view__label--unified {
    flex: 1 1 100%;
  }

  .prism-diff-view__label-arrow {
    color: var(--prism-ink-muted, #898781);
  }

  .prism-diff-view__actions {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--prism-space-2, 4px);
    margin-left: auto;
  }

  .prism-diff-view__count {
    font-size: var(--prism-text-sm, 12px);
    color: var(--prism-ink-muted, #898781);
    font-variant-numeric: var(--prism-figures-tabular, tabular-nums);
  }

  /* ================================================================ *
   * Viewport and rows
   *
   * One vertical scroller holding both panes (SPEC §2.3): split panes cannot
   * drift apart, because there is nothing to keep in sync.
   * ================================================================ */

  .prism-diff-view__viewport {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    background: var(--prism-surface-1, #fcfcfb);
  }

  .prism-diff-view__viewport[hidden] {
    display: none;
  }

  .prism-diff-view__canvas {
    position: relative;
    width: 100%;
  }

  .prism-diff-view__band {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    will-change: transform;
  }

  /* Monospace by default, because character-level alignment between two panes
     is what a diff is for. Prose is a different job: a host comparing written
     paragraphs sets one property and gets its own reading face.
       .my-panel .prism-diff-view { --prism-diff-font-family: var(--prism-font-sans); } */
  .prism-diff-view__row {
    display: flex;
    width: 100%;
    height: var(--prism-diff-row-height, 20px);
    font-family: var(--prism-diff-font-family, var(--prism-font-mono, ui-monospace, Consolas, monospace));
    font-size: var(--prism-diff-font-size, var(--prism-text-sm, 12px));
    line-height: var(--prism-diff-row-height, 20px);
  }

  .prism-diff-view__side {
    display: flex;
    flex: 1 1 50%;
    min-width: 0;
    overflow: hidden;
  }

  /* The pane divider. A border rather than a gap, so the two halves read as
     one document rather than two panels. */
  .prism-diff-view__side[data-side="right"] {
    border-left: 1px solid var(--prism-border-hairline, #e1e0d9);
  }

  .prism-diff-view[data-view="unified"] .prism-diff-view__side {
    flex: 1 1 100%;
  }

  .prism-diff-view__gutter {
    flex: none;
    box-sizing: content-box;
    width: 4ch;
    padding: 0 var(--prism-space-2, 4px);
    text-align: right;
    color: var(--prism-ink-muted, #898781);
    font-variant-numeric: var(--prism-figures-tabular, tabular-nums);
    background: var(--prism-surface-2, #f0efec);
    border-right: 1px solid var(--prism-border-hairline, #e1e0d9);
    user-select: none;
  }

  /* The glyph that carries the meaning when the tint cannot: a literal + or −,
     so a colourblind reader, a printed page and Windows high contrast all still
     say which side a line is on. */
  .prism-diff-view__marker {
    flex: none;
    width: 2ch;
    text-align: center;
    color: var(--prism-ink-secondary, #52514e);
    user-select: none;
  }

  .prism-diff-view__text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    padding-right: var(--prism-space-3, 8px);
  }

  /* Translated rather than scrolled: the horizontal offset comes from the
     sticky strips below, one per pane. */
  .prism-diff-view__code {
    display: inline-block;
    white-space: pre;
    tab-size: 4;
  }

  .prism-diff-view__side[data-side="left"] .prism-diff-view__code {
    transform: translateX(calc(-1 * var(--prism-diff-pan-left, 0px)));
  }

  .prism-diff-view__side[data-side="right"] .prism-diff-view__code {
    transform: translateX(calc(-1 * var(--prism-diff-pan-right, 0px)));
  }

  /* ================================================================ *
   * Change tints
   * ================================================================ */

  .prism-diff-view__side--add {
    background: var(--prism-diff-add-bg, #e6f4e6);
  }

  .prism-diff-view__side--del {
    background: var(--prism-diff-del-bg, #fbe9e9);
  }

  .prism-diff-view__side--ours {
    background: var(--prism-diff-conflict-ours-bg, #e3eefc);
  }

  .prism-diff-view__side--theirs {
    background: var(--prism-diff-conflict-theirs-bg, #ece7fa);
  }

  .prism-diff-view__side--base {
    background: var(--prism-surface-sunken, #f0efec);
    color: var(--prism-ink-secondary, #52514e);
  }

  /* A filler exists so the two panes line up. It is aria-hidden in the DOM and
     visually recessive here — never a tint that could read as a change. */
  .prism-diff-view__side--filler {
    background: var(--prism-surface-sunken, #f0efec);
  }

  .prism-diff-view__side--filler .prism-diff-view__gutter,
  .prism-diff-view__side--filler .prism-diff-view__marker {
    background: transparent;
    border-right-color: transparent;
  }

  /* Word-level emphasis, from the intraline feature. Two steps of the same hue
     as the row tint, so it reads as "more of this change" rather than as a
     different kind of one. */
  .prism-diff-view__seg--emphasis {
    border-radius: var(--prism-radius-sm, 3px);
  }

  /* "Heading 2 → Heading 3". The chip a host's `lineNote` produces, for the
     change that is invisible once the markup is rendered. Inline after the
     text rather than floated over it, so it survives wrapping and never
     collides with the chooser. */
  .prism-diff-view__note {
    display: inline-block;
    margin-left: var(--prism-space-3, 8px);
    padding: 0 var(--prism-space-2, 4px);
    border-radius: var(--prism-radius-sm, 3px);
    background: var(--prism-surface-2, #f0efec);
    color: var(--prism-ink-secondary, #52514e);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    font-size: var(--prism-text-xs, 11px);
    white-space: nowrap;
    vertical-align: baseline;
  }

  .prism-diff-view__side--add .prism-diff-view__seg--emphasis {
    background: var(--prism-diff-add-emphasis, #b7e3b7);
  }

  .prism-diff-view__side--del .prism-diff-view__seg--emphasis {
    background: var(--prism-diff-del-emphasis, #f5c2c2);
  }

  /* Where navigation last landed.
   *
   * A stripe down the edge, not a box round each row. The marker goes on every
   * row of the change — that is what makes it findable — so anything with four
   * sides draws a heavy rectangle per line and turns a five-line change into a
   * stack of boxes. A 2px rule on one edge abuts cleanly between rows and reads
   * as one region, which is what it is. It also stays out of the way of the
   * tints, which already mean something else. */
  .prism-diff-view__row.is-current-change {
    position: relative;
  }

  .prism-diff-view__row.is-current-change::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: var(--prism-z-sticky, 10);
    width: 2px;
    background: var(--prism-accent, #2a78d6);
  }

  /* ================================================================ *
   * Wrap path (SPEC §2.3)
   *
   * Soft wrapping breaks the fixed-row-height assumption the virtualizer
   * needs, so this path builds every row and hands the windowing to the
   * browser: `content-visibility: auto` keeps offscreen rows out of layout,
   * and `contain-intrinsic-size` is the height it assumes until one scrolls
   * into view.
   * ================================================================ */

  .prism-diff-view--wrap .prism-diff-view__band {
    position: static;
    transform: none;
  }

  .prism-diff-view--wrap .prism-diff-view__row {
    height: auto;
    min-height: var(--prism-diff-row-height, 20px);
    content-visibility: auto;
    contain-intrinsic-size: auto var(--prism-diff-row-height, 20px);
  }

  .prism-diff-view--wrap .prism-diff-view__code {
    display: block;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    transform: none;
  }

  .prism-diff-view--wrap .prism-diff-view__gutter,
  .prism-diff-view--wrap .prism-diff-view__marker {
    align-self: stretch;
  }

  /* ================================================================ *
   * Horizontal strips — one per pane, stuck to the bottom of the viewport
   * ================================================================ */

  .prism-diff-view__hbars {
    position: sticky;
    bottom: 0;
    z-index: var(--prism-z-sticky, 10);
    display: flex;
    width: 100%;
    background: var(--prism-surface-1, #fcfcfb);
  }

  .prism-diff-view__hbars[hidden] {
    display: none;
  }

  /* Given an explicit height, because a scrollbar taller than its container is
     a scrollbar nobody can grab: the strip's only content is a 1px-tall track,
     so without this it collapses and the affordance disappears. */
  .prism-diff-view__hbar {
    flex: 1 1 50%;
    min-width: 0;
    height: var(--prism-space-4, 12px);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: var(--prism-border-strong, #c3c2b7) transparent;
  }

  .prism-diff-view__hbar::-webkit-scrollbar {
    height: var(--prism-space-4, 12px);
  }

  .prism-diff-view__hbar::-webkit-scrollbar-track {
    background: transparent;
  }

  .prism-diff-view__hbar::-webkit-scrollbar-thumb {
    border: 3px solid transparent;
    border-radius: var(--prism-radius-pill, 999px);
    background: var(--prism-border-strong, #c3c2b7);
    background-clip: content-box;
  }

  .prism-diff-view[data-view="unified"] .prism-diff-view__hbar[data-side="right"] {
    display: none;
  }

  .prism-diff-view[data-view="unified"] .prism-diff-view__hbar[data-side="left"] {
    flex: 1 1 100%;
  }

  .prism-diff-view__hbar-track {
    height: 1px;
  }

  /* ================================================================ *
   * Conflict blocks
   * ================================================================ */

  .prism-diff-view__row--conflict-header {
    align-items: center;
    gap: var(--prism-space-3, 8px);
    height: auto;
    min-height: var(--prism-control-height-md, 30px);
    padding: var(--prism-space-1, 2px) var(--prism-space-3, 8px);
    background: var(--prism-surface-2, #f0efec);
    border-top: 2px solid var(--prism-diff-conflict-border, #c07a10);
    border-bottom: 1px solid var(--prism-border-hairline, #e1e0d9);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    font-size: var(--prism-text-sm, 12px);
  }

  .prism-diff-view__conflict-title {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--prism-space-2, 4px);
    min-width: 0;
    font-weight: var(--prism-weight-medium, 500);
  }

  .prism-diff-view__conflict-badge {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: var(--prism-space-5, 16px);
    height: var(--prism-space-5, 16px);
    border-radius: var(--prism-radius-pill, 999px);
    background: var(--prism-diff-conflict-border, #c07a10);
    color: var(--prism-ink-on-accent, #ffffff);
    font-weight: var(--prism-weight-bold, 600);
  }

  .prism-diff-view__conflict-controls {
    display: flex;
    flex: none;
    gap: var(--prism-space-2, 4px);
  }

  .prism-diff-view__row--resolved {
    background: var(--prism-surface-2, #f0efec);
  }

  /* ================================================================ *
   * Accept-changes feature — keep / discard, per change
   *
   * The buttons float over the change's first row rather than occupying a row
   * of their own: a control row per change would move every line down and turn
   * a 195-change diff into a form. They are invisible until the pointer is on
   * the change, focus is inside them, the change is the one navigation landed
   * on, or a decision has been made — so a diff being *read* looks like a diff.
   * ================================================================ */

  .prism-diff-view__row.has-hunk-actions {
    position: relative;
  }

  /* No card, no shadow, no padding of its own: a panel behind the buttons
     punches a neutral hole through the row's tint, which reads as the diff
     being pushed out of the way. The buttons sit *on* the text — each one
     carries only as much background as it needs to stay legible. */
  .prism-diff-view__hunk-actions {
    position: absolute;
    top: 50%;
    right: var(--prism-space-3, 8px);
    z-index: var(--prism-z-dropdown, 100);
    display: flex;
    gap: var(--prism-space-1, 2px);
    transform: translateY(-50%);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--prism-duration-fast, 100ms) var(--prism-ease, ease);
  }

  /* Deliberately lighter than `prism-button--sm`: this control appears over
     content the reader is in the middle of reading, so it stays quiet until it
     is being used. Still 24px tall — under that is below the minimum touch
     target, and "discreet" is not worth a control a thumb cannot hit. */
  .prism-diff-view__choice {
    height: var(--prism-control-height-sm, 24px);
    padding: 0 var(--prism-space-3, 8px);
    border: 1px solid var(--prism-border, rgba(11, 11, 11, 0.1));
    border-radius: var(--prism-radius-sm, 3px);
    background: var(--prism-surface-1, #fcfcfb);
    color: var(--prism-ink-secondary, #52514e);
    font-family: inherit;
    font-size: var(--prism-text-xs, 11px);
    line-height: 1;
    cursor: pointer;
  }

  .prism-diff-view__choice:hover {
    border-color: var(--prism-border-strong, #c3c2b7);
    color: var(--prism-ink-primary, #0b0b0b);
  }

  .prism-diff-view__choice:focus-visible {
    outline: var(--prism-focus-ring-width, 2px) solid var(--prism-focus-ring, #2a78d6);
    outline-offset: var(--prism-focus-ring-offset, 1px);
  }

  .prism-diff-view__choice[aria-pressed="true"] {
    border-color: var(--prism-accent, #2a78d6);
    background: var(--prism-accent-subtle, #cde2fb);
    color: var(--prism-ink-primary, #0b0b0b);
    font-weight: var(--prism-weight-medium, 500);
  }

  /* The second click of a two-step destructive choice. It is the one control
     here that is allowed to shout. */
  .prism-diff-view__choice.is-confirming {
    border-color: var(--prism-status-critical, #d03b3b);
    background: var(--prism-status-critical, #d03b3b);
    color: var(--prism-ink-on-accent, #ffffff);
    font-weight: var(--prism-weight-medium, 500);
  }

  .prism-diff-view__hunk-actions.is-hovered,
  .prism-diff-view__hunk-actions.is-decided,
  .prism-diff-view__hunk-actions:focus-within,
  .prism-diff-view__row.is-current-change .prism-diff-view__hunk-actions {
    opacity: 1;
    pointer-events: auto;
  }

  /* A decided change keeps both sides on screen — the decision has to be
     reviewable and reversible — but the side that lost recedes. The pressed
     button is what actually carries the state, in `aria-pressed`, so this is
     reinforcement rather than the only signal. */
  .prism-diff-view__row.is-chosen-left .prism-diff-view__side--add,
  .prism-diff-view__row.is-chosen-right .prism-diff-view__side--del {
    opacity: 0.45;
    background: var(--prism-surface-sunken, #f0efec);
  }

  /* The kept side needs no mark of its own: the rejected one receding is the
     contrast, and the pressed button is what actually states the decision in
     words. A second accent bar here would also sit exactly where the
     current-change stripe does, and two meanings on one edge is one too many. */

  /* The narrow chooser. Below ~560px a line of prose wraps to a dozen display
     lines, so there is no "beside the text" left to hang a control off — and a
     24px overlay button is not a touch target. The choices move to a bar under
     the diff, targeting whichever change was last tapped or navigated to, with
     the counter between them: the destructive choice is never under a thumb
     aiming for the safe one. */
  .prism-diff-view--narrow-chooser .prism-diff-view__hunk-actions {
    display: none;
  }

  .prism-diff-view__change-bar {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: space-between;
    gap: var(--prism-space-3, 8px);
    padding: var(--prism-space-2, 4px) var(--prism-space-3, 8px);
    border-top: 1px solid var(--prism-border-strong, #c3c2b7);
    background: var(--prism-surface-2, #f0efec);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    font-size: var(--prism-text-sm, 12px);
  }

  .prism-diff-view__change-bar .prism-diff-view__choice {
    /* 44px, not 24px: this one is aimed at with a thumb. */
    min-width: var(--prism-space-8, 48px);
    height: 44px;
    padding: 0 var(--prism-space-5, 16px);
    font-size: var(--prism-text-md, 13px);
  }

  .prism-diff-view__change-bar-count {
    color: var(--prism-ink-secondary, #52514e);
    font-variant-numeric: var(--prism-figures-tabular, tabular-nums);
  }

  /* ================================================================ *
   * Collapse feature — the "214 unchanged lines" expander
   * ================================================================ */

  .prism-diff-view__row--expander {
    align-items: center;
    justify-content: flex-start;
    gap: var(--prism-space-3, 8px);
    padding: 0 var(--prism-space-3, 8px);
    background: var(--prism-surface-sunken, #f0efec);
    border-top: 1px solid var(--prism-border-hairline, #e1e0d9);
    border-bottom: 1px solid var(--prism-border-hairline, #e1e0d9);
    font-family: var(--prism-font-sans, system-ui, sans-serif);
    font-size: var(--prism-text-sm, 12px);
    color: var(--prism-ink-secondary, #52514e);
  }

  .prism-diff-view__expander {
    font-family: var(--prism-font-sans, system-ui, sans-serif);
  }

  /* ================================================================ *
   * States and notices
   * ================================================================ */

  .prism-diff-view__state {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 0;
    padding: var(--prism-space-6, 24px);
  }

  .prism-diff-view__state[hidden] {
    display: none;
  }

  .prism-diff-view__skeleton {
    display: flex;
    flex-direction: column;
    gap: var(--prism-space-2, 4px);
    width: 100%;
    margin-top: var(--prism-space-4, 12px);
  }

  .prism-diff-view__skeleton-line {
    height: var(--prism-diff-row-height, 20px);
    border-radius: var(--prism-radius-sm, 3px);
    background: var(--prism-surface-sunken, #f0efec);
  }

  .prism-diff-view__status {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--prism-space-4, 12px);
    padding: 0 var(--prism-space-3, 8px);
    background: var(--prism-surface-2, #f0efec);
    font-size: var(--prism-text-sm, 12px);
  }

  .prism-diff-view__status:empty {
    display: none;
  }

  .prism-diff-view__notice {
    display: flex;
    gap: var(--prism-space-2, 4px);
    margin: var(--prism-space-2, 4px) 0;
  }

  .prism-diff-view__notice-title {
    font-weight: var(--prism-weight-medium, 500);
    color: var(--prism-ink-secondary, #52514e);
  }

  .prism-diff-view__notice-detail {
    color: var(--prism-ink-muted, #898781);
  }

  /* ================================================================ *
   * The probe — a zero-width element whose height IS the line height, so JS
   * measures what the tokens actually resolved to rather than assuming.
   * ================================================================ */

  .prism-diff-view__probe {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: var(--prism-diff-line-height, 20px);
    visibility: hidden;
    pointer-events: none;
  }

  /* ================================================================ *
   * Windows high contrast: every tint above is dropped by the OS, so the
   * structure has to survive without them. The glyph column and a border per
   * changed side are what carry the design there.
   * ================================================================ */

  @media (forced-colors: active) {
    .prism-diff-view__side--add,
    .prism-diff-view__side--del,
    .prism-diff-view__side--ours,
    .prism-diff-view__side--theirs {
      border-inline-start: 3px solid CanvasText;
    }

    .prism-diff-view__seg--emphasis {
      outline: 1px solid CanvasText;
      forced-color-adjust: none;
    }

    .prism-diff-view__row.is-current-change::before {
      background: Highlight;
      forced-color-adjust: none;
    }

    /* Opacity survives forced colours but reads as "faint", not as "rejected".
       The border is what says it there. */
    .prism-diff-view__row.is-chosen-left .prism-diff-view__side--add,
    .prism-diff-view__row.is-chosen-right .prism-diff-view__side--del {
      opacity: 1;
      border-inline-start-style: dotted;
    }
  }
}
