body {
  background-color: #fff !important;
}

/* Replaces Bootstrap's border-triangle caret with a Font Awesome chevron-down.
   Double-class selector beats Bootstrap's .dropdown-toggle::after specificity. */
.dropdown-toggle.dropdown-toggle-fa::after {
  font-family: "Font Awesome 7 Pro";
  font-weight: 400;
  content: "\f107";
  border-top: 0;
  border-right: 0;
  border-left: 0;
  vertical-align: baseline;
  margin-left: 0.25em;
  display: inline-block;
}

/* Square off the tab headers in the editor's sections dropdown and drop the
   outer edges (top, plus the far-left and far-right) so they sit flush with the
   menu; emphasise the active tab. */
.dropdown-menu .nav-tabs .nav-link {
  border-radius: 0;
  border-top: 0;
}

.dropdown-menu .nav-tabs .nav-item:first-child .nav-link {
  border-left: 0;
}

.dropdown-menu .nav-tabs .nav-item:last-child .nav-link {
  border-right: 0;
}

.dropdown-menu .nav-tabs .nav-link.active {
  font-weight: 600;
}

/* Both pickers are toggled manually (no Bootstrap Popper), so neither gets
   viewport-aware positioning for free — and .dropdown-menu alone resolves to
   inset:0, which parks the menu ON its trigger instead of under it. Anchor both
   deterministically below the trigger and flush with the wrap's left edge, so
   they cannot spill off the left of the screen; enine-media-picker.js adds
   .enine-picker-flip-right to right-anchor the media one when a right-side
   trigger would otherwise overflow the right edge. The wrap is
   .position-relative in every partial that uses it. Markup:
   themes/_editor/auto/_edit_picker_media{,_config}.html,
   themes/_editor/auto/_edit_picker_icon.html and
   themes/_editor/control/_media_add_tile.html. */
/* The editor's widget header, and the square cells in it.

   NOTHING HERE DECLARES A HEIGHT, and that absence is the fix for #66. The header
   used to state `height: 4rem` for exactly one reason: a square whose side comes
   from its HEIGHT needs that height DEFINITE, because `aspect-ratio` only
   transfers a size to the other axis when the first one is definite up front. So
   the square read `height: 100%; aspect-ratio: 1`, and the header declared a
   height for it to be a percentage OF.

   That worked in Blink and Gecko and collapsed in WebKit. The percentage is
   resolved across three nested flex layers whose heights come from
   `align-items: stretch`, and WebKit treats a stretched height as INDEFINITE for
   percentage resolution — so `height: 100%` computed to `auto`, `aspect-ratio`
   took its width from the glyph inside instead of from the row, and every square
   picker cell came out a sliver wide in Safari while looking correct everywhere
   else. Both pickers collapsed together because both are this one rule.

   OBSERVED IN WEBKITGTK (Epiphany), the same engine family as Safari and the first
   one available here that could actually run it: the cells were not slivers, they
   were each as wide as their own contents — one width per glyph. That is the real
   symptom, and it is NOT what the paragraph above guessed.

   TWO THINGS ARE REQUIRED, and each was measured rather than reasoned:

   `min-width: 0` — the actual cause. A flex item's automatic minimum size floors
   its width at MIN-CONTENT, and that floor beats the width `aspect-ratio` derives.
   So any cell whose glyph is wider than the square wins against the ratio and comes
   out oblong, which is why the cells disagreed with each other rather than all
   collapsing together: the media and cover triggers carry `overflow-hidden` in fill
   mode, which also suppresses the automatic minimum, and the icon trigger does not.
   Reproducible in CHROMIUM with a wide enough glyph (measured 102x66 against an
   intended 66x66), so this was never WebKit-specific — WebKit just crosses the
   threshold at the glyph widths this editor actually renders. Nothing here needs a
   browser conditional.

   A DEFINITE HEIGHT to transfer from — `aspect-ratio` only carries a size to the
   other axis when the first one is definite up front. The header declares one, and
   every level between it and the square carries `h-100` so the percentage resolves
   against a definite box at each step instead of a stretched one. A single missing
   `h-100` breaks the chain: the levels are `control/_{icon,cover,marks}_cell.html`
   and the wrap in each picker partial. Measured without it (stretch instead, plus
   `min-width: 0`): the square takes its width from content, `aspect-ratio` then
   inflates its HEIGHT to match, and the header grows to 104px chasing it.

   The declared height is a `calc()` off `--enine-ctl`, the gutter button's side and
   the only hardcoded dimension in the widget, so the header and the gutter beside it
   still agree by derivation rather than by two numbers kept in step by hand.

   Markup: themes/_editor/widget/_header.html,
   themes/_editor/auto/_edit_picker_icon.html,
   themes/_editor/auto/_edit_picker_media_config.html,
   themes/_editor/control/_cover_cell.html. */
/* THE WIDGET'S ONE HARDCODED DIMENSION (#66), and the two literals in the rules
   below are the only numbers this editor's layout authors. Everything else — the
   gutter's width, the buttons' height, the header's height, the picker squares'
   side — derives from them. That is the whole point of the token: it replaced
   `height: 4rem` on the header sitting beside `height: 66px` on the gutter, two
   numbers that had to agree, derived from each other only in a comment, with
   nothing checking that they still did.

   WHY 31px, AND WHY IT IS ALLOWED TO BE A BARE NUMBER. It is the side of one gutter
   button — a control size, chosen because it reads right against the header, and
   answerable to nothing but taste. `docs/UI_STANDARDS.md` treats a hardcoded
   dimension as a smell because it usually means the structure is not deriving its
   own size; here the structure derives everything FROM this, so the number is the
   root of the derivation rather than a leaf that drifted.

   Its oddness is a fossil. It was 31 because the old arrangement needed
   2 x 31 + 4 == 66 to match the header's measured row height, and 2rem (32px) would
   have missed by 2. Now that the header derives from this instead, 32px would work
   just as well and read more idiomatically — but changing it moves every widget
   header by 2px, so it stays until someone wants that.

   THE 0.25rem BELOW IS A SECOND LITERAL, and it is a mirror, not a choice: it must
   equal Bootstrap's `gap-1`, which `control/_gutter.html` carries on the gutter to
   space the two buttons. A gutter is therefore two buttons plus that gap, and the
   header is exactly as tall as a gutter. Change the class on the markup and this
   breaks silently — the header and the gutter would simply stop lining up, with no
   error anywhere. It is written as a literal because CSS cannot read a utility
   class's value; if Bootstrap's spacer scale is ever re-mapped, grep for it. */
[data-enine-widget-frame] {
  --enine-ctl: 31px;
}

[data-enine-widget-header] {
  height: calc(2 * var(--enine-ctl) + 0.25rem);
}

.enine-picker-square {
  height: 100%;
  aspect-ratio: 1;
  min-width: 0;
  padding: 1rem;
}

.enine-media-picker-dropdown,
.enine-icon-picker-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: auto;
  margin-top: 0.25rem;
}

.enine-media-picker-dropdown.enine-picker-flip-right {
  left: auto;
  right: 0;
}

/* The gutter's favicon picker (#42, control/_gutter.html's `is_site` branch)
   sits in a 31px `.enine-gutter-btn` square, not a header cell — small enough
   that an oversized favicon thumbnail could bulge the gutter's fixed width.
   Clipping the WRAPPER instead of just the trigger squeezed the dropdown into
   that same 31px box, since `.enine-media-picker-dropdown` is a sibling of
   the trigger inside one shared wrap, not a descendant of it — clip the
   trigger alone and the dropdown, positioned off that wrap, is unaffected. */
.enine-gutter-btn .enine-media-trigger {
  overflow: hidden;
}

/* A disabled Delete item (a draft has nothing to delete yet) still read as a
   full-strength destructive action, because Bootstrap 5 text utilities carry
   `!important` — `.text-danger`'s colour beats `.dropdown-item:disabled`'s own,
   which only ever wins against an UN-important declaration. Muting by opacity
   is a different property, so it stacks instead of fighting: still red, just
   faded, the same "unset/inert" language `opacity-25` already gives an empty
   picker glyph elsewhere on this widget. */
.dropdown-item.text-danger:disabled {
  opacity: 0.35;
}

/* Cap the thumbnail grid so a media-rich site can't grow the dropdown taller
   than the viewport (it would otherwise spill off-screen or collide with the
   navbar). The search/upload bar is a sibling above the grid, so it stays
   pinned while only the thumbnails scroll. */
.enine-media-grid {
  max-height: 16rem;
  overflow-y: auto;
}

[data-url] .clickable,
[data-url] .clickable * {
  cursor: pointer;
}

a, a:hover {
  color: unset;
}

a.btn {
  color: var(--bs-btn-color, unset);
}

a.btn:hover {
  color: var(--bs-btn-hover-color, var(--bs-btn-color, unset));
}

img.media, video.media {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
  object-position: center;
  background-color: #00000020;
}

img.media.square, video.media.square {
  aspect-ratio: 1;
}

.scalable-html {
  --cols: 1;
  font-size: calc(16px / var(--cols));
  --bs-spacer: calc(1rem / var(--cols)) !important;
}
.scalable-html h1 {
  font-size: 2.5em !important;
}
.scalable-html h2 {
  font-size: 2em !important;
}
.scalable-html h3 {
  font-size: 1.75em !important;
}
.scalable-html h4 {
  font-size: 1.5em !important;
}
.scalable-html h5 {
  font-size: 1.25em !important;
}
.scalable-html h6 {
  font-size: 1em !important;
}

.scalable-html .display-1 {
  font-size: 5em !important;
}
.scalable-html .display-2 {
  font-size: 4.5em !important;
}
.scalable-html .display-3 {
  font-size: 4em !important;
}
.scalable-html .display-4 {
  font-size: 3.5em !important;
}
.scalable-html .display-5 {
  font-size: 3em !important;
}
.scalable-html .display-6 {
  font-size: 2.5em !important;
}


.scalable-html.col-6, .scalable-html.col-md-6, .scalable-html.col-lg-6 {
    --cols: 2;
}

.scalable-html.col-4, .scalable-html.col-md-4, .scalable-html.col-lg-4 {
    --cols: 3;
}

.scalable-html.col-3, .scalable-html.col-md-3, .scalable-html.col-lg-3 {
    --cols: 4;
}

.scalable-html.col-2, .scalable-html.col-md-2, .scalable-html.col-lg-2 {
    --cols: 6;
}

.container-narrow {
  max-width: 480px;
}

.tabular-nums {
  font-variant-numeric: tabular-nums;
}

.legal a:hover {
  background-color: yellow;
}

.legal {
  line-height: 1.5;
  text-align: justify;
  font-size: 0.875rem;
}

.legal .level-1 {
  padding: 0.5rem 1rem;
  margin: 0.5rem -1rem;
  counter-increment: count 1;
  border: 1px solid transparent;
}

.legal .level-1:hover {
  background-color: #00000010;
  border: 1px solid #00000040;
  border-radius: 0.5rem;
}

.legal .level-1 .heading {
  font-weight: 700;
}

.legal .level-1 ul {
  padding-inline-start: 1em;
  list-style-type: square;
}

.legal .level-2 {
  padding: 0;
  margin-top: 0.5rem;
}

.legal .level-2:not(:last-child) {
  margin-bottom: 0.5rem;
}

.legal .level-2:last-child {
  margin-bottom: 0;
}

.section-tools {
  opacity: 0;
  transition: opacity 0.15s;
}

section:hover .section-tools {
  opacity: 1;
}

@media (hover: none) {
  .section-tools {
    opacity: 1;
  }
}

.tree-chevron {
  transition: transform 0.2s ease;
}

[aria-expanded="true"] .tree-chevron {
  transform: rotate(-180deg);
}

/* Vertical Classes */
.text-vertical  { writing-mode: sideways-lr; white-space: nowrap; }
@media (min-width: 576px)  { .text-vertical-sm  { writing-mode: sideways-lr; white-space: nowrap; } }
@media (min-width: 768px)  { .text-vertical-md  { writing-mode: sideways-lr; white-space: nowrap; } }
@media (min-width: 992px)  { .text-vertical-lg  { writing-mode: sideways-lr; white-space: nowrap; } }
@media (min-width: 1200px) { .text-vertical-xl  { writing-mode: sideways-lr; white-space: nowrap; } }
@media (min-width: 1400px) { .text-vertical-xxl { writing-mode: sideways-lr; white-space: nowrap; } }

/* Responsive width "undo": full-width by default (form-control), auto at lg+.
   Bootstrap 5.2.3 ships no responsive width utilities, so the navbar search
   box needs this to go full-width in the collapsed mobile menu. */
@media (min-width: 992px) { .w-lg-auto { width: auto !important; } }

/* Pair with .border (or .border-top / -end / -bottom / -start): draws the border
   in the foreground colour of whatever ground it is sitting on.

   `bg-primary`, `bg-secondary` and `bg-info` each set `color` to their paired
   foreground token, and an element on no ground inherits --bs-body-color, so
   currentColor is always the foreground actually in effect. That is why this is
   currentColor rather than var(--bs-border-color): four of the six stock themes
   redefine that variable per background and two do not, and a theme that paints
   a ground without redefining it would draw grey borders on colour. Nothing has
   to opt in, and a new theme gets it for free.

   !important is required, not decorative. Bootstrap 5.2.3 generates every border
   utility with it — `.border-end{border-right:… var(--bs-border-color)!important}`
   — so no ordinary rule can reach border-color no matter how specific. This file
   loads after bootstrap.min.css (qux.css @imports it, and templates/_blank.html
   links this one after qux.css), so an equally-important rule here wins on source
   order. The themes' own `.bg-* { --bs-border-color: … !important }` overrides the
   variable rather than the property and so does not compete. */
.border-inherit {
  border-color: currentColor !important;
}

/* Horizontal "Undo" Classes */
.text-horizontal  { writing-mode: horizontal-tb; }
@media (min-width: 576px)  { .text-horizontal-sm  { writing-mode: horizontal-tb; } }
@media (min-width: 768px)  { .text-horizontal-md  { writing-mode: horizontal-tb; } }
@media (min-width: 992px)  { .text-horizontal-lg  { writing-mode: horizontal-tb; } }
@media (min-width: 1200px) { .text-horizontal-xl  { writing-mode: horizontal-tb; } }
@media (min-width: 1400px) { .text-horizontal-xxl { writing-mode: horizontal-tb; } }

/* Editor heading scale. Deliberately NOT .h4 / .h5: _theme_base.html points
   those (and h1-h6) at --enine-font-heading, and `body` at --enine-font-body, so
   a site's own design tokens would decide how its editing chrome is typeset. The
   editor is not the site. These pin Bootstrap 5.2.3's own h4 and h5 — including
   the responsive step h4 carries — against --bs-font-sans-serif, which nothing
   in the repo overrides. */
.enine-h4,
.enine-h5 {
  font-family: var(--bs-font-sans-serif);
  font-weight: 500;
  line-height: 1.2;
}
.enine-h4 {
  font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
  .enine-h4 {
    font-size: 1.5rem;
  }
}
.enine-h5 {
  font-size: 1.25rem;
}

/* ── Edit-mode chrome and widgets ─────────────────────────────────────────────
   The editor's artifact widget (themes/_editor/widget/) is built almost
   entirely from Bootstrap 5.2.3 utilities. These are the things utilities
   cannot express.

   Corners are Bootstrap's .rounded-1 in the markup, and border colours come from
   the general .border-inherit utility. */

/* The pencil dropdown's Settings / Values flyout on a SECTION widget
   (themes/_editor/control/_panel_dropdown.html).

   Bootstrap 5.2.3 has no submenu primitive. Rather than write one, each item is
   a `collapse` toggle — which brings its own aria-expanded and keyboard
   handling — and this moves the expanded panel to the LEFT of the menu. So
   collapse owns whether it is open and CSS owns where it sits, instead of a
   bespoke widget owning both.

   Left rather than right because the menu is already right-aligned
   (.dropdown-menu-end) against the widget's action cell: opening further right
   would leave the viewport. */
.enine-panel-menu {
  min-width: 13rem;
}

/* A section's panel items are collapse toggles, so there is no `.active` class
   to hang the state on — Bootstrap maintains `aria-expanded` instead. Styling
   the attribute rather than adding a class in script keeps one source of truth
   for "which panel is open" on both widget roles. */


/* Two alignments at once, which is why there is an inner div rather than putting
   the flex on the label: the CELL sits at the top of its grid row (so a 3-row
   textarea does not drag the label into the middle of the box), while the text and
   the info icon inside it are centred against each other.

   Bootstrap's own `col-form-label-sm` padding is left alone on the label so the
   text's first line meets the input beside it — that padding is exactly what makes
   a label line up with a form-control, and zeroing it is what forced an earlier
   vertical-centring hack. */
.enine-field-label-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The per-field info icon. Cursor and offset only — the popover itself is
   Bootstrap's, initialised by the document-wide pass in _theme_base.html. */
.enine-field-help {
  cursor: help;
  /* `auto` rather than a fixed gap: this is what right-aligns the icon inside the
     label cell, independent of how long the label text is. */
  margin-inline-start: auto;
  flex-shrink: 0;
  /* No size or colour of its own — it reads as part of the label, so it takes the
     label's font-size and currentColor. Any `fa-*` size class or opacity utility
     on the icon would defeat that, which is why none is in the markup. */
  font-size: inherit;
  color: inherit;
}

/* The ancestry strip under an editor widget (themes/_editor/widget/_crumbs.html).
   Colour and opacity are Bootstrap utilities in the markup — inherited
   foreground, .opacity-50 — so nothing here names a colour. What utilities
   cannot express is the truncation: it has to be PER CRUMB, or one long
   ancestor title pushes the rest of the chain off the row. A max-width in `ch`
   rather than a character count so the budget tracks the rendered font. */
.enine-crumbs .enine-crumb {
  max-width: 18ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.enine-crumbs .enine-crumb:hover,
.enine-crumbs .enine-crumb:focus {
  text-decoration: underline !important;
}

/* The edit-mode toast (enineEditorNotify in common/js/enine-editor.js).
   Edit mode had no message surface at all: the dashboard's #sm-bar-message lives
   in site_manager.js, which the theme preview page does not load, so a failed
   inline save showed a red outline and nothing else.

   z-index sits above the editor chrome deliberately. The pickers in this file
   use 1050 (Bootstrap's dropdown layer); an error about a save the user just
   attempted has to be readable over an open picker, not behind it. Fixed rather
   than absolute so it does not scroll away from a field the user is still
   looking at, and pointer-events are restored on the cards alone so the host
   never swallows a click meant for the page beneath it. */
.enine-toast-host {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 1080;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(24rem, calc(100vw - 2rem));
  pointer-events: none;
}

.enine-toast {
  display: flex;
  align-items: start;
  gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--bs-border-radius, 0.375rem);
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  font-size: 0.875rem;
  line-height: 1.4;
  pointer-events: auto;
  /* An error message can be long — a field name plus a validator's sentence —
     and truncating the part that says what to fix defeats the point. */
  overflow-wrap: anywhere;
}

.enine-toast-error {
  background-color: var(--bs-danger, #dc3545);
  color: #ffffff;
}

.enine-toast-close {
  flex-shrink: 0;
  margin-left: auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 1.125rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.75;
}

.enine-toast-close:hover,
.enine-toast-close:focus {
  opacity: 1;
}

/* The page chrome bar (themes/_editor/_mode_pills.html) borrows the `navbar`
   class purely to inherit whatever background each theme gives its navbar —
   every stock theme sets `.navbar { background-color: … }` from its own colour
   token, and none of them agree on which token. What it must NOT inherit is the
   sticky positioning that comes with those same rules: the bar sits in DOM order
   under the navbar, and two stickies pinned to top:0 would overlap. Two classes
   deep, because the themes' own single-class `.navbar` rule lands later in the
   cascade and would otherwise win. */
.navbar.enine-chrome-bar {
  position: static;
  z-index: auto;
}

/* The chrome is not the site, so the site does not get to typeset it.
   _theme_base.html points `body` at --enine-font-body, which the bar would
   otherwise inherit, and a site's custom_css is injected raw after this file —
   so this needs !important to hold, and has to reach descendants because the bar
   is made of .btn and .dropdown-toggle, names custom CSS plausibly targets.
   Sizes still come from Bootstrap's own .btn-sm; only the family is pinned.

   The :not() is load-bearing. Font Awesome sets `font-family: var(--_fa-family)`
   on the icon element itself and does NOT mark it !important, so a blanket
   `.enine-chrome-bar *` with !important wins over it and every glyph in the bar
   renders as nothing. Anything carrying a fa-* class is left alone.

   Best-effort against custom_css that uses !important with a more specific
   selector — nothing in a stylesheet can be made truly unreachable. */
.enine-chrome-bar,
.enine-chrome-bar *:not([class*="fa-"]) {
  font-family: var(--bs-font-sans-serif) !important;
}

/* The add tile's picker sits in a grid column whose siblings follow it in the
   DOM, so it needs the lift to open over them rather than under. */
[data-enine-media-add] .enine-media-picker-dropdown {
  z-index: 1055;
}

/* Every thumbnail in edit mode is a 16/9 box the whole file fits inside —
   contain, never cover, so nothing crops the thing being picked, and square
   because nothing in the editor is rounded. One class, used by the tiles, both
   picker triggers, and the picker's own grid. */
.enine-edit-thumb {
  aspect-ratio: 16 / 9;
  object-fit: contain;
  /* Matches .rounded-1, which the tiles and triggers carry as a class. Declared
     here because the picker builds its grid thumbnails in JavaScript and this is
     the one class they all share. */
  border-radius: var(--bs-border-radius-sm);
}

/* Drop feedback while a file is dragged over a widget body. Inset so it reads
   as the body lighting up rather than the widget gaining a border. */
.enine-drop-active {
  outline: 2px dashed var(--bs-primary);
  outline-offset: -4px;
}


/* The section widget's gutter (#30, #31) — the two controls that act on a widget
   without being part of it, sitting outside its border so the border stays a clean
   rectangle.

   A fixed WIDTH, because the gutter has to be the SAME width whether or not it
   holds controls: a draft renders it empty, and if it collapsed to nothing the
   draft's widget would sit left of every saved sibling above it. It is the button's
   own square and nothing more — the flex gap on the row does the spacing.

   THIS WIDTH IS THE ONLY HARDCODED DIMENSION IN THE WIDGET (#66), and everything else
   sizes off it: the buttons are `width: 100%` of it and square themselves with
   `aspect-ratio`, the two of them plus their gap are what the header's row measures,
   and the header's own picker squares take their side from that row's height. One
   number, at the top, and every other dimension derived from it.

   NO HEIGHT, and no `justify-content`. Both existed to work around the header
   declaring a height this gutter had to match: 66px was that height copied by hand,
   and `flex-end` pushed the buttons to the bottom of it. The gutter now DEFINES that
   height rather than chasing it, so its natural height is the right one by
   construction.

   No colour is declared either. Both buttons carry `border-inherit`, which resolves
   to currentColor — the foreground the band's bg-primary / bg-secondary sets — so
   they cannot come out grey on a coloured ground in the two themes that define no
   border-colour token. */
.enine-gutter {
  width: var(--enine-ctl);
}

/* The widget's column beside the gutter.

   `min-width: 0` is the whole reason this class exists. A flex item defaults to
   `min-width: auto`, which will not shrink below its content's min-content width.
   The children strip inside is `flex-nowrap`, so ITS min-content width is the sum
   of every chip — meaning a widget with many children pushed this column wider
   than the flex row and the widget escaped its container horizontally.

   With `min-width: 0` the column tracks the container instead, and ChildrenStrip's
   capacity logic hides what does not fit, which is the mechanism that was always
   supposed to handle it. */
.enine-widget-col {
  min-width: 0;
}

/* No rule for the collapsed widget's bottom corners (#66), deliberately.

   The header carries `rounded-bottom` — Bootstrap's own class — while the body's row
   is hidden, and drops it when the body is showing. The template renders the opening
   position and `WidgetCollapse.apply()` toggles it thereafter, alongside the other
   facts that follow the region. Nothing here has to reach past a `!important` border
   utility to restate what that class already says. */

/* Square, and small enough not to compete with the widget's own header controls.
   Bootstrap's .btn padding would make these rectangular, so it is overridden here
   rather than with a pile of utility classes on the markup.

   NEITHER DIMENSION IS A NUMBER HERE (#66). The width is `100%` of the gutter, whose
   own width is the one hardcoded dimension in the widget (`--enine-ctl`, see its
   comment above for why that number is allowed to be bare); the height follows from
   `aspect-ratio`.

   NOTE THE DIRECTION, because it is why this rule never had the bug the picker
   squares did: it transfers from a DEFINITE width to a height. `aspect-ratio` only
   carries a size to the other axis when the first one is definite up front, and a
   width that is a percentage of a declared 31px always is. The picker squares run
   the other way — height to width — which is why they need a declared height on the
   header and an unbroken `h-100` chain to reach it.

   This rule used to carry the arithmetic instead: "31px, not 2rem (32px)", because
   two buttons plus their gap had to add up to the header row's MEASURED height
   (4rem plus borders, 66px) or the gutter overran its bottom. That number needed
   re-measuring by hand whenever either side moved and nothing checked it. Now the
   header's height derives from `--enine-ctl` too, so both sides come from one
   source and there is nothing left to keep in step. */
.enine-gutter-btn {
  width: 100%;
  aspect-ratio: 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* SortableJS's own classes. The dragged band keeps its layout so the page does not
   jump as it moves; the placeholder is dimmed rather than hidden, because a gap
   that shows nothing makes the drop target ambiguous. */
.sortable-ghost {
  opacity: 0.35;
}

.sortable-drag .enine-widget {
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

/* The handle is the only part of a section that initiates a drag, so it is the only
   part that should advertise it.

   `!important`: the handle is a `<button type="button" class="btn ...">`, and
   Bootstrap matches it on `[type=button]:not(:disabled) { cursor: pointer; }`
   (bootstrap.css) — an attribute selector PLUS a pseudo-class, (0,2,0), which
   beats `button[data-enine-drag-handle]`'s (0,1,1) outright. Source order
   (site.css after bootstrap.css) only decides a TIE; this is not one, so the
   fix is the same one `.border-inherit` already needs against Bootstrap's own
   `!important` border utilities, for the same reason: nothing else here can
   reach a property Bootstrap has claimed at higher specificity. */
[data-enine-drag-handle] {
  cursor: grab !important;
}

[data-enine-drag-handle]:active {
  cursor: grabbing !important;
}

/* The children strip while a drag is in flight.

   Truncation is suspended for the duration (ChildrenStrip.beginDrag shows every
   chip and hides the overflow cell), so the row is now wider than the strip. It has
   to scroll rather than spill: the row is `flex-nowrap`, and the widget's column
   carries `min-width: 0`, so without an explicit overflow the extra chips would be
   clipped and undroppable.

   Only while dragging. At rest the strip shows exactly what fits and counts the
   rest, which is what the overflow cell is for. */
.enine-strip-dragging {
  overflow-x: auto;
}

/* A chip advertises that it can be dragged, but only in the strip — a section's
   drag handle is its own control and already says so.

   The link inside is named explicitly, not left to inherit: an <a href> gets its
   own cursor from the UA stylesheet, which beats an inherited value regardless of
   specificity — so without this the column showed the browser's pointer while the
   div around it correctly showed the hand, and the two disagreed exactly where the
   chip is most clicked (control/_children_chip.html's stretched link fills the
   whole column). */
[data-enine-child],
[data-enine-child] a {
  cursor: grab;
}

[data-enine-child]:active,
[data-enine-child] a:active {
  cursor: grabbing;
}

/* The widget body's three panes — Content, Settings, Values (#37).

   The box must not change height as you switch between them. Content is the anchor
   by decision, and Content's height cannot be written down: it is a six-row textarea
   plus the media manager, whose tiles are a 16:9 ratio of a responsive column, so it
   differs at every breakpoint and with every container width.

   So Content stays in the layout and DEFINES the height. It is hidden with
   `visibility` rather than `display` when another pane is active — visibility keeps
   the box occupied while still removing the content from the tab order and from
   screen readers. Settings and Values are absolutely positioned over that box and
   scroll within it.

   The selector hangs off `data-enine-active-pane` on the BODY rather than a class on
   each pane, because the rule has to reach the content pane to un-hide it and a
   class on a sibling cannot. */
.enine-widget-body {
  position: relative;
}

.enine-widget-body:not([data-enine-active-pane="content"]) > [data-enine-pane="content"] {
  visibility: hidden;
}

.enine-widget-body > [data-enine-pane="settings"],
.enine-widget-body > [data-enine-pane="values"],
.enine-widget-body > [data-enine-pane="media"],
.enine-widget-body > [data-enine-pane="mapping"],
.enine-widget-body > [data-enine-pane="general"],
.enine-widget-body > [data-enine-pane="contact"],
.enine-widget-body > [data-enine-pane="appearance"] {
  display: none;
  position: absolute;
  inset: 0;
  overflow-y: auto;
}

.enine-widget-body[data-enine-active-pane="settings"] > [data-enine-pane="settings"],
.enine-widget-body[data-enine-active-pane="values"] > [data-enine-pane="values"],
.enine-widget-body[data-enine-active-pane="media"] > [data-enine-pane="media"],
.enine-widget-body[data-enine-active-pane="mapping"] > [data-enine-pane="mapping"],
.enine-widget-body[data-enine-active-pane="general"] > [data-enine-pane="general"],
.enine-widget-body[data-enine-active-pane="contact"] > [data-enine-pane="contact"],
.enine-widget-body[data-enine-active-pane="appearance"] > [data-enine-pane="appearance"] {
  display: block;
}

/* The Media pane (#51) is mobile-only — reachable from the pencil dropdown's
   own Media item, itself `d-md-none`. This is the belt half of belt-and-
   braces: PanelModes (enine-editor.js) already switches a widget still
   showing "media" back to "content" the instant the viewport crosses up to
   `md`, but that is a `resize`-driven JS write, not instantaneous, and a
   widget that has not yet run its JS (or whose listener has not fired yet)
   must still never show this pane at a width the icon/cover header cells
   have already reclaimed. Equal specificity to the active-pane rule above,
   so source order — this comes after — is what wins at `md` and up. */
@media (min-width: 768px) {
  .enine-widget-body[data-enine-active-pane="media"] > [data-enine-pane="media"] {
    display: none;
  }
}

/* Below `md`, the Content pane's own gallery is redundant with the Media
   pane's copy (control/_panel_media.html) — both show the same
   artifact.images.all, and the Media pane is the one reachable from the
   pencil dropdown at this width. Hiding it here, rather than not rendering
   it at all, keeps the include in widget/_body.html identical at every
   width; only its visibility is breakpoint-gated.

   The compensating height lives on the OTHER side of the breakpoint, and as
   the HTML attribute rather than a CSS calc(). control/_content_editor.html
   ships `rows="11"` — the browser's own native sizing, no line-height math
   here to get subtly wrong — which is what the field renders at below `md`,
   approximating six rows plus the one gallery row it replaces. This rule
   pins it back down to what six rows actually measures out to once the
   gallery reappears at `md` and up: `.form-control`'s default line-height
   (6 × 1.5rem = 9rem) plus this field's own `p-3` (2rem) = `11rem`. A first
   pass tried the opposite direction — grow the mobile height by a
   `min-height: 60vh` addition — which swung wildly by device and had
   nothing to do with what was actually lost; sizing the LARGER state
   natively and pinning only the smaller, unchanged one is both simpler and
   exact instead of approximate on the side that matters more (`md` and up
   is every desktop and tablet render, mobile is the one side this was
   always going to be a rough match on). */
@media (max-width: 767.98px) {
  .enine-widget-body > [data-enine-pane="content"] > [data-enine-media-manager] {
    display: none;
  }
}

@media (min-width: 768px) {
  .enine-widget-body > [data-enine-pane="content"] > textarea {
    height: 11rem;
  }
}

/* Digit-glyph kerning for the audit badges in the pencil menu (#33).

   Copied from `site_manager/_audit_styles.html`, which is a <style> block in the
   MANAGER templates and is therefore not loaded by the theme preview page the editor
   runs on. Copying the sidebar's badge markup without this renders the digit glyphs
   un-kerned — subtly wrong, and easy to miss.

   The glyphs are `fa-solid fa-0` … `fa-9`, one per digit, so a two-digit count is two
   icons that need pulling back together. */
.fa-num i + i {
  margin-left: -0.3em;
}
