/* ═══════════════════════════════════════════════════════════════════════════
   TabCode — Editor surface.

   The editor is a real <textarea> with transparent glyphs sitting on top of a
   <pre> that paints the same text in colour. The browser owns the caret, the
   selection, the scrolling, the IME and the virtual keyboard — we own nothing
   but the paint.

       .tc-editor
       ├── .tc-gutter          line numbers, vertically translated
       └── .tc-body            the text column: everything right of the gutter
           ├── .tc-highlight   <pre>, clipped, pointer-events:none
           │   └── .tc-mirror  the metric twin of the textarea
           │       └── .tc-line
           └── .tc-input       <textarea>, transparent glyphs, visible caret

   THE METRIC CONTRACT

   .tc-mirror and .tc-input must lay text out identically, character for
   character. Every property that can move a glyph is declared once, for both
   of them, in the `.tc-mirror, .tc-input` rule. If you add a font, spacing or
   wrapping property to one, add it to the other in the same rule — a one-sided
   change is exactly how the caret drifts away from the text.

   Two traps worth naming:
   • Ligatures. A textarea can join "=>" into one glyph; the <pre> cannot,
     because the tokenizer splits it across <span>s. Different advance width,
     drifting text. Ligatures are therefore disabled on both.
   • Scrollbars. A visible scrollbar shrinks the textarea's content box and
     changes where lines wrap. The textarea's scrollbars are hidden so both
     boxes stay the same width.
   ═══════════════════════════════════════════════════════════════════════════ */

.tc-editor {
    /* Metrics. The view writes these; nothing else should. */
    --tc-font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
    --tc-font-size: 14px;
    --tc-line-height: 1.5;
    --tc-tab-size: 4;
    --tc-pad-x: 0.75rem;
    --tc-pad-top: 0.375rem;
    /* Room to scroll past the last line so the caret is never pinned to the
       bottom edge — and, on phones, never trapped under the keyboard. */
    --tc-pad-bottom: 40vh;
    --tc-gutter-width: 3.5rem;

    /* Colours, overridden per theme further down. */
    --tc-caret: var(--text, #f8f8f2);
    --tc-selection: rgba(120, 160, 255, 0.32);
    --tc-active-line: rgba(255, 255, 255, 0.045);
    --tc-guide: rgba(255, 255, 255, 0.09);

    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: var(--bg-editor);
    color: var(--text);
    contain: layout paint;
}

/* One box for both text surfaces, so the gutter and the minimap are subtracted
   once instead of on each of them. Nothing inside needs to know they exist. */
.tc-body {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--tc-gutter-width);
    right: 0;
    z-index: 1;
}

.tc-editor.has-minimap .tc-body { right: 3.75rem; }

/* ── The metric contract ─────────────────────────────────────────────────── */

.tc-mirror,
.tc-input {
    margin: 0;
    padding: var(--tc-pad-top) var(--tc-pad-x) var(--tc-pad-bottom);
    border: 0;
    box-sizing: border-box;

    font-family: var(--tc-font);
    font-size: var(--tc-font-size);
    font-weight: 400;
    font-style: normal;
    line-height: var(--tc-line-height);
    letter-spacing: 0;
    word-spacing: 0;
    /* See "Two traps worth naming" above. */
    font-kerning: none;
    font-variant-ligatures: none;
    font-feature-settings: 'liga' 0, 'clig' 0, 'calt' 0;

    tab-size: var(--tc-tab-size);
    -moz-tab-size: var(--tc-tab-size);
    text-align: left;
    text-indent: 0;
    text-transform: none;

    white-space: pre;
    overflow-wrap: normal;
    word-break: normal;
}

/* Word wrap flips both surfaces at once. */
.tc-editor.is-wrapped .tc-mirror,
.tc-editor.is-wrapped .tc-input {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    word-break: break-word;
}

/* ── Input layer ─────────────────────────────────────────────────────────── */

.tc-input {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: block;
    width: 100%;
    height: 100%;
    resize: none;
    outline: none;
    background: transparent;
    /* Transparent glyphs, visible caret. -webkit-text-fill-color is the one
       iOS honours; `color` alone still paints text there. */
    color: transparent;
    -webkit-text-fill-color: transparent;
    caret-color: var(--tc-caret);
    overflow: auto;
    /* Hidden, not absent: the box still scrolls, it just draws no bar. A drawn
       bar would narrow the content box and desynchronise wrapping. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    overscroll-behavior: contain;
}

.tc-input::-webkit-scrollbar { width: 0; height: 0; }

.tc-input::selection { background: var(--tc-selection); }
.tc-input::-moz-selection { background: var(--tc-selection); }

/* ── Paint layer ─────────────────────────────────────────────────────────── */

.tc-highlight {
    position: absolute;
    inset: 0;
    z-index: 1;
    /* A <pre> carries a UA margin and font. Both are cleared here so the
       mirror inside starts at the same origin as the textarea. */
    margin: 0;
    padding: 0;
    overflow: hidden;
    pointer-events: none;
    contain: layout paint;
}

.tc-mirror {
    /* Scrolled by transform rather than scrollTop: it composites on the GPU
       and stays in step with the textarea without a layout pass per frame. */
    position: absolute;
    top: 0;
    left: 0;
    min-width: 100%;
    width: max-content;
    will-change: transform;
}

.tc-editor.is-wrapped .tc-mirror {
    width: 100%;
}

.tc-line {
    display: block;
    min-height: 1lh;
}

/* An empty line has no inline content and would collapse to zero height,
   pushing every line below it out of step with the textarea. A zero-width
   space gives it a line box without adding a break opportunity to real text. */
.tc-line:empty::after {
    content: '\200B';
}

.tc-line.is-active {
    background: var(--tc-active-line);
}

/* Indent guides ride on the leading whitespace only, so they stop where the
   code starts. 1ch is the advance of a character in a monospace font, which
   makes this exact without measuring anything. */
.tc-indent {
    background-image: repeating-linear-gradient(
        to right,
        var(--tc-guide) 0 1px,
        transparent 1px calc(var(--tc-tab-size) * 1ch)
    );
}

/* Tabs keep their real width — replacing them with an arrow glyph would
   change the advance and shift the whole line. The marker is a border. */
.tc-tab {
    border-left: 1px solid var(--tc-guide);
    margin-left: -1px;
}

.tc-invisible {
    color: var(--text-muted);
}

/* ── Gutter ──────────────────────────────────────────────────────────────── */

.tc-gutter {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 3;
    width: var(--tc-gutter-width);
    overflow: hidden;
    background: var(--bg-chrome);
    color: var(--text-muted);
    font-family: var(--tc-font);
    font-size: var(--tc-font-size);
    line-height: var(--tc-line-height);
    text-align: right;
    user-select: none;
    -webkit-user-select: none;
    contain: layout paint;
}

.tc-gutter-inner {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    padding: var(--tc-pad-top) 0.625rem 0 0;
    will-change: transform;
}

.tc-gutter-line {
    display: block;
    min-height: 1lh;
    font-variant-numeric: tabular-nums;
}

.tc-gutter-line.is-active {
    color: var(--text);
}

/* Annotation dots: a lint error or warning on that line. */
.tc-gutter-line[data-mark]::before {
    content: '';
    position: absolute;
    left: 0.3125rem;
    width: 0.375rem;
    height: 0.375rem;
    margin-top: 0.4em;
    border-radius: 50%;
}

.tc-gutter-line[data-mark='error']::before { background: var(--danger, #f92672); }
.tc-gutter-line[data-mark='warning']::before { background: var(--warning, #fd971f); }

.tc-editor.no-gutter { --tc-gutter-width: 0px; }
.tc-editor.no-gutter .tc-gutter { display: none; }

/* ── Minimap ─────────────────────────────────────────────────────────────── */

.tc-minimap {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 4;
    width: 3.75rem;
    background: var(--bg-chrome);
    border-left: 1px solid var(--border);
    cursor: pointer;
    touch-action: none;
}

.tc-minimap[hidden] { display: none; }

/* ── Themes ──────────────────────────────────────────────────────────────── */

.tc-tabcode-light {
    --bg-editor: #fbfbfa;
    --text: #22221f;
    --tc-selection: rgba(60, 120, 220, 0.22);
    --tc-active-line: rgba(0, 0, 0, 0.04);
    --tc-guide: rgba(0, 0, 0, 0.1);
}

.tc-tabcode-contrast {
    --bg-editor: #000;
    --text: #fff;
    --tc-selection: rgba(0, 110, 255, 0.55);
    --tc-active-line: rgba(255, 255, 255, 0.1);
    --tc-guide: rgba(255, 255, 255, 0.25);
}

/* ── Tokens ──────────────────────────────────────────────────────────────── */

.tok-comment { color: #75715e; font-style: italic; }
.tok-keyword { color: #f92672; }
.tok-string { color: #e6db74; }
.tok-number,
.tok-constant { color: #ae81ff; }
.tok-type { color: #66d9ef; font-style: italic; }
.tok-function { color: #a6e22e; }
.tok-variable { color: #fd971f; }
.tok-attribute { color: #a6e22e; }
.tok-punctuation,
.tok-operator { color: #f8f8f2; }
.tok-identifier { color: var(--text); }
.tok-regexp { color: #e6db74; }
.tok-tag { color: #f92672; }

.tc-tabcode-light .tok-comment { color: #8a8a7a; }
.tc-tabcode-light .tok-keyword { color: #c4157a; }
.tc-tabcode-light .tok-constant { color: #7a3fc0; }
.tc-tabcode-light .tok-string { color: #8a6a00; }
.tc-tabcode-light .tok-function,
.tc-tabcode-light .tok-attribute { color: #4a7c00; }
.tc-tabcode-light .tok-type { color: #0a6f85; }
.tc-tabcode-light .tok-variable { color: #a35200; }
.tc-tabcode-light .tok-punctuation,
.tc-tabcode-light .tok-operator { color: #22221f; }

.tc-tabcode-contrast .tok-comment { color: #9de89d; font-style: normal; }
.tc-tabcode-contrast .tok-keyword,
.tc-tabcode-contrast .tok-tag { color: #ff7ab2; }
.tc-tabcode-contrast .tok-constant { color: #d4b8ff; }
.tc-tabcode-contrast .tok-string { color: #ffe066; }
.tc-tabcode-contrast .tok-function { color: #b8ff5c; }
.tc-tabcode-contrast .tok-type { color: #7ce8ff; }
.tc-tabcode-contrast .tok-variable { color: #ffb055; }
.tc-tabcode-contrast .tok-punctuation,
.tc-tabcode-contrast .tok-operator { color: #fff; }

/* Italics cost a synthetic-oblique advance in some fallback fonts, which would
   shift the painted text off the caret. Kept only where the font is real. */
@supports not (font-synthesis-style: none) {
    .tok-comment,
    .tok-type { font-style: normal; }
}

/* ── Reduced motion ──────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    .tc-mirror,
    .tc-gutter-inner { will-change: auto; }
}
