/* ===========================================================================
   GODOT BITMAP FONT EDITOR — the tool's own interface.

   The editor arrived as a standalone dark-only page with its own palette and
   every rule written inline on the elements. Both had to go: inline styles
   cannot answer a theme swap (JS/layout.js only flips a data-theme attribute,
   the values behind it live in color-scheme.css), and a second hard-coded
   palette would have made the tool the one panel on the site that ignores the
   reader's choice.

   So everything below is either a site token or a token derived from one. The
   three exceptions are the pixel grid's own colours — an "on" pixel, the
   baseline tint, and the glyph previews — which need to hold their meaning
   rather than their hue, and so are declared once as --bf-* below and given a
   light-mode value alongside. The canvas previews cannot read CSS at all, so
   the same variables are read back out in JS (see readGridPalette() in
   bitmap-font-editor.js) and redrawn when the theme changes.
   =========================================================================== */

/* ---- Editor tokens (dark) ----
   Declared on the dialog as well as on the app, and that is not tidiness, it
   is required. The import dialog is a SIBLING of #bf-app rather than a child
   of it (the app carries data-reveal, and an ancestor mid-transform drags a
   top-layer dialog around with it), so it inherits nothing from the app.

   Custom properties fail quietly and badly when that happens: a font-family of
   var(--bf-sans) with no --bf-sans in scope is invalid at computed-value time,
   which does NOT fall back to the surrounding font, it falls back to the
   property's initial value. That is why the whole dialog came out in Times
   until this was split in two - along with every border, surface colour and
   cell size in there, which had all quietly gone to their initial values too. */
.bf-app,
.bf-dialog {
    --bf-surface: var(--color-primary-a10);
    --bf-surface-raised: var(--color-primary-a20);
    --bf-border: var(--color-card-border);
    --bf-border-strong: var(--color-primary-a20);

    /* An "on" pixel is the same amber in both themes. It is meant to read as a
       lit LED on the board this font is drawn for, and an amber that shifted
       with the theme would stop meaning that. */
    --bf-pixel-on: #ffab3d;
    --bf-pixel-on-dim: #c87828;
    --bf-cell-off: #15181e;
    --bf-cell-edge: rgba(255, 255, 255, 0.05);
    --bf-baseline-fill: #0e1c2e;
    --bf-baseline-edge: #23456d;
    --bf-baseline-text: #6fa3dd;
    --bf-preview-bg: #101318;

    --bf-mono: "JetBrains Mono", "Fira Mono", "Consolas", ui-monospace, monospace;
    --bf-sans: "Circular Spotify", "Inter", sans-serif;

    --bf-cell-size: clamp(13px, 3.4vw, 20px);

    /* A base font on both roots. The site sets its fonts per element rather
       than on body, so anything inside the tool that does not name one of its
       own inherits the browser default, which is a serif. Every text element
       below does name one, but this means a span added later cannot quietly
       come out in Times. Scoped to the tool, so the rest of the page is
       untouched. */
    font-family: var(--bf-sans);
}

.bf-app {
    background: var(--bf-surface);
    border: 1px solid var(--bf-border);
    border-radius: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    color: var(--color-text-1);
}

:root[data-theme="light"] .bf-app,
:root[data-theme="light"] .bf-dialog {
    --bf-cell-off: #c9ced6;
    --bf-cell-edge: rgba(0, 0, 0, 0.07);
    --bf-baseline-fill: #b9d2ee;
    --bf-baseline-edge: #6f9bcb;
    --bf-baseline-text: #2f5f8c;
    --bf-pixel-on-dim: #d98b2a;
    --bf-preview-bg: #dfe3e8;
}

/* ---------------------------------------------------------------------------
   Toolbar
   --------------------------------------------------------------------------- */

.bf-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 12px;
    background: var(--bf-surface-raised);
    border-bottom: 1px solid var(--bf-border);
}

.bf-toolbar-spacer {
    margin-left: auto;
}

.bf-tabs {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
}

.bf-field {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--bf-sans);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-2);
    white-space: nowrap;
}

.bf-field-hint {
    color: var(--color-primary-a30);
    font-size: 11px;
    font-weight: 400;
}

.bf-sep {
    width: 1px;
    align-self: stretch;
    min-height: 22px;
    background: var(--bf-border-strong);
    margin: 0 2px;
}

/* ---------------------------------------------------------------------------
   Controls

   One base .bf-btn with modifiers, rather than the four unrelated button
   classes the original had. Sizes are set so every control clears a 32px hit
   target on a mouse and the toolbar still fits a phone; the icon buttons in
   the glyph tools go to 36px because they are the ones actually poked with a
   thumb.
   --------------------------------------------------------------------------- */

.bf-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 32px;
    padding: 6px 14px;
    border-radius: 8px;
    border: 1px solid var(--bf-border-strong);
    background: transparent;
    color: var(--color-text-2);
    font-family: var(--bf-sans);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color var(--dur-hover) var(--ease-soft),
        border-color var(--dur-hover) var(--ease-soft),
        color var(--dur-hover) var(--ease-soft),
        transform var(--dur-hover) var(--ease-pop);
}

@media (hover: hover) {
    .bf-btn:hover {
        background: var(--color-primary-a20);
        border-color: var(--color-primary-a30);
        color: var(--color-text-1);
    }
}

.bf-btn:active {
    transform: scale(0.97);
    transition-duration: var(--dur-press);
}

.bf-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    transform: none;
}

.bf-btn--primary {
    background: var(--color-selected-2);
    border-color: var(--color-selected-2);
    color: #ffffff;
    padding: 8px 18px;
    font-size: 13px;
}

@media (hover: hover) {
    .bf-btn--primary:hover {
        background: var(--color-selected-3);
        border-color: var(--color-selected-3);
        color: #06121e;
    }
}

.bf-btn--accent {
    color: var(--color-accent-text);
    border-color: var(--color-selected-1);
}

@media (hover: hover) {
    .bf-btn--accent:hover {
        background: rgba(91, 148, 198, 0.14);
        border-color: var(--color-selected-2);
        color: var(--color-accent-text);
    }
}

.bf-btn--danger {
    color: var(--color-warning-1);
    border-color: var(--color-warning-2);
}

@media (hover: hover) {
    .bf-btn--danger:hover {
        background: rgba(231, 140, 140, 0.12);
        border-color: var(--color-warning-1);
        color: var(--color-warning-1);
    }
}

/* Square controls that carry a glyph rather than a word (flip, shift, invert).
   The label is on aria-label and in the tooltip; the symbol is the affordance. */
.bf-btn--icon {
    min-width: 36px;
    min-height: 36px;
    padding: 6px;
    font-size: 15px;
    font-family: var(--bf-mono);
}

/* Tabs. aria-selected is the state, so the styling keys off it rather than a
   class — there is then no way for the two to disagree. */
.bf-tab {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 7px 14px;
    min-height: 32px;
    font-family: var(--bf-sans);
    font-size: 12px;
    font-weight: 700;
    color: var(--color-text-2);
    cursor: pointer;
    transition:
        background-color var(--dur-hover) var(--ease-soft),
        color var(--dur-hover) var(--ease-soft);
}

@media (hover: hover) {
    .bf-tab:hover {
        background: var(--color-primary-a10);
        color: var(--color-text-1);
    }
}

.bf-tab[aria-selected="true"] {
    background: var(--color-selected-2);
    color: #ffffff;
}

.bf-tab:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Every interactive thing in the tool shares one focus ring, and it is on
   :focus-visible only so a mouse click does not leave one behind. The dialog
   is named alongside the app for the same reason as the tokens above: it is
   not inside it, so scoping this to .bf-app alone left every control in the
   import dialog with no focus ring at all. */
:is(.bf-app, .bf-dialog) :is(button, input, textarea, select, a):focus-visible {
    outline: 2px solid var(--color-selected-3);
    outline-offset: 2px;
}

.bf-num {
    width: 60px;
    padding: 6px 8px;
    border-radius: 6px;
    border: 1px solid var(--bf-border-strong);
    background: var(--color-primary-a0);
    color: var(--color-text-1);
    font-family: var(--bf-mono);
    font-size: 12px;
    transition: border-color var(--dur-hover) var(--ease-soft);
}

.bf-num:focus {
    outline: none;
    border-color: var(--color-selected-2);
}

.bf-range {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 4px;
    border-radius: 2px;
    background: var(--bf-border-strong);
    cursor: pointer;
}

.bf-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-selected-2);
    border: 2px solid var(--bf-surface);
    cursor: pointer;
}

.bf-range::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-selected-2);
    border: 2px solid var(--bf-surface);
    cursor: pointer;
}

/* Bigger grab target on touch. A 16px thumb is sized for a mouse cursor,
   not a fingertip, and a slider is the one control on this page where a
   slightly-off tap can't just be retried like a mis-tapped button can, it
   drags to the wrong value instead. */
@media (pointer: coarse) {
    .bf-range {
        height: 8px;
    }

    .bf-range::-webkit-slider-thumb {
        width: 26px;
        height: 26px;
    }

    .bf-range::-moz-range-thumb {
        width: 26px;
        height: 26px;
    }
}

.bf-textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 12px;
    border-radius: 8px;
    border: 1px solid var(--bf-border-strong);
    background: var(--color-primary-a0);
    color: var(--color-text-1);
    font-family: var(--bf-mono);
    font-size: 12px;
    line-height: 1.5;
    resize: vertical;
    transition: border-color var(--dur-hover) var(--ease-soft);
}

.bf-textarea:focus {
    outline: none;
    border-color: var(--color-selected-2);
}

.bf-file {
    display: block;
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid var(--bf-border-strong);
    background: var(--color-primary-a0);
    color: var(--color-text-2);
    font-family: var(--bf-sans);
    font-size: 12px;
    cursor: pointer;
}

.bf-file::file-selector-button {
    margin-right: 10px;
    padding: 6px 12px;
    border-radius: 6px;
    border: 1px solid var(--bf-border-strong);
    background: var(--color-primary-a20);
    color: var(--color-text-1);
    font-family: var(--bf-sans);
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color var(--dur-hover) var(--ease-soft);
}

.bf-file::file-selector-button:hover {
    background: var(--color-primary-a30);
}

/* ---------------------------------------------------------------------------
   Status pills

   Used for the parse result, the font-load result and the copy confirmation.
   Each one is also an aria-live region in the markup, so the text has to say
   what happened on its own — the colour is a second signal, never the only one.
   --------------------------------------------------------------------------- */

.bf-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border-radius: 24px;
    font-family: var(--bf-sans);
    font-size: 11.5px;
    font-weight: 600;
}

.bf-status:empty {
    display: none;
}

.bf-status[data-tone="ok"] {
    background: rgba(74, 222, 128, 0.14);
    color: #38b869;
}

:root:not([data-theme="light"]) .bf-status[data-tone="ok"] {
    color: #4ade80;
}

.bf-status[data-tone="error"] {
    background: rgba(231, 140, 140, 0.16);
    color: var(--color-warning-2);
}

:root:not([data-theme="light"]) .bf-status[data-tone="error"] {
    color: var(--color-warning-1);
}

.bf-status[data-tone="info"] {
    background: rgba(91, 148, 198, 0.16);
    color: var(--color-accent-text);
}

/* ---------------------------------------------------------------------------
   Panels
   --------------------------------------------------------------------------- */

.bf-panel {
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.bf-panel[hidden] {
    display: none;
}

.bf-panel-hint {
    margin: 0;
    font-family: var(--bf-sans);
    font-size: 13px;
    line-height: 1.6;
    color: var(--color-text-2);
}

.bf-panel-hint code {
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--color-primary-a0);
    color: var(--color-text-1);
    font-family: var(--bf-mono);
    font-size: 12px;
}

.bf-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------------------------------------------------------------------------
   Editor layout

   Desktop: a rail of every glyph down the left, the one being edited on the
   right. Below 780px the rail becomes a single horizontal strip above the
   grid — a 95-character vertical list on a phone would push the thing the
   reader is actually editing off the bottom of the screen.
   --------------------------------------------------------------------------- */

.bf-editor {
    display: grid;
    grid-template-columns: 188px minmax(0, 1fr);
    gap: 0;
    padding: 0;
}

.bf-rail {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 4px;
    padding: 8px;
    max-height: 620px;
    overflow: auto;
    border-right: 1px solid var(--bf-border);
    background: var(--color-primary-a0);
}

.bf-glyph {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 4px;
    border-radius: 6px;
    border: 1px solid transparent;
    background: transparent;
    cursor: pointer;
    transition:
        border-color var(--dur-hover) var(--ease-soft),
        background-color var(--dur-hover) var(--ease-soft);
}

@media (hover: hover) {
    .bf-glyph:hover {
        border-color: var(--color-primary-a30);
    }
}

.bf-glyph[aria-pressed="true"] {
    border-color: var(--color-selected-2);
    background: rgba(91, 148, 198, 0.16);
}

.bf-glyph canvas {
    display: block;
    image-rendering: pixelated;
}

.bf-glyph-label {
    font-family: var(--bf-mono);
    font-size: 9px;
    line-height: 1;
    color: var(--color-primary-a30);
}

.bf-glyph[aria-pressed="true"] .bf-glyph-label {
    color: var(--color-text-1);
}

.bf-canvas {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px;
    min-width: 0;
    overflow: auto;
}

.bf-tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}

.bf-tool-label {
    font-family: var(--bf-sans);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-primary-a30);
    white-space: nowrap;
}

.bf-current-glyph {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 8px;
    background: var(--color-primary-a0);
    border: 1px solid var(--bf-border-strong);
    font-family: var(--bf-mono);
    font-size: 18px;
    color: var(--color-text-1);
}

/* ---------------------------------------------------------------------------
   Pixel grid

   Cells are plain divs with classes, not inline colours, so a theme swap
   repaints them with no JS involved. touch-action: none on the grid is what
   makes drag-painting work on a phone at all — without it the first move is
   claimed by the scroller and the pointer events stop arriving.
   --------------------------------------------------------------------------- */

.bf-grid {
    display: inline-flex;
    flex-direction: column;
    width: fit-content;
    border: 1px solid var(--bf-border-strong);
    border-radius: 8px;
    overflow: hidden;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}

.bf-grid:empty {
    display: none;
}

.bf-grid-row {
    display: flex;
}

.bf-rownum {
    flex-shrink: 0;
    width: 24px;
    height: var(--bf-cell-size);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 5px;
    box-sizing: border-box;
    background: var(--color-primary-a0);
    border-right: 1px solid var(--bf-border-strong);
    font-family: var(--bf-mono);
    font-size: 9px;
    color: var(--color-primary-a30);
}

.bf-grid-row.is-baseline .bf-rownum {
    background: var(--bf-baseline-fill);
    color: var(--bf-baseline-text);
}

.bf-cell {
    flex-shrink: 0;
    width: var(--bf-cell-size);
    height: var(--bf-cell-size);
    box-sizing: border-box;
    background: var(--bf-cell-off);
    border: 1px solid var(--bf-cell-edge);
    cursor: crosshair;
    transition: background-color 60ms linear;
}

.bf-grid-row.is-baseline .bf-cell {
    background: var(--bf-baseline-fill);
    border-color: var(--bf-baseline-edge);
}

/* Last, so an on pixel wins over the baseline tint underneath it. */
.bf-cell.is-on,
.bf-grid-row.is-baseline .bf-cell.is-on {
    background: var(--bf-pixel-on);
}

/* The keyboard caret. Drawn inside the cell's own edge so it does not shift
   the grid, and only present while the grid actually has focus — see
   paintCursor() in bitmap-font-editor.js. */
.bf-cell.is-cursor {
    position: relative;
    outline: 2px solid var(--color-selected-3);
    outline-offset: -2px;
}

.bf-grid:focus-visible {
    outline: 2px solid var(--color-selected-3);
    outline-offset: 3px;
}

.bf-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    font-family: var(--bf-sans);
    font-size: 11px;
    color: var(--color-primary-a30);
}

.bf-legend span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.bf-swatch {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    background: var(--bf-pixel-on);
}

.bf-swatch--baseline {
    background: var(--bf-baseline-fill);
    border: 1px solid var(--bf-baseline-edge);
}

.bf-empty {
    font-family: var(--bf-sans);
    font-size: 13px;
    color: var(--color-primary-a30);
    margin: 0;
}

@media (max-width: 780px) {
    .bf-editor {
        grid-template-columns: minmax(0, 1fr);
    }

    .bf-rail {
        flex-wrap: nowrap;
        max-height: none;
        overflow-x: auto;
        overflow-y: hidden;
        border-right: none;
        border-bottom: 1px solid var(--bf-border);
    }

    .bf-glyph {
        flex-shrink: 0;
    }
}

/* ---------------------------------------------------------------------------
   Output panel
   --------------------------------------------------------------------------- */

.bf-output {
    min-height: 320px;
    font-size: 11.5px;
}

/* ---------------------------------------------------------------------------
   Font import dialog

   A real <dialog> rather than a div with a z-index: it puts itself in the top
   layer (so it clears the site's fixed title bar without a z-index race), traps
   focus, and answers Escape on its own. The site's own Escape handler in
   js_main.js would otherwise open the sidebar behind it — see the capture-phase
   listener in bitmap-font-editor.js for that half.
   --------------------------------------------------------------------------- */

.bf-dialog {
    width: min(560px, calc(100vw - 32px));
    max-height: min(86vh, 860px);
    padding: 0;
    border: 1px solid var(--bf-border-strong);
    border-radius: 16px;
    background: var(--color-primary-a10);
    color: var(--color-text-1);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
    overflow: hidden;
}

.bf-dialog::backdrop {
    background: rgba(0, 0, 0, 0.6);
}

.bf-dialog-body {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 20px;
    max-height: min(86vh, 860px);
    box-sizing: border-box;
    overflow-y: auto;
}

.bf-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.bf-dialog-head h3 {
    margin: 0;
    font-family: var(--bf-sans);
    font-size: 15px;
    font-weight: 700;
    color: var(--color-text-1);
}

.bf-dialog-sep {
    height: 1px;
    background: var(--bf-border-strong);
}

.bf-section {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.bf-label {
    font-family: var(--bf-sans);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--color-text-2);
}

.bf-label-value {
    color: var(--color-text-1);
    font-family: var(--bf-mono);
}

.bf-hint {
    font-family: var(--bf-sans);
    font-size: 10.5px;
    line-height: 1.5;
    color: var(--color-primary-a30);
}

.bf-grid-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}

@media (max-width: 480px) {
    .bf-grid-2 {
        grid-template-columns: 1fr;
    }
}

.bf-choices {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.bf-choice {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 12px;
    border-radius: 8px;
    border: 1px solid var(--bf-border-strong);
    font-family: var(--bf-sans);
    font-size: 11.5px;
    font-weight: 600;
    color: var(--color-text-2);
    cursor: pointer;
    transition:
        border-color var(--dur-hover) var(--ease-soft),
        background-color var(--dur-hover) var(--ease-soft),
        color var(--dur-hover) var(--ease-soft);
}

.bf-choice input {
    accent-color: var(--color-selected-2);
    margin: 0;
}

.bf-choice:has(input:checked) {
    border-color: var(--color-selected-2);
    background: rgba(91, 148, 198, 0.16);
    color: var(--color-text-1);
}

/* :has() is recent enough to be worth a fallback. The script mirrors the
   checked state onto the label as .is-checked, which every browser can style. */
.bf-choice.is-checked {
    border-color: var(--color-selected-2);
    background: rgba(91, 148, 198, 0.16);
    color: var(--color-text-1);
}

/* "Every character in the font" starts disabled and only lights up once a
   font is loaded and its cmap table has actually been read — see is-checked
   above for why both the :has() and the mirrored class are here. */
.bf-choice:has(input:disabled),
.bf-choice.is-disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

.bf-preview {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 3px;
    min-height: 52px;
    padding: 8px;
    border-radius: 8px;
    border: 1px solid var(--bf-border-strong);
    background: var(--bf-preview-bg);
}

.bf-preview canvas {
    display: block;
    image-rendering: pixelated;
}

.bf-preview-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
}

.bf-preview-label {
    font-family: var(--bf-mono);
    font-size: 7px;
    line-height: 1;
    color: var(--color-primary-a30);
}

.bf-preview-empty {
    width: 100%;
    align-self: center;
    text-align: center;
    font-family: var(--bf-sans);
    font-size: 11.5px;
    color: var(--color-primary-a30);
}

.bf-dialog-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.bf-dialog-actions .bf-hint {
    margin-right: auto;
}
