RichRender Development Source Code Library


Current Files:

Rich Render Core - JS:
 Show Source Code
/**
 * RichRender Core
 * Shared constants, element tables, pip utilities, and stream builder.
 */

var RR = (function () {

  // ─── Control characters ──────────────────────────────────────────────────
  var SOH = 0x01;         // Block element OPEN
  var STX = 0x02;         // Style span OPEN
  var ETX = 0x03;         // Style span CLOSE
  var EOT = 0x04;         // Block element CLOSE
  var SO  = 0x0E;         // Inline element OPEN
  var SI  = 0x0F;         // Inline element CLOSE
  var META_OPEN  = 0xFE;  // Meta region OPEN
  var META_CLOSE = 0xFF;  // Meta region CLOSE

  // Meta region types
  var META_ATTR    = 0x00;
  var META_COMMENT = 0x01;

  // Set of all RichRender control bytes (for fast lookup)
  var CONTROL = { 0x01:1, 0x02:1, 0x03:1, 0x04:1, 0x0E:1, 0x0F:1, 0xFE:1, 0xFF:1 };

  // ═══════════════════════════════════════════════════════════════════════════
  // STYLE-PIP BYTE ONE — flags (spec-defined, fixed)
  // ═══════════════════════════════════════════════════════════════════════════
  //
  //   bit 7   continuation    1 = byte two follows
  //   bit 6   variant-hi      ┐ 00=normal, 01=super, 10=sub, 11=highlight
  //   bit 5   variant-lo      ┘
  //   bit 4   underline
  //   bit 3   strikethrough
  //   bit 2   italic
  //   bit 1   bold
  //   bit 0   reserved        must be 0

  var PIP_BOLD          = 0x02;  // bit 1
  var PIP_ITALIC        = 0x04;  // bit 2
  var PIP_STRIKETHROUGH = 0x08;  // bit 3
  var PIP_UNDERLINE     = 0x10;  // bit 4
  var PIP_VARIANT_MASK  = 0x60;  // bits 6–5
  var PIP_SUPER         = 0x20;  // bits 6–5 = 01
  var PIP_SUB           = 0x40;  // bits 6–5 = 10
  var PIP_HIGHLIGHT     = 0x60;  // bits 6–5 = 11  (default yellow, byte two can refine)
  var PIP_CONTINUATION  = 0x80;  // bit 7

  // All style bits (excluding continuation)
  var PIP_STYLE_MASK = PIP_BOLD | PIP_ITALIC | PIP_STRIKETHROUGH | PIP_UNDERLINE | PIP_VARIANT_MASK;

  // Legacy alias
  var PIP_VERT_MASK = PIP_VARIANT_MASK;

  // ═══════════════════════════════════════════════════════════════════════════
  // STYLE-PIP BYTE TWO — color and font (spec-defined, fixed)
  // ═══════════════════════════════════════════════════════════════════════════
  //
  //   bit 7   continuation    1 = byte three follows
  //   bit 6   bg-hi           ┐ 00=none, 01=yellow, 10=green, 11=pink
  //   bit 5   bg-lo           ┘
  //   bit 4   fg-3 (high)     ┐
  //   bit 3   fg-2            │ foreground color index 0–15
  //   bit 2   fg-1            │
  //   bit 1   fg-0 (low)      ┘
  //   bit 0   font            0=serif, 1=sans-serif

  var P2_CONTINUATION = 0x80;   // bit 7
  var P2_BG_MASK      = 0x60;   // bits 6–5
  var P2_BG_NONE      = 0x00;   // 00
  var P2_BG_YELLOW    = 0x20;   // 01
  var P2_BG_GREEN     = 0x40;   // 10
  var P2_BG_PINK      = 0x60;   // 11
  var P2_FG_MASK      = 0x1E;   // bits 4–1
  var P2_FG_SHIFT     = 1;      // shift right 1 to get index 0–15
  var P2_FONT_BIT     = 0x01;   // bit 0: 0=serif, 1=sans-serif

  // ─── Default foreground palette (spec-defined, no preamble needed) ───────
  var DEFAULT_FG = [
    '',        // 0  default/inherit
    '#000000', // 1  black
    '#555555', // 2  dark gray
    '#CC0000', // 3  red
    '#800000', // 4  maroon
    '#DD6600', // 5  orange
    '#008800', // 6  green
    '#005500', // 7  dark green
    '#0066CC', // 8  blue
    '#003366', // 9  navy
    '#7700AA', // 10 purple
    '#008888', // 11 teal
    '#885500', // 12 brown
    '#AAAAAA', // 13 light gray
    '#FFFFFF', // 14 white
    '#CCAA00', // 15 gold
  ];

  // ─── Default background palette (spec-defined) ──────────────────────────
  var DEFAULT_BG = [
    '',        // 0  none/transparent
    '#FFFF00', // 1  yellow highlight
    '#90EE90', // 2  green highlight
    '#FFB6C1', // 3  pink highlight
  ];

  // ═══════════════════════════════════════════════════════════════════════════
  // STYLE-PIP BYTE THREE — size and typographic extras (spec-defined, fixed)
  // ═══════════════════════════════════════════════════════════════════════════
  //
  //   bit 7   continuation    1 = byte four follows (preamble-defined)
  //   bit 6   size-hi         ┐
  //   bit 5   size-mid        │ font size index 0–7
  //   bit 4   size-lo         ┘
  //   bit 3   reserved
  //   bit 2   double underline
  //   bit 1   overline
  //   bit 0   small-caps

  var P3_CONTINUATION    = 0x80;  // bit 7
  var P3_SIZE_MASK       = 0x70;  // bits 6–4
  var P3_SIZE_SHIFT      = 4;     // shift right 4 to get index 0–7
  var P3_RESERVED        = 0x08;  // bit 3
  var P3_DOUBLE_UNDERLINE = 0x04; // bit 2
  var P3_OVERLINE        = 0x02;  // bit 1
  var P3_SMALLCAPS       = 0x01;  // bit 0

  // ─── Default size table (spec-defined, no preamble needed) ──────────────
  // Index 0 = default/inherit. Indices 1–7 = largest to smallest.
  var DEFAULT_SIZES = [
    '',       // 0  default/inherit (1em)
    '2em',    // 1  ~h1
    '1.5em',  // 2  ~h2
    '1.17em', // 3  ~h3
    '1em',    // 4  ~h4 (explicit body)
    '0.85em', // 5  small / caption
    '0.75em', // 6  x-small / fine print
    '0.6em',  // 7  xx-small / legal
  ];

  // ─── Heading detection — three-byte pattern lookup ──────────────────────
  // The format has no heading concept; these are recognized style combos.
  // size index + bold → heading level (inflator emits <h1>–<h6>)
  var HEADING_PATTERNS = {
    1: 'h1',  // size 1 + bold
    2: 'h2',  // size 2 + bold
    3: 'h3',  // size 3 + bold
    4: 'h4',  // size 4 + bold
  };

  // ─── Lite-Wire default element sets ───────────────────────────────────────
  // These elements work out of the box with no preamble, like the color palette.

  var LITEWIRE_BLOCKS = {
    9:1, 10:1, 11:1, 12:1, 13:1, 14:1,  // h1–h6
    15:1,   // p
    17:1,   // blockquote
    18:1,   // ul
    19:1,   // ol
    20:1,   // li
    40:1,   // hr
  };

  var LITEWIRE_INLINES = {
    1:1,    // code
    15:1,   // a
  };

  // Void (self-closing) block elements — no </tag> on EOT
  var VOID_BLOCKS = { hr:1 };

  // ─── Block element indices (Section 5.1) ─────────────────────────────────
  var BLOCK = {
    div:1, section:2, article:3, aside:4, nav:5,
    header:6, footer:7, main:8,
    h1:9, h2:10, h3:11, h4:12, h5:13, h6:14,
    p:15, pre:16, blockquote:17,
    ul:18, ol:19, li:20,
    dl:21, dt:22, dd:23,
    table:24, thead:25, tbody:26, tfoot:27, tr:28,
    th:29, td:30, caption:31, colgroup:32, col:33,
    figure:34, figcaption:35, details:36, summary:37,
    fieldset:38, legend:39,
    hr:40, form:41, dialog:42, pagebreak:43
  };

  // ─── Inline element indices (Section 5.2) ────────────────────────────────
  var INLINE = {
    code:1, kbd:2, mark:3, abbr:4, cite:5,
    samp:6, 'var':7, ins:8, del:9, dfn:10,
    time:11, span:12, em:13, strong:14, a:15,
    q:16, bdi:17, bdo:18, data:19,
    ruby:20, rt:21, rp:22, wbr:23,
    output:24, meter:25, progress:26
  };

  // Reverse lookup
  var BLOCK_NAMES = {};
  for (var k in BLOCK) BLOCK_NAMES[BLOCK[k]] = k;
  var INLINE_NAMES = {};
  for (var k in INLINE) INLINE_NAMES[INLINE[k]] = k;

  // ─── RichStream — byte stream builder ────────────────────────────────────

  function RichStream() { this.bytes = []; }

  RichStream.prototype.write = function (b) {
    if (typeof b === 'number') this.bytes.push(b & 0xFF);
    else if (b instanceof Uint8Array) for (var i = 0; i < b.length; i++) this.bytes.push(b[i]);
    return this;
  };

  RichStream.prototype.writeText = function (str) {
    var enc = new TextEncoder().encode(str);
    for (var i = 0; i < enc.length; i++) this.bytes.push(enc[i]);
    return this;
  };

  RichStream.prototype.openBlock = function (idx) { return this.write(SOH).write(idx); };
  RichStream.prototype.closeBlock = function ()    { return this.write(EOT); };
  RichStream.prototype.openInline = function (idx) { return this.write(SO).write(idx); };
  RichStream.prototype.closeInline = function ()   { return this.write(SI); };

  RichStream.prototype.openStyle = function (pip)  { return this.write(STX).write(pip); };
  RichStream.prototype.closeStyle = function (clearMask) {
    this.write(ETX);
    if (clearMask !== undefined) this.write(clearMask);
    return this;
  };

  RichStream.prototype.meta = function (type, content) {
    return this.write(META_OPEN).write(type).writeText(content).write(META_CLOSE);
  };

  RichStream.prototype.toBytes = function () { return new Uint8Array(this.bytes); };

  // ─── Pip description (for debugging) ─────────────────────────────────────

  function describePip(b, b2, b3) {
    if (b === 0 && !b2 && !b3) return 'plain';
    var parts = [];
    if (b & PIP_BOLD)          parts.push('bold');
    if (b & PIP_ITALIC)        parts.push('italic');
    if (b & PIP_STRIKETHROUGH) parts.push('strike');
    if (b & PIP_UNDERLINE)     parts.push('underline');
    var vert = b & PIP_VERT_MASK;
    if (vert === PIP_SUPER)     parts.push('super');
    else if (vert === PIP_SUB)  parts.push('sub');
    else if (vert === PIP_HIGHLIGHT) parts.push('highlight');
    if (b & PIP_CONTINUATION)  parts.push('+cont');
    if (b2) {
      var fgIdx = (b2 & P2_FG_MASK) >> P2_FG_SHIFT;
      if (fgIdx) parts.push('fg:' + fgIdx);
      var bgIdx = (b2 & P2_BG_MASK) >> 5;
      if (bgIdx) parts.push('bg:' + ['','yellow','green','pink'][bgIdx]);
      if (b2 & P2_FONT_BIT) parts.push('sans');
      if (b2 & P2_CONTINUATION) parts.push('+cont2');
    }
    if (b3) {
      var sizeIdx = (b3 & P3_SIZE_MASK) >> P3_SIZE_SHIFT;
      if (sizeIdx) parts.push('size:' + sizeIdx);
      if (b3 & P3_SMALLCAPS) parts.push('small-caps');
      if (b3 & P3_OVERLINE) parts.push('overline');
      if (b3 & P3_DOUBLE_UNDERLINE) parts.push('dbl-underline');
      if (b3 & P3_CONTINUATION) parts.push('+cont3');
    }
    return parts.join('+') || 'plain';
  }

  // ─── Hex dump ────────────────────────────────────────────────────────────

  function hexDump(bytes, perLine) {
    perLine = perLine || 16;
    var lines = [];
    for (var i = 0; i < bytes.length; i += perLine) {
      var hex = [], ascii = '';
      for (var j = 0; j < perLine; j++) {
        if (i + j < bytes.length) {
          var b = bytes[i + j];
          hex.push(b.toString(16).padStart(2, '0'));
          ascii += (b >= 0x20 && b < 0x7F) ? String.fromCharCode(b) : '.';
        } else { hex.push('  '); ascii += ' '; }
      }
      lines.push(i.toString(16).padStart(6, '0') + '  ' +
        hex.slice(0,8).join(' ') + '  ' + hex.slice(8).join(' ') + '  |' + ascii + '|');
    }
    return lines.join('\n');
  }

  // ─── Disassembler — annotated view of a byte stream ──────────────────────

  function disassemble(bytes) {
    var out = [], pos = 0, indent = 0;
    var decoder = new TextDecoder('utf-8', { fatal: false });
    function pad() { return '  '.repeat(indent); }
    function hex(b) { return '0x' + b.toString(16).padStart(2, '0'); }

    while (pos < bytes.length) {
      var b = bytes[pos];

      if (b === SOH) {
        var idx = bytes[++pos];
        out.push(pad() + 'SOH [' + (BLOCK_NAMES[idx] || idx) + ']');
        indent++;
        pos++;
      } else if (b === EOT) {
        indent = Math.max(0, indent - 1);
        out.push(pad() + 'EOT');
        pos++;
      } else if (b === STX) {
        var pip = bytes[++pos]; pos++;
        var pip2 = 0, pip3 = 0;
        if (pip & PIP_CONTINUATION) { pip2 = bytes[pos++]; }
        if (pip2 & P2_CONTINUATION) { pip3 = bytes[pos++]; }
        var hexStr = hex(pip) + (pip2 ? ' ' + hex(pip2) : '') + (pip3 ? ' ' + hex(pip3) : '');
        out.push(pad() + 'STX [' + describePip(pip, pip2, pip3) + ' ' + hexStr + ']');
        indent++;
      } else if (b === ETX) {
        indent = Math.max(0, indent - 1);
        pos++;  // consume ETX
        // Read clear mask pip (always follows ETX)
        if (pos < bytes.length) {
          var cPip = bytes[pos++];
          var cPip2 = 0, cPip3 = 0;
          if ((cPip & PIP_CONTINUATION) && pos < bytes.length) { cPip2 = bytes[pos++]; }
          if ((cPip2 & P2_CONTINUATION) && pos < bytes.length) { cPip3 = bytes[pos++]; }
          if (cPip === 0) {
            out.push(pad() + 'ETX [clear all]');
          } else {
            var cHex = hex(cPip) + (cPip2 ? ' ' + hex(cPip2) : '') + (cPip3 ? ' ' + hex(cPip3) : '');
            out.push(pad() + 'ETX [clear ' + describePip(cPip, cPip2, cPip3) + ' ' + cHex + ']');
          }
        } else {
          out.push(pad() + 'ETX');
        }
      } else if (b === SO) {
        var idx = bytes[++pos];
        out.push(pad() + 'SO  [' + (INLINE_NAMES[idx] || idx) + ']');
        indent++;
        pos++;
      } else if (b === SI) {
        indent = Math.max(0, indent - 1);
        out.push(pad() + 'SI');
        pos++;
      } else if (b === META_OPEN) {
        var type = bytes[++pos];
        pos++;
        var metaBytes = [];
        while (pos < bytes.length && bytes[pos] !== META_CLOSE) {
          metaBytes.push(bytes[pos++]);
        }
        var metaText = decoder.decode(new Uint8Array(metaBytes));
        var typeName = type === META_ATTR ? 'attr' : type === META_COMMENT ? 'comment' : hex(type);
        out.push(pad() + 'META [' + typeName + '] ' + JSON.stringify(metaText));
        if (pos < bytes.length) pos++; // skip META_CLOSE
      } else {
        // Text content — accumulate until we hit a control byte
        var textBytes = [];
        while (pos < bytes.length && !CONTROL[bytes[pos]] && bytes[pos] >= 0x09) {
          textBytes.push(bytes[pos++]);
        }
        if (textBytes.length > 0) {
          var text = decoder.decode(new Uint8Array(textBytes));
          out.push(pad() + text.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r'));
        } else {
          // Unknown byte
          out.push(pad() + '??? ' + hex(b));
          pos++;
        }
      }
    }
    return out.join('\n');
  }

  // ─── Public API ──────────────────────────────────────────────────────────

  return {
    SOH: SOH, STX: STX, ETX: ETX, EOT: EOT, SO: SO, SI: SI,
    META_OPEN: META_OPEN, META_CLOSE: META_CLOSE,
    META_ATTR: META_ATTR, META_COMMENT: META_COMMENT,
    CONTROL: CONTROL,
    PIP_BOLD: PIP_BOLD, PIP_ITALIC: PIP_ITALIC,
    PIP_STRIKETHROUGH: PIP_STRIKETHROUGH, PIP_UNDERLINE: PIP_UNDERLINE,
    PIP_VARIANT_MASK: PIP_VARIANT_MASK,
    PIP_VERT_MASK: PIP_VERT_MASK, PIP_SUPER: PIP_SUPER, PIP_SUB: PIP_SUB,
    PIP_HIGHLIGHT: PIP_HIGHLIGHT, PIP_CONTINUATION: PIP_CONTINUATION,
    PIP_STYLE_MASK: PIP_STYLE_MASK,
    P2_CONTINUATION: P2_CONTINUATION,
    P2_BG_MASK: P2_BG_MASK, P2_BG_NONE: P2_BG_NONE,
    P2_BG_YELLOW: P2_BG_YELLOW, P2_BG_GREEN: P2_BG_GREEN, P2_BG_PINK: P2_BG_PINK,
    P2_FG_MASK: P2_FG_MASK, P2_FG_SHIFT: P2_FG_SHIFT,
    P2_FONT_BIT: P2_FONT_BIT,
    DEFAULT_FG: DEFAULT_FG, DEFAULT_BG: DEFAULT_BG,
    P3_CONTINUATION: P3_CONTINUATION,
    P3_SIZE_MASK: P3_SIZE_MASK, P3_SIZE_SHIFT: P3_SIZE_SHIFT,
    P3_DOUBLE_UNDERLINE: P3_DOUBLE_UNDERLINE,
    P3_OVERLINE: P3_OVERLINE, P3_SMALLCAPS: P3_SMALLCAPS,
    DEFAULT_SIZES: DEFAULT_SIZES, HEADING_PATTERNS: HEADING_PATTERNS,
    LITEWIRE_BLOCKS: LITEWIRE_BLOCKS, LITEWIRE_INLINES: LITEWIRE_INLINES,
    VOID_BLOCKS: VOID_BLOCKS,
    BLOCK: BLOCK, INLINE: INLINE,
    BLOCK_NAMES: BLOCK_NAMES, INLINE_NAMES: INLINE_NAMES,
    RichStream: RichStream,
    describePip: describePip, hexDump: hexDump, disassemble: disassemble
  };
})();

if (typeof module !== 'undefined') module.exports = RR;
Rich Render Converter - JS:
 Show Source Code
/**
 * RichRender Format Converters
 *
 * Wire/Lite-Wire/Bit-Wise ↔ Editor format conversions.
 *
 * wireToEditor(bytes)  — expand byte stream into per-character pip array
 * editorToWire(doc)    — compress editor doc into byte stream (run-length)
 * editorToInflate(doc) — shortcut: editor → wire → HTML via rrInflate
 */

var RRConvert = (function () {

  var decoder = new TextDecoder('utf-8', { fatal: false });

  // ═══════════════════════════════════════════════════════════════════════
  // WIRE → EDITOR
  // Walk byte stream, track current pip state, expand text into tuples.
  // Block/inline frames become marker characters (newlines for blocks).
  // ═══════════════════════════════════════════════════════════════════════

  function wireToEditor(bytes) {
    var pos = 0, len = bytes.length;
    var chars = [];
    var pip1 = 0, pip2 = 0, pip3 = 0;
    var blockStack = [];

    while (pos < len) {
      var b = bytes[pos];

      // ── Block open (SOH) ──
      if (b === RR.SOH) {
        pos++;
        var idx = bytes[pos++];
        var tag = RR.BLOCK_NAMES[idx] || 'div';
        // Skip meta attributes for now — store as marker
        if (pos < len && bytes[pos] === RR.META_OPEN) {
          pos++;  // META_OPEN
          var metaType = bytes[pos++];
          // Consume meta content
          while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
          if (pos < len) pos++;  // META_CLOSE
        }
        // Push block marker with tag info
        chars.push({ ch: '\n', p1: 0, p2: 0, p3: 0, block: tag, blockIdx: idx, blockOpen: true });
        blockStack.push(idx);
        continue;
      }

      // ── Block close (EOT) ──
      if (b === RR.EOT) {
        pos++;
        var idx = blockStack.pop();
        // Don't add a marker for void blocks or at end
        continue;
      }

      // ── Inline open (SO) ──
      if (b === RR.SO) {
        pos++;
        var idx = bytes[pos++];
        var tag = RR.INLINE_NAMES[idx] || 'span';
        var attrs = null;
        if (pos < len && bytes[pos] === RR.META_OPEN) {
          pos++;
          var metaType = bytes[pos++];
          if (metaType === RR.META_ATTR) {
            var metaBytes = [];
            while (pos < len && bytes[pos] !== RR.META_CLOSE) metaBytes.push(bytes[pos++]);
            if (pos < len) pos++;
            attrs = decoder.decode(new Uint8Array(metaBytes));
          } else {
            while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
            if (pos < len) pos++;
          }
        }
        // Store inline open as a zero-width marker
        chars.push({ ch: '', p1: pip1, p2: pip2, p3: pip3, inline: tag, inlineIdx: idx, inlineOpen: true, attrs: attrs });
        continue;
      }

      // ── Inline close (SI) ──
      if (b === RR.SI) {
        pos++;
        chars.push({ ch: '', p1: pip1, p2: pip2, p3: pip3, inlineClose: true });
        continue;
      }

      // ── Style open (STX) — sets full style ──
      if (b === RR.STX) {
        pos++;
        var p1 = bytes[pos++];
        var p2 = 0, p3 = 0;
        if (p1 & RR.PIP_CONTINUATION) p2 = bytes[pos++];
        if (p2 & RR.P2_CONTINUATION) p3 = bytes[pos++];
        pip1 = p1 & RR.PIP_STYLE_MASK;
        pip2 = p2 & ~RR.P2_CONTINUATION;
        pip3 = p3 & ~RR.P3_CONTINUATION;
        continue;
      }

      // ── Style close (ETX) — clear mask ──
      if (b === RR.ETX) {
        pos++;
        // Read clear mask pip (always follows ETX)
        if (pos < len) {
          var cm1 = bytes[pos++];
          var cm2 = 0, cm3 = 0;
          if (cm1 & RR.PIP_CONTINUATION && pos < len) { cm2 = bytes[pos++]; }
          if (cm2 & RR.P2_CONTINUATION && pos < len)  { cm3 = bytes[pos++]; }
          if (cm1 === 0) {
            // ETX [0x00] = clear all
            pip1 = 0; pip2 = 0; pip3 = 0;
          } else {
            pip1 &= ~(cm1 & RR.PIP_STYLE_MASK);
            if (cm2) pip2 &= ~(cm2 & ~RR.P2_CONTINUATION);
            if (cm3) pip3 &= ~(cm3 & ~RR.P3_CONTINUATION);
          }
        }
        continue;
      }

      // ── Meta region (standalone) ──
      if (b === RR.META_OPEN) {
        pos++;
        var metaType = bytes[pos++];
        while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
        if (pos < len) pos++;
        continue;
      }

      // ── Text content ──
      var start = pos;
      while (pos < len && !RR.CONTROL[bytes[pos]]) {
        pos++;
      }
      if (pos > start) {
        var text = decoder.decode(bytes.slice(start, pos));
        for (var i = 0; i < text.length; i++) {
          chars.push({ ch: text[i], p1: pip1, p2: pip2, p3: pip3 });
        }
      }
    }

    return new RREditor.EditorDoc(chars);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // EDITOR → WIRE
  // Walk character array, detect pip transitions, emit STX/ETX frames.
  // Block/inline markers emit SOH/EOT and SO/SI.
  // ═══════════════════════════════════════════════════════════════════════

  function editorToWire(doc) {
    var s = new RR.RichStream();
    var chars = doc.chars;

    // ── Style stack for nested spans ──
    // Each entry: { p1, p2, p3 } — the cumulative style at that nesting depth
    var styleStack = [];

    function curStyle() {
      return styleStack.length > 0 ? styleStack[styleStack.length - 1] : { p1: 0, p2: 0, p3: 0 };
    }

    function styleEqual(a, b) {
      return a.p1 === b.p1 && a.p2 === b.p2 && a.p3 === b.p3;
    }

    function writePipBytes(p1, p2, p3) {
      var outP1 = p1;
      if (p2) outP1 |= RR.PIP_CONTINUATION;
      s.write(outP1);
      if (p2) {
        var outP2 = p2;
        if (p3) outP2 |= RR.P2_CONTINUATION;
        s.write(outP2);
        if (p3) s.write(p3);
      }
    }

    function closeAll() {
      while (styleStack.length > 0) {
        var closing = styleStack.pop();
        s.write(RR.ETX);
        if (styleStack.length > 0) {
          // Clear mask = bits to remove (difference between closing and outer)
          var outer = styleStack[styleStack.length - 1];
          var cm1 = (closing.p1 & RR.PIP_STYLE_MASK) & ~(outer.p1 & RR.PIP_STYLE_MASK);
          var cm2 = closing.p2 & ~outer.p2;
          var cm3 = closing.p3 & ~outer.p3;
          writePipBytes(cm1, cm2, cm3);
        } else {
          // Closing last span — ETX [0x00] clear all
          s.write(0x00);
        }
      }
    }

    // Determine if target style is "additive" over current (pure superset
    // on byte-one flags, and any byte-two/three fields that change are
    // additions, not replacements of existing non-zero fields).
    function isAdditive(cur, tgt) {
      var curFlags = cur.p1 & RR.PIP_STYLE_MASK;
      var tgtFlags = tgt.p1 & RR.PIP_STYLE_MASK;
      // All current flags must still be set in target
      if ((tgtFlags & curFlags) !== curFlags) return false;
      // Must actually add something
      if (tgtFlags === curFlags && tgt.p2 === cur.p2 && tgt.p3 === cur.p3) return false;
      // Byte two: if current has fg/bg/font, target must keep them
      if (cur.p2) {
        var curFg = cur.p2 & RR.P2_FG_MASK, tgtFg = tgt.p2 & RR.P2_FG_MASK;
        var curBg = cur.p2 & RR.P2_BG_MASK, tgtBg = tgt.p2 & RR.P2_BG_MASK;
        var curFont = cur.p2 & RR.P2_FONT_BIT, tgtFont = tgt.p2 & RR.P2_FONT_BIT;
        if (curFg && tgtFg !== curFg) return false;
        if (curBg && tgtBg !== curBg) return false;
        if (curFont && tgtFont !== curFont) return false;
      }
      // Byte three: if current has size/extras, target must keep them
      if (cur.p3) {
        var curSz = cur.p3 & RR.P3_SIZE_MASK, tgtSz = tgt.p3 & RR.P3_SIZE_MASK;
        if (curSz && tgtSz !== curSz) return false;
        var curExtras = cur.p3 & 0x07, tgtExtras = tgt.p3 & 0x07;
        if ((tgtExtras & curExtras) !== curExtras) return false;
      }
      return true;
    }

    // Check if target is a subset — some outer stack entry matches it
    function findRevert(tgt) {
      for (var k = styleStack.length - 2; k >= 0; k--) {
        if (styleEqual(styleStack[k], tgt)) return k;
      }
      return -1;
    }

    function transitionTo(tgt) {
      var cur = curStyle();
      if (styleEqual(cur, tgt)) return;  // no change

      var tgtStyle = (tgt.p1 & RR.PIP_STYLE_MASK) | tgt.p2 | tgt.p3;

      // ── Revert to unstyled ──
      if (tgtStyle === 0) {
        closeAll();
        return;
      }

      // ── Currently unstyled → open flat ──
      if (styleStack.length === 0) {
        s.write(RR.STX);
        writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
        styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
        return;
      }

      // ── Additive: nest inside current ──
      if (isAdditive(cur, tgt)) {
        s.write(RR.STX);
        writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
        styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
        return;
      }

      // ── Revert: target matches an outer stack entry ──
      var revertIdx = findRevert(tgt);
      if (revertIdx >= 0) {
        // Close spans down to that level, each ETX with clear mask
        while (styleStack.length > revertIdx + 1) {
          var closing = styleStack.pop();
          s.write(RR.ETX);
          if (styleStack.length > 0) {
            var outer = styleStack[styleStack.length - 1];
            var cm1 = (closing.p1 & RR.PIP_STYLE_MASK) & ~(outer.p1 & RR.PIP_STYLE_MASK);
            var cm2 = closing.p2 & ~outer.p2;
            var cm3 = closing.p3 & ~outer.p3;
            writePipBytes(cm1, cm2, cm3);
          } else {
            s.write(0x00);
          }
        }
        return;
      }

      // ── Otherwise: close all, open fresh ──
      closeAll();
      s.write(RR.STX);
      writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
      styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
    }

    // ── Look-ahead: does the current style resume after whitespace? ──
    // Skips whitespace regardless of its own pip values —
    // spaces/tabs/newlines render the same styled or unstyled.
    function curStyleResumes(idx) {
      var cur = curStyle();
      var j = idx + 1;
      while (j < chars.length) {
        var cj = chars[j];
        if (cj.blockOpen || cj.inlineOpen || cj.inlineClose) return false;
        if (cj.ch !== ' ' && cj.ch !== '\n' && cj.ch !== '\t') break;
        j++;
      }
      if (j >= chars.length) return false;
      var cj = chars[j];
      if (cj.blockOpen || cj.inlineOpen || cj.inlineClose) return false;
      return cj.p1 === cur.p1 && cj.p2 === cur.p2 && cj.p3 === cur.p3;
    }

    function isWhitespace(ch) { return ch === ' ' || ch === '\n' || ch === '\t'; }

    for (var i = 0; i < chars.length; i++) {
      var c = chars[i];

      // ── Block marker ──
      if (c.blockOpen) {
        closeAll();
        s.openBlock(c.blockIdx);
        continue;
      }
      if (c.block && !c.blockOpen) {
        closeAll();
        s.closeBlock();
        continue;
      }

      // ── Inline marker ──
      if (c.inlineOpen) {
        s.openInline(c.inlineIdx);
        if (c.attrs) {
          s.meta(RR.META_ATTR, c.attrs);
        }
        continue;
      }
      if (c.inlineClose) {
        s.closeInline();
        continue;
      }

      // ── Block newline markers (no content) ──
      if (c.ch === '\n' && c.block) {
        continue;
      }

      // ── Style transition ──
      var charStyle = (c.p1 & RR.PIP_STYLE_MASK) | c.p2 | c.p3;

      if (charStyle !== 0) {
        // Styled whitespace inside a span: if its style matches the current
        // span or is unstyled, and the current style resumes, absorb it.
        if (isWhitespace(c.ch) && styleStack.length > 0) {
          var cur = curStyle();
          var matchesCur = c.p1 === cur.p1 && c.p2 === cur.p2 && c.p3 === cur.p3;
          if (matchesCur || curStyleResumes(i)) {
            s.writeText(c.ch);
            continue;
          }
        }
        transitionTo({ p1: c.p1, p2: c.p2, p3: c.p3 });
        s.writeText(c.ch);
      } else if (styleStack.length > 0 && isWhitespace(c.ch)) {
        // Unstyled whitespace inside a styled run: keep span open if same style resumes
        if (curStyleResumes(i)) {
          s.writeText(c.ch);
        } else {
          closeAll();
          s.writeText(c.ch);
        }
      } else {
        closeAll();
        s.writeText(c.ch);
      }
    }

    closeAll();
    return s.toBytes();
  }

  // ═══════════════════════════════════════════════════════════════════════
  // EDITOR → HTML (shortcut via inflate)
  // ═══════════════════════════════════════════════════════════════════════

  function editorToHtml(doc) {
    return rrInflate(editorToWire(doc));
  }

  // ── Public API ─────────────────────────────────────────────────────────

  return {
    wireToEditor: wireToEditor,
    editorToWire: editorToWire,
    editorToHtml: editorToHtml
  };

})();

if (typeof module !== 'undefined') module.exports = RRConvert;
Rich Render HTML Inflate - JS:
 Show Source Code
/**
 * RichRender Universal Inflator
 *
 * Reads any RichRender byte stream — Bit-Wise, Lite-Wire, or Wire Format —
 * and produces Render-HTML with mdr- classes for round-trip identification.
 *
 * Handles all control characters:
 *   STX/ETX  — style spans (bytes 1–3: flags, color/font, size/extras)
 *   SOH/EOT  — block elements (p, h1–h6, lists, blockquote, etc.)
 *   SO/SI    — inline elements (code, a, etc.)
 *   META     — attribute regions (href, etc.) and comments
 *
 * A Bit-Wise stream contains only STX/ETX — the block/inline handlers
 * simply never fire. One inflator, all tiers.
 */

var rrInflate = (function () {

  var decoder = new TextDecoder('utf-8', { fatal: false });

  function escHtml(t) {
    return t.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  function escAttr(t) {
    return t.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  // ── Pip reading ────────────────────────────────────────────────────────

  function readPip(bytes, state) {
    var p1 = bytes[state.pos++];
    var p2 = 0, p3 = 0;
    if (p1 & RR.PIP_CONTINUATION) { p2 = bytes[state.pos++]; }
    if (p2 & RR.P2_CONTINUATION)  { p3 = bytes[state.pos++]; }
    return { p1: p1, p2: p2, p3: p3 };
  }

  // ── Heading detection ──────────────────────────────────────────────────

  function detectHeading(pip, pip3) {
    if (!(pip & RR.PIP_BOLD)) return null;
    if (!pip3) return null;
    var sizeIdx = (pip3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT;
    return RR.HEADING_PATTERNS[sizeIdx] || null;
  }

  // ── Pip → HTML tags ────────────────────────────────────────────────────

  function pipToTags(pip, pip2, pip3) {
    var style = pip & RR.PIP_STYLE_MASK;
    var opens = [], closes = [];
    var vert = style & RR.PIP_VARIANT_MASK;
    var hasDoubleUnderline = pip3 && (pip3 & RR.P3_DOUBLE_UNDERLINE);

    // ── Heading pattern ──
    var heading = detectHeading(pip, pip3);
    if (heading) {
      var hCss = [];
      if (pip2) {
        var fgIdx = (pip2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT;
        if (fgIdx > 0 && RR.DEFAULT_FG[fgIdx]) hCss.push('color:' + RR.DEFAULT_FG[fgIdx]);
        if (pip2 & RR.P2_FONT_BIT) hCss.push('font-family:sans-serif');
      }
      var hStyle = hCss.length > 0 ? ' style="' + hCss.join(';') + '"' : '';
      opens.push('<' + heading + ' class="mdr-set-' + heading + '"' + hStyle + '>');
      closes.unshift('</' + heading + '>');

      if (style & RR.PIP_ITALIC) { opens.push('<em class="mdr-i">'); closes.unshift('</em>'); }
      if (!hasDoubleUnderline && (style & RR.PIP_UNDERLINE)) { opens.push('<u class="mdr-u">'); closes.unshift('</u>'); }
      if (style & RR.PIP_STRIKETHROUGH) { opens.push('<s class="mdr-s">'); closes.unshift('</s>'); }
      if (pip3) {
        if (pip3 & RR.P3_SMALLCAPS) { opens.push('<span class="mdr-sc" style="font-variant:small-caps">'); closes.unshift('</span>'); }
        if (pip3 & RR.P3_OVERLINE) { opens.push('<span class="mdr-ol" style="text-decoration:overline">'); closes.unshift('</span>'); }
        if (hasDoubleUnderline) { opens.push('<span class="mdr-du" style="text-decoration:underline double">'); closes.unshift('</span>'); }
      }
      return { open: opens.join(''), close: closes.join('') };
    }

    // ── Non-heading: byte two + three style span ──
    var css = [], spanClasses = [];
    if (pip2) {
      var fgIdx = (pip2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT;
      if (fgIdx > 0 && RR.DEFAULT_FG[fgIdx]) { css.push('color:' + RR.DEFAULT_FG[fgIdx]); spanClasses.push('mdr-fg'); }
      if (vert !== RR.PIP_HIGHLIGHT && (pip2 & RR.P2_BG_MASK)) {
        var bgIdx = (pip2 & RR.P2_BG_MASK) >> 5;
        if (RR.DEFAULT_BG[bgIdx]) { css.push('background-color:' + RR.DEFAULT_BG[bgIdx]); spanClasses.push('mdr-bg'); }
      }
      if (pip2 & RR.P2_FONT_BIT) { css.push('font-family:sans-serif'); spanClasses.push('mdr-font'); }
    }
    if (pip3) {
      var sizeIdx = (pip3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT;
      if (sizeIdx > 0 && RR.DEFAULT_SIZES[sizeIdx]) { css.push('font-size:' + RR.DEFAULT_SIZES[sizeIdx]); spanClasses.push('mdr-sz'); }
    }
    if (css.length > 0) {
      opens.push('<span class="' + spanClasses.join(' ') + '" style="' + css.join(';') + '">');
      closes.unshift('</span>');
    }

    // Byte one: semantic tags
    if (style & RR.PIP_BOLD) { opens.push('<strong class="mdr-b">'); closes.unshift('</strong>'); }
    if (style & RR.PIP_ITALIC) { opens.push('<em class="mdr-i">'); closes.unshift('</em>'); }
    if (hasDoubleUnderline) {
      opens.push('<span class="mdr-du" style="text-decoration:underline double">'); closes.unshift('</span>');
    } else if (style & RR.PIP_UNDERLINE) {
      opens.push('<u class="mdr-u">'); closes.unshift('</u>');
    }
    if (style & RR.PIP_STRIKETHROUGH) { opens.push('<s class="mdr-s">'); closes.unshift('</s>'); }

    if (vert === RR.PIP_SUPER) { opens.push('<sup class="mdr-sup">'); closes.unshift('</sup>'); }
    else if (vert === RR.PIP_SUB) { opens.push('<sub class="mdr-sub">'); closes.unshift('</sub>'); }
    else if (vert === RR.PIP_HIGHLIGHT) {
      var hlBg = RR.DEFAULT_BG[1];
      if (pip2 && (pip2 & RR.P2_BG_MASK)) {
        var bgIdx = (pip2 & RR.P2_BG_MASK) >> 5;
        hlBg = RR.DEFAULT_BG[bgIdx] || hlBg;
      }
      opens.push('<mark class="mdr-hl" style="background-color:' + hlBg + '">'); closes.unshift('</mark>');
    }

    // Byte three: typographic extras
    if (pip3) {
      if (pip3 & RR.P3_SMALLCAPS) { opens.push('<span class="mdr-sc" style="font-variant:small-caps">'); closes.unshift('</span>'); }
      if (pip3 & RR.P3_OVERLINE) { opens.push('<span class="mdr-ol" style="text-decoration:overline">'); closes.unshift('</span>'); }
    }

    return { open: opens.join(''), close: closes.join('') };
  }

  // ── Meta region reader ─────────────────────────────────────────────────

  function readMeta(bytes, state) {
    var metaBytes = [];
    while (state.pos < bytes.length && bytes[state.pos] !== RR.META_CLOSE) {
      metaBytes.push(bytes[state.pos++]);
    }
    if (state.pos < bytes.length) state.pos++;  // consume META_CLOSE
    return decoder.decode(new Uint8Array(metaBytes));
  }

  // Parse "key=value\nkey2=value2" attribute string
  function parseAttrs(str) {
    var attrs = {};
    var lines = str.split('\n');
    for (var i = 0; i < lines.length; i++) {
      var eq = lines[i].indexOf('=');
      if (eq > 0) {
        attrs[lines[i].substring(0, eq)] = lines[i].substring(eq + 1);
      }
    }
    return attrs;
  }

  // Build HTML attribute string from parsed attrs
  function attrsToHtml(attrObj) {
    var s = '';
    for (var k in attrObj) {
      s += ' ' + escAttr(k) + '="' + escAttr(attrObj[k]) + '"';
    }
    return s;
  }

  // ── Main inflate function ──────────────────────────────────────────────

  function inflate(bytes) {
    var state = { pos: 0 };
    var len = bytes.length;
    var out = '';

    var styleStack = [];   // STX/ETX: { close, pip }
    var blockStack = [];   // SOH/EOT: tag name
    var inlineStack = [];  // SO/SI: tag name

    while (state.pos < len) {
      var b = bytes[state.pos];

      // ── Block open (SOH) ──────────────────────────────────────────────
      if (b === RR.SOH) {
        state.pos++;
        var idx = bytes[state.pos++];
        var tag = RR.BLOCK_NAMES[idx] || 'div';
        var attrStr = '';

        // Check for meta attribute region immediately after index
        if (state.pos < len && bytes[state.pos] === RR.META_OPEN) {
          state.pos++;  // consume META_OPEN
          var metaType = bytes[state.pos++];
          if (metaType === RR.META_ATTR) {
            attrStr = attrsToHtml(parseAttrs(readMeta(bytes, state)));
          } else {
            readMeta(bytes, state);  // consume and discard
          }
        }

        out += '<' + tag + ' class="mdr-' + tag + '"' + attrStr + '>';
        blockStack.push(tag);
        continue;
      }

      // ── Block close (EOT) ─────────────────────────────────────────────
      if (b === RR.EOT) {
        state.pos++;
        var tag = blockStack.pop();
        if (tag && !RR.VOID_BLOCKS[tag]) {
          out += '</' + tag + '>';
        }
        continue;
      }

      // ── Inline open (SO) ──────────────────────────────────────────────
      if (b === RR.SO) {
        state.pos++;
        var idx = bytes[state.pos++];
        var tag = RR.INLINE_NAMES[idx] || 'span';
        var attrStr = '';

        // Check for meta attribute region
        if (state.pos < len && bytes[state.pos] === RR.META_OPEN) {
          state.pos++;
          var metaType = bytes[state.pos++];
          if (metaType === RR.META_ATTR) {
            attrStr = attrsToHtml(parseAttrs(readMeta(bytes, state)));
          } else {
            readMeta(bytes, state);
          }
        }

        out += '<' + tag + ' class="mdr-' + tag + '"' + attrStr + '>';
        inlineStack.push(tag);
        continue;
      }

      // ── Inline close (SI) ─────────────────────────────────────────────
      if (b === RR.SI) {
        state.pos++;
        var tag = inlineStack.pop();
        if (tag) out += '</' + tag + '>';
        continue;
      }

      // ── Style span open (STX) — full style ─────────────────────────────
      if (b === RR.STX) {
        state.pos++;
        var pip = readPip(bytes, state);
        var fullStyle = pip.p1 & RR.PIP_STYLE_MASK;

        // Determine delta tags vs full tags based on enclosing style
        var enclosingStyle = styleStack.length > 0 ? styleStack[styleStack.length - 1].pip : null;
        var emitP1 = fullStyle;
        // If nested inside another style, only emit the delta tags for byte one
        if (enclosingStyle) {
          emitP1 = fullStyle & ~(enclosingStyle.p1 & RR.PIP_STYLE_MASK);
        }
        var emitPip = emitP1 | (pip.p1 & RR.PIP_CONTINUATION);
        // For byte two/three, emit only if different from enclosing
        var emitP2 = pip.p2, emitP3 = pip.p3;
        if (enclosingStyle) {
          if (pip.p2 === enclosingStyle.p2) emitP2 = 0;
          if (pip.p3 === enclosingStyle.p3) emitP3 = 0;
        }
        var tags = pipToTags(emitPip, emitP2, emitP3);

        styleStack.push({ close: tags.close, pip: { p1: fullStyle, p2: pip.p2, p3: pip.p3 } });
        out += tags.open;
        continue;
      }

      // ── Style span close (ETX) — clear mask ──────────────────────────
      if (b === RR.ETX) {
        state.pos++;
        var entry = styleStack.pop();
        if (entry) out += entry.close;
        // Read clear mask pip (always follows ETX)
        if (state.pos < len) {
          readPip(bytes, state);  // consume clear mask (1–3 bytes)
        }
        continue;
      }

      // ── Standalone meta region ────────────────────────────────────────
      if (b === RR.META_OPEN) {
        state.pos++;
        var metaType = bytes[state.pos++];
        var content = readMeta(bytes, state);
        if (metaType === RR.META_COMMENT) {
          out += '<!-- ' + escHtml(content) + ' -->';
        }
        continue;
      }

      // ── Text content ──────────────────────────────────────────────────
      var start = state.pos;
      while (state.pos < len && !RR.CONTROL[bytes[state.pos]]) {
        state.pos++;
      }
      if (state.pos > start) {
        out += escHtml(decoder.decode(bytes.slice(start, state.pos)));
      }
    }

    return out;
  }

  // ── Public API ─────────────────────────────────────────────────────────

  return inflate;

})();

if (typeof module !== 'undefined') module.exports = { rrInflate: rrInflate };
Rich Render Editor - JS:
 Show Source Code
/**
 * RichRender Editor Format
 *
 * Per-character pip array for cursor-addressable editing.
 * Each position is { ch: string, p1: number, p2: number, p3: number }.
 * Cursor maps directly to array index. No parsing, no tree walking.
 *
 * Painting API: apply/remove/toggle style bits on ranges.
 * All operations are bitwise math on pip bytes.
 */

var RREditor = (function () {

  // ── Document: array of { ch, p1, p2, p3 } ─────────────────────────────

  function EditorDoc(chars) {
    this.chars = chars || [];  // [{ ch, p1, p2, p3 }, ...]
  }

  // ── Factory: create from plain text ────────────────────────────────────

  EditorDoc.fromText = function (text) {
    var chars = [];
    for (var i = 0; i < text.length; i++) {
      chars.push({ ch: text[i], p1: 0, p2: 0, p3: 0 });
    }
    return new EditorDoc(chars);
  };

  // ── Length ──────────────────────────────────────────────────────────────

  EditorDoc.prototype.length = function () { return this.chars.length; };

  // ── Plain text extraction ──────────────────────────────────────────────

  EditorDoc.prototype.text = function (from, to) {
    var start = from || 0;
    var end = to !== undefined ? to : this.chars.length;
    var out = '';
    for (var i = start; i < end; i++) out += this.chars[i].ch;
    return out;
  };

  // ── Cursor operations ──────────────────────────────────────────────────

  EditorDoc.prototype.insertAt = function (pos, text, p1, p2, p3) {
    p1 = p1 || 0; p2 = p2 || 0; p3 = p3 || 0;
    var items = [];
    for (var i = 0; i < text.length; i++) {
      items.push({ ch: text[i], p1: p1, p2: p2, p3: p3 });
    }
    Array.prototype.splice.apply(this.chars, [pos, 0].concat(items));
    return this;
  };

  EditorDoc.prototype.deleteRange = function (from, to) {
    this.chars.splice(from, to - from);
    return this;
  };

  EditorDoc.prototype.charAt = function (pos) {
    return this.chars[pos] || null;
  };

  // ── Selection / slice ──────────────────────────────────────────────────

  EditorDoc.prototype.slice = function (from, to) {
    var sliced = this.chars.slice(from, to);
    // Deep copy
    var copy = [];
    for (var i = 0; i < sliced.length; i++) {
      var c = sliced[i];
      copy.push({ ch: c.ch, p1: c.p1, p2: c.p2, p3: c.p3 });
    }
    return new EditorDoc(copy);
  };

  // ═══════════════════════════════════════════════════════════════════════
  // PAINTING — bitwise style operations on ranges
  // ═══════════════════════════════════════════════════════════════════════

  // ── Byte one: flag painting ────────────────────────────────────────────

  EditorDoc.prototype.applyFlag = function (from, to, flag) {
    for (var i = from; i < to; i++) this.chars[i].p1 |= flag;
    return this;
  };

  EditorDoc.prototype.removeFlag = function (from, to, flag) {
    for (var i = from; i < to; i++) this.chars[i].p1 &= ~flag;
    return this;
  };

  EditorDoc.prototype.toggleFlag = function (from, to, flag) {
    // If ALL chars in range have the flag, remove it; otherwise apply it
    var allSet = true;
    for (var i = from; i < to; i++) {
      if ((this.chars[i].p1 & flag) !== flag) { allSet = false; break; }
    }
    return allSet ? this.removeFlag(from, to, flag) : this.applyFlag(from, to, flag);
  };

  EditorDoc.prototype.hasFlag = function (pos, flag) {
    var c = this.chars[pos];
    return c ? (c.p1 & flag) === flag : false;
  };

  // ── Byte one: variant field (super/sub/highlight) ──────────────────────
  // These are mutually exclusive in the variant mask

  EditorDoc.prototype.setVariant = function (from, to, variant) {
    for (var i = from; i < to; i++) {
      this.chars[i].p1 = (this.chars[i].p1 & ~RR.PIP_VARIANT_MASK) | variant;
    }
    return this;
  };

  EditorDoc.prototype.clearVariant = function (from, to) {
    return this.setVariant(from, to, 0);
  };

  // ── Byte two: foreground color ─────────────────────────────────────────

  EditorDoc.prototype.setFgColor = function (from, to, colorIndex) {
    for (var i = from; i < to; i++) {
      var c = this.chars[i];
      c.p2 = (c.p2 & ~RR.P2_FG_MASK) | ((colorIndex << RR.P2_FG_SHIFT) & RR.P2_FG_MASK);
      this._syncContinuation(i);
    }
    return this;
  };

  EditorDoc.prototype.getFgColor = function (pos) {
    var c = this.chars[pos];
    return c ? (c.p2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT : 0;
  };

  // ── Byte two: background color ─────────────────────────────────────────

  EditorDoc.prototype.setBgColor = function (from, to, bgIndex) {
    for (var i = from; i < to; i++) {
      var c = this.chars[i];
      c.p2 = (c.p2 & ~RR.P2_BG_MASK) | ((bgIndex << 5) & RR.P2_BG_MASK);
      this._syncContinuation(i);
    }
    return this;
  };

  EditorDoc.prototype.getBgColor = function (pos) {
    var c = this.chars[pos];
    return c ? (c.p2 & RR.P2_BG_MASK) >> 5 : 0;
  };

  // ── Byte two: font ─────────────────────────────────────────────────────

  EditorDoc.prototype.setFont = function (from, to, sans) {
    for (var i = from; i < to; i++) {
      var c = this.chars[i];
      if (sans) c.p2 |= RR.P2_FONT_BIT;
      else c.p2 &= ~RR.P2_FONT_BIT;
      this._syncContinuation(i);
    }
    return this;
  };

  // ── Byte three: font size ──────────────────────────────────────────────

  EditorDoc.prototype.setSize = function (from, to, sizeIndex) {
    for (var i = from; i < to; i++) {
      var c = this.chars[i];
      c.p3 = (c.p3 & ~RR.P3_SIZE_MASK) | ((sizeIndex << RR.P3_SIZE_SHIFT) & RR.P3_SIZE_MASK);
      this._syncContinuation(i);
    }
    return this;
  };

  EditorDoc.prototype.getSize = function (pos) {
    var c = this.chars[pos];
    return c ? (c.p3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT : 0;
  };

  // ── Byte three: typographic extras ─────────────────────────────────────

  EditorDoc.prototype.applyP3Flag = function (from, to, flag) {
    for (var i = from; i < to; i++) {
      this.chars[i].p3 |= flag;
      this._syncContinuation(i);
    }
    return this;
  };

  EditorDoc.prototype.removeP3Flag = function (from, to, flag) {
    for (var i = from; i < to; i++) {
      this.chars[i].p3 &= ~flag;
      this._syncContinuation(i);
    }
    return this;
  };

  EditorDoc.prototype.toggleP3Flag = function (from, to, flag) {
    var allSet = true;
    for (var i = from; i < to; i++) {
      if (!(this.chars[i].p3 & flag)) { allSet = false; break; }
    }
    return allSet ? this.removeP3Flag(from, to, flag) : this.applyP3Flag(from, to, flag);
  };

  // ── Continuation bit sync ──────────────────────────────────────────────
  // Automatically sets/clears continuation bits based on whether
  // higher bytes have any non-zero content.

  EditorDoc.prototype._syncContinuation = function (pos) {
    var c = this.chars[pos];
    // p3 has content? → p2 needs continuation
    if (c.p3 & ~RR.P3_CONTINUATION) {
      c.p2 |= RR.P2_CONTINUATION;
    } else {
      c.p2 &= ~RR.P2_CONTINUATION;
      c.p3 = 0;  // no content, clear entirely
    }
    // p2 has content? → p1 needs continuation
    if (c.p2 & ~RR.P2_CONTINUATION) {
      c.p1 |= RR.PIP_CONTINUATION;
    } else if (!(c.p2 & RR.P2_CONTINUATION)) {
      c.p1 &= ~RR.PIP_CONTINUATION;
      c.p2 = 0;  // no content, clear entirely
    } else {
      // p2 has no own content but has continuation (p3 has content)
      c.p1 |= RR.PIP_CONTINUATION;
    }
  };

  // ── Query: get style state at position ─────────────────────────────────

  EditorDoc.prototype.styleAt = function (pos) {
    var c = this.chars[pos];
    if (!c) return null;
    return {
      bold:          !!(c.p1 & RR.PIP_BOLD),
      italic:        !!(c.p1 & RR.PIP_ITALIC),
      underline:     !!(c.p1 & RR.PIP_UNDERLINE),
      strikethrough: !!(c.p1 & RR.PIP_STRIKETHROUGH),
      variant:       c.p1 & RR.PIP_VARIANT_MASK,
      fgColor:       (c.p2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT,
      bgColor:       (c.p2 & RR.P2_BG_MASK) >> 5,
      sans:          !!(c.p2 & RR.P2_FONT_BIT),
      size:          (c.p3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT,
      smallCaps:     !!(c.p3 & RR.P3_SMALLCAPS),
      overline:      !!(c.p3 & RR.P3_OVERLINE),
      doubleUnder:   !!(c.p3 & RR.P3_DOUBLE_UNDERLINE),
      p1: c.p1, p2: c.p2, p3: c.p3
    };
  };

  // ── Query: is range uniform for a given style? ─────────────────────────

  EditorDoc.prototype.rangeHasFlag = function (from, to, flag) {
    for (var i = from; i < to; i++) {
      if ((this.chars[i].p1 & flag) !== flag) return false;
    }
    return true;
  };

  // ── Public API ─────────────────────────────────────────────────────────

  return { EditorDoc: EditorDoc };

})();

if (typeof module !== 'undefined') module.exports = RREditor;
RichMarkdown - CSS:
 Show Source Code
/**
 * RichMarkdown CSS
 * Companion stylesheet for richmd.js
 * Override defaults via CSS custom properties on :root or any ancestor element.
 */

/* ─── Variables ──────────────────────────────────────────────────────────────── */

:root {
  --mdr-font-family:        inherit;
  --mdr-font-size:          1rem;
  --mdr-line-height:        1.5em;
  --mdr-color:              inherit;

  --mdr-heading-font:       inherit;
  --mdr-heading-weight:     bold;
  --mdr-h1-size:            2em;
  --mdr-h2-size:            1.5em;
  --mdr-h3-size:            1.25em;
  --mdr-h4-size:            1.1em;
  --mdr-h5-size:            1em;
  --mdr-h6-size:            0.9em;

  --mdr-code-font:          monospace;
  --mdr-code-bg:            rgba(0,0,0,0.06);
  --mdr-code-radius:        3px;
  --mdr-pre-bg:             rgba(0,0,0,0.04);
  --mdr-pre-radius:         4px;
  --mdr-pre-padding:        1em;

  --mdr-blockquote-border:  3px solid currentColor;
  --mdr-blockquote-opacity: 0.6;
  --mdr-blockquote-padding: 0 0 0 1em;

  --mdr-link-color:         inherit;
  --mdr-link-decoration:    underline;

  --mdr-hr-border:          1px solid currentColor;
  --mdr-hr-opacity:         0.2;
  --mdr-hr-margin:          1.5em 0;

  --mdr-table-border:       1px solid rgba(0,0,0,0.15);
  --mdr-table-cell-padding: 0.4em 0.75em;
  --mdr-th-bg:              rgba(0,0,0,0.05);

  --mdr-task-gap:           0.4em;
  --mdr-task-done-opacity:  0.5;

  --mdr-collapse-bg-opacity: 0.1;

  --mdr-en-size:            0.85em;
  --mdr-en-opacity:         0.75;
  --mdr-en-divider-margin:  2em 0 1em;
}

/* ─── Paragraphs ─────────────────────────────────────────────────────────────── */

.mdr-p {
  margin: 0;
  padding: 0;
  font-family: var(--mdr-font-family);
  font-size: var(--mdr-font-size);
  line-height: var(--mdr-line-height);
  color: var(--mdr-color);
}

.mdr-empty {
  display: block;
  height: var(--mdr-line-height);
}

/* ─── Headings ───────────────────────────────────────────────────────────────── */

.mdr-h1, .mdr-h2, .mdr-h3,
.mdr-h4, .mdr-h5, .mdr-h6 {
  font-family: var(--mdr-heading-font);
  font-weight: var(--mdr-heading-weight);
  line-height: 1.2;
  margin: 0;
  padding: 0;
}

.mdr-h1 { font-size: var(--mdr-h1-size); }
.mdr-h2 { font-size: var(--mdr-h2-size); }
.mdr-h3 { font-size: var(--mdr-h3-size); }
.mdr-h4 { font-size: var(--mdr-h4-size); }
.mdr-h5 { font-size: var(--mdr-h5-size); }
.mdr-h6 { font-size: var(--mdr-h6-size); }

/* ─── Inline formatting ──────────────────────────────────────────────────────── */

.mdr-b  { font-weight: bold; }
.mdr-i  { font-style: italic; }
.mdr-u  { text-decoration: underline; }
.mdr-s  { text-decoration: line-through; }
.mdr-del { text-decoration: line-through; color: var(--mdr-del-color, #e06c75); background: var(--mdr-del-bg, rgba(224, 108, 117, 0.1)); }
.mdr-ins { text-decoration: none; color: var(--mdr-ins-color, #61afef); background: var(--mdr-ins-bg, rgba(97, 175, 239, 0.1)); }
.mdr-mark { background: var(--mdr-mark-bg, rgba(229, 192, 123, 0.3)); }
.mdr-sup, .mdr-sub { font-size: 0.75em; line-height: 0; position: relative; vertical-align: baseline; }
.mdr-sup { top: -0.5em; }
.mdr-sub { bottom: -0.25em; }

/* ─── Abbreviations ─────────────────────────────────────────────────────────── */

.mdr-abbr {
  text-decoration: underline dotted;
  text-underline-offset: 0.15em;
  cursor: help;
}

/* ─── Keyboard input ────────────────────────────────────────────────────────── */

.mdr-kbd {
  font-family: var(--mdr-code-font);
  font-size: 0.85em;
  padding: 0.1em 0.4em;
  border: 1px solid rgba(0,0,0,0.2);
  border-radius: 3px;
  background: rgba(0,0,0,0.04);
  box-shadow: 0 1px 0 rgba(0,0,0,0.1);
}

/* ─── Code ───────────────────────────────────────────────────────────────────── */

.mdr-code {
  font-family: var(--mdr-code-font);
  font-size: 0.9em;
  background: var(--mdr-code-bg);
  border-radius: var(--mdr-code-radius);
  padding: 0.1em 0.3em;
}

.mdr-pre {
  font-family: var(--mdr-code-font);
  font-size: 0.9em;
  background: var(--mdr-pre-bg);
  border-radius: var(--mdr-pre-radius);
  padding: var(--mdr-pre-padding);
  overflow-x: auto;
  margin: 0;
  white-space: pre;
}

.mdr-pre .mdr-code {
  background: none;
  border-radius: 0;
  padding: 0;
  font-size: inherit;
}

/* ─── Links ──────────────────────────────────────────────────────────────────── */

.mdr-a {
  color: var(--mdr-link-color);
  text-decoration: var(--mdr-link-decoration);
}

.mdr-anchor {
  font-size: 0.75em;
  color: var(--mdr-anchor-color, rgba(128,128,128,0.6));
  text-decoration: none;
  font-family: var(--mdr-code-font);
  vertical-align: super;
  scroll-margin-top: 6em;
}

/* ─── Blockquote ─────────────────────────────────────────────────────────────── */

.mdr-blockquote {
  margin: 0;
  padding: var(--mdr-blockquote-padding);
  border-left: var(--mdr-blockquote-border);
  opacity: var(--mdr-blockquote-opacity);
  font-style: italic;
}

/* ─── Alignment ─────────────────────────────────────────────────────────────── */

.mdr-align-center { text-align: center; }
.mdr-align-right  { text-align: right; }

/* ─── Horizontal rule ────────────────────────────────────────────────────────── */

.mdr-hr {
  border: none;
  border-top: var(--mdr-hr-border);
  opacity: var(--mdr-hr-opacity);
  margin: var(--mdr-hr-margin);
}

/* ─── Lists ──────────────────────────────────────────────────────────────────── */

.mdr-ul, .mdr-ol {
  margin: 0;
  padding-left: 1.5em;
}

.mdr-li {
  line-height: var(--mdr-line-height);
  margin: 0;
  padding: 0;
}

/* ─── Task lists ─────────────────────────────────────────────────────────────── */

.mdr-task, .mdr-task-done {
  margin: 0;
  padding: 0;
  display: flex;
  align-items: baseline;
  gap: var(--mdr-task-gap);
  line-height: var(--mdr-line-height);
  list-style: none;
}

.mdr-task::before {
  content: "☐";
  flex-shrink: 0;
}

.mdr-task.mdr-task-done::before {
  content: "☑";
}

.mdr-task.mdr-task-done {
  opacity: var(--mdr-task-done-opacity);
  text-decoration: line-through;
}

/* ─── Tables ─────────────────────────────────────────────────────────────────── */

.mdr-table {
  border-collapse: collapse;
  width: 100%;
  margin: 0;
  font-size: var(--mdr-font-size);
}

.mdr-caption {
  font-weight: bold;
  margin-bottom: 0.5em;
  text-align: left;
}

/* PM schema uses data-caption attr (content hole can't have siblings) */
.mdr-table[data-caption]::before {
  content: attr(data-caption);
  display: table-caption;
  caption-side: top;
  font-weight: bold;
  margin-bottom: 0.5em;
  text-align: left;
}

.mdr-th, .mdr-td {
  border: var(--mdr-table-border);
  padding: var(--mdr-table-cell-padding);
  text-align: left;
  vertical-align: top;
}

.mdr-th {
  background: var(--mdr-th-bg);
  font-weight: bold;
}

/* Table styles */
.mdr-table-layout,
.mdr-table-layout .mdr-th,
.mdr-table-layout .mdr-td {
  border: none;
}
.mdr-table-layout .mdr-th {
  background: none;
  font-weight: normal;
}

.mdr-table-striped .mdr-th {
  background: var(--mdr-th-bg);
}
.mdr-table-striped tbody tr:nth-child(even) .mdr-td {
  background: rgba(0,0,0,0.03);
}

/* ─── Collapse ───────────────────────────────────────────────────────────────── */

.mdr-collapse {
  margin: 0;
}

.mdr-collapse summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}

.mdr-collapse summary::-webkit-details-marker {
  display: none;
}

.mdr-collapse-summary::before {
  content: "";
  display: inline-block;
  width: 1.8em;
  height: 1em;
  margin-right: 0.4em;
  vertical-align: middle;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 19'%3E%3Cg transform='translate(-8.44 -194.91)'%3E%3Cpath opacity='0.15' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath fill='none' stroke='%23000' stroke-width='0.6' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath d='M13.35 212.9c-2.48-.02-3.84-1.3-3.86-3.6-.03-2.3 0-8.13-.05-9.67-.04-1.54 1.23-3.69 3.6-3.68 2.37.01 25.09-.04 26.8-.05 1.71-.01 3.6 1.33 3.58 3.01-.01 1.68.02 7.36.01 10.82.04 2.3-1.93 3.16-4.03 3.16-2.11 0-23.57.03-26.05.01zm28.38-12.59c-.03-1.42-.16-2.8-3.15-2.82-2.99-.02-22.69-.03-24.86-.02-2.17.01-2.53.78-2.55 2.96-.02 2.19.02 6.23-.03 8.28-.05 2.06 1.02 2.47 2.84 2.52 1.82.05 15.39.05 24.99.01 1.86-.01 2.71-.64 2.72-2.31 0-1.67.08-5.93.03-8.64zm-28.51 9.42c-.03-.03-.06-1.48-.06-3.21 0-2.34.03-3.18.1-3.25.14-.14 1.04-.13 1.16.01.07.08.09 1.01.07 3.29l-.02 3.17-.6.02c-.33.01-.62 0-.65-.03zm4.55-.41c-.04-.06-.63-1.24-1.39-2.86-.96-2.06-1.37-3.02-1.35-3.18.02-.18.12-.28.44-.46.5-.27 1.39-1.16 1.59-1.6.16-.35.47-.52.84-.44.29.06 6.23 2.63 6.5 2.81.29.14.15.26.05.52-.08.21-.21.47-.29.56-.14.17-.17.16-3.43-1.26-1.81-.79-3.33-1.42-3.37-1.4-.04.01 1.26 1.26 2.89 2.77 1.63 1.51 2.56 2.42 2.56 2.45 0 .02-.16.23-.35.46-.22.26-.41.41-.5.39-.08-.01-1-.85-2.55-2.28-1.54-1.43-2.86-2.63-2.92-2.66-.07-.04-.11-.03-.11.04 0 .06.61 1.39 1.34 2.95.73 1.57 1.26 2.69 1.24 2.74-.03.09-.9.57-1.03.54-.12-.03-.11-.05-.15-.1zm10.31-3.76c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm4.9-.08c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm5.07 0c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm-24.85-4.19c-.03-.07-.05-.45-.05-.85 0-1.22.4-1.61 1.63-1.61 1.01 0 .99-.01.99.71 0 .7-.01.71-.77.71h-.5v.45c0 .7-.02.72-.68.72-.45 0-.59-.03-.63-.13zm4.36-1.14c-.14-.14-.13-1.11.01-1.23.08-.07 1.23-.09 4.12-.07l4.01.02v.67.67l-4.02.02c-3.06.02-4.04 0-4.12-.08z'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 19'%3E%3Cg transform='translate(-8.44 -194.91)'%3E%3Cpath opacity='0.15' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath fill='none' stroke='%23000' stroke-width='0.6' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath d='M13.35 212.9c-2.48-.02-3.84-1.3-3.86-3.6-.03-2.3 0-8.13-.05-9.67-.04-1.54 1.23-3.69 3.6-3.68 2.37.01 25.09-.04 26.8-.05 1.71-.01 3.6 1.33 3.58 3.01-.01 1.68.02 7.36.01 10.82.04 2.3-1.93 3.16-4.03 3.16-2.11 0-23.57.03-26.05.01zm28.38-12.59c-.03-1.42-.16-2.8-3.15-2.82-2.99-.02-22.69-.03-24.86-.02-2.17.01-2.53.78-2.55 2.96-.02 2.19.02 6.23-.03 8.28-.05 2.06 1.02 2.47 2.84 2.52 1.82.05 15.39.05 24.99.01 1.86-.01 2.71-.64 2.72-2.31 0-1.67.08-5.93.03-8.64zm-28.51 9.42c-.03-.03-.06-1.48-.06-3.21 0-2.34.03-3.18.1-3.25.14-.14 1.04-.13 1.16.01.07.08.09 1.01.07 3.29l-.02 3.17-.6.02c-.33.01-.62 0-.65-.03zm4.55-.41c-.04-.06-.63-1.24-1.39-2.86-.96-2.06-1.37-3.02-1.35-3.18.02-.18.12-.28.44-.46.5-.27 1.39-1.16 1.59-1.6.16-.35.47-.52.84-.44.29.06 6.23 2.63 6.5 2.81.29.14.15.26.05.52-.08.21-.21.47-.29.56-.14.17-.17.16-3.43-1.26-1.81-.79-3.33-1.42-3.37-1.4-.04.01 1.26 1.26 2.89 2.77 1.63 1.51 2.56 2.42 2.56 2.45 0 .02-.16.23-.35.46-.22.26-.41.41-.5.39-.08-.01-1-.85-2.55-2.28-1.54-1.43-2.86-2.63-2.92-2.66-.07-.04-.11-.03-.11.04 0 .06.61 1.39 1.34 2.95.73 1.57 1.26 2.69 1.24 2.74-.03.09-.9.57-1.03.54-.12-.03-.11-.05-.15-.1zm10.31-3.76c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm4.9-.08c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm5.07 0c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm-24.85-4.19c-.03-.07-.05-.45-.05-.85 0-1.22.4-1.61 1.63-1.61 1.01 0 .99-.01.99.71 0 .7-.01.71-.77.71h-.5v.45c0 .7-.02.72-.68.72-.45 0-.59-.03-.63-.13zm4.36-1.14c-.14-.14-.13-1.11.01-1.23.08-.07 1.23-.09 4.12-.07l4.01.02v.67.67l-4.02.02c-3.06.02-4.04 0-4.12-.08z'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
  transition: transform 0.15s;
}

.mdr-collapse[open] .mdr-collapse-summary::before {
  transform: rotate(90deg);
}

.mdr-collapse-body {
  padding-left: 1.2em;
  display: none;
}

.mdr-collapse[open] > .mdr-collapse-body {
  display: block;
}

/* ─── Endnotes ──────────────────────────────────────────────────────────────── */

.mdr-en-ref {
  text-decoration: none;
  font-size: 0.75em;
  vertical-align: super;
}

.mdr-endnotes {
  font-size: var(--mdr-en-size);
  opacity: var(--mdr-en-opacity);
}

.mdr-en {
  margin: 0;
  padding: 0;
  line-height: var(--mdr-line-height);
}

/* In WYSIWYG, PM toDOM only outputs the content hole — use pseudo-elements
   to display the endnote number and return arrow visually. */
.mdr-en[data-en]::before {
  content: attr(data-en);
  font-size: 0.75em;
  vertical-align: super;
  font-weight: bold;
  margin-right: 0.3em;
}

.mdr-en[data-en]::after {
  content: ' \21A9';
  font-size: 0.85em;
  opacity: 0.5;
  margin-left: 0.3em;
}

.mdr-en-back {
  text-decoration: none;
  margin-left: 0.3em;
}

.mdr-hr.mdr-en-separator {
  margin: var(--mdr-en-divider-margin);
  width: 30%;
  margin-left: 0;
}

/* ─── Callout ────────────────────────────────────────────────────────────────── */

.mdr-callout {
  display: block;
  padding: 0.75em 1em;
  margin: 0.5em 0;
  background: var(--mdr-callout-bg, rgba(0,0,0,0.04));
  border-left: 3px solid var(--mdr-callout-border, currentColor);
  border-radius: var(--mdr-code-radius);
}

/* Block callouts show their type as a label */
div.mdr-callout::before {
  display: block;
  font-weight: bold;
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.3em;
}

.mdr-callout-note    { --mdr-callout-border: #4a90d9; --mdr-callout-bg: rgba(74,144,217,0.08); }
.mdr-callout-note::before    { content: "Note";    color: #4a90d9; }

.mdr-callout-warning { --mdr-callout-border: #e0a030; --mdr-callout-bg: rgba(224,160,48,0.08); }
.mdr-callout-warning::before { content: "Warning"; color: #e0a030; }

.mdr-callout-tip     { --mdr-callout-border: #50b060; --mdr-callout-bg: rgba(80,176,96,0.08); }
.mdr-callout-tip::before     { content: "Tip";     color: #50b060; }

.mdr-callout-caution { --mdr-callout-border: #d04040; --mdr-callout-bg: rgba(208,64,64,0.08); }
.mdr-callout-caution::before { content: "Caution"; color: #d04040; }

.mdr-callout-important { --mdr-callout-border: #8b5cf6; --mdr-callout-bg: rgba(139,92,246,0.08); }
.mdr-callout-important::before { content: "Important"; color: #8b5cf6; }

/* ─── Page break (print) ─────────────────────────────────────────────────────── */

.mdr-hr.mdr-page-break {
  display: none;
}

@media print {
  .mdr-hr.mdr-page-break {
    display: block;
    page-break-after: always;
    border: none;
    opacity: 0;
    margin: 0;
  }
}


Page last modified on March 30, 2026, at 10:31 PM