johannes-kaindl_json-editor/styles.css
Johannes Kaindl 8948d2acc3
style(lint): drop !important — use selector specificity for flash + copy-btn reveal
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 00:12:58 +02:00

876 lines
27 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* json-editor — styles
Direction B visual redesign. Theme-aware: every colour derives from an
Obsidian CSS variable; hardcoded values appear only as var() fallbacks. */
/* ─── Design tokens ───────────────────────────────────────────────────
Scoped to the plugin's top-level surfaces; inherited by descendants.
The tooltip and code-block embeds live outside the file view, so each
plugin root declares the tokens itself. */
.json-toolbar,
.json-editor-body,
.json-error-banner,
.json-codeblock,
.json-tooltip {
--jv-bg: var(--background-primary);
--jv-bg-panel: var(--background-secondary);
--jv-bg-inset: var(--background-secondary-alt, var(--background-secondary));
--jv-border: var(--background-modifier-border);
--jv-border-hover: var(--background-modifier-border-hover, var(--background-modifier-border));
--jv-hover: var(--background-modifier-hover);
--jv-fg: var(--text-normal);
--jv-fg-muted: var(--text-muted);
--jv-fg-faint: var(--text-faint);
--jv-accent: var(--interactive-accent);
--jv-accent-text: var(--text-on-accent);
--jv-syntax-key: var(--color-cyan, #4ec9b0);
--jv-syntax-string: var(--color-green, #6a9955);
--jv-syntax-number: var(--color-blue, #569cd6);
--jv-syntax-boolean: var(--color-purple, #c586c0);
--jv-syntax-null: var(--text-faint);
--jv-syntax-punct: var(--text-muted);
--jv-space-1: 2px;
--jv-space-2: 4px;
--jv-space-3: 6px;
--jv-space-4: 8px;
--jv-space-5: 12px;
--jv-space-6: 16px;
--jv-radius-sm: 4px;
--jv-radius-md: 6px;
--jv-radius-lg: 8px;
--jv-nest-tint: color-mix(in srgb, var(--background-modifier-border) 22%, transparent);
--jv-match-bg: var(--text-highlight-bg, rgba(255, 214, 10, 0.3));
--jv-match-fg: var(--text-normal);
--jv-dur-fast: 120ms;
--jv-dur-base: 150ms;
--jv-ease: ease-out;
}
/* ─── Toolbar ─────────────────────────────────────────────────────────
Unified bar: breadcrumb, search, mode toggle. */
.json-toolbar {
display: flex;
align-items: center;
gap: var(--jv-space-5);
padding: var(--jv-space-4) var(--jv-space-5);
background: var(--jv-bg-panel);
border-bottom: 1px solid var(--jv-border);
}
/* ─── Breadcrumb ──────────────────────────────────────────────────── */
.json-breadcrumb {
flex-shrink: 1;
min-width: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--jv-space-2);
font-family: var(--font-interface);
font-size: var(--font-ui-smaller);
color: var(--jv-fg-muted);
}
.bc-seg {
/* Reset native <button> chrome — these render as inline breadcrumb text. */
background: none;
border: none;
font: inherit;
padding: var(--jv-space-1) var(--jv-space-3);
border-radius: var(--jv-radius-sm);
color: var(--jv-fg-muted);
cursor: pointer;
transition: background var(--jv-dur-fast) var(--jv-ease), color var(--jv-dur-fast) var(--jv-ease);
}
.bc-seg:hover {
background: var(--jv-hover);
color: var(--jv-fg);
}
.bc-seg:focus-visible {
outline: 2px solid var(--jv-accent);
outline-offset: -1px;
color: var(--jv-fg);
}
/* Current location: emphasized by weight, not a filled accent chip — a filled
breadcrumb segment reads as a button/tab rather than a path location. */
.bc-seg-terminal {
color: var(--jv-fg);
font-weight: 600;
}
.bc-sep {
color: var(--jv-fg-faint);
cursor: default;
user-select: none;
}
/* ─── Search bar ──────────────────────────────────────────────────── */
.json-search-bar {
display: flex;
align-items: center;
gap: var(--jv-space-2);
background: var(--jv-bg-inset);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-sm);
padding: var(--jv-space-1) var(--jv-space-3);
flex: 1;
min-width: 0;
max-width: 32em;
transition: border-color var(--jv-dur-fast) var(--jv-ease);
}
.json-search-bar:focus-within {
border-color: var(--jv-accent);
}
.json-search-icon {
flex-shrink: 0;
color: var(--jv-fg-muted);
}
.json-search-input {
flex: 1;
min-width: 0;
border: none;
outline: none;
background: transparent;
color: var(--jv-fg);
font-family: var(--font-interface);
font-size: inherit;
padding: 0;
}
.json-search-input::placeholder {
color: var(--jv-fg-faint);
}
.json-search-count {
color: var(--jv-fg-muted);
font-size: 0.85em;
white-space: nowrap;
font-family: var(--font-interface);
}
.json-search-count.is-empty {
color: var(--jv-fg-faint);
font-style: italic;
}
.json-search-clear {
background: none;
border: none;
color: var(--jv-fg-muted);
cursor: pointer;
padding: 0 var(--jv-space-2);
font-size: 1.1em;
line-height: 1;
border-radius: var(--jv-radius-sm);
transition: color var(--jv-dur-fast) var(--jv-ease), background var(--jv-dur-fast) var(--jv-ease);
}
.json-search-clear:hover {
color: var(--jv-fg);
background: var(--jv-hover);
}
/* ─── Mode toggle ─────────────────────────────────────────────────────
Segmented control. */
.json-mode-toggle {
display: inline-flex;
flex-shrink: 0;
gap: var(--jv-space-1);
padding: var(--jv-space-1);
background: var(--jv-bg-inset);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-md);
}
.json-mode-pill {
padding: var(--jv-space-2) var(--jv-space-5);
border: none;
border-radius: var(--jv-radius-sm);
background: transparent;
color: var(--jv-fg-muted);
cursor: pointer;
font-size: var(--font-ui-small);
font-family: var(--font-interface);
transition: background var(--jv-dur-fast) var(--jv-ease), color var(--jv-dur-fast) var(--jv-ease);
}
.json-mode-pill:hover:not(:disabled):not(.active) {
color: var(--jv-fg);
}
.json-mode-pill.active {
background: var(--jv-accent);
color: var(--jv-accent-text);
}
.json-mode-pill:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* ─── Editor body ─────────────────────────────────────────────────── */
.json-editor-body {
padding: var(--jv-space-5);
background: var(--jv-bg);
}
/* ─── Tree ────────────────────────────────────────────────────────── */
.json-tree-root {
font-family: var(--font-monospace);
font-size: var(--font-ui-small);
line-height: 1.6;
color: var(--jv-fg);
}
.json-container {
display: block;
}
/* Nested blocks: a subtle tint that deepens as overlays stack.
Capped at depth 3 — deeper containers sit on the depth-3 tint already. */
.json-container[data-depth="1"]:not(.is-collapsed),
.json-container[data-depth="2"]:not(.is-collapsed),
.json-container[data-depth="3"]:not(.is-collapsed) {
background: var(--jv-nest-tint);
border-radius: var(--jv-radius-md);
padding: var(--jv-space-2) var(--jv-space-3);
margin: var(--jv-space-2) 0;
}
.json-content {
display: block;
padding-left: var(--jv-space-5);
border-left: 2px solid var(--jv-border);
margin-left: var(--jv-space-3);
opacity: 1;
overflow: hidden;
/* No max-height cap on the expanded state: a finite cap (was 5000px) hard-
clipped large trees with no scrollbar. The collapsed state still uses
max-height: 0 to hide children; the open direction snaps rather than
animating (accepted trade-off — see blocker 1.7). */
transition: opacity var(--jv-dur-fast) var(--jv-ease);
}
.json-content.collapsed {
max-height: 0;
opacity: 0;
}
/* ─── Collapse toggle & chip ──────────────────────────────────────── */
.json-collapse-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
cursor: pointer;
user-select: none;
color: var(--jv-fg-faint);
}
.json-collapse-toggle:hover {
color: var(--jv-fg);
}
.json-collapse-toggle svg {
transition: transform var(--jv-dur-base) var(--jv-ease);
}
.json-collapse-toggle.is-open svg {
transform: rotate(90deg);
}
.json-collapse-chip {
display: none;
}
.json-container.is-collapsed > .json-collapse-chip {
display: inline-block;
margin-left: var(--jv-space-2);
padding: var(--jv-space-1) var(--jv-space-3);
border-radius: var(--jv-radius-lg);
background: var(--jv-bg-inset);
color: var(--jv-fg-muted);
font-size: 0.85em;
}
/* When collapsed, the chip stands in for the brackets + children. */
.json-container.is-collapsed > .json-bracket {
display: none;
}
/* ─── Row ─────────────────────────────────────────────────────────── */
.json-row {
display: flex;
align-items: flex-start;
padding: var(--jv-space-1) var(--jv-space-3);
border-radius: var(--jv-radius-sm);
position: relative;
transition: background var(--jv-dur-fast) var(--jv-ease);
}
.json-row:hover {
background: color-mix(in srgb, var(--jv-accent) 8%, transparent);
box-shadow: inset 2px 0 0 0 var(--jv-accent);
}
/* Higher specificity instead of !important so the scroll-flash wins over the
:hover / .json-match / .json-on-path row backgrounds. */
.json-tree-root .json-row.json-row-flash {
background: var(--jv-accent);
color: var(--jv-accent-text);
box-shadow: none;
}
.json-key {
color: var(--jv-syntax-key);
font-family: var(--font-interface);
font-weight: 600;
margin-right: var(--jv-space-3);
}
.json-bracket {
color: var(--jv-syntax-punct);
}
.json-index {
color: var(--jv-fg-faint);
font-size: 0.9em;
margin-right: var(--jv-space-2);
font-family: var(--font-monospace);
}
.json-marker {
color: var(--jv-fg-faint);
margin-right: var(--jv-space-2);
opacity: 0.6;
font-family: var(--font-monospace);
}
/* ─── Values ──────────────────────────────────────────────────────── */
.json-string {
color: var(--jv-syntax-string);
word-break: break-word;
}
.json-number {
color: var(--jv-syntax-number);
}
.json-boolean {
color: var(--jv-syntax-boolean);
}
.json-null {
color: var(--jv-syntax-null);
font-style: italic;
}
.json-editable {
cursor: pointer;
border-bottom: 1px dotted transparent;
}
.json-editable:hover {
border-bottom-color: var(--jv-fg-muted);
}
.json-inline-edit {
font-family: var(--font-monospace);
font-size: inherit;
padding: var(--jv-space-1) var(--jv-space-3);
border: 1.5px solid var(--jv-accent);
border-radius: var(--jv-radius-sm);
background: var(--jv-bg);
color: var(--jv-fg);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--jv-accent) 15%, transparent);
}
/* ─── Copy button ─────────────────────────────────────────────────── */
.json-copy-btn {
margin-left: auto;
padding: 0 var(--jv-space-3);
font-family: var(--font-interface);
font-size: 0.85em;
background: transparent;
color: var(--jv-fg-muted);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-sm);
cursor: pointer;
opacity: 0;
transition: opacity var(--jv-dur-fast) var(--jv-ease), color var(--jv-dur-fast) var(--jv-ease),
background var(--jv-dur-fast) var(--jv-ease);
}
.json-row:hover .json-copy-btn {
opacity: 0.7;
}
/* Scoped (0,4,0) so opacity:1 wins over the :hover / :focus-within reveal rules
(which set 0.7) without !important. */
.json-tree-root .json-row .json-copy-btn:hover {
opacity: 1;
color: var(--jv-fg);
background: var(--jv-hover);
}
.json-tree-root .json-row .json-copy-btn.copied {
color: var(--text-success, var(--jv-syntax-string));
opacity: 1;
}
/* ─── Error banner ────────────────────────────────────────────────── */
.json-error-banner {
margin: var(--jv-space-4) var(--jv-space-5);
padding: var(--jv-space-4) var(--jv-space-5);
background: color-mix(in srgb, var(--text-error) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--text-error) 35%, transparent);
border-left: 3px solid var(--text-error);
border-radius: var(--jv-radius-md);
color: var(--text-error);
font-family: var(--font-monospace);
font-size: var(--font-ui-smaller);
}
.json-error-banner::before {
content: "⚠";
margin-right: var(--jv-space-3);
}
/* ─── Empty state ─────────────────────────────────────────────────── */
.json-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--jv-space-5);
padding: 64px var(--jv-space-6);
color: var(--jv-fg-muted);
}
.json-empty-state::before {
content: "{ }";
font-family: var(--font-monospace);
font-size: 34px;
color: var(--jv-fg-faint);
border: 2px dashed var(--jv-border);
border-radius: var(--jv-radius-lg);
padding: var(--jv-space-4) var(--jv-space-6);
}
.json-empty-state-title {
color: var(--jv-fg);
font-weight: 600;
font-size: 1.1em;
}
.json-empty-state-hint {
color: var(--jv-fg-muted);
font-size: var(--font-ui-smaller);
}
.json-empty-state-init {
padding: var(--jv-space-3) var(--jv-space-6);
background: var(--jv-accent);
color: var(--jv-accent-text);
border: none;
border-radius: var(--jv-radius-md);
cursor: pointer;
font-family: var(--font-interface);
}
.json-empty-state-init:hover {
background: var(--interactive-accent-hover, var(--jv-accent));
}
/* ─── Tooltip ─────────────────────────────────────────────────────── */
.json-tooltip {
position: absolute;
z-index: var(--layer-tooltip, 100);
padding: var(--jv-space-3) var(--jv-space-4);
background: var(--jv-bg-panel);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-md);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
font-size: var(--font-ui-smaller);
max-width: 320px;
pointer-events: none;
}
.json-tooltip[hidden] {
display: none;
}
.json-tooltip .tt-meta {
margin-bottom: var(--jv-space-3);
color: var(--jv-fg-muted);
}
.json-tooltip .tt-type {
display: inline-block;
padding: 0 var(--jv-space-3);
border-radius: var(--jv-radius-sm);
background: var(--jv-accent);
color: var(--jv-accent-text);
font-size: 0.85em;
font-weight: 600;
text-transform: uppercase;
}
.json-tooltip .tt-path {
color: var(--jv-fg-muted);
}
.json-tooltip .tt-preview {
font-family: var(--font-monospace);
color: var(--jv-fg);
word-break: break-word;
white-space: pre-wrap;
}
/* ─── Code-block embed ────────────────────────────────────────────── */
.json-codeblock {
margin: var(--jv-space-4) 0;
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-md);
background: var(--jv-bg-inset);
overflow: hidden;
}
.json-codeblock-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--jv-space-3) var(--jv-space-5);
background: var(--jv-bg-panel);
border-bottom: 1px solid var(--jv-border);
}
.json-codeblock-label {
font-size: 0.8em;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--jv-accent);
}
.json-codeblock.is-error .json-codeblock-label {
color: var(--text-error);
}
.json-codeblock-copy {
padding: 0 var(--jv-space-3);
background: transparent;
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-sm);
color: var(--jv-fg-muted);
font-size: 0.8em;
cursor: pointer;
transition: color var(--jv-dur-fast) var(--jv-ease), background var(--jv-dur-fast) var(--jv-ease);
}
.json-codeblock-copy:hover {
color: var(--jv-fg);
background: var(--jv-hover);
}
.json-codeblock-copy.copied {
color: var(--text-success, var(--jv-syntax-string));
}
.json-codeblock-error {
padding: var(--jv-space-4) var(--jv-space-5);
font-family: var(--font-monospace);
font-size: var(--font-ui-smaller);
color: var(--text-error);
}
.json-codeblock .json-tree-root {
padding: var(--jv-space-4) var(--jv-space-5);
}
/* ─── Row actions (rename + delete) ───────────────────────────────── */
/* Hover-revealable, matches the .json-copy-btn pattern. */
.json-row-actions {
display: inline-flex;
align-items: center;
gap: var(--jv-space-1);
margin-left: var(--jv-space-2);
opacity: 0;
transition: opacity var(--jv-dur-fast) var(--jv-ease);
}
.json-row:hover > .json-row-actions,
.json-row:focus-within > .json-row-actions,
.json-row:focus > .json-row-actions {
opacity: 1;
}
.json-row-action {
background: none;
border: 1px solid transparent;
border-radius: var(--jv-radius-sm);
color: var(--jv-fg-muted);
cursor: pointer;
padding: 0 var(--jv-space-2);
font-family: var(--font-interface);
font-size: 0.85em;
line-height: 1.4;
transition: color var(--jv-dur-fast) var(--jv-ease), background var(--jv-dur-fast) var(--jv-ease),
border-color var(--jv-dur-fast) var(--jv-ease);
}
.json-row-action:hover {
color: var(--jv-fg);
background: var(--jv-hover);
border-color: var(--jv-border);
}
.json-row-delete:hover {
color: var(--text-error, #d32f2f);
}
/* ─── Add affordance (per container) ──────────────────────────────── */
.json-add-affordance {
margin-top: var(--jv-space-2);
}
.json-add-trigger {
background: none;
border: 1px dashed var(--jv-border);
border-radius: var(--jv-radius-sm);
color: var(--jv-fg-muted);
cursor: pointer;
padding: var(--jv-space-2) var(--jv-space-4);
font-family: var(--font-interface);
font-size: 0.85em;
transition: color var(--jv-dur-fast) var(--jv-ease), border-color var(--jv-dur-fast)
var(--jv-ease), background var(--jv-dur-fast) var(--jv-ease);
}
.json-add-trigger:hover {
color: var(--jv-fg);
border-color: var(--jv-accent);
background: var(--jv-hover);
border-style: solid;
}
.json-add-input {
background: var(--jv-bg-inset);
border: 1px solid var(--jv-accent);
border-radius: var(--jv-radius-sm);
color: var(--jv-fg);
padding: var(--jv-space-1) var(--jv-space-3);
font-family: var(--font-monospace);
font-size: inherit;
outline: none;
}
/* Empty containers — make the brackets sit tightly without a content area
pushing them apart visually. The chip "{ 0 keys }" stays muted. */
.json-tree-root .json-container.is-empty .json-collapse-chip {
font-style: italic;
opacity: 0.7;
}
/* Inline key-rename input (extends .json-inline-edit) */
.json-key-rename {
color: var(--jv-syntax-key);
}
/* ─── Search filter (strict-hide + match highlight) ───────────────── */
/* When the filter is active, hide every row that is neither a match
nor on the ancestor chain of a match. */
.json-tree-root.json-filter-active .json-row:not(.json-match):not(.json-on-path) {
display: none;
}
/* Match highlight — applies to the key span and any primitive value
span inside the matching row. */
.json-tree-root .json-match > .json-key,
.json-tree-root .json-match > .json-string,
.json-tree-root .json-match > .json-number,
.json-tree-root .json-match > .json-boolean,
.json-tree-root .json-match > .json-null {
background: var(--jv-match-bg);
color: var(--jv-match-fg);
border-radius: 2px;
padding: 0 2px;
box-shadow: 0 0 0 1px var(--jv-match-bg);
}
/* ─── Schema banner ──────────────────────────────────────────────── */
.json-schema-banner {
margin: 0;
padding: var(--jv-space-3) var(--jv-space-5);
font-family: var(--font-interface);
font-size: 0.9em;
color: var(--text-error, #d32f2f);
background: color-mix(in srgb, var(--text-error, #d32f2f) 8%, var(--jv-bg-inset));
border-bottom: 1px solid var(--jv-border);
}
.json-schema-banner.is-schema-parse-error {
background: color-mix(in srgb, var(--text-warning, #c8862e) 8%, var(--jv-bg-inset));
color: var(--text-warning, #c8862e);
}
/* ─── Lossy-roundtrip warn banner (blocker 1.4) ──────────────────── */
.json-lossy-banner {
margin: 0;
padding: var(--jv-space-3) var(--jv-space-5);
font-family: var(--font-interface);
font-size: 0.9em;
color: var(--text-warning, #c8862e);
background: color-mix(in srgb, var(--text-warning, #c8862e) 8%, var(--jv-bg-inset));
border-bottom: 1px solid var(--jv-border);
}
.json-lossy-banner::before {
content: "⚠";
margin-right: var(--jv-space-3);
}
/* ─── Large-file warn banner (blocker 4.1) ───────────────────────── */
.json-large-file-banner {
display: flex;
align-items: center;
flex-wrap: wrap; /* on narrow/mobile, the button wraps below the message */
gap: var(--jv-space-3);
margin: 0;
padding: var(--jv-space-3) var(--jv-space-5);
font-family: var(--font-interface);
font-size: 0.9em;
color: var(--text-warning, #c8862e);
background: color-mix(in srgb, var(--text-warning, #c8862e) 8%, var(--jv-bg-inset));
border-bottom: 1px solid var(--jv-border);
}
.json-large-file-message {
flex: 1;
min-width: 12em;
}
.json-large-file-message::before {
content: "⚠";
margin-right: var(--jv-space-3);
}
/* Native Obsidian button look (the missing font-family made it render in the
UA default/monospace; padding matches the .json-empty-state-init precedent so
the label has comfortable breathing room). */
.json-large-file-load {
margin-left: auto;
white-space: nowrap;
font-family: var(--font-interface);
font-size: var(--font-ui-small);
padding: var(--jv-space-3) var(--jv-space-5);
border: none;
border-radius: var(--jv-radius-md);
background: var(--interactive-normal);
color: var(--text-normal);
box-shadow: var(--input-shadow);
cursor: pointer;
}
.json-large-file-load:hover {
background: var(--interactive-hover);
}
/* The `hidden` attribute must override the class-level `display` — they have
equal specificity, so author source-order would otherwise keep these elements
visible even when toggled off via el.hidden (the same trap already fixed for
.json-tooltip). Without this, e.g. the large-file banner's button shows on
every file and the search-clear (×) shows with an empty query. */
.json-large-file-banner[hidden],
.json-schema-banner[hidden],
.json-loss-banner[hidden],
.json-search-bar[hidden],
.json-search-count[hidden],
.json-search-clear[hidden] {
display: none;
}
/* ─── Row error marker ───────────────────────────────────────────── */
.json-row.json-row-error {
outline: 1px solid var(--text-error, #d32f2f);
outline-offset: -1px;
border-radius: var(--jv-radius-sm);
}
.json-row.json-row-error > .json-key,
.json-row.json-row-error > .json-index {
color: var(--text-error, #d32f2f);
}
/* ─── Drag handle (per row) ──────────────────────────────────────── */
.json-drag-handle {
display: inline-block;
width: 1em;
margin-right: var(--jv-space-1);
color: var(--jv-fg-muted);
cursor: grab;
user-select: none;
font-size: 0.85em;
line-height: 1;
opacity: 0;
transition: opacity var(--jv-dur-fast) var(--jv-ease);
}
.json-row:hover > .json-drag-handle,
.json-row:focus-within > .json-drag-handle,
.json-row:focus > .json-drag-handle {
opacity: 0.6;
}
.json-row:hover > .json-drag-handle:hover {
opacity: 1;
}
.json-row.json-dragging {
opacity: 0.4;
}
.json-row.json-drop-target-before {
box-shadow: inset 0 2px 0 0 var(--jv-accent);
}
.json-row.json-drop-target-after {
box-shadow: inset 0 -2px 0 0 var(--jv-accent);
}
/* ─── Type-switch menu (popover) ──────────────────────────────────── */
.json-type-menu {
display: inline-flex;
flex-wrap: wrap;
gap: var(--jv-space-1);
margin-top: var(--jv-space-2);
margin-left: var(--jv-space-5);
padding: var(--jv-space-2);
background: var(--jv-bg-inset);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-sm);
box-shadow: var(--jv-shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.12));
z-index: 10;
}
.json-type-option {
background: var(--jv-bg);
border: 1px solid var(--jv-border);
border-radius: var(--jv-radius-sm);
color: var(--jv-fg);
cursor: pointer;
padding: var(--jv-space-1) var(--jv-space-3);
font-family: var(--font-interface);
font-size: 0.85em;
transition: background var(--jv-dur-fast) var(--jv-ease), border-color var(--jv-dur-fast)
var(--jv-ease);
}
.json-type-option:hover:not(:disabled) {
background: var(--jv-hover);
border-color: var(--jv-accent);
}
.json-type-option:disabled,
.json-type-option.is-current {
opacity: 0.5;
cursor: default;
background: var(--jv-bg-inset);
}
/* ─── Reduced motion ──────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.json-content,
.json-collapse-toggle svg,
.json-row,
.json-copy-btn,
.bc-seg,
.json-mode-pill,
.json-codeblock-copy,
.json-drag-handle,
.json-type-option {
transition: none;
}
}
/* ─── Copy button: reveal without hover (keyboard / touch-laptop, 4.3.1) ─── */
.json-row:focus-within .json-copy-btn {
opacity: 0.7;
}
/* ─── Mobile: larger tap targets; hover-only affordances are not rendered ─── */
body.is-mobile .json-collapse-toggle {
min-width: 44px;
min-height: 44px;
margin: -15px 0; /* grow only the hit area; keep the visual chevron size */
}
/* Toolbar icon buttons (undo/redo): centered icon; square via .clickable-icon. */
.json-toolbar-btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Touch targets — Apple HIG 44pt / Material 48dp (audit §4.4). Keeps the
toolbar's interactive controls tappable on mobile. */
body.is-mobile .json-toolbar-btn,
body.is-mobile .json-search-clear {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
body.is-mobile .json-mode-pill {
min-height: 44px;
}
/* Mobile value-editing controls: tapping a value opens an inline field (text /
number / key-rename) or a boolean checkbox, and "+ Add" adds a value — give
them comfortable tap/type targets too (audit §4.4). */
body.is-mobile .json-inline-edit:not([type="checkbox"]),
body.is-mobile .json-add-input,
body.is-mobile .json-add-trigger {
min-height: 44px;
padding: var(--jv-space-2) var(--jv-space-3);
}
body.is-mobile .json-inline-edit[type="checkbox"] {
width: 22px;
height: 22px;
}
body.is-mobile .json-large-file-load {
min-height: 44px;
}