/* Echo CMS playground — WYSIWYG styles. Plain CSS (native nesting). Theme via [data-theme]. */

:root {
  /* --ec-accent = Эхо brand #f0776c. Semantic token contract — see base/systems/theme.md +
     base/systems/dark-theme.md. LIGHT values below = the shipped colors (byte-identical); the dark
     palette is a considered set (not a naive invert) under [data-theme="dark"]. */
  --ec-bg: #f7f8fa; --ec-surface: #ffffff; --ec-surface-2: #fcfcfd;
  --ec-fg: #25282d; --ec-fg-muted: #9099a4; --ec-fg-soft: #4b5560;
  --ec-border: #ebedf0; --ec-accent: #f0776c; --ec-accent-fg: #ffffff; --ec-brand-red: #e13e39;
  --ec-selected: rgba(240,119,108,.08); --ec-hover: rgba(0,0,0,.035); --ec-half-accent: #e167557d;
  --ec-danger: #d23f57; --ec-success: #16a34a; --ec-warning: #d98324; --ec-shadow: rgba(20,25,35,.14);
  /* reader / main-page secondary palette (base/MainPage.md Figma spec). LIGHT = the shipped literals,
     byte-identical; a considered dark set so the MainPage widgets flip in the editor's dark canvas
     preview (the reader itself is pinned light, render.ts). See base/systems/dark-theme.md. */
  --ec-ink-strong: #1a1a1a; --ec-ink-strong-ct: #ffffff; --ec-fg-faint: #808080; --ec-fg-secondary: #5a626c;
  --ec-border-card: #eceef1; --ec-border-ctl: #dfe3e8; --ec-red-deep: #c0392b;
  /* back-compat ALIASES: original bare names map 1:1 onto the tokens so existing chrome keeps working.
     They are re-declared under [data-theme="dark"] too — a var() alias declared only at :root would
     NOT flip (it substitutes the :root token value at :root, then inherits that literal). */
  --bg: var(--ec-bg); --canvas: var(--ec-surface); --panel: var(--ec-surface-2);
  --ink: var(--ec-fg); --muted: var(--ec-fg-muted); --ink-soft: var(--ec-fg-soft);
  --line: var(--ec-border); --accent: var(--ec-accent); --brand-red: var(--ec-brand-red);
  --sel: var(--ec-selected); --hover: var(--ec-hover); --half-accent: var(--ec-half-accent);
  --err: var(--ec-danger);
}
[data-theme="dark"] {
  --ec-bg: #15171c; --ec-surface: #1b1e24; --ec-surface-2: #181b20;
  --ec-fg: #e6e8eb; --ec-fg-muted: #79828d; --ec-fg-soft: #aab2bc;
  --ec-border: #2a2f37; --ec-accent: #f0776c; --ec-accent-fg: #ffffff; --ec-brand-red: #e13e39;
  --ec-selected: rgba(240,119,108,.16); --ec-hover: rgba(255,255,255,.05); --ec-half-accent: #e167557d;
  --ec-danger: #ef5164; --ec-success: #34d066; --ec-warning: #e69a3d; --ec-shadow: rgba(0,0,0,.45);
  /* reader / main-page secondary palette — dark values (considered, not naive invert) */
  --ec-ink-strong: #e6e8eb; --ec-ink-strong-ct: #15171c; --ec-fg-faint: #8b939d; --ec-fg-secondary: #aab2bc;
  --ec-border-card: #2a2f37; --ec-border-ctl: #333a45; --ec-red-deep: #f0776c;
  /* aliases re-pointed so descendants under .EC-app[data-theme=dark] resolve the DARK tokens */
  --bg: var(--ec-bg); --canvas: var(--ec-surface); --panel: var(--ec-surface-2);
  --ink: var(--ec-fg); --muted: var(--ec-fg-muted); --ink-soft: var(--ec-fg-soft);
  --line: var(--ec-border); --accent: var(--ec-accent); --brand-red: var(--ec-brand-red);
  --sel: var(--ec-selected); --hover: var(--ec-hover); --half-accent: var(--ec-half-accent);
  --err: var(--ec-danger);
}

* { box-sizing: border-box; }
body { margin: 0; font: 15px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--ink);  }

a, button {
    transition: color 0.2s, background 0.2s;
}

.EC-app { display: flex; flex-direction: column; height: 100vh; }

/* public reader page (SSR, /read/<slug>) */
.EC-read { margin: 0 auto; padding: 56px 24px 120px; }   /* articles: readable line length */
.EC-read--main { max-width: none; padding: 24px 32px 80px; }              /* main page: FLUID — flexes to the full viewport width (base/MainPage.md §1) */
.EC-read--wrapped { max-width: 860px; padding-top: 16px; }                /* material page wrapped in site chrome (masthead+footer inside the padded column, like the editor canvas) */
.EC-read--title { font-size: 34px; line-height: 1.15; font-weight: 800; letter-spacing: -.02em; margin: 0 0 8px; }
.EC-read--meta { color: var(--muted); font-size: 13px; margin-bottom: 32px; }
.EC-read--body > .EC-blk { margin: 0 0 18px; }
.EC-read--body img.EC-img { max-width: 100%; border-radius: 8px; display: block; }

/* aggregation blocks (main page) */
.EC-agg { margin: 0 0 28px; }
.EC-feed--ph { font-size: 13px; color: var(--muted); border: 1px dashed var(--line); border-radius: 8px; padding: 14px 16px; background: var(--panel); }
.EC-feed--empty { font-size: 13px; color: var(--muted); font-style: italic; padding: 10px 0; }
.EC-feed--cat { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); margin-bottom: 4px; }
.EC-feed--lead { display: block; }   /* container */
.EC-feed--leaditem { display: block; text-decoration: none; color: var(--ink); border-bottom: 1px solid var(--line); padding-bottom: 18px; }
.EC-feed--leaditem .EC-feed--ttl { font-size: 30px; line-height: 1.15; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.EC-feed--headlines { list-style: none; margin: 0; padding: 0; }
.EC-feed--li { padding: 8px 0; border-bottom: 1px solid var(--line); }
.EC-feed--hl { text-decoration: none; color: var(--ink); font-weight: 600; }
.EC-feed--hl:hover { color: var(--accent); }
.EC-feed--headlines:empty::after, .EC-feed--grid:empty::after { content: 'No published articles yet'; color: var(--muted); font-style: italic; }
.EC-feed--grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.EC-feed--lede { font-size: 13px; color: var(--muted); margin: 4px 0 0; line-height: 1.45; }
/* «Размер лида» (ledeSize -> .EC-feed--lede--s|m|l|xl, core/blocks/aggregation.ts) — titleSize's twin on the
   lede (owner, 2026-08-20). Declared AFTER the 13px base so a tier wins at equal (0,1,0) specificity without
   an !important, exactly like the title tiers below. Inside a grid ROW these are superseded by the cell-derived
   ladder in the row block's published css: (core/blocks/layout.ts), which is the one definition for all three
   surfaces; this pair is the PLAIN-COLUMN fallback. */
.EC-feed--lede--s { font-size: 11px; }
.EC-feed--lede--m { font-size: 13px; }
.EC-feed--lede--l { font-size: 16px; }
.EC-feed--lede--xl { font-size: 20px; }
.EC-feed--leadimg { width: 100%; max-height: var(--ec-imgh, 320px); object-fit: cover; border-radius: 10px; margin-bottom: 10px; display: block; }
/* the material-card TEASER parts on the `lead` preset (core/blocks/feedRender.ts#leadTeaser): the date the
   toggle appends, and the title-size tiers. Declared here, AFTER `.EC-feed--leaditem .EC-feed--ttl`'s 30px
   base above, so the tier wins at equal (0,2,0) specificity without an !important. */
.EC-feed--leaditem .EC-feed--time { display: block; margin-top: 6px; font-size: 13px; color: var(--muted, #6b6b6b); }
.EC-feed--leaditem .EC-feed--ttl--s { font-size: 18px; }
.EC-feed--leaditem .EC-feed--ttl--m { font-size: 24px; }
.EC-feed--leaditem .EC-feed--ttl--l { font-size: 30px; }
.EC-feed--leaditem .EC-feed--ttl--xl { font-size: 38px; }
/* THE LEFT-IMAGE `lead` (imgPos:'left' → `EC-feed--imgleft`, core/blocks/feedRender.ts#leadTeaser). The rules
   above are the preset's NATIVE stack; this is the very horizontal split `.EC-feed--portraititem` renders, and
   deliberately through the SAME `--ec-imgshare` seam — so editor/imageGizmo.ts#attachCardSplit drives BOTH
   presets with one variable, and «Слева» finally means something on the default preset (owner, 2026-08-20:
   «one 12x6 block that have left image, but it is displaying at the top»).
   The photo drops the stack's `max-height`/`margin-bottom` and FILLS the media column instead: a left image is
   bounded SIDEWAYS (imgShare), never downwards (imgHeight) — the same mutual exclusion the renderer enforces by
   emitting only one of the two vars. `--ec-imgshare` stays a FALLBACK var, so an untouched left card renders the
   historic 280px column and emits no `style` attribute at all. */
.EC-feed--leaditem.EC-feed--imgleft { display: grid; grid-template-columns: var(--ec-imgshare, 280px) minmax(0, 1fr); gap: 18px; align-items: center; }
.EC-feed--leaditem.EC-feed--imgleft > .EC-feed--leadimg,
.EC-feed--leaditem.EC-feed--imgleft > .EC-feed--crop { max-height: none; height: 100%; min-height: 0; margin-bottom: 0; align-self: stretch; }
.EC-feed--leadbody { min-width: 0; }
/* one column on a phone, exactly like portrait — and the photo goes back to being bounded DOWNWARDS, because
   `height:100%` inside a content-sized single-column grid has nothing left to resolve against. */
@media (max-width: 640px) {
  .EC-feed--leaditem.EC-feed--imgleft { grid-template-columns: minmax(0, 1fr); }
  .EC-feed--leaditem.EC-feed--imgleft > .EC-feed--leadimg,
  .EC-feed--leaditem.EC-feed--imgleft > .EC-feed--crop { height: auto; max-height: var(--ec-imgh, 320px); margin-bottom: 10px; }
}
.EC-feed--cardimg { width: 100%; height: 130px; object-fit: cover; border-radius: 8px; margin-bottom: 8px; display: block; }
/* opinion: author-forward columns */
.EC-feed--opinion { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
.EC-feed--op { text-decoration: none; color: var(--ink); border-left: 3px solid var(--accent); padding-left: 12px; }
.EC-feed--opauthor { font-weight: 700; font-size: 13px; margin-bottom: 3px; }
/* podcast: media cards */
.EC-feed--podcast { display: flex; flex-direction: column; gap: 12px; }
.EC-feed--pod { display: flex; gap: 12px; text-decoration: none; color: var(--ink); border: 1px solid var(--line); border-radius: 10px; padding: 10px; }
.EC-feed--pod:hover { border-color: var(--accent); }
/* `--ec-imgshare` / `--ec-imgh` are the material-card's two axes, and `podcast` gets them on the same terms
   as `portrait` (owner, 2026-08-20: «podcast … top positioned image prop is not working there»). FALLBACK
   vars, so an untouched podcast card still renders the historic 96x96 thumb and emits no `style` attribute
   at all — the byte-for-byte rule in renderMaterialTeaser. */
.EC-feed--podimg { width: var(--ec-imgshare, 96px); height: 96px; object-fit: cover; border-radius: 8px; flex: 0 0 auto; }
.EC-feed--podbody { min-width: 0; }
/* imgPos:'top' — the card stacks and the photo spans it, exactly as .EC-feed--portraititem.EC-feed--imgtop
   does. ⚠ The crop WINDOW is sized with the photo, never separately: that symmetry is the invariant a crop
   wrapper must not break (see `.EC-feed--crop` at the end of this file), and it is what stops the photo
   jumping the moment it is grabbed. */
.EC-feed--pod.EC-feed--imgtop { flex-direction: column; }
.EC-feed--pod.EC-feed--imgtop > .EC-feed--podimg,
.EC-feed--pod.EC-feed--imgtop > .EC-feed--crop { width: 100%; height: var(--ec-imgh, 200px); }
.EC-feed--pod.EC-feed--imgleft > .EC-feed--crop,
.EC-feed--pod:not(.EC-feed--imgtop) > .EC-feed--crop { width: var(--ec-imgshare, 96px); height: 96px; flex: 0 0 auto; }
/* portrait: large feature, image beside text */
/* `--ec-imgshare` is the material-card's image/text SPLIT (core/blocks/aggregation.ts `imgShare`, dragged by
   editor/imageGizmo.ts#attachCardSplit). It is a FALLBACK var, so an untouched card renders the historic 280px
   and no `style` attribute is emitted at all — the byte-for-byte rule in renderMaterialTeaser. */
.EC-feed--portraititem { display: grid; grid-template-columns: var(--ec-imgshare, 280px) 1fr; gap: 18px; text-decoration: none; color: var(--ink); align-items: center; }
.EC-feed--portraitimg { width: 100%; height: 200px; object-fit: cover; border-radius: 10px; }
.EC-feed--portraititem .EC-feed--ttl { font-size: 24px; font-weight: 800; line-height: 1.2; }
/* material-card TEASER field-visibility (Vitaly #2+#3): imgPos top/none collapse to a single column (image stacks
   above the body, or is absent); left = the default 280px+1fr grid above. titleSize tiers override the 24px base. */
.EC-feed--portraititem.EC-feed--imgtop, .EC-feed--portraititem.EC-feed--noimg { grid-template-columns: 1fr; }
/* `--ec-imgh` is the material-card's TOP-image HEIGHT (core/blocks/aggregation.ts `imgHeight`, dragged by
   editor/imageGizmo.ts#attachCardTopHeight) — the third of the same pattern, after `imgShare` for the left
   split and the image block's own `maxHeight`. A FALLBACK var again: an untouched top card keeps the historic
   200px and emits no `style` attribute at all. Inside a grid ROW the fallback is the cell-derived height in the
   row block's published css: (core/blocks/layout.ts), which is where a row's own numbers live.
   ⚠ The crop WINDOW is sized with the photo, never separately — see `.EC-feed--crop` at the end of this file. */
.EC-feed--portraititem.EC-feed--imgtop > .EC-feed--portraitimg,
.EC-feed--portraititem.EC-feed--imgtop > .EC-feed--crop { height: var(--ec-imgh, 200px); }
.EC-feed--portraititem .EC-feed--time { display: block; margin-top: 6px; font-size: 13px; color: var(--muted, #6b6b6b); }
.EC-feed--portraititem .EC-feed--ttl--s { font-size: 18px; }
.EC-feed--portraititem .EC-feed--ttl--m { font-size: 24px; }
.EC-feed--portraititem .EC-feed--ttl--l { font-size: 30px; }
.EC-feed--portraititem .EC-feed--ttl--xl { font-size: 38px; }
@media (max-width: 640px) { .EC-feed--portraititem { grid-template-columns: 1fr; } }
/* breaking: timestamped list */
.EC-feed--breaking { list-style: none; margin: 0; padding: 0; }
.EC-feed--brk { padding: 10px 0; border-bottom: 1px solid var(--line); }
.EC-feed--brklink { text-decoration: none; color: var(--ink); }
.EC-feed--brkmeta { display: flex; gap: 10px; align-items: baseline; }
.EC-feed--time { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.EC-feed--air { display: inline-block; margin: 2px 0; font-size: 12px; font-weight: 600; color: var(--accent); font-variant-numeric: tabular-nums; }

/* newsletter CTA + footer */
.EC-cta { background: var(--sel); border-radius: 12px; padding: 22px 24px; margin: 0 0 28px; text-align: center; }
.EC-cta--h { font-size: 20px; font-weight: 800; margin: 0 0 6px; }
.EC-cta--t { color: var(--muted); margin: 0; }
.EC-footer { color: var(--muted); font-size: 13px; border-top: 1px solid var(--line); padding: 20px 0; margin-top: 28px; }

/* masthead (page header block): logo + nav */
.EC-mast { display: flex; align-items: center; gap: 24px; border-bottom: 2px solid var(--ink); padding: 8px 0 12px; margin: 0 0 22px; }
.EC-mast--logo { font-size: 26px; font-weight: 900; letter-spacing: -.03em; color: var(--ink); text-decoration: none; }

/* details panel: array<blockRef> manager */
.EC-reflist { display: flex; flex-direction: column; gap: 4px; }
.EC-reflist--row { display: flex; align-items: center; gap: 4px; padding: 4px 6px; border: 1px solid var(--line); border-radius: 6px; }
.EC-reflist--t { flex: 1; font-size: 13px; }
.EC-reflist--b { border: none; background: transparent; color: var(--muted); cursor: pointer; width: 22px; height: 22px; border-radius: 4px; }
.EC-reflist--b:hover { background: var(--hover); color: var(--ink); }
.EC-reflist--b[disabled] { opacity: .3; pointer-events: none; }
.EC-reflist--addbtn { align-self: flex-start; font: inherit; font-size: 12px; padding: 4px 9px; border: 1px dashed var(--line); border-radius: 6px; background: transparent; color: var(--accent); cursor: pointer; }
.EC-reflist--addbtn:hover { border-color: var(--accent); background: var(--sel); }

/* layout: columns = a row of `column` slots. SSR: EC-col is the flex item (inline flex from settings).
   Editor: the children row (.EC-slot--row) is the flex container; each column's wrapper is flexed inline. */
.EC-cols { display: flex; gap: 22px; align-items: flex-start; margin: 0 0 24px; }
.EC-col { min-width: 0; }
/* .EC-slot--row also carries .EC-nest--list (flex-direction:column) — win the direction explicitly */
.EC-nest--list.EC-slot--row { display: flex; flex-direction: row; gap: 22px; align-items: flex-start; flex: 1; }
.EC-slot--row > .EC-nest--item { min-width: 0; }            /* flex set inline per column (exact widths) */
/* the "+ Column" / "+ Block" add affordance is positioned absolutely (see .EC-nest--add below) so it
   never sits in the layout flow — the editor matches the published result. */
/* a slot is always a visible drop target, even when empty */
.EC-slot { min-height: 24px; }
.EC-blk--section, .EC-col { min-height: 28px; }
@media (max-width: 720px) { .EC-cols, .EC-slot--row { flex-direction: column; } }

/* recursive List container layouts (items styled by their item-template: EC-nav--it / EC-people--it) */
.EC-list--nav { display: flex; flex-wrap: wrap; gap: 4px 18px; border-bottom: 2px solid var(--ink); padding: 0 0 10px; margin: 0 0 24px; }
.EC-list--people { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin: 0 0 28px; }
.EC-list--stack { display: flex; flex-direction: column; gap: 6px; }

/* site nav (menu block) */
.EC-nav { display: flex; flex-wrap: wrap; gap: 4px 18px; border-bottom: 2px solid var(--ink); padding: 0 0 10px; margin: 0 0 24px; }
.EC-nav--it { text-decoration: none; color: var(--ink); font-weight: 700; font-size: 14px; text-transform: uppercase; letter-spacing: .02em; }
.EC-nav--it:hover { color: var(--accent); }

/* people / contributors grid */
.EC-people { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin: 0 0 28px; }
.EC-people--it { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px; text-decoration: none; color: var(--ink); border: 1px solid var(--line); border-radius: 10px; padding: 12px; }
.EC-people--it:hover { border-color: var(--accent); }
.EC-people--av { width: 46px; height: 46px; border-radius: 50%; background: var(--sel); color: var(--accent); display: inline-flex; align-items: center; justify-content: center; font-weight: 800; font-size: 18px; flex: 0 0 auto; }
.EC-people--av::before { content: '👤'; opacity: .5; }
.EC-people--av:not(:empty)::before { content: none; }   /* real content (avatar image later) wins */
.EC-people--av.has-img { background-size: cover; background-position: center; }
.EC-people--av.has-img::before { content: none; }       /* photo present → hide the placeholder */
.EC-people--nm { font-weight: 700; font-size: 14px; }
.EC-people--role { font-size: 12px; color: var(--muted); }

/* carousel layout — horizontally scrollable strip (drag + ‹ › arrows wired by core/carousel.ts) */
.EC-carousel-wrap { position: relative; margin: 0 0 24px; }
.EC-list--carousel { display: flex; overflow-x: auto; gap: 14px; padding: 8px 2px; scrollbar-width: thin; cursor: grab; scroll-behavior: smooth; }
.EC-list--carousel.EC-grabbing { cursor: grabbing; scroll-behavior: auto; }   /* free drag while grabbing */
.EC-list--carousel > * { flex: 0 0 auto; }
.EC-list--carousel .EC-people--it { width: 100px; border: none; padding: 6px; }
/* FILMSTRIP (base/GalleryWizard.md) — the simplest gallery: uniform fixed HEIGHT, natural width, horizontal
   scroll. The flex row is on the list container PUBLIC-side (.EC-list--filmstrip holds items directly) AND on
   the editor's inner .EC-nest--list (the canvas wraps items in .EC-nest--list/.EC-nest--item) — the same rule
   written for both surfaces (SSR emits no .EC-nest wrapper). --ec-gal-h = the fixed height. */
.EC-list--filmstrip, .EC-list--filmstrip .EC-nest--list { display: flex; flex-direction: row; flex-wrap: nowrap; gap: 8px; overflow-x: auto; align-items: flex-start; padding: 4px 2px 8px; scrollbar-width: thin; }
.EC-list--filmstrip > *, .EC-list--filmstrip .EC-nest--item { flex: 0 0 auto; min-width: 0; }
.EC-gal2--it { margin: 0; display: flex; flex-direction: column; gap: 4px; }
/* the image fills the fixed height; width follows the (already-cropped-in-URL) aspect. !important beats the
   inline scalingStyle the image widget writes on the <img> (core/blocks/widgets.tsx).
   ⚠ DIRECT CHILD of .EC-img2 only — a bare `.EC-gal2--it img` also hit the crop gizmo's TWO overlay layers
   (.EC-img2 > .EC-ig--panwrap > .EC-ig--bleed/pan > img), whose per-frame inline panStyle (width 200% / left
   -50% …) the !important here overrode → the live preview snapped to fixed-height/top-left and never tracked
   the gesture (owner: "offset wrong, image top-left, no live preview"). The gizmo overlay must obey panStyle. */
.EC-list--filmstrip .EC-gal2--it .EC-img2 > img.EC-blk--img {
  height: var(--ec-gal-h, 240px) !important; width: auto !important; max-width: none !important; display: block; border-radius: 6px; object-fit: cover; }
.EC-gal2--cap { font-size: 12px; line-height: 1.3; color: var(--muted); margin: 0; }
.EC-gal2--cap:empty { display: none; }
/* the «+ add image» affordance as a FILMSTRIP TILE at the end of the strip — the base .EC-nest--add is an
   absolutely-positioned bottom pill (vertical-slot rule), which the fixed-height strip clipped + scrolled.
   3-class selector beats the base rules; static + full-height + always shown reads as "the next slide". */
.EC-list--filmstrip .EC-nest--list > .EC-nest--add {
  position: static; display: inline-flex; align-items: center; justify-content: center; text-align: center;
  flex: 0 0 auto; inset: auto; transform: none; height: var(--ec-gal-h, 240px); min-width: 96px; padding: 0 14px;
  white-space: normal; box-shadow: none; }
.EC-list--filmstrip .EC-nest--list > .EC-nest--add::after { content: ''; }
.EC-list--carousel .EC-people--av { width: 60px; height: 60px; font-size: 22px; }
.EC-carousel--arrow { position: absolute; top: 38px; width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--line); background: var(--canvas); color: var(--ink); cursor: pointer; z-index: 3; box-shadow: 0 1px 6px rgba(0,0,0,.14); display: flex; align-items: center; justify-content: center; }
.EC-carousel--prev { left: -6px; } .EC-carousel--next { right: -6px; }

/* live player */
.EC-player { display: flex; align-items: center; gap: 14px; background: var(--ink); color: #fff; border-radius: 12px; padding: 14px 18px; margin: 0 0 28px; }
.EC-player--play { width: 40px; height: 40px; border-radius: 50%; background: var(--accent); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 16px; flex: 0 0 auto; }
.EC-player--info { display: flex; align-items: center; gap: 10px; }
.EC-player--badge { font-size: 10px; font-weight: 800; letter-spacing: .06em; padding: 2px 7px; border-radius: 20px; }
.EC-player--on { background: var(--err); color: #fff; }
.EC-player--off { background: rgba(255,255,255,.18); color: #fff; }
.EC-player--ttl { font-weight: 600; }
.EC-feed--card { text-decoration: none; color: var(--ink); border: 1px solid var(--line); border-radius: 10px; padding: 14px; background: var(--canvas); }
.EC-feed--card:hover { border-color: var(--accent); }
.EC-feed--card .EC-feed--ttl { font-size: 16px; font-weight: 600; line-height: 1.3; }
/* a freshly-arrived feed item grows in + flashes (reconcile onEnter — main page live updates) */
@keyframes EC-grow-in { from { opacity: 0; transform: translateY(-8px) scaleY(.6); } to { opacity: 1; transform: none; } }
.EC-enter { animation: EC-grow-in .35s ease-out, EC-flash 1.1s ease-out; transform-origin: top; }
@keyframes EC-flash { 0%,100% { background: transparent; } 18% { background: color-mix(in srgb, var(--accent) 16%, transparent); } }

/* home / article list */
.EC-home { max-width: 1030px; margin: 0 auto; padding: 20px 24px 80px; }   /* widened for the left tag-filter column; the list keeps ~820 */
.EC-home--head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
.EC-home--logo { font-size: 24px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
/* T67 — the materials list is a column-aligned grid TABLE (not a flex stack of cards). Tracks: «title»
   (flexes) · «обновлено» (T65) · «опубликовано» (T65) · actions (status mark + live icon + hide). Rows are
   `display: contents` (below), so every row's cells share these tracks and line up down the list. Full-width
   children (the paging sentinel, the empty-state) span every column. */
.EC-art--list { display: grid; grid-template-columns: minmax(0, 1fr) max-content max-content max-content; align-content: start; }
.EC-art--list > .EC-d--sentinel, .EC-art--list > .EC-art--empty { grid-column: 1 / -1; }
/* home: type tabs + search bar; infinite-scroll sentinel (friendly type names from page-types) */
.EC-home--bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
/* The «Мои материалы» refusal row: the server did not acknowledge `mine=1`, so the filter went back off and
   the list below is NOT filtered. Warning-coloured because it reports a failure, not a hint. */
.EC-home--note { margin: -6px 0 12px; padding: 7px 10px; border: 1px solid var(--err, #c2461f); border-radius: 8px;
  font-size: 12px; line-height: 1.4; color: var(--err, #c2461f); background: var(--sel, transparent); }
.EC-home--bar .EC-d--tabs { flex: 1 1 auto; }
.EC-home--search { flex: 0 0 240px; max-width: 240px; }
.EC-home--groupn { font-weight: 600; color: var(--accent); background: var(--sel); border-radius: 10px; padding: 0 6px; font-size: 11px; margin-left: 2px; }
.EC-d--tab.is-on .EC-home--groupn { background: var(--canvas); }
/* left tag-filter column (attached to the left of the article list; the list keeps its width) */
.EC-home--side { flex: 0 0 190px; display: flex; flex-direction: column; gap: 3px; position: sticky; top: 16px; max-height: calc(100vh - 32px); overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }
.EC-home--side::-webkit-scrollbar { width: 8px; }
.EC-home--side::-webkit-scrollbar-thumb { background: var(--ec-border); border-radius: 4px; }
.EC-home--body .EC-art--list { flex: 1 1 auto; min-width: 0; }
.EC-home--sidehd { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 12px 0 4px; }
/* T77 — «актуальные / все» scope toggle above the tag panel (base/MaterialsTable.md §5). A segmented pair;
   «актуальные» (the default, in-use tags only) is the current filtered view, «все» adds the unused vocabulary. */
.EC-home--tagscope { display: flex; gap: 4px; margin: 0 0 8px; }
.EC-home--tagscope .EC-btn { flex: 1 1 0; font-size: 12px; padding: 3px 6px; justify-content: center; }
.EC-tagf { display: flex; align-items: center; gap: 7px; font: inherit; font-size: 13px; text-align: left; padding: 4px 9px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--ec-fg); cursor: pointer; }
.EC-tagf:hover { background: var(--sel); }
.EC-tagf--dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.EC-tagf--nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-tagf--n { flex: 0 0 auto; font-size: 11px; font-weight: 600; opacity: .65; }
.EC-tagf.is-on { color: #fff; }
.EC-tagf.is-on .EC-tagf--dot { background: #fff !important; }
.EC-tagf.is-on .EC-tagf--n { opacity: .9; }
.EC-d--sentinel { height: 1px; }
/* Row = `display: contents`: it draws no box of its own, its cells become the grid items. Whole-row click
   still works (events bubble to the row element) and paging still `insertBefore(sentinel)`s a real element. */
.EC-art--row { display: contents; cursor: pointer; }
/* T70 density: rows ~half the pre-table card height — a single hairline divider between rows, no per-row
   card border/radius, and tight vertical padding (the pre-T67 card was 14px pad + 1px border + 10px radius). */
.EC-art--cell { display: flex; flex-direction: column; justify-content: center; min-width: 0; padding: 4px 16px; background: var(--canvas); border-bottom: 1px solid var(--line); transition: background .1s; }
.EC-art--row:hover > .EC-art--cell { background: var(--hover); }
/* T65 — the two date columns. Each cell stacks a tiny muted label over its value (no header row in this
   contents-grid to hang column names on, so the cell names itself), kept right-aligned + compact for T70. */
.EC-art--c-updated, .EC-art--c-published { align-items: flex-end; gap: 1px; white-space: nowrap; font-size: 12px; color: var(--muted); }
.EC-art--dlbl { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); opacity: .7; }
.EC-art--upd, .EC-art--pub { color: var(--muted); }
/* T64/T69 — the actions cell: draft mark · live icon · hide, right-aligned. */
.EC-art--c-actions { flex-direction: row; align-items: center; justify-content: flex-end; gap: 8px; white-space: nowrap; }
.EC-art--main { flex: 1; min-width: 0; }
.EC-art--title { font-size: 15px; font-weight: 600; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-art--meta { font-size: 12px; line-height: 1.2; color: var(--muted); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* T64 — published is the silent default (no badge is rendered); this styles only the DRAFT mark that a
   non-published row carries. A small dot precedes the word so it reads as a positive state, not chrome. */
.EC-art--badge { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; padding: 2px 8px 2px 7px; border-radius: 20px; display: inline-flex; align-items: center; gap: 5px; }
.EC-art--badge.is-draft { background: var(--hover); color: var(--muted); }
.EC-art--badge.is-draft::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
/* T69 — the live/preview link is an ICON ONLY (masked SVG, external-link arrow), tinted with the accent and
   inheriting on hover. title="live" carries the label; no visible text.
   OWNER FEEDBACK — like the protected Hide button, it is REVEALED only on row hover (opacity, not display, so
   it stays in the layout + tab order) and stays reachable by keyboard (:focus-visible). */
.EC-art--live { flex: none; width: 15px; height: 15px; background: var(--accent); cursor: pointer; opacity: 0; transition: opacity .12s;
  -webkit-mask: var(--ec-live-ic) center/contain no-repeat; mask: var(--ec-live-ic) center/contain no-repeat; }
.EC-art--row:hover .EC-art--live, .EC-art--live:focus-visible { opacity: 1; }
.EC-art--live:hover { background: var(--ink); }
/* OWNER FEATURE — the PENCIL that opens the editor (the navigation the whole-row click used to do; the row
   click now toggles the detail overlay). Masked-SVG token, same theming discipline as the live/funnel icons;
   revealed on row hover + keyboard-reachable, like them. */
.EC-art--edit { flex: none; width: 15px; height: 15px; padding: 0; border: 0; cursor: pointer; background: var(--muted); opacity: 0; transition: opacity .12s, background .12s;
  -webkit-mask: var(--ec-edit-ic) center/contain no-repeat; mask: var(--ec-edit-ic) center/contain no-repeat; }
.EC-art--row:hover .EC-art--edit, .EC-art--edit:focus-visible { opacity: 1; }
.EC-art--edit:hover { background: var(--accent); }
/* T85 — the row's material-TYPE icon, PREFIXING the title (base/MaterialsTable.md §2). The title line becomes
   a flex row so the slim glyph sits left of the headline; the meta byline stays on its own line below. The
   icon is a masked-SVG token (per-type via .EC-tic--<type>), themed muted like the row's chrome — ALWAYS
   visible (it marks identity), unlike the hover-only live/edit actions. */
.EC-art--titline { display: flex; align-items: center; gap: 7px; min-width: 0; }
.EC-art--typeic { flex: none; width: 14px; height: 14px; background: var(--muted);
  -webkit-mask: var(--_tic) center/contain no-repeat; mask: var(--_tic) center/contain no-repeat; }
.EC-tic--article   { --_tic: var(--ec-type-article-ic); }
.EC-tic--news      { --_tic: var(--ec-type-news-ic); }
.EC-tic--opinion   { --_tic: var(--ec-type-opinion-ic); }
.EC-tic--broadcast { --_tic: var(--ec-type-broadcast-ic); }
.EC-tic--podcast   { --_tic: var(--ec-type-podcast-ic); }
.EC-tic--video     { --_tic: var(--ec-type-video-ic); }
.EC-tic--poll      { --_tic: var(--ec-type-poll-ic); }
.EC-tic--person    { --_tic: var(--ec-type-person-ic); }
.EC-tic--event     { --_tic: var(--ec-type-event-ic); }
.EC-tic--page      { --_tic: var(--ec-type-page-ic); }
.EC-tic--other     { --_tic: var(--ec-type-other-ic); }
/* the row whose detail overlay is open — its cells hold the hover tint so the anchored row stays legible */
.EC-art--row.is-active > .EC-art--cell { background: var(--hover); }
.EC-art--empty { padding: 40px; text-align: center; color: var(--muted); border: 1px dashed var(--line); border-radius: 10px; }

/* ── BROADCAST (эфир) ROW (T79–T84, base/MaterialsTable.md §6) — additive; article rows untouched. The эфир
   row shares the 4 table columns; for a scheduled airing the date column POSITIONS carry the slot (T80/T84):
   the day in «обновлено», the from–to time chip(s) in «опубликовано». ── */
.EC-brow--day { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.EC-art--c-published .EC-brow--slot { display: inline-block; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.EC-art--c-published .EC-brow--slot + .EC-brow--slot { margin-top: 2px; }   /* multiple airings stack */
/* T81 — colour states. All four colours are semantic tokens defined in BOTH the light and dark palettes
   (:root + [data-theme="dark"]), so the chip flips with the theme. */
.EC-brow--slot.is-onair  { color: var(--brand-red); }   /* ON AIR now — the brand red, the loudest state */
.EC-brow--slot.is-today  { color: var(--ec-warning); }  /* later today — warm, upcoming soon */
.EC-brow--slot.is-future { color: var(--ink-soft); }    /* a future day — normal, quiet */
.EC-brow--slot.is-past   { color: var(--muted); }        /* already aired — muted */
/* T83 — attachment-presence icons (video / audio / transcript). Masked-SVG tokens (the EC-tic precedent),
   REFERENCED via .EC-att--<k>, coloured by the row so they theme + survive dark mode. No emoji, no inline SVG. */
.EC-brow--atts { display: inline-flex; align-items: center; gap: 5px; margin-left: 7px; }
.EC-brow--att { flex: none; width: 13px; height: 13px; background: var(--muted);
  -webkit-mask: var(--_att) center/contain no-repeat; mask: var(--_att) center/contain no-repeat; }
.EC-att--video      { --_att: var(--ec-att-video-ic); }
.EC-att--audio      { --_att: var(--ec-att-audio-ic); }
.EC-att--transcript { --_att: var(--ec-att-transcript-ic); }

/* ===== ROW-DETAIL OVERLAY (owner feature, home/materialDetail.tsx) ===== */
/* An absolutely-positioned panel over the right of the table — from the «Обновлено» column (left set at open
   time off the header cell) to the right edge — leaving the title column readable. The inner panel is sticky so
   a full article stays in view while the page scrolls; it scrolls internally when taller than the viewport. */
.EC-mdo { position: absolute; top: 0; right: 0; bottom: 0; z-index: 6; }
.EC-mdo--panel { position: sticky; top: 8px; display: flex; flex-direction: column; max-height: calc(100vh - 16px);
  background: var(--canvas); border: 1px solid var(--line); border-radius: 12px; box-shadow: -8px 0 30px -18px rgba(0,0,0,.4); overflow: hidden; }
/* STICKY head (actions + title + badges + meta) never scrolls; only the BODY (the article) does. */
.EC-mdo--head { flex: 0 0 auto; padding: 14px 18px 12px; border-bottom: 1px solid var(--line); }
.EC-mdo--body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 18px 22px; }
.EC-mdo--top { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
/* the pencil (edit) — reuses .EC-art--edit's masked glyph, but ALWAYS visible here (not hover-gated like the row) */
.EC-mdo--edit { opacity: 1; }
.EC-mdo--gear { font-size: 14px; line-height: 1; padding: 3px 7px; }
.EC-mdo--live { position: static; opacity: 1; width: 15px; height: 15px; }
/* the ✕ — LAST in the action row, pushed to the far right */
.EC-mdo--close { margin-left: auto; width: 28px; height: 28px; border: 1px solid var(--line);
  background: var(--canvas); color: var(--muted); border-radius: 8px; cursor: pointer; font-size: 15px; line-height: 1; }
.EC-mdo--close:hover { background: var(--hover); color: var(--ink); }
.EC-mdo--badges { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0; }
.EC-mdo--badge { font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 20px; white-space: nowrap; }
.EC-mdo--badge.is-ec { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.EC-mdo--badge.is-main { background: var(--hover); color: var(--ink); }
.EC-mdo--tags { display: inline-flex; gap: 5px; flex-wrap: wrap; }
/* tag / Раздел chips styled like the left-column filter chips (EC-tagf) — non-interactive here */
.EC-mdo--tagf { padding: 2px 8px; font-size: 12px; cursor: default; }
.EC-mdo--tagf:hover { background: transparent; }
.EC-mdo--title { font-size: 20px; font-weight: 700; line-height: 1.25; margin: 2px 0 4px; }
/* the meta block: «Опубликовано» / «Обновлено» date lines + the views/👍/👎 counters line */
.EC-mdo--meta { font-size: 12px; color: var(--muted); display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.EC-mdo--counters { display: flex; align-items: center; gap: 9px; margin-top: 4px; }
.EC-mdo--cnt { display: inline-flex; align-items: center; gap: 5px; }
.EC-mdo--cnt::before { content: ''; width: 14px; height: 14px; flex: none; background: currentColor;
  -webkit-mask-position: center; mask-position: center; -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.EC-mdo--cnt-v::before { -webkit-mask-image: var(--ec-views-ic); mask-image: var(--ec-views-ic); }
.EC-mdo--cnt-up::before { -webkit-mask-image: var(--thumb); mask-image: var(--thumb); }
.EC-mdo--cnt-down::before { -webkit-mask-image: var(--thumb); mask-image: var(--thumb); transform: rotate(180deg); }
.EC-mdo--cntsep { color: var(--line); }
.EC-mdo--article { font-size: 14px; line-height: 1.55; }
.EC-mdo--article img { max-width: 100%; height: auto; }
.EC-mdo--note { color: var(--muted); font-style: italic; padding: 8px 0; }
/* the ⚙ dialog's protected Hide (same two-step confirm the row used to carry, now un-misclickable behind ⚙) */
.EC-mdo--settings { display: flex; flex-direction: column; gap: 8px; min-width: 200px; }
.EC-mdo--hideconf { display: flex; gap: 8px; }

/* home header ⚙ → Settings menu: identity + own materials + «Пригласить стажёра» (base/TraineeRole.md) */
.EC-set--btn { font-size: 15px; line-height: 1; padding: 3px 7px; }
.EC-pop.EC-set--pop { width: 380px; max-width: 92vw; }   /* +40px for the roster rows (email + last-activity + chips) */
.EC-set { display: flex; flex-direction: column; gap: 8px; font-size: 13px; outline: none; padding-bottom: 4px; }
.EC-set--me { display: flex; align-items: center; gap: 10px; padding: 4px 0 8px; border-bottom: 1px solid var(--line); }
.EC-set--av { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; font-size: 13px; font-weight: 700; color: #fff; }
.EC-set--who { min-width: 0; }
.EC-set--nm { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-set--sub { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-set--h { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-top: 4px; }
.EC-set--list { display: flex; flex-direction: column; gap: 1px; }
/* The one rule in the menu: link rows above, «Мои стажёры» + invite below. Both the box and its neighbours
   collapse when empty — `.EC-set` is a gapped flex column, so an empty child would still cost 8px of dead
   space and a separator that separates nothing is exactly what the box must never look like. */
.EC-set--hr { width: 100%; height: 0; margin: 0; border: 0; border-top: 1px solid var(--line); }
.EC-set--sep:empty, .EC-set--list:empty, .EC-set--acts:empty { display: none; }
.EC-set--row { display: flex; align-items: center; gap: 8px; padding: 5px 7px; border-radius: 7px; text-decoration: none; color: var(--ink); }
.EC-set--row:hover, .EC-set--row:focus-visible { background: var(--hover); }
.EC-set--rowt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-set--acts { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.EC-set--item { text-align: left; }
.EC-set--muted { color: var(--muted); font-size: 12px; padding: 4px 0; }
.EC-set--err { color: var(--err); font-size: 12px; padding: 4px 0; }
.EC-set--ok { font-weight: 600; }
.EC-set--note { color: var(--muted); font-size: 12px; line-height: 1.4; }
.EC-set--form { display: flex; flex-direction: column; gap: 9px; padding: 4px 0 6px; }
.EC-set--res { display: flex; flex-direction: column; gap: 7px; padding: 9px; border: 1px solid var(--line); border-radius: 8px; background: var(--sel); }
.EC-set--urlrow { display: flex; gap: 6px; }
.EC-set--urlrow .EC-input { flex: 1 1 auto; min-width: 0; font-size: 12px; }

/* «Мои стажёры» — the mentor's roster inside the same ⚙ panel (base/TraineeRole.md §UI 2) */
.EC-tr, .EC-tr--list { display: flex; flex-direction: column; gap: 2px; }
.EC-tr--row { display: flex; flex-direction: column; gap: 4px; padding: 6px 7px; border-radius: 8px; }
.EC-tr--row:hover, .EC-tr--row:focus-within { background: var(--hover); }
.EC-tr--main { display: flex; align-items: center; gap: 9px; }
.EC-tr--av { width: 28px; height: 28px; font-size: 11px; }
.EC-tr--who { flex: 1 1 auto; min-width: 0; }
.EC-tr--sub { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 11px; color: var(--muted); }
.EC-tr--mail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.EC-tr--seen.is-pending { color: var(--err); }
/* [x] revoke: revealed by hover on the row AND by keyboard focus (:focus-within above + :focus-visible here).
   Hidden with OPACITY, never display:none — it must stay in the tab order for anyone who cannot hover. */
.EC-tr--x { opacity: 0; flex: 0 0 auto; border: 0; background: none; color: var(--muted); font-size: 17px; line-height: 1; cursor: pointer; padding: 0 4px; border-radius: 6px; transition: opacity .12s; }
.EC-tr--row:hover .EC-tr--x, .EC-tr--row:focus-within .EC-tr--x, .EC-tr--x:focus-visible { opacity: 1; }
.EC-tr--x:hover, .EC-tr--x:focus-visible { color: var(--err); background: var(--sel); }
.EC-tr--chips { display: flex; flex-wrap: wrap; gap: 4px; padding-left: 37px; }
.EC-tr--chip { font-size: 11px; padding: 2px 8px; border-radius: 11px; border: 1px solid var(--line); background: var(--bg); color: var(--ink); cursor: pointer; }
.EC-tr--chip:hover { background: var(--hover); }
.EC-tr--chip.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.EC-tr--act { font-size: 11px; padding: 2px 8px; border-radius: 11px; border: 1px dashed var(--line); background: none; color: var(--accent); cursor: pointer; }
.EC-tr--act.is-on { color: var(--muted); }
.EC-tr--none { font-size: 11px; color: var(--muted); }
.EC-tr--tgl { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--muted); padding-left: 37px; cursor: pointer; }
.EC-tr--tgl input { margin: 0; }
.EC-tr--exp { display: flex; flex-direction: column; gap: 1px; padding-left: 31px; }

/* review flow (base/TraineeRole.md §UI 3-4). The toolbar chip states «на проверке» + to whom — it must read as
   STATUS, not as a button, so no hover/pointer affordance. Two colours: waiting (amber) vs sent back (red). */
.EC-revchip { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; padding: 3px 9px; border-radius: 11px; border: 1px solid transparent; white-space: nowrap; cursor: default; }
.EC-revchip.is-wait { background: #fef3c7; border-color: #f0c36d; color: #7a4b00; }
.EC-revchip.is-back { background: #fde2e0; border-color: #e9a49e; color: #8a2018; }
.EC-rq--row { display: flex; flex-direction: column; gap: 4px; padding: 7px 8px; border: 1px solid var(--line); border-radius: 7px; }
.EC-rq--top { display: flex; align-items: baseline; gap: 8px; }
.EC-rq--t { font-size: 13px; font-weight: 600; color: var(--ink); text-decoration: none; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-rq--t:hover { text-decoration: underline; }
.EC-rq--sub { font-size: 11px; color: var(--muted); }
.EC-rq--drift { font-size: 11px; line-height: 1.35; color: #b45309; }
.EC-rq--acts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.EC-rq--tabs { display: flex; gap: 6px; margin-bottom: 5px; }

/* review composer (editor/reviewComposer.tsx) — the thread + the box you answer in. Mounted under the
   editor's publish control and inside every queue row, so it must sit happily in a 300px popup column. */
.EC-rc { display: flex; flex-direction: column; gap: 6px; }
.EC-rc--list { display: flex; flex-direction: column; gap: 5px; }
.EC-rc--msg { border-left: 2px solid var(--line); padding-left: 7px; }
.EC-rc--msg.is-note { border-left-color: #e9a49e; }
.EC-rc--msg.is-finished { border-left-color: #f0c36d; }
.EC-rc--who { font-size: 11px; color: var(--muted); }
.EC-rc--txt { font-size: 12px; line-height: 1.4; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.EC-rc--more { align-self: flex-start; background: none; border: 0; padding: 0; font: inherit; font-size: 11px; color: var(--muted); text-decoration: underline; cursor: pointer; }
.EC-rc--box { display: flex; flex-direction: column; gap: 5px; }
textarea.EC-rc--ta { min-height: 54px; line-height: 1.4; resize: vertical; overflow-y: auto; }
.EC-rc--bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.EC-rc--hint { flex: 1 1 140px; min-width: 0; font-size: 11px; line-height: 1.3; color: var(--muted); }
.EC-rc--hint.is-need { color: #b45309; }
.EC-rc--err { font-size: 12px; line-height: 1.35; color: #c0392b; }

/* editor toolbar: article title + back link */
.EC-back { text-decoration: none; color: var(--muted); font-size: 18px; padding: 2px 8px; border-radius: 6px; }
.EC-back:hover { background: var(--hover); color: var(--ink); }
.EC-title { font: inherit; font-size: 14px; font-weight: 600; border: 1px solid transparent; background: transparent; color: var(--ink); padding: 3px 8px; border-radius: 6px; width: 90px; min-width: 90px; }   /* width set by autoGrow */
.EC-title:hover { border-color: var(--line); }
.EC-title:focus { outline: none; border-color: var(--accent); background: var(--canvas); }
.EC-tb--sep { width: 1px; align-self: stretch; background: var(--line); margin: 2px 4px; }
.EC-uri { display: inline-flex; align-items: center; font-size: 12px; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.EC-uri--pfx { opacity: .6; }
.EC-slug { font: inherit; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; border: 1px solid transparent; background: transparent; color: var(--accent); padding: 2px 4px; border-radius: 5px; width: 60px; }   /* width set by autoGrow */
.EC-slug:hover { border-color: var(--line); }
.EC-slug:focus { outline: none; border-color: var(--accent); background: var(--canvas); }
.EC-art--slug { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent); opacity: .8; }
.EC-art--row.is-hidden > .EC-art--cell { opacity: .5; }   /* row is display:contents → dim the cells, not the (box-less) row */
.EC-art--hide { border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; padding: 4px 8px; border-radius: 6px; opacity: 0; }
.EC-art--row:hover .EC-art--hide { opacity: 1; }
.EC-art--hide:hover { background: var(--hover); color: var(--ink); }
/* REDESIGN item #2 — the protected-hide two-step confirm. The confirm/cancel pair is NOT hover-gated (unlike
   the armed .EC-art--hide), so an armed prompt stays visible while the pointer moves to it; «✕» cancels. */
.EC-art--hideconf { display: inline-flex; align-items: center; gap: 4px; }
.EC-art--hideyes { border: none; background: var(--brand-red); color: #fff; cursor: pointer; font-size: 12px; padding: 4px 9px; border-radius: 6px; }
.EC-art--hideyes:hover { filter: brightness(1.08); }
.EC-art--hideno { border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; padding: 4px 7px; border-radius: 6px; }
.EC-art--hideno:hover { background: var(--hover); color: var(--ink); }

/* REDESIGN item #1/#3/#4 — the single column-HEADER row (display:contents so its cells share the list's grid
   tracks and line up over their columns). It replaces the per-row date labels and the standalone sortbar. */
.EC-art--head { display: contents; }
.EC-art--hcell { position: sticky; top: 0; z-index: 3; flex-direction: row; align-items: center; padding-top: 8px; padding-bottom: 8px; border-bottom: 2px solid var(--line); }
.EC-art--hcell.EC-art--c-updated, .EC-art--hcell.EC-art--c-published { justify-content: flex-end; }
.EC-art--search { width: 100%; min-width: 0; }   /* the Title header IS the search box (item #4) */
.EC-art--sorth { border: none; background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; padding: 3px 6px; border-radius: 6px; display: inline-flex; align-items: center; gap: 4px; }
.EC-art--sorth:hover { background: var(--hover); color: var(--ink); }
.EC-art--hcell.is-sort .EC-art--sorth { color: var(--ink); }
.EC-art--arr { font-size: 11px; font-weight: 700; color: var(--accent); }

/* OWNER FEEDBACK A — the date filter lives ON the «Опубликовано» header: a funnel icon to the LEFT of the
   label (grey inactive → accent when a range is set) and, when active, the selected range in accent, smaller,
   BELOW the header. The published header cell therefore stacks (icon+label row over the range text). */
.EC-art--hcell.EC-art--c-published { flex-direction: column; align-items: flex-end; gap: 2px; }
.EC-art--hpub { display: inline-flex; align-items: center; gap: 4px; }
/* the funnel: a masked SVG token so it themes with the background colour (grey → accent), like --ec-live-ic */
.EC-art--datefilter { flex: none; width: 14px; height: 14px; padding: 0; border: 0; background: var(--muted); cursor: pointer;
  -webkit-mask: var(--ec-filter-ic) center/contain no-repeat; mask: var(--ec-filter-ic) center/contain no-repeat; }
.EC-art--datefilter:hover { background: var(--ink); }
.EC-art--datefilter.is-active { background: var(--accent); }
/* the selected range under the header — accent, smaller; clicking it re-opens the picker */
.EC-art--daterange { border: 0; background: transparent; color: var(--accent); cursor: pointer; font: inherit; font-size: 10px; line-height: 1.2; letter-spacing: 0; text-transform: none; font-weight: 600; padding: 0; white-space: nowrap; }
.EC-art--daterange:hover { text-decoration: underline; }

/* REDESIGN items #4/#5/T105 — the ONE top filter bar (date-range · active section/tag chips). */
.EC-home--filters { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; margin: 8px 0 14px; }
.EC-home--chips { min-height: 0; }
.EC-home--chip { display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 13px; padding: 4px 10px; border: 1px solid var(--line); border-radius: 20px; background: var(--canvas); color: var(--ec-fg); cursor: pointer; }
.EC-home--chip:hover { background: var(--hover); }
.EC-home--chiptx { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.EC-home--chipx { font-size: 11px; opacity: .55; }

/* REDESIGN item #5 — the minimal Разделы/Теги TAB strip atop the left panel. */
.EC-home--tagtabs { display: flex; gap: 2px; margin-bottom: 8px; border-bottom: 1px solid var(--line); }
.EC-home--tagtab { flex: 1 1 0; font: inherit; font-size: 13px; padding: 6px 8px; border: none; background: transparent; color: var(--muted); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.EC-home--tagtab:hover { color: var(--ink); }
.EC-home--tagtab.is-on { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }

/* toolbar — quiet */
.EC-toolbar { display: flex; align-items: center; gap: 18px; padding: 7px 14px; background: var(--panel); border-bottom: 1px solid var(--line); }
.EC-group { display: flex; align-items: center; gap: 3px; }
.EC-group--lbl { font-size: 11px; color: var(--muted); margin-right: 5px; }
.EC-seg, .EC-btn { font: inherit; font-size: 13px; padding: 3px 9px; border: 1px solid transparent; background: transparent; color: var(--muted); border-radius: 6px; cursor: pointer; }
.EC-seg:hover, .EC-btn:hover { background: var(--hover); color: var(--ink); }
.EC-seg.is-on, .EC-btn.is-on { background: var(--canvas); color: var(--accent); border-color: var(--line); }
/* icon segmented multivalue toggle (device / theme) */
.EC-iseg { display: inline-flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.EC-iseg--b { font: inherit; font-size: 14px; line-height: 1; padding: 4px 8px; border: none; background: transparent; color: var(--muted); cursor: pointer; }
.EC-iseg--b + .EC-iseg--b { border-left: 1px solid var(--line); }
.EC-iseg--b:hover { background: var(--hover); color: var(--ink); }
.EC-iseg--b.is-on { background: var(--canvas); color: var(--accent); }

/* icon button (panel toggles / collapse) */
.EC-icon { font-size: 15px; line-height: 1; padding: 4px 8px; }
.EC-tgl { border: 1px solid var(--line); }
.EC-tgl.is-on { color: var(--accent); border-color: var(--accent); background: var(--canvas); }
.EC-tgl--right { margin-left: 4px; }
/* History button = save-state indicator (border color) + a transient floating status label below it. */
.EC-histwrap { position: relative; display: inline-flex; }
.EC-histbtn.is-saved { border-color: var(--ec-success); box-shadow: 0 0 0 1px var(--ec-success) inset; }   /* saved/up-to-date */
.EC-histbtn.is-editing { border-color: var(--line); }                                    /* editing */
.EC-histbtn.is-detached { border-color: var(--ec-warning); box-shadow: 0 0 0 1px var(--ec-warning) inset; }  /* on a history branch */
.EC-histbtn.is-conflict { border-color: #dc2626; box-shadow: 0 0 0 1px #dc2626 inset; }   /* sync conflict / offline */
.EC-histlabel { position: absolute; top: calc(100% + 5px); right: 0; white-space: nowrap; font-size: 11px; color: var(--muted); background: var(--panel); border: 1px solid var(--line); border-radius: 5px; padding: 2px 7px; box-shadow: 0 3px 10px var(--ec-shadow); opacity: 0; transform: translateY(-3px); pointer-events: none; transition: opacity .35s ease, transform .35s ease; z-index: 50; }
.EC-histlabel.is-show { opacity: 1; transform: translateY(0); }

.EC-save { font-size: 12px; color: var(--muted); }
.EC-save.is-saved { color: #2faf5a; }
.EC-btn.is-off { opacity: .35; pointer-events: none; }

/* top-panel undo/redo split buttons (main = one step, ▾/right-click = dropdown) */
.EC-undgrp { display: inline-flex; }
.EC-undgrp .EC-seg { padding: 3px 7px; }
.EC-und--caret { padding: 3px 3px; margin-left: -3px; font-size: 10px; }
.EC-seg.is-off { opacity: .4; }
.EC-blk--missing { display: none; }   /* dangling block reference → render nothing (safety placeholder) */
/* presence: active-editor avatars (base/Multiplayer.md) — colored initials, overlapped */
.EC-presence-wrap { display: inline-flex; align-items: center; margin-right: 8px; }
.EC-presence { display: inline-flex; align-items: center; }
.EC-presence--av { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; margin-left: -6px; border-radius: 50%; border: 2px solid var(--panel); font-size: 10px; font-weight: 700; color: #fff; box-shadow: 0 1px 2px rgba(20,25,35,.2); }
.EC-presence--av:first-child { margin-left: 0; }
.EC-presence--more { margin-left: 4px; font-size: 11px; color: var(--muted); }
/* Head picker (base/CollabModel.md §7a): Live + colleagues' draft branches. Functional pass; UX polish later. */
.EC-headpicker { display: none; align-items: center; gap: 4px; margin-right: 8px; }
.EC-headpicker.is-on { display: inline-flex; }
.EC-head { display: inline-flex; align-items: center; gap: 4px; padding: 0px 0px 0px 6px; border: 1px solid var(--line); border-radius: 16px; background: var(--canvas); color: var(--muted); font-size: 11px; cursor: pointer;  white-space: nowrap; min-height: 24px; box-sizing: border-box; }
.EC-head.is-empty { padding: 0 10px; }   /* no avatars → balanced horizontal padding (the min-height keeps it the same size as chips with people) */
.EC-head:hover { border-color: var(--accent); color: var(--accent); }
/* The head THIS TAB (socket) is currently in — the per-tab accent. NOT keyed by account, so each tab of the
   same user (a shared link opened twice) highlights its OWN current head (base/CollabModel.md §7a). */
.EC-head.is-here { background: var(--accent); color: #fff; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.EC-head--lbl { padding: 0 2px; }
.EC-head { padding-right: 4px; }   /* room for the trailing avatars */
/* Per-person avatar INSIDE a head chip — clickable, jumps to where that person is (their own history/draft room). */
.EC-head--av { width: 18px; height: 18px; font-size: 9px; border-width: 2px; cursor: pointer; }
.EC-head--av:hover, .EC-head--av.is-here { box-shadow: 0 0 0 2px var(--accent); z-index: 1; }
/* Detached-from-live banner (base/CollabModel.md §7): shown when viewing/editing an earlier version (a branch). */
.EC-livebanner { display: none; align-items: center; gap: 6px; margin-right: 8px; padding: 3px 10px; border: 1px solid var(--ec-warning); border-radius: 6px; background: #fff5e8; color: #a35a00; font-size: 12px; font-weight: 600; cursor: pointer; }
.EC-livebanner.is-on { display: inline-flex; animation: ec-pulse 1.2s ease 2; }
.EC-livebanner:hover { background: #ffe9cc; }
/* dark: the cream bg (#fff5e8) + amber ink (#a35a00) become a translucent warning tint + light amber */
[data-theme="dark"] .EC-livebanner { background: color-mix(in srgb, var(--ec-warning) 18%, transparent); color: var(--ec-warning); }
[data-theme="dark"] .EC-livebanner:hover { background: color-mix(in srgb, var(--ec-warning) 28%, transparent); }
.EC-presence-wrap.has-others { animation: ec-pulse 1.2s ease 2; }
.EC-presence-wrap.is-hidden { display: none; }   /* head picker is showing → it already lists who's where */
@keyframes ec-pulse { 50% { filter: brightness(1.3); } }
/* Remote cursor (base/Multiplayer.md): a thin colored caret at a co-author's exact text position + a name flag. */
.EC-rcaret { position: absolute; width: 2px; border-radius: 1px; pointer-events: none; z-index: 6; animation: ec-rcaret-in .12s ease; transition: left .08s linear, top .08s linear, height .08s linear; }
.EC-rcaret--flag { position: absolute; top: -1.15em; left: -1px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; line-height: 1.3; font-weight: 600; color: #fff; background: inherit; padding: 0 4px; border-radius: 3px 3px 3px 0; box-shadow: 0 1px 2px rgba(20,25,35,.25); opacity: 0; transition: opacity .15s ease; }
.EC-rcaret:hover .EC-rcaret--flag, .EC-rcaret.is-fresh .EC-rcaret--flag { opacity: .96; }
@keyframes ec-rcaret-in { from { opacity: 0; } }
/* Interactive chart (editor): drag nodes to pin; «↻» resets to autolayout (base/Chart.md). */
.EC-blk--chart { position: relative; }
.EC-chart--auto { position: absolute; top: 6px; right: 6px; z-index: 2; width: 26px; height: 26px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--muted); cursor: pointer; font-size: 14px; line-height: 1; }
.EC-chart--auto:hover, .EC-chart--help:hover { color: var(--accent); border-color: var(--accent); }
.EC-chart--help { position: absolute; top: 6px; right: 36px; z-index: 2; width: 26px; height: 26px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--muted); cursor: pointer; }
/* node SHAPES (base/Chart.md): rect (default) · rhombus (condition) · ellipse */
.doc-chart__node.shape-ellipse { border-radius: 50%; text-align: center; padding: 20px 28px; }
.doc-chart__node.shape-ellipse::before { display: none; }
.doc-chart__node.shape-rhombus { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); text-align: center; padding: 28px 34px; border-radius: 0; }
.doc-chart__node.shape-rhombus::before { display: none; }
/* per-node control bar (a canvas sibling above the node → never clipped by a shape) */
.EC-chart--bar { position: absolute; display: flex; gap: 2px; z-index: 6; opacity: 0; pointer-events: none; transition: opacity .1s; }
.EC-chart--bar.show { opacity: 1; pointer-events: auto; }
.EC-chart--bar button { width: 20px; height: 20px; padding: 0; font-size: 12px; line-height: 1; border: 1px solid var(--line); border-radius: 5px; background: var(--canvas); color: var(--muted); cursor: pointer; }
.EC-chart--bar button:hover { color: var(--accent); border-color: var(--accent); }
.EC-blk--chart .doc-chart__t[contenteditable], .EC-blk--chart .doc-chart__b[contenteditable] { outline: none; cursor: text; border-radius: 3px; }
.EC-blk--chart .doc-chart__t[contenteditable]:focus, .EC-blk--chart .doc-chart__b[contenteditable]:focus { box-shadow: 0 0 0 2px var(--accent); }
/* syntax legend */
.EC-chart--legend { position: absolute; top: 38px; right: 6px; z-index: 7; display: none; width: 250px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 6px 24px var(--ec-shadow); font-size: 12px; color: var(--ink-soft); }
.EC-chart--legend.show { display: block; }
.EC-chart--legend b { display: block; margin: 7px 0 3px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.EC-chart--legend b:first-child { margin-top: 0; }
.EC-chart--legend b span { text-transform: none; letter-spacing: 0; font-weight: 400; }
.EC-chart--legend code { font-size: 11.5px; background: var(--canvas); padding: 1px 4px; border-radius: 4px; }
.EC-chart--legend .EC-chart--lh { color: var(--muted); margin-top: 3px; }
.EC-toolbar .EC-lang { padding: 3px 6px; font-size: 12px; color: var(--muted); cursor: pointer; }
.EC-toolbar .EC-lang:hover { color: var(--ink); }
.EC-toolbar .EC-lang.is-translating { color: var(--accent); background: var(--canvas); border: 1px solid var(--accent); border-radius: 6px; }
/* Translation mode: a thin accent rail down the canvas so it's clear you're editing a translation, not the source. */
.EC-translating .EC-canvas { box-shadow: inset 3px 0 0 var(--accent); }
.EC-hist--menu { min-width: 190px; }

/* commit graph drawer */
.EC-drawer { width: 0; overflow: hidden; transition: width .15s ease; border-left: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; }
.EC-app.history-open .EC-drawer { width: 300px; }
.EC-drawer--head { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; border-bottom: 1px solid var(--line); font-weight: 600; flex: 0 0 auto; }
.EC-drawer--body { overflow: auto; }
.EC-graph { padding: 6px 0; }
.EC-graph--row { display: flex; gap: 10px; padding: 6px 12px; cursor: pointer; position: relative; }
.EC-graph--row:hover { background: var(--hover); }
/* The commit currently being PREVIEWED — left accent bar + tint, so it's clear which version you're looking at. */
.EC-graph--row.is-viewing { background: var(--sel); box-shadow: inset 3px 0 0 var(--accent); }
.EC-graph--row::before { content: ''; position: absolute; left: 21px; top: 0; bottom: 0; width: 2px; background: var(--line); }
.EC-graph--dot { position: relative; z-index: 1; width: 12px; height: 12px; border-radius: 50%; border: 2px solid; flex: 0 0 auto; margin-top: 3px; }
/* commit author avatar (initial in a colored circle) — replaces the email; one-line "<time> · <summary>" */
.EC-cmt--av { position: relative; z-index: 1; box-sizing: border-box; width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto; margin-top: 1px; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 10px; font-weight: 700; border: 2px solid var(--panel); }
.EC-cmt--av.is-actual { box-shadow: 0 0 0 2px var(--accent); }
.EC-graph--line { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; font-size: 12px; min-height: 20px; }
.EC-graph--pub { display: inline-block; background: color-mix(in srgb, var(--accent), #000 20%); color: #fff; font-weight: 600; font-size: 10px; line-height: 1.5; padding: 0 7px; border-radius: 10px; margin-left: 4px }
.EC-graph--sep { color: var(--muted); }
.EC-graph--dot.is-actual { box-shadow: 0 0 0 3px var(--sel); }
.EC-graph--msg { font-size: 13px; }
.EC-graph--meta { font-size: 11px; color: var(--muted); }
.EC-graph--time { cursor: help; }
.EC-sum { display: inline-flex; flex-wrap: wrap; gap: 8px; font-size: 12px; }
.EC-sum--grp { display: inline-flex; align-items: center; gap: 2px; color: var(--ink); }
.EC-sum--grp.is-add { color: var(--ec-success); }      /* added → green */
.EC-sum--grp.is-remove { color: #dc2626; }   /* removed → red */
.EC-sum--ic { display: inline-flex; align-items: center; }
.EC-graph--row.is-offline .EC-graph--msg { color: #dc2626; font-weight: 600; }
/* History as a floating dialog (popup). Leaves on top, scrollable commit list, slim actions. */
.EC-pop.EC-histpop { width: 360px; }
.EC-histpanel { display: flex; flex-direction: column; gap: 8px; }
.EC-histpanel .EC-graph { overflow-y: auto; max-height: 46vh; margin: 0 -4px; padding: 0 4px; }
/* «Версии документа»: the joinable-room heads as inline chips, with a small section header.
   Omitted entirely when only the published version exists → the dialog shows just the commit list. */
.EC-versions { display: flex; flex-direction: column; gap: 5px; }
.EC-versions--hd, .EC-versions--list { font-size: 12px; font-weight: 400; letter-spacing: .04em; color: var(--muted); }
.EC-versions--list { margin-top: 6px }
.EC-leaves { display: flex; flex-flow: row wrap; gap: 6px; }
.EC-leaf { text-align: left; padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--canvas); color: var(--ink); cursor: pointer; font-size: 12px; white-space: nowrap; }
.EC-leaf:hover { border-color: var(--accent); }
.EC-leaf.is-active { border-color: var(--accent); background: var(--accent); color: #fff; }
/* Опубликовать / Экспорт live in the dialog header (next to pin/close), compact. */
.EC-hist--act { padding: 2px 9px; font-size: 11px; }
.EC-pcfg--hide { margin-bottom: 8px; text-align: left; }
.EC-graph--row.is-local { background: var(--sel); cursor: default; }
.EC-graph--dot.is-local { background: transparent !important; border: 2px dashed var(--accent) !important; }
.EC-graph--local { display: flex; gap: 6px; margin-top: 5px; }
.EC-graph--desc { font-size: 12px; padding: 4px 6px; flex: 1; }
.EC-graph--push { border: 1px solid var(--line); background: var(--canvas); color: var(--accent); border-radius: 6px; cursor: pointer; padding: 0 9px; }
.EC-graph--push:hover { border-color: var(--accent); }
.EC-graph--push.is-off { opacity: .4; pointer-events: none; }

/* split */
.EC-main { flex: 1; display: flex; min-height: 0; position: relative; }
/* Floating Structure toggle — sits just under the title bar at the canvas top-left; the opaque tree panel
   (higher z-index) overflows/covers it when open, so it never costs toolbar width. */
.EC-treetgl { position: absolute; left: 8px; top: 8px; z-index: 1; }

/* outline / tree-view (left, hideable) */
.EC-tree { position: relative; z-index: 2; width: 0; overflow: hidden; transition: width .15s ease; border-right: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; flex: 0 0 auto; }
/* 180px default, user-resizable via --ec-tree-w (set + persisted by the Formo resizer). */
.EC-app.tree-open .EC-tree { width: var(--ec-tree-w, 180px); }
.EC-tree--head { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; border-bottom: 1px solid var(--line); font-weight: 600; flex: 0 0 auto; }
.EC-tree--body { overflow: auto; padding: 6px 0; }
.EC-tree--row { display: flex; align-items: center; gap: 7px; padding: 4px 12px 4px 0; cursor: pointer; font-size: 13px; white-space: nowrap; border-radius: 5px; }
.EC-tree--row:hover { background: var(--hover); }
.EC-tree--row.is-sel { background: var(--sel); color: var(--accent); box-shadow: inset 2px 0 0 var(--accent); }
.EC-tree--tw { width: 18px; text-align: center; font-size: 18px; line-height: 1; color: var(--muted); cursor: pointer; flex: 0 0 auto; }
.EC-tree--tw:hover { color: var(--ink); }
.EC-tree--ic { width: 16px; text-align: center; opacity: .7; flex: 0 0 auto; }
.EC-tree--lbl { overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; }
/* hide-eye: hover-revealed on the right of a tree row; always shown + solid when the block is hidden */
.EC-tree--eye { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 18px; box-sizing: border-box; border: 0; background: transparent; cursor: pointer; font-size: 12px; line-height: 1; padding: 0; border-radius: 4px; opacity: 0; filter: grayscale(1); }
.EC-tree--row:hover .EC-tree--eye { opacity: .6; }
.EC-tree--eye:hover { opacity: 1 !important; background: var(--hover); }
.EC-tree--row.is-hidden .EC-tree--eye { opacity: .85; filter: none; }
.EC-tree--row.is-hidden .EC-tree--lbl, .EC-tree--row.is-hidden .EC-tree--ic { opacity: .45; }
/* hidden block: gone on the canvas too (the editor IS a live preview) — manage it from the tree */
.EC-row.is-hidden { display: none !important; }
/* …and collapse its nested wrapper so it stops holding a flex/grid slot (e.g. a hidden column) */
.EC-nest--item:has(> .EC-row.is-hidden) { display: none !important; }
.EC-hidden-block { display: none !important; }
/* details-panel eye toggle */
.EC-details--titlerow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.EC-details--titlerow .EC-details--title { flex: 1 1 auto; }
.EC-details--titlerow .EC-pcfg--hide { margin: 0; }   /* «Скрыть материал» sits on the section header, not its own row (item 5) */
.EC-eye { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 22px; box-sizing: border-box; border: 0; background: transparent; cursor: pointer; font-size: 15px; line-height: 1; padding: 0; border-radius: 5px; filter: grayscale(1); opacity: .7; }
.EC-eye:hover { background: var(--hover); opacity: 1; }
.EC-eye.is-hidden { filter: none; opacity: 1; }
.EC-canvasWrap { flex: 1; overflow: auto; padding: 36px 20px; display: flex; justify-content: center; align-items: flex-start;}
/* overflow: clip + a clip-margin so the left drag gutter (-20px) + selection header are REACHABLE (not cropped),
   while wider content still clips (no banner spill). Was `overflow:hidden`, which cropped the dragger → blocks
   felt undraggable. Modern browsers honor the margin; older fall back to clipping at the edge (prior behavior). */
.EC-canvas { width: 820px; max-width: 100%; background: var(--canvas); min-height: 60vh; transition: width .15s ease; box-shadow: 0px 0px 14px 3px rgb(177 180 184 / 89%); border-radius: 4px; overflow: clip; overflow-clip-margin: 30px; }
/* Top block's floating toolbar sits ABOVE the block (bottom: calc(100% - 4px)); for the FIRST row it would
   render above the canvas top and be clipped by the canvas overflow:hidden, making it unreachable. Drop just
   that toolbar to sit inside the block's top edge so it stays fully visible + clickable (item 5). The first
   canvas child is the begin insertion-gap, so the first row is `.EC-gap:first-child + .EC-row`. */
.EC-canvas > .EC-gap:first-child + .EC-row.is-selected > .EC-bv--header { bottom: auto; top: 4px; }
.EC-side { position: relative; z-index: 2; width: 0; border-left: 1px solid var(--line); background: var(--panel); overflow: hidden; transition: width .15s ease; }
/* 250px default, user-resizable via --ec-side-w (set + persisted by the Formo resizer). */
.EC-app.details-open .EC-side { width: var(--ec-side-w, 250px); overflow: auto; }
/* During an active drag the width transition is suspended so the panel tracks the pointer 1:1. */
.EC-app.is-resizing .EC-side, .EC-app.is-resizing .EC-tree { transition: none; }
/* Floating Settings toggle — mirror of .EC-treetgl on the right; the opaque details panel overflows it when open. */
.EC-settgl { position: absolute; right: 8px; top: 8px; z-index: 1; }
.EC-details { padding: 8px 6px 8px 8px }
.EC-side--close { margin: 0; }
.EC-side .EC-side--close { margin: 6px 4px; position: absolute; right: 0; }
.EC-details { width: 100%; box-sizing: border-box; }   /* fills the (now resizable) panel; clipped by .EC-side overflow while it animates */
/* Panel resize grips (Formo resizer, editor/resizer.ts) — thin col-resize strips absolutely positioned in the
   (non-scrolling) .EC-main at each panel's boundary, driven by the same --ec-tree-w / --ec-side-w vars. Shown
   only while the panel is open; z-index above the panels so the grip is always grabbable. */
.EC-resizer { position: absolute; top: 0; bottom: 0; width: 9px; z-index: 6; cursor: col-resize; touch-action: none; display: none; }
.EC-app.tree-open .EC-resizer--right { display: block; left: calc(var(--ec-tree-w, 180px) - 4px); }      /* structure panel right edge */
.EC-app.details-open .EC-resizer--left { display: block; right: calc(var(--ec-side-w, 250px) - 4px); }    /* details panel left edge */
.EC-resizer::after { content: ''; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); background: transparent; transition: background .1s ease; }
.EC-resizer:hover::after, .EC-resizer.is-active::after { background: var(--accent); }

/* block row — calm: gutter handle in the margin, no content box */
.EC-row { position: relative; border-radius: 5px; }
.EC-row:hover { outline: 1px dashed var(--half-accent); }
.EC-row:hover:has(.EC-row:hover) { outline: 0 }
.EC-row.is-selected { outline: 1px dashed var(--half-accent);}
/* only the DEEPEST hovered row tints — ancestors of a hovered row stay clear (no compounding) */
.EC-row:not(.is-selected):has(.EC-row:hover) { background: transparent; }
/* SELECTION IS A BORDER, NOT A FILL (owner, 2026-08-20: «remove the blue background on a selected
   block — the border is enough»). The blue was `box-shadow: 0 0 3px 5px var(--sel)`, a 5px spread of
   the selection tint bleeding around the box; the dashed outline above + this left rule carry the
   state on their own. `--sel` still tints the tree row, the commit graph and the checkout flash. */
.EC-row.is-selected { border-left: 1px solid var(--half-accent); box-sizing: border-box;margin-left: -1px; }
/* Ghost trailing paragraph (click-below): a placeholder, not yet a committed block — dim until you type. */
.EC-row.EC-ghost { opacity: .55; }
.EC-row.EC-ghost.is-selected { opacity: 1; }
/* Gentle "this version touched here" cue when checking out a commit — a soft accent wash that fades out. */
@keyframes ec-blk-flash { 0% { background: var(--sel); box-shadow: inset 2px 0 0 var(--accent); } 100% { background: transparent; box-shadow: inset 2px 0 0 transparent; } }
.EC-row.EC-blk-flash { animation: ec-blk-flash 1.6s ease-out; }
.EC-gutter { position: absolute; left: -26px; top: 0; display: flex; align-items: flex-start; padding-top: 3px; opacity: 0; transition: opacity .1s; }
/* only the INNERMOST hovered row shows its handle (direct-child + :has hides ancestors) */
.EC-row:hover > .EC-gutter { opacity: .85; }
.EC-row.is-selected > .EC-gutter { opacity: 1; }   /* the active/current block: handle just shown, not hover-gated */
.EC-row:has(.EC-row:hover) > .EC-gutter { opacity: 0; }
/* nested blocks' handles are hidden until their PARENT is selected (then one level is revealed) */
.EC-nest--item .EC-gutter, .EC-nest--single .EC-gutter { display: none; }
.EC-childvis > .EC-gutter { display: flex; opacity: .85; }
.EC-childvis:has(.EC-row:hover) > .EC-gutter { opacity: 0; }
/* …and a nested block shows ITS OWN handle once SELECTED (not hover-gated: the handle sits left of the
   row, so a hover trigger would vanish as the pointer leaves the row to reach it — and the gap belongs
   to the parent). Selecting the block keeps it stable + reachable, same render path as .EC-childvis. */
.EC-nest--item > .EC-row.is-selected > .EC-gutter { display: flex; opacity: 1; }
/* хват (drag grip): a framed, grabbable target — bordered box, brighter glyph */
.EC-handle { cursor: grab; color: var(--ink-soft, var(--muted)); font-size: 22px; line-height: 1; user-select: none; padding: 3px 5px; border-radius: 6px; border: 1px solid var(--line); background: var(--canvas); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
.EC-handle:hover { background: var(--sel); border-color: var(--half-accent); color: var(--accent); opacity: 1; }
/* persistent per-type glyph in the gutter (idle affordance); hands off to the drag handle on hover/select */
.EC-typeico { position: absolute;
    left: -18px;
    top: 6px;
    padding-top: 4px;
    font-size: 14px;
    line-height: 1.2;
    color: var(--muted);
    opacity: .5;
    transition: opacity .1s;
    pointer-events: none;
    user-select: none;
    width: 12px;
    text-align: right; }
.EC-row:hover > .EC-typeico, .EC-row.is-selected > .EC-typeico { opacity: 0; }
.EC-nest--item .EC-typeico, .EC-nest--single .EC-typeico { display: none; }
.EC-bv--body { padding: 3px 4px; }

/* preview hides affordances */
.EC-app.is-preview .EC-gutter, .EC-app.is-preview .EC-typeico, .EC-app.is-preview .EC-gap, .EC-app.is-preview .EC-bv--header { display: none !important; }
.EC-app.is-preview .EC-row { background: transparent; box-shadow: none; }

/* block selection header — inline toolbar on the block (type switcher + more) */
.EC-bv--header { position: absolute; left: 0; bottom: calc(100% - 4px); display: none; align-items: center; gap: 2px; padding: 3px; background: var(--canvas); border: 1px solid var(--line); border-radius: 7px; box-shadow: 0 2px 12px rgba(0,0,0,.14); z-index: 5; }
/* ONLY the selected block's own header (direct child) — not its nested children's headers */
.EC-row.is-selected > .EC-bv--header { display: flex; }
.EC-bh--type { font: inherit; font-size: 12px; font-weight: 600; padding: 3px 8px; border: none; background: transparent; color: var(--ink); border-radius: 5px; cursor: pointer; white-space: nowrap; }
.EC-bh--type:hover { background: var(--sel); color: var(--accent); }
.EC-bh--more, .EC-bh--cfg { border: none; background: transparent; color: var(--muted); cursor: pointer; width: 24px; height: 24px; border-radius: 5px; font-size: 14px; line-height: 1; }
.EC-bh--more:hover, .EC-bh--cfg:hover { background: var(--sel); color: var(--accent); }

/* details panel close (collapse the escape hatch) */
.EC-side--close { float: right; border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 18px; line-height: 1; border-radius: 5px; width: 26px; height: 26px; }
.EC-side--close:hover { background: var(--sel); color: var(--ink); }
.EC-app.is-preview [contenteditable] { outline: none; }

/* insertion gaps (begin / between / end) */
.EC-gap { height: 16px; margin: -3px 0; display: flex; align-items: center; cursor: pointer; position: relative; }
.EC-gap--line { flex: 1; height: 8px; background: var(--accent); border-radius: 4px; opacity: 0; transition: opacity .1s, box-shadow .1s; }
/* left:-28px, NOT -32: `.EC-canvas` clips at `overflow-clip-margin: 30px` (:536), so a 26px-wide button
   at -32 spans -32…-6 and loses its outer 2px by construction. -28 spans -28…-2, inside the margin. */
.EC-gap--plus { position: absolute; left: -28px; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: var(--accent); font-weight: 700; line-height: 1; border: 1px solid var(--half-accent); border-radius: 7px; background: var(--canvas); box-shadow: 0 1px 3px rgba(0,0,0,.12); opacity: 0; font-size: 20px; }
.EC-gap:hover .EC-gap--line { opacity: 1; box-shadow: 0 0 7px color-mix(in srgb, var(--accent) 55%, transparent); }
.EC-gap:hover .EC-gap--plus { opacity: 1; }

/* drag drop indicator */
.EC-dropline { position: fixed; height: 2px; background: var(--accent); border-radius: 2px; z-index: 900; pointer-events: none; }
body.EC-isDragging { cursor: grabbing; user-select: none; }

/* compiled-block host wrapper — transparent to layout */
.EC-blkhost { display: contents; }

/* nested blocks (blockRef / array<blockRef>) */
.EC-blk--section { border: 1px solid var(--line); border-radius: 8px; padding: 8px; }
.EC-nest--list { display: flex; flex-direction: column; gap: 6px; }
.EC-nest--item { position: relative; border-radius: 6px; /*padding: 2px 6px;*/ }
/* no background here — the nested block's own EC-row handles the (single, deepest) highlight */
/* add affordance is OUT of flow (absolute at the slot's add-edge), shown on hover / when empty */
.EC-nest--add { position: absolute; display: none; z-index: 5; font: inherit; font-size: 12px; padding: 3px 9px; border: 1px dashed var(--accent); border-radius: 16px; background: var(--canvas); color: var(--accent); cursor: pointer; white-space: nowrap; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
.EC-nest--add:hover { background: var(--sel); }
.EC-slot { position: relative; }
/* vertical slot (section/column content) → add at the bottom */
.EC-slot:not(.EC-slot--row) > .EC-nest--add { left: 50%; bottom: -12px; transform: translateX(-50%); }
.EC-slot:not(.EC-slot--row) > .EC-nest--add::after { content: ' ↓'; }
/* row slot (columns) → add at the right */
.EC-slot--row > .EC-nest--add { right: -14px; top: -9px; transform: translateY(-50%); }
.EC-slot--row > .EC-nest--add::after { content: ' →'; }
/* +add shows when: the slot is hovered DIRECTLY (not over a child item), the slot's flex block is the
   active/selected one, or the slot is empty. Hovering a child column no longer reveals the parent's +. */
.EC-slot:hover:not(:has(.EC-nest--item:hover)) > .EC-nest--add,
.EC-row.is-selected > .EC-bv--body .EC-slot--row:not(:has(.EC-nest--item:hover)) > .EC-nest--add,
.EC-slot:not(:has(.EC-nest--item)) > .EC-nest--add { display: inline-flex; }
.EC-blk--ref { border-left: 2px solid var(--accent); padding-left: 8px; }
/* author index: А–Я letter bar + search box (parameterized routing) */
.EC-lbar { display: flex; flex-wrap: wrap; gap: 2px; margin-bottom: 12px; }
.EC-lbar--l { padding: 2px 7px; border-radius: 5px; color: var(--accent); text-decoration: none; font-size: 13px; line-height: 1.6; }
.EC-lbar--l:hover { background: var(--sel); }
.EC-lbar--l.is-on { background: var(--accent); color: #fff; }
.EC-lsearch { margin-bottom: 14px; }
.EC-lsearch--in { font: inherit; padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--canvas); color: var(--ink); width: min(320px, 100%); }
/* person row (person-inline item template) — horizontal avatar + name + role */
.EC-person--row { display: flex; align-items: center; gap: 12px; padding: 8px 6px; border-bottom: 1px solid var(--line); text-decoration: none; color: var(--ink); }
.EC-person--row:hover { background: var(--hover); }
.EC-person--rav { width: 44px; height: 44px; border-radius: 50%; background: var(--sel) center/cover no-repeat; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; }
.EC-person--rav:not(.has-img)::before { content: '👤'; opacity: .4; }
.EC-person--rbody { display: flex; flex-direction: column; }
.EC-person--rname { font-weight: 600; }
.EC-person--rrole { font-size: .85em; color: var(--muted); }
/* layout OUTLET (route slot) — editor only: a labeled dashed region; the route fills it at request time.
   The `children` inside are the authored fallback / design-time placeholder. Public output has no chrome. */
.EC-outlet--edit { border: 1px dashed var(--half-accent, color-mix(in srgb, var(--accent) 45%, transparent)); border-radius: 8px; padding: 22px 10px 10px; margin: 4px 0; position: relative; background: color-mix(in srgb, var(--accent) 4%, transparent); }
.EC-outlet--edit::before { content: '▤ слот «' attr(data-slot) '» — заполняется маршрутом'; position: absolute; top: 4px; left: 8px; font-size: 11px; font-weight: 600; letter-spacing: .02em; color: var(--accent); text-transform: uppercase; opacity: .85; }
.EC-app.is-preview .EC-nest--add { display: none; }
.EC-callout { background: var(--sel); border-radius: 8px; padding: 12px 14px; }
.EC-blk--image figcaption { font-size: .85em; color: var(--muted); margin-top: 4px; }
/* size constraints: figure max-width (inline) bounds width; --ec-imgh (inline) bounds height. Applies to
   the rendered img AND the editor's bound-image placeholder, so the editor matches the page (live). */
.EC-blk--image img { max-width: 100%; max-height: var(--ec-imgh, none); height: auto; }
.EC-blk--divider hr { border: none; border-top: 1px solid var(--line); margin: 8px 0; }
.EC-blk--embed { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; color: var(--muted); display: flex; gap: 8px; align-items: baseline; }
.EC-embed--tag { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; background: var(--sel); color: var(--accent); padding: 1px 6px; border-radius: 4px; }
/* ---- Interactive / engagement blocks (poll · donate · embed cards) ---- */
.EC-is-off { display: none !important; }

/* Poll — reader markup: question + option buttons with a results-bar placeholder (votes = backend). */
.EC-poll { display: flex; flex-direction: column; gap: 10px; border: 1px solid var(--line); border-radius: 10px; padding: 16px; background: var(--canvas); }
.EC-poll--q { font-weight: 700; font-size: 1.05em; }
.EC-poll--opts { display: flex; flex-direction: column; gap: 8px; }
.EC-poll--opt { position: relative; overflow: hidden; text-align: left; font: inherit; cursor: pointer; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--ink); display: flex; align-items: center; gap: 8px; }
.EC-poll--opt:hover { border-color: var(--accent); }
.EC-poll--optt { position: relative; z-index: 1; flex: 1; }
.EC-poll--pct { position: relative; z-index: 1; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12px; }
.EC-poll--bar { position: absolute; inset: 0 auto 0 0; width: 0; background: var(--sel); z-index: 0; transition: width .4s; }
/* Poll — editor widget: question input + option rows. */
.EC-pollwrap { gap: 8px; }
.EC-poll--qin { font: inherit; font-weight: 700; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--ink); width: 100%; }
.EC-poll--rows { display: flex; flex-direction: column; gap: 6px; }
.EC-poll--row { display: flex; align-items: center; gap: 4px; }
.EC-poll--rowin { flex: 1; font: inherit; padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--canvas); color: var(--ink); }
/* Menu block items editor (label + href per row) — same idiom as the poll options editor. */
.EC-menu--rows { display: flex; flex-direction: column; gap: 6px; }
.EC-menu--row { display: flex; align-items: center; gap: 4px; }
/* Logo block (region-left palette): optional image + text, wrapped in a link. */
.EC-logo { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; font-weight: 700; }
.EC-logo img { max-height: 40px; width: auto; }
.EC-logo--txt { font-size: 22px; line-height: 1.1; }

/* Donate — heading + text + native progress bar + CTA. The <progress> shows a filled accent bar. */
.EC-don { display: flex; flex-direction: column; gap: 10px; border: 1px solid var(--line); border-radius: 10px; padding: 18px; background: var(--panel); }
.EC-don--head { font-weight: 800; font-size: 1.2em; }
.EC-don--text { color: var(--muted); font-size: .95em; }
.EC-don--bar { -webkit-appearance: none; appearance: none; width: 100%; height: 10px; border: none; border-radius: 6px; background: var(--line); overflow: hidden; }
.EC-don--bar::-webkit-progress-bar { background: var(--line); border-radius: 6px; }
.EC-don--bar::-webkit-progress-value { background: var(--accent); border-radius: 6px; transition: width .4s; }
.EC-don--bar::-moz-progress-bar { background: var(--accent); border-radius: 6px; }
.EC-don--goal { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.EC-don--btn { align-self: flex-start; text-decoration: none; font-weight: 700; padding: 9px 18px; border-radius: 8px; background: var(--accent); color: #fff; }
.EC-don--btn:hover { background: var(--brand-red); }

/* Embed cards (youtube · tweet · link) — shared media+body card; differ only by the accent stripe. */
.EC-card { display: flex; gap: 14px; text-decoration: none; color: var(--ink); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--canvas); }
.EC-card:hover { border-color: var(--accent); }
.EC-card--media { flex: 0 0 160px; align-self: stretch; min-height: 100px; background-size: cover; background-position: center; background-color: var(--sel); }
.EC-card--body { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px; min-width: 0; }
.EC-card--ttl { font-weight: 700; font-size: 1.05em; }
.EC-card--desc { color: var(--muted); font-size: .9em; line-height: 1.45; }
.EC-card--youtube { border-left: 3px solid #ff0000; }
.EC-card--tweet { border-left: 3px solid #1d9bf0; }
.EC-card--link { border-left: 3px solid var(--accent); }

.EC-blk--cover { position: relative; border-radius: 10px; overflow: hidden; min-height: 160px; display: flex; }
.EC-blk--cover .EC-img2 { position: absolute; inset: 0; }
.EC-blk--cover .EC-img2 img { width: 100%; height: 100%; object-fit: cover; }
.EC-cover--text { position: relative; z-index: 1; margin: auto; padding: 18px; color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.6); font-size: 1.4em; font-weight: 700; }
.EC-blk--quote { border-left: 3px solid var(--accent); padding-left: 14px; font-style: italic; }
.EC-blk--quote cite { display: block; font-style: normal; font-size: .85em; color: var(--muted); margin-top: 4px; }

/* editable text spans: inline (string) vs block (multiline) — valid inside <p>/<hN> */
.EC-inline { display: inline; min-height: 0; outline: none; }
/* An EMPTY inline field (e.g. a quote's Author) would collapse to zero width → on focus the placeholder vanishes
   and there's nothing to click/see ("it disappears"). Give it a clickable box so the caret lands + stays. */
.EC-inline:empty { display: inline-block; min-width: 5em; }
.EC-inline:empty::before { content: attr(data-ph); color: var(--muted); }
.EC-inline:empty:focus::before { opacity: .5; }
/* «Контент блока» details placeholder for the inline body field (edited on the canvas, not here). */
.EC-d--bodyhint { color: var(--muted); font-size: 12px; font-style: italic; padding: 4px 0; }
.EC-block { display: block; outline: none; }

/* semantic heading sizes — real <h1>/<h2>/<h3> tags (clean markup) */
.EC-blk--text-block { margin: .25em 0; }
h1.EC-blk--text-block { font-size: 1.9em; font-weight: 700; line-height: 1.25; }
h2.EC-blk--text-block { font-size: 1.5em; font-weight: 700; line-height: 1.3; }
h3.EC-blk--text-block { font-size: 1.2em; font-weight: 600; line-height: 1.35; }

/* gallery (array<image>) */
.EC-blk--gallery .EC-gallery--grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
.EC-gallery--item { position: relative; }
.EC-gallery--item .EC-img2 img { width: 100%; height: 140px; object-fit: cover; border-radius: 6px; }
.EC-gallery--rm { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; border: none; border-radius: 50%; background: rgba(0,0,0,.6); color: #fff; cursor: pointer; }
.EC-gallery--item[draggable] { cursor: grab; }
.EC-gallery--item.is-dragging { opacity: .4; }
.EC-gallery--item.is-over { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.EC-gallery--item.is-ghost { opacity: .8; }
.EC-gallery--item.is-ghost .EC-img2--drop { min-height: 132px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 10px; }
.EC-app.is-preview .EC-gallery--rm { display: none; }

/* text + headings */
.EC-blk--text { outline: none; min-height: 1.5em; }
.EC-blk--text:empty:not(:focus)::before { content: attr(data-ph); color: var(--muted); }
.EC-blk--sub { font-size: .9em; color: var(--muted); }
.EC-blk--text mark { background: color-mix(in srgb, #ffd54a 55%, transparent); border-radius: 2px; padding: 0 1px; }
[data-theme="dark"] .EC-blk--text mark { background: color-mix(in srgb, #ffd54a 30%, transparent); color: var(--ink); }
.EC-ref { text-decoration: none; border-radius: 4px; padding: 0 4px; font-weight: 500; }
.EC-ref--author { color: var(--accent); background: var(--sel); }
.EC-ref--article { color: var(--accent); border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }

/* inline image: drag-drop dropzone + on-element controls */
.EC-img2 { position: relative; }
.EC-img2.is-drop { outline: 2px dashed var(--accent); outline-offset: 2px; border-radius: 8px; }
.EC-img2--drop { border: 1px dashed var(--line); border-radius: 8px; padding: 26px; text-align: center; color: var(--muted); cursor: pointer; }
.EC-img2--hint { margin-bottom: 4px; }
.EC-link { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; padding: 0; }
/* on-image controls (hover-only): replace/library on the LEFT, remove on the RIGHT */
.EC-img2--bar { position: absolute; top: 6px; left: 6px; right: 6px; display: none; justify-content: space-between; align-items: flex-start; }
.EC-img2:hover .EC-img2--bar { display: flex; }
.EC-img2--barL { display: flex; gap: 4px; }
.EC-img2--bar button { width: 28px; height: 28px; border: none; border-radius: 6px; background: rgba(0,0,0,.55); color: #fff; cursor: pointer; font-size: 14px; }
.EC-img2--bar button:hover { background: rgba(0,0,0,.85); }
.EC-img2--rm:hover { background: var(--err) !important; }
.EC-app.is-preview .EC-img2--bar { display: none !important; }

/* ON-CANVAS IMAGE GIZMOS (editor/imageGizmo.ts) — «how much space, and where it crops». EDITOR CHROME:
   it lives here and NEVER in a block's published `css:`, which ships verbatim into every page and into
   the frozen contract (base/EditorChrome.md R1). Geometry lifted from the experiment
   (experiments/grid-constructor/src/style.css:154-159, :198): 10px grips flush to the edge, a dark
   gradient with a 26x2 white bar, opacity 0 -> 1 on hover over .12s. Everything sits INSIDE the box, so
   nothing can be clipped by an ancestor's overflow (base/EditorChrome.md §0). */
.EC-img2.is-pannable img.EC-blk--img { cursor: grab; }
.EC-img2.is-panning img.EC-blk--img { cursor: grabbing; }
/* `pointer-events:none` while invisible is not cosmetic: a 10px strip flush to the image's bottom/right
   edge would otherwise swallow a press meant for the photo (a pan) or for the block (a selection). */
.EC-ig--gr { position: absolute; z-index: 4; opacity: 0; pointer-events: none; transition: opacity .12s; }
.EC-img2:hover .EC-ig--gr { opacity: 1; pointer-events: auto; }
/* GEOMETRY COMES FROM JS (editor/imageGizmo.ts#placeGrips), not from `inset`: the grips must hug the
   <img>, and `.EC-img2` stays column-wide while the photo inside it shrinks. So no left/right/bottom
   here — only thickness, cursor and paint. */
.EC-ig--gh { cursor: ns-resize; background: linear-gradient(0deg, rgba(0,0,0,.35), transparent); border-bottom-left-radius: 6px; }
.EC-ig--gh::after { content: ""; position: absolute; left: 50%; bottom: 3px; transform: translateX(-50%); width: 26px; height: 2px; border-radius: 2px; background: rgba(255,255,255,.85); }
.EC-ig--gw { cursor: ew-resize; background: linear-gradient(270deg, rgba(0,0,0,.35), transparent); border-top-right-radius: 6px; }
.EC-ig--gw::after { content: ""; position: absolute; top: 50%; right: 3px; transform: translateY(-50%); width: 2px; height: 26px; border-radius: 2px; background: rgba(255,255,255,.85); }
/* the CORNER: both axes at once. Drawn as the classic two-line dog-ear so it reads as a resize affordance
   and not as a third edge strip. */
.EC-ig--gc { cursor: nwse-resize; background: linear-gradient(315deg, rgba(0,0,0,.45), transparent 70%); border-bottom-right-radius: 6px; }
.EC-ig--gc::after { content: ""; position: absolute; right: 4px; bottom: 4px; width: 10px; height: 10px;
  border-right: 2px solid rgba(255,255,255,.9); border-bottom: 2px solid rgba(255,255,255,.9); border-radius: 0 0 3px 0; }
/* the live readout — the px value the gesture will write, so the author is not sizing blind */
.EC-ig--tag { position: absolute; right: 8px; bottom: 8px; z-index: 6; pointer-events: none;
  padding: 3px 7px; border-radius: 5px; font: 600 11px/1.2 ui-monospace, monospace;
  background: rgba(0,0,0,.72); color: #fff; letter-spacing: .02em; }
/* mid-drag the pointer routinely leaves the box; without this the grip you are holding fades out under
   your own cursor (the drag itself survives — it listens on `document`). */
.EC-img2.is-sizing .EC-ig--gr { opacity: 1 !important; pointer-events: auto; }
.EC-img2.is-sizing { cursor: nwse-resize; }
/* the selected block shows its grips without asking for hover: the author already said «this one». */
.EC-row.is-selected .EC-ig--gr { opacity: 1; pointer-events: auto; }
/* THE PAN PREVIEW — two layers over the same asset, which is where this goes past the experiment.
   `app.js:746` moves a background-position, so the author sees what they HAVE and never what they are
   giving up. Here the un-clipped BLEED shows the rest of the photo dimmed while you aim, and the clipped
   WINDOW shows the crop at full strength with an accent outline — that outline IS the crop rect, live on
   the canvas, which until now only the ⚙ dialog could show. Both are driven by one `panStyle(c)`. */
.EC-ig--panwrap { position: absolute; inset: 0; pointer-events: none; z-index: 5; }
.EC-ig--bleed, .EC-ig--pan { position: absolute; }
.EC-ig--bleed { overflow: visible; opacity: .22; filter: saturate(.35); }
.EC-ig--pan { overflow: hidden; border-radius: 6px;   /* == .EC-blk--img below */
  outline: 2px solid var(--accent); outline-offset: -2px;
  box-shadow: 0 8px 26px rgba(0,0,0,.28); }
/* the centre guides: the snap must be SEEN, or a rect that quietly stopped tracking reads as a bug */
.EC-ig--pan::before, .EC-ig--pan::after { content: ""; position: absolute; background: var(--accent); opacity: 0; transition: opacity .08s; }
.EC-ig--pan::before { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-.5px); }
.EC-ig--pan::after { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-.5px); }
.EC-ig--pan.is-cx::before, .EC-ig--pan.is-cy::after { opacity: .9; }
.EC-ig--bleed img, .EC-ig--pan img { position: absolute; max-width: none; max-height: none; object-fit: cover; user-select: none; }
/* The GRABBING cursor belongs on the wrapper, not the preview: `.EC-ig--panwrap` is pointer-events:none
   so the gesture's own `document` listeners keep receiving every move even outside the block's box. */
.EC-img2.is-panning { cursor: grabbing; }
/* A gallery tile and a cover size their <img> from the LAYOUT (styles.css .EC-gallery--item / .EC-blk--cover
   above), so a max-height/max-width grip there would fight the layout instead of expressing it. Excluded
   HERE and not in JS: at attach time the wrapper is not in the document yet, so `closest()` sees nothing.
   The PAN is deliberately still allowed in both — repositioning a cover-cropped tile is the whole point. */
.EC-gallery--item .EC-ig--gr, .EC-blk--cover .EC-ig--gr { display: none !important; }
/* chrome never answers the pointer mid-drag, and never prints (base/EditorChrome.md §3g) */
body.EC-isDragging .EC-ig--gr { opacity: 0 !important; pointer-events: none !important; }
.EC-app.is-preview .EC-ig--gr { display: none !important; }
.EC-app.is-preview .EC-img2.is-pannable img.EC-blk--img { cursor: default; }
.EC-app.is-preview .EC-ig--panwrap { display: none !important; }
.EC-lvl--1 { font-size: 1.9em; font-weight: 700; line-height: 1.25; margin: .2em 0; }
.EC-lvl--2 { font-size: 1.5em; font-weight: 700; line-height: 1.3; margin: .2em 0; }
.EC-lvl--3 { font-size: 1.2em; font-weight: 600; line-height: 1.35; }
.EC-blk--img { border-radius: 6px; }
.EC-blk--imgEmpty { color: var(--muted); font-style: italic; padding: 16px; text-align: center; border: 1px dashed var(--line); border-radius: 6px; }
.EC-blk--field { color: var(--muted); font-size: 13px; }
.EC-nestAdd { color: var(--muted); font-size: 12px; padding: 4px 8px; border: 1px dashed var(--line); border-radius: 5px; cursor: pointer; margin-top: 6px; display: inline-block; }
.EC-nestAdd:hover { color: var(--accent); border-color: var(--accent); }

/* image-paragraph layouts */
.EC-blk--imageParagraph { display: flex; gap: 18px; align-items: flex-start; }
.EC-blk--imageParagraph .EC-blk--imgWrap { flex: 0 0 40%; }
.EC-blk--imageParagraph .EC-blk--text { flex: 1; }
.EC-pos--left { flex-direction: row; }
.EC-pos--right { flex-direction: row-reverse; }
.EC-pos--top, .EC-pos--full { flex-direction: column; }
.EC-pos--top .EC-blk--imgWrap, .EC-pos--full .EC-blk--imgWrap { width: 100%; flex: 1 1 auto; }

/* format bar (selection bold/italic) */
.EC-fmt { position: absolute; transform: translate(-50%, -100%); display: flex; gap: 2px; padding: 3px; background: var(--ink); border-radius: 6px; box-shadow: 0 4px 14px rgba(0,0,0,.25); z-index: 1100; }
.EC-fmt--btn { border: none; background: transparent; color: #fff; width: 26px; height: 24px; border-radius: 4px; cursor: pointer; font-size: 13px; }
.EC-fmt--btn:hover { background: rgba(255,255,255,.18); }
.EC-fmt--btn.is-b { font-weight: 800; } .EC-fmt--btn.is-i { font-style: italic; }
.EC-fmt--btn.is-s { text-decoration: line-through; }
.EC-fmt--hl { background: #ebc6076e; padding: 0 2px; border-radius: 2px; }   /* highlight-tool swatch (always visible) */
.EC-fmt--btn.is-active { background: var(--accent); color: #fff; }   /* selection HAS this mark */
mark { background: #fef08a; color: inherit; border-radius: 2px; padding: 0 1px; }   /* Highlight — yellowish */

/* details panel */
.EC-details--empty { color: var(--muted); font-style: italic; padding: 10px 2px; }
.EC-details--head { padding-bottom: 12px; border-bottom: 1px solid var(--line); margin-bottom: 12px; }
.EC-details--title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 6px; }
.EC-crumb { color: var(--accent); cursor: pointer; text-decoration: none; }
.EC-crumb:hover { text-decoration: underline; }
.EC-crumb--sep { color: var(--muted); margin: 0 2px; }
.EC-details--fields { display: flex; flex-direction: column; gap: 13px; }
.EC-details--meta { margin-top: 16px; font-size: 11px; color: var(--muted); }

/* fields */
.EC-field { display: flex; flex-direction: column; gap: 4px; }
/* Horizontal field row — label + control on ONE line (page-config: Тип страницы, Авторы). Wraps only when the
   control (e.g. author chips) doesn't fit. Keeps the page-config compact instead of a tall vertical stack. */
.EC-field--row { flex-direction: row; align-items: center; gap: 10px; flex-wrap: wrap; }
.EC-field--row > .EC-field--label { flex: 0 0 auto; min-width: 82px; }
.EC-field--row > select, .EC-field--row > .EC-input, .EC-field--row > .EC-ref { flex: 1 1 120px; min-width: 0; }
/* Chip fields «Состояние» (EC-dtags) + «Связанные материалы» (EC-rel): the chips take the FULL row width and
   wrap below their label, instead of being squeezed into a ~140px flex-basis beside it — in the narrow article
   details panel that basis crammed the chips into a tall column and stretched them (a layout that only ever
   looked right in the wide main-CMS page). `flex-basis:100%` forces the container onto its own line (the parent
   is flex-wrap), so chips flow across the whole panel width. */
.EC-field--row > .EC-rel, .EC-field--row > .EC-dtags--chips { flex: 1 1 100%; min-width: 0; }
.EC-field--label { font-size: 12px; color: var(--muted); }
.EC-field--label.is-error { color: var(--err); }
/* «?» help affordance — a field/section description surfaced as a hover tooltip (native title=) instead of a
   stacked hint line (item 4). The whole label/title is the trigger (cursor:help); «?» just signals it exists. */
.EC-hashelp { cursor: help; display: inline-flex; align-items: center; gap: 5px; }
.EC-help { display: inline-flex; align-items: center; justify-content: center; width: 13px; height: 13px; border-radius: 50%; border: 1px solid var(--line); color: var(--muted); font-size: 9px; font-weight: 700; line-height: 1; flex: 0 0 auto; }
.EC-hashelp:hover .EC-help { border-color: var(--accent); color: var(--accent); }
/* Shared form-input chrome — ONE source of truth for every input. Always render form controls via the kit
   (data/ui.tsx#fieldInput); don't hand-roll inputs (see base/UIPrimitives.md). The two legacy class names
   (.EC-input / .EC-d--in) are kept identical so input look + focus is fixed in one place, never per-control. */
.EC-input, .EC-d--in { font: inherit; font-size: 13px; padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); }
.EC-input { width: 100%; }
.EC-input:focus, .EC-d--in:focus { outline: none; border-color: var(--accent); }
textarea.EC-input, textarea.EC-d--in { min-height: 58px; resize: vertical; }

/* image field + crop */
.EC-img { display: flex; flex-direction: column; gap: 10px; }
.EC-img--preview { width: 100%; border-radius: 6px; border: 1px solid var(--line); }
.EC-img--previewEmpty { padding: 26px; text-align: center; color: var(--muted); border: 1px dashed var(--line); border-radius: 6px; }
.EC-img--actions { display: flex; gap: 6px; flex-wrap: wrap; }
.EC-img--actions .EC-btn { border-color: var(--line); color: var(--ink); }
.EC-crop { display: flex; flex-direction: column; gap: 6px; }
.EC-crop--stage { position: relative; user-select: none; line-height: 0; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.EC-crop--img { width: 100%; display: block; pointer-events: none; }
.EC-crop--rect { position: absolute; border: 2px solid var(--accent); background: rgba(47,111,237,.12); cursor: move; box-shadow: 0 0 0 9999px rgba(0,0,0,.3); }
.EC-crop--h { position: absolute; width: 12px; height: 12px; background: var(--accent); border: 2px solid #fff; border-radius: 50%; }
.EC-crop--h-nw { left: -6px;  top: -6px;    cursor: nwse-resize; }
.EC-crop--h-ne { right: -6px; top: -6px;    cursor: nesw-resize; }
.EC-crop--h-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
.EC-crop--h-sw { left: -6px;  bottom: -6px; cursor: nesw-resize; }
.EC-crop--h-n  { left: 50%; top: -6px;    transform: translateX(-50%); cursor: ns-resize; }
.EC-crop--h-s  { left: 50%; bottom: -6px; transform: translateX(-50%); cursor: ns-resize; }
.EC-crop--h-w  { left: -6px;  top: 50%; transform: translateY(-50%); cursor: ew-resize; }
.EC-crop--h-e  { right: -6px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
.EC-crop--ratios { display: flex; flex-wrap: wrap; gap: 4px; }
.EC-crop--ratio { padding: 2px 8px; font-size: 11px; border: 1px solid var(--line); border-radius: 999px; background: var(--panel, #fff); color: var(--muted); cursor: pointer; }
.EC-crop--ratio:hover { border-color: var(--accent); }
.EC-crop--ratio.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.EC-crop--readout { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* library modal */
.EC-modal { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.EC-modal--card { background: var(--canvas); border-radius: 12px; width: min(720px, 92vw); max-height: 82vh; overflow: auto; padding: 14px 16px; }
.EC-modal--head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-weight: 600; }
/* Fork chooser (editor/forkChoice.tsx) — the version-divergence dialog. A narrower card than the default,
   because it is a question, not a workspace; the options are ordinary .EC-btn so they inherit the theme. */
.EC-forkModal .EC-modal--card { width: min(460px, 92vw); display: flex; flex-direction: column; gap: 9px; }
.EC-forkNote { color: var(--ec-fg-secondary); font-size: 13px; line-height: 1.5; }
.EC-forkOpt { display: block; width: 100%; text-align: left; padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px; }
.EC-forkOpt--n { color: var(--muted); font-size: 12px; line-height: 1.45; margin-top: 3px; white-space: normal; }
/* library picker — restructured browser: folders + recent / folder-grid / search */
.EC-lib--card { display: flex; flex-direction: column; width: min(860px, 94vw); height: 82vh; overflow: hidden; }
.EC-lib--bar { display: flex; gap: 10px; align-items: stretch; margin-bottom: 10px; }
.EC-lib--search { flex: 1; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--canvas); color: inherit; font-size: 13px; }
.EC-lib--body { flex: 1; min-height: 0; overflow-y: auto; }
.EC-lib--h { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin: 8px 2px; }
.EC-lib--crumb { text-transform: none; letter-spacing: 0; color: inherit; font-weight: 600; }
.EC-lib--back { border: 1px solid var(--line); background: var(--canvas); color: inherit; border-radius: 6px; padding: 3px 8px; cursor: pointer; font-size: 12px; }
.EC-lib--back:hover { border-color: var(--accent); color: var(--accent); }
/* folder tiles */
.EC-lib--folders { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin-bottom: 6px; }
.EC-lib--folder { border: 1px solid var(--line); border-radius: 8px; padding: 12px 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 4px; transition: border-color .1s ease, background .1s ease; }
.EC-lib--folder:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.EC-lib--folderIcon { font-size: 26px; line-height: 1; }
.EC-lib--folderName { font-size: 13px; font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-lib--folderCount { font-size: 11px; color: var(--muted); }
/* asset grid + scroll region (infinite scroll) */
.EC-lib--scroll { max-height: 100%; overflow-y: auto; }
.EC-lib--grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.EC-lib--item, .EC-lib--upload { border: 1px solid var(--line); border-radius: 8px; overflow: hidden; cursor: pointer; display: flex; flex-direction: column; position: relative; }
.EC-lib--item img { width: 100%; height: 110px; object-fit: cover; display: block; }
.EC-lib--item:hover { border-color: var(--accent); }
.EC-lib--cap { padding: 6px 8px; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.EC-lib--tagbtn { position: absolute; top: 4px; right: 4px; border: none; border-radius: 6px; background: rgba(0,0,0,.5); color: #fff; cursor: pointer; padding: 2px 5px; font-size: 12px; line-height: 1; opacity: 0; transition: opacity .1s ease; }
.EC-lib--item:hover .EC-lib--tagbtn { opacity: 1; }
.EC-lib--tagedit { padding: 6px; border-top: 1px solid var(--line); cursor: default; }
.EC-lib--sentinel { height: 1px; }
.EC-lib--empty { padding: 24px; text-align: center; color: var(--muted); font-size: 13px; }
.EC-lib--upload { align-items: center; justify-content: center; padding: 0 16px; min-height: 0; color: var(--accent); font-weight: 600; white-space: nowrap; cursor: pointer; }

/* export / durable-storage view */
.EC-exp--note { font-size: 12px; color: var(--muted); margin-bottom: 12px; }
.EC-exp--file { margin-bottom: 12px; }
.EC-exp--path { font-size: 12px; font-weight: 600; color: var(--accent); margin-bottom: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.EC-exp--code { margin: 0; padding: 10px 12px; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink); overflow: auto; max-height: 280px; white-space: pre; }

/* menus */
.EC-menu { background: var(--canvas); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 8px 28px rgba(0,0,0,.18); min-width: 180px; z-index: 1000; padding: 4px; }
.EC-menu--item { padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.EC-menu--item:hover { background: var(--sel); color: var(--accent); }
.EC-menu--sep { height: 1px; background: var(--line); margin: 4px 2px; }

/* block-type palette: search on top, one column per semantic group, so the long list fits on screen */
.EC-palette { display: flex; flex-direction: column; padding: 6px; max-width: min(92vw, 760px); max-height: 74vh; }
.EC-palette--search { margin: 2px 4px 6px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px; background: var(--canvas); color: var(--ink); outline: none; }
.EC-palette--search:focus { border-color: var(--accent); }
.EC-palette--body { display: flex; gap: 0; overflow: auto; }
.EC-palette--col { display: flex; flex-direction: column; min-width: 148px; }
.EC-palette--col + .EC-palette--col { border-left: 1px solid var(--line); margin-left: 6px; padding-left: 6px; }
.EC-palette--group { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); padding: 4px 8px 3px; }
.EC-palette--item { display: flex; align-items: center; gap: 8px; padding: 5px 9px; border-radius: 6px; cursor: pointer; font-size: 13px; white-space: nowrap; }
.EC-palette--item:hover { background: var(--sel); color: var(--accent); }
.EC-palette--ico { display: inline-block; width: 16px; text-align: center; opacity: .85; flex: none; }
/* "Convert to" row (turn-into heading levels) above the palette groups. */
.EC-palette--convert { display: flex; gap: 4px; margin: 0 4px 6px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.EC-palette--conv { font: inherit; font-size: 12px; padding: 4px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); cursor: pointer; }
.EC-palette--conv:hover { border-color: var(--accent); color: var(--accent); }
.EC-palette--conv.is-on { background: var(--accent); color: #fff; border-color: var(--accent); }
.EC-palette--empty { padding: 12px 14px; color: var(--muted); font-size: 13px; }

/* ---- /data — Collections admin (entity sets / linked records) ---- */
.EC-d { max-width: 1100px; margin: 0 auto; padding: 28px 24px 64px; }
.EC-d--head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.EC-d--user { font: inherit; font-size: 12px; padding: 3px 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); }
.EC-d--whoami { margin-left: 6px; }
.EC-d--body { display: grid; grid-template-columns: 230px 1fr; gap: 22px; align-items: start; }
.EC-d--aside { position: sticky; top: 16px; }
.EC-d--asidehd { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 8px 4px; }
.EC-d--cols { display: flex; flex-direction: column; gap: 2px; }
.EC-d--col { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 7px 10px; border-radius: 7px; cursor: pointer; border: 1px solid transparent; }
.EC-d--col:hover { background: var(--hover); }
.EC-d--col.is-sel { background: var(--sel); border-color: var(--half-accent); }
.EC-d--col-name { font-size: 13px; font-weight: 600; }
.EC-d--panel { min-width: 0; }
.EC-d--phd { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--line); margin-bottom: 18px; }
.EC-d--ptitle { margin: 0 0 3px; font-size: 20px; font-weight: 800; letter-spacing: -.01em; }
.EC-d--muted { font-size: 12px; color: var(--muted); }
.EC-d--muted code, .EC-d--ptitle + .EC-d--muted code { font-family: ui-monospace, Menlo, monospace; color: var(--accent); }
.EC-d--empty { padding: 40px 0; }
.EC-d--caps { display: flex; gap: 4px; flex-wrap: wrap; }
.EC-d--cap { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; color: var(--accent); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; }
.EC-d--cap.is-mini { padding: 0 4px; }
.EC-d--sec { margin-bottom: 26px; }
.EC-d--sechd { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; margin-top: 22px}
.EC-d--sechd b { font-size: 14px; }
.EC-d--recs { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.EC-d--rec { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 7px; }
.EC-d--rec-label { font-size: 13px; font-weight: 600; flex: 1 1 auto; }
.EC-d--form { display: flex; flex-direction: column; gap: 10px; padding: 14px; border: 1px solid var(--line); border-radius: 9px; background: var(--panel); margin: 6px 0; }
.EC-d--field { display: flex; flex-direction: column; gap: 4px; }
.EC-d--lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.EC-d--area { resize: vertical; }   /* .EC-d--in chrome is unified with .EC-input (one source of truth — see top of file) */
.EC-d--bool { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink); }
/* schema field flags as chips — checkbox hidden, selected shown by a rounded accent border */
.EC-d--flag { gap: 0; cursor: pointer; user-select: none; padding: 2px 4px; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--ink); transition: border-color .12s, background .12s, color .12s; }
.EC-d--flag:hover { border-color: var(--half-accent); }
.EC-d--flag input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.EC-d--flag:has(input:checked) { border-color: var(--accent); background: var(--sel); color: var(--accent); font-weight: 600; }
.EC-d--formbar { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.EC-d--schema { display: flex; flex-direction: column; gap: 5px; margin-bottom: 8px; }
.EC-d--frow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px; border: 1px solid var(--line); border-radius: 7px; }
.EC-d--frow--field { gap: 4px; padding: 0; border: 0; margin-bottom: 4px; flex-wrap: nowrap; }
.EC-d--key { font-family: ui-monospace, Menlo, monospace; width: 120px; }
.EC-d--enumval { width: 84px; }
.EC-d--chip { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: var(--accent); background: var(--sel); border: 1px solid var(--line); border-radius: 12px; padding: 2px 4px 2px 10px; }
.EC-d--chip .EC-btn--sm { padding: 0 5px; min-width: 0; }
/* enum-values trigger chip ("N значений") — accent pill that opens the value editor in a popup */
.EC-d--enumchip { cursor: pointer; padding: 2px 10px; white-space: nowrap; }
.EC-d--enumchip:hover { border-color: var(--accent); }
.EC-d--enumpop { display: flex; flex-direction: column; gap: 4px; min-width: 240px; }
.EC-d--enumrow { display: flex; gap: 4px; align-items: center; }
.EC-d--enumrow .EC-d--enumval { flex: 1; width: auto; min-width: 60px; }
.EC-d--enumedit { display: inline-flex; gap: 4px; align-items: center; }
.EC-d--enumsrc { width: 140px; }
.EC-d--grant { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 10px; border: 1px dashed var(--line); border-radius: 8px; margin-top: 6px; }
.EC-btn--sm { font-size: 12px; padding: 2px 8px; border: 1px solid var(--line); }

/* ---- /data tabs, search, modal, combobox ---- */
.EC-d--tabs { display: flex; gap: 2px; flex-wrap: wrap; }
.EC-d--tab { font: inherit; font-size: 13px; padding: 5px 12px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--muted); cursor: pointer; }
.EC-d--tab:hover { background: var(--hover); color: var(--ink); }
.EC-d--tab.is-on { background: var(--sel); color: var(--accent); border-color: var(--half-accent); font-weight: 600; }
.EC-d--tabbody { padding-top: 4px; }
.EC-d--listbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.EC-d--search { flex: 1 1 auto; max-width: 360px; }
.EC-btn.is-danger { color: #d6493b; }
.EC-btn.is-danger:hover { background: rgba(214,73,59,.1); border-color: rgba(214,73,59,.4); }
.EC-d--overlay { position: fixed; inset: 0; background: rgba(0,0,0,.32); display: flex; align-items: center; justify-content: center; z-index: 50; }
.EC-d--modal { background: var(--canvas); border: 1px solid var(--line); border-radius: 12px; padding: 22px; max-width: 420px; box-shadow: 0 16px 48px rgba(0,0,0,.24); }
.EC-d--modaltext { font-size: 14px; line-height: 1.5; margin-bottom: 16px; color: var(--ink); }
.EC-d--combo { position: relative; flex: 1 1 200px; min-width: 180px; }
/* routing slot input binder: select + optional static input, side by side (NOT EC-d--combo — its
   flex-basis becomes a 200px HEIGHT inside the column-flex EC-d--field) */
.EC-d--ibind { display: flex; gap: 6px; align-items: center; }
.EC-d--ibind > * { flex: 1 1 0; min-width: 0; }
.EC-d--combomenu { position: absolute; left: 0; right: 0; top: calc(100% + 3px); background: var(--canvas); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.14); z-index: 20; max-height: 240px; overflow: auto; padding: 3px; }
.EC-d--comboitem { padding: 6px 9px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.EC-d--comboitem:hover { background: var(--sel); color: var(--accent); }

/* ---- /data inline record editor + live template preview ---- */
.EC-d--rec.is-editing { border-color: var(--accent); background: var(--sel); }
.EC-d--rowvis { flex: 1 1 auto; min-width: 0; }
.EC-d--reckey { font-family: ui-monospace, Menlo, monospace; opacity: .7; flex: 0 0 auto; }
.EC-d--editform { display: flex; flex-direction: column; gap: 10px; padding: 14px; margin: 2px 0 8px; border: 1px solid var(--accent); border-radius: 9px; background: var(--panel); }
.EC-d--barspacer { flex: 1 1 auto; }
.EC-d--tplaction { font-size: 13px; color: var(--accent); border-bottom: 1px dashed var(--accent); cursor: pointer; padding-bottom: 1px; }
.EC-d--tplaction:hover { opacity: .8; }
.EC-d--tplsel { max-width: 220px; }
.EC-d--preview { margin-top: 6px; padding: 16px; border: 1px dashed var(--line); border-radius: 9px; background: var(--canvas); }

/* ---- in-place popup (data/popup.tsx) ---- */
.EC-pop { background: var(--canvas); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 12px 40px rgba(0,0,0,.22); min-width: 220px; max-width: 360px; }
.EC-pop--head { display: flex; align-items: center; gap: 6px; padding: 7px 8px 7px 12px; border-bottom: 1px solid var(--line); cursor: move; user-select: none; }
.EC-pop--pin { filter: grayscale(1); opacity: .5; }
.EC-pop--pin.is-on { filter: none; opacity: 1; }
.EC-pop--title { font-size: 13px; font-weight: 600; }
.EC-pop--spacer { flex: 1 1 auto; }
.EC-pop--ico { font: inherit; font-size: 13px; line-height: 1; background: transparent; border: 0; color: var(--muted); cursor: pointer; padding: 3px 5px; border-radius: 6px; }
.EC-pop--ico:hover { background: var(--hover); color: var(--ink); }
.EC-pop--ico.is-on { color: var(--accent); }
.EC-pop--body { padding: 6px 12px; max-height: 70vh; overflow: auto; }
.EC-pop--wide { max-width: 480px; }   /* material create/edit forms — room for description + video preview */
.EC-pop--calrange { max-width: 96vw; }   /* date-RANGE popup — width auto shrink-wraps the two month panels (≈532px); cap to the viewport */
/* Light spawn animation — a subtle fade + scale-in when a popup mounts (position/flip already resolved by the
   time visibility flips on, so this plays fully on-screen). Honors reduced-motion. */
.EC-pop { animation: EC-pop-in .12s ease-out both; }
@keyframes EC-pop-in { from { opacity: 0; transform: translateY(2px) scale(.98); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .EC-pop { animation: none; } }
.EC-pop--arrow { position: absolute; width: 10px; height: 10px; background: var(--canvas); border: 1px solid var(--line); transform: rotate(45deg); }
.EC-pop[data-side="bottom"] .EC-pop--arrow { border-right: 0; border-bottom: 0; }
.EC-pop[data-side="top"]    .EC-pop--arrow { border-left: 0; border-top: 0; }
.EC-pop[data-side="right"]  .EC-pop--arrow { border-top: 0; border-right: 0; }
.EC-pop[data-side="left"]   .EC-pop--arrow { border-bottom: 0; border-left: 0; }

/* ---- tags field (data/tags.tsx) ---- */
.EC-tags { display: flex; flex-direction: column; gap: 6px; }
.EC-tags--chips { display: flex; flex-wrap: wrap; gap: 5px; }
.EC-tags--chip { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--accent); background: var(--sel); border: 1px solid var(--line); border-radius: 12px; padding: 2px 4px 2px 9px; }
.EC-tags--rm { font: inherit; line-height: 1; border: 0; background: transparent; color: var(--muted); cursor: pointer; padding: 0 3px; border-radius: 8px; }
.EC-tags--rm:hover { color: var(--accent); }
.EC-tags--inwrap { position: relative; }
.EC-tags--input { width: 100%; font: inherit; font-size: 13px; padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); }
.EC-tags--input:focus { outline: none; border-color: var(--accent); }
.EC-tags--sug { position: absolute; left: 0; right: 0; top: calc(100% + 3px); background: var(--canvas); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.14); z-index: 3100; padding: 3px; max-height: 200px; overflow: auto; }
.EC-tags--sugit { padding: 5px 8px; border-radius: 6px; font-size: 13px; color: var(--accent); cursor: pointer; }
.EC-tags--sugit:hover { background: var(--sel); }

/* ---- schedule board page (schedule/main.tsx) ---- */
.EC-sched { height: 100vh; display: flex; flex-direction: column; }
.EC-sched--head { display: flex; align-items: center; justify-content: space-between; padding: 10px 18px; border-bottom: 1px solid var(--line); }
.EC-sched--main { flex: 1; min-height: 0; display: flex; }
.EC-sched--side { flex: 0 0 270px; border-right: 1px solid var(--line); padding: 14px; overflow: auto; }
.EC-sched--mini .EC-cal { width: 240px; padding: 4px; }
.EC-sched--board { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.EC-sched--toolbar { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.EC-sched--zoom { display: flex; gap: 2px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 2px; }
.EC-sched--zb { font: inherit; font-size: 12px; border: 0; background: transparent; color: var(--muted); padding: 4px 9px; border-radius: 6px; cursor: pointer; }
.EC-sched--zb:hover { color: var(--accent); }
.EC-sched--zb.is-on { background: var(--canvas); color: var(--accent); box-shadow: 0 1px 3px rgba(0,0,0,.1); }
.EC-sched--filters { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
.EC-sched--ftext { max-width: 220px; }
.EC-sched--fmine { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--muted); white-space: nowrap; }
.EC-sched--grid { flex: 1; min-height: 0; padding: 0 14px; box-sizing: border-box; }
.EC-sched--month { padding: 18px; }
.EC-sched--month .EC-cal { width: 320px; }
.EC-sched--pop { min-width: 180px; }
.EC-sched--pop-title { font-weight: 600; margin-bottom: 4px; }
.EC-sched--pop-meta { font-size: 12px; color: var(--muted); }
.EC-sched--pop-btns { display: flex; gap: 6px; margin-top: 10px; }
.EC-sched--pop .EC-field { margin-bottom: 8px; }
.EC-sched--dur { width: 90px; }
.EC-sched--desc { width: 100%; box-sizing: border-box; resize: vertical; font: inherit; font-size: 13px; }

/* week grid (ui/weekgrid.tsx) */
.EC-wg { height: 100%; display: flex; flex-direction: column; }
.EC-wg--scroll { flex: 1; min-height: 0; overflow: auto; }
.EC-wg--inner { position: relative; }
.EC-wg--head { position: sticky; top: 0; z-index: 3; display: flex; background: var(--canvas); border-bottom: 1px solid var(--line); }
.EC-wg--dh { flex: 0 0 auto; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 6px 0; border-left: 1px solid var(--line); }
.EC-wg--dh.is-today { background: var(--sel); }
.EC-wg--dh-wd { font-size: 11px; color: var(--muted); text-transform: capitalize; }
.EC-wg--dh.is-weekend .EC-wg--dh-wd { color: var(--accent); }
.EC-wg--dh-dm { font-size: 14px; font-weight: 600; }
.EC-wg--body { position: relative; display: flex; }
.EC-wg--gut { position: sticky; left: 0; z-index: 2; flex: 0 0 auto; background: var(--canvas); border-right: 1px solid var(--line); }
.EC-wg--hl { position: absolute; right: 6px; transform: translateY(-50%); font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }
.EC-wg--col { flex: 0 0 auto; box-sizing: border-box; position: relative; border-left: 1px solid var(--line); background-image: linear-gradient(to bottom, var(--line) 1px, transparent 1px); background-repeat: repeat-y; }
.EC-wg--col.is-today { background-color: rgba(240,119,108,.03); }
.EC-wg--ev { position: absolute; box-sizing: border-box; overflow: hidden; background: var(--sel); border: 1px solid var(--line); border-left: 3px solid var(--accent); border-radius: 5px; padding: 2px 5px; font-size: 11px; line-height: 1.25; cursor: grab; user-select: none; }
.EC-wg--ev.EC-wg--rep { border-left-color: #1f9c8b; border-left-style: dashed; opacity: .9; }      /* ПОВТОР (repeat) slot */
.EC-wg--ev.EC-wg--cont { border-left-color: var(--muted); }                                         /* продолжение (continuation after a split) */
.EC-wg--ev:hover { box-shadow: 0 2px 8px rgba(0,0,0,.14); z-index: 1; }
.EC-wg--ev.is-draft { border-style: dashed; }
.EC-wg--ev.is-filtered { opacity: .25; }
/* a segment of an event that crosses a day boundary: flatten the continuing edge */
.EC-wg--ev.is-cont-top { border-top-left-radius: 0; border-top-right-radius: 0; border-top-style: dotted; }
.EC-wg--ev.is-cont-bottom { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom-style: dotted; }
.EC-wg--ev-t { font-variant-numeric: tabular-nums; color: var(--muted); margin-right: 4px; }
.EC-wg--ev-title { font-weight: 600; color: var(--ink); }
.EC-wg--ev-draft { display: block; font-size: 9px; color: var(--accent); text-transform: uppercase; letter-spacing: .03em; }
.EC-wg--grip { position: absolute; left: 0; right: 0; bottom: 0; height: 6px; cursor: ns-resize; }
.EC-wg--ghost { position: absolute; box-sizing: border-box; background: var(--sel); border: 1px dashed var(--accent); border-radius: 5px; font-size: 11px; padding: 2px 5px; pointer-events: none; z-index: 5; color: var(--accent); }
.EC-wg--ghost--new { left: 1px; right: 1px; }
/* current-time line — spans all columns at the time-of-day (server-synced clock) */
.EC-wg--now { position: absolute; height: 0; border-top: 2px solid var(--accent); z-index: 4; pointer-events: none; }
.EC-wg--now::before { content: ''; position: absolute; left: -4px; top: -5px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }

/* ---- collection calendar admin (data/calendar.tsx) ---- */
.EC-caladm--h { font-size: 18px; margin: 0 0 16px; }
.EC-caladm { display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; }
.EC-caladm .EC-cal { border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 6px 24px rgba(0,0,0,.06); }
.EC-caladm--list { min-width: 240px; flex: 1; max-width: 360px; }
.EC-caladm--day { font-weight: 600; text-transform: capitalize; margin-bottom: 8px; }
.EC-caladm--empty { color: var(--muted); font-style: italic; }
.EC-caladm--row { display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--line); }
.EC-caladm--time { color: var(--accent); font-variant-numeric: tabular-nums; min-width: 44px; }
.EC-caladm--name { color: var(--ink); }
.EC-cal--day.has-events .EC-cal--num { font-weight: 700; }

/* ---- datetime field control (data/datetimefield.tsx) ---- */
.EC-dt { display: flex; align-items: center; gap: 4px; }
.EC-dt--btn { flex: 1; text-align: left; font: inherit; font-size: 13px; padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); cursor: pointer; }
.EC-dt--btn:hover { border-color: var(--accent); }
.EC-dt--btn.is-empty { color: var(--muted); }
.EC-dt--clear { flex: 0 0 auto; font: inherit; line-height: 1; border: 1px solid var(--line); background: var(--canvas); color: var(--muted); cursor: pointer; border-radius: 6px; padding: 5px 8px; }
.EC-dt--clear:hover { color: var(--accent); border-color: var(--accent); }

/* ---- calendar + time picker (ui/calendar.tsx — Scheduler slot UI) ---- */
.EC-cal { width: 268px; background: var(--canvas); color: var(--ink); border-radius: 12px; padding: 10px; font: 13px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-user-select: none; user-select: none; }
.EC-cal--head { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; }
.EC-cal--nav { flex: 0 0 30px; height: 30px; border: 0; background: transparent; color: var(--muted); font-size: 18px; line-height: 1; border-radius: 8px; cursor: pointer; }
.EC-cal--nav:hover { background: var(--sel); color: var(--accent); }
.EC-cal--title { flex: 1; height: 30px; border: 0; background: transparent; color: var(--ink); font-size: 13px; font-weight: 600; border-radius: 8px; cursor: pointer; }
.EC-cal--title:hover { background: var(--sel); color: var(--accent); }
.EC-cal--title.is-static { cursor: default; } .EC-cal--title.is-static:hover { background: transparent; color: var(--ink); }
.EC-cal--body { min-height: 232px; }   /* reserve 6-row height so 4–5-row months don't shrink the popup */
.EC-cal--week { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; }
.EC-cal--wd { text-align: center; font-size: 11px; color: var(--muted); padding: 4px 0; }
.EC-cal--wd.is-weekend { color: var(--accent); opacity: .8; }
.EC-cal--days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; transition: opacity .15s; }
.EC-cal--day { position: relative; aspect-ratio: 1; display: flex; align-items: center; justify-content: center; border: 1px solid transparent; background: transparent; color: var(--ink); font: inherit; border-radius: 8px; cursor: pointer; }
.EC-cal--day:hover { background: var(--sel); }
.EC-cal--day.is-out { color: var(--muted); opacity: .45; }
.EC-cal--day.is-weekend .EC-cal--num { color: var(--accent); }
.EC-cal--day.is-weekend.is-out .EC-cal--num { color: var(--muted); }
.EC-cal--day.is-today { border-color: var(--accent); color: var(--accent); font-weight: 700; box-shadow: inset 0 0 0 1px var(--accent); }
.EC-cal--day.is-sel, .EC-cal--day.is-sel:hover { background: var(--accent); border-color: var(--accent); }
.EC-cal--day.is-sel .EC-cal--num { color: #fff; }
.EC-cal--day.is-disabled { color: var(--muted); opacity: .3; cursor: not-allowed; pointer-events: none; }
/* range mode (T71/T72): highlighted span + endpoints + between-clicks preview */
.EC-cal--day.is-in-range { background: var(--sel); border-radius: 0; }
.EC-cal--day.is-range-start { border-top-left-radius: 8px; border-bottom-left-radius: 8px; }
.EC-cal--day.is-range-end { border-top-right-radius: 8px; border-bottom-right-radius: 8px; }
/* endpoints ALWAYS carry a solid accent fill so their white text reads — even mid-selection when only `from`
   is set (no `to`, no in-range fill yet). The :not() keeps the preview dim (below) from stealing the fill:
   a hovered half-open pick puts is-in-range+is-preview on the start cell too, and that higher-specificity
   rule would otherwise override the accent → white-on-light = invisible (BUG 1). */
.EC-cal--day.is-range-start, .EC-cal--day.is-range-end,
.EC-cal--day.is-range-start.is-preview, .EC-cal--day.is-range-end.is-preview { background: var(--accent); border-color: var(--accent); opacity: 1; }
.EC-cal--day.is-range-start .EC-cal--num, .EC-cal--day.is-range-end .EC-cal--num { color: #fff; }
.EC-cal--day.is-in-range.is-preview:not(.is-range-start):not(.is-range-end) { background: var(--sel); opacity: .7; }
/* two-panel range view. The container WIDENS to hold both grids (BUG 2): .EC-cal is a fixed 268px, so two
   panels must grow it to content width instead of squeezing into the single-panel box (which overlapped). */
.EC-cal:has(.EC-cal--panels) { width: max-content; max-width: none; }
.EC-cal--panels { display: flex; gap: 16px; align-items: flex-start; }
.EC-cal--panel { flex: 0 0 248px; min-width: 0; display: flex; flex-direction: column; }
/* each panel's own head: ‹ [month] [year] › — month + year are drill buttons (→ 12-month / 12-year grid).
   Arrows appear only on the MOVABLE panel (they wrap just the current calendar); the anchor's title is
   centered via balancing spacers. */
.EC-cal--phead { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; min-height: 30px; }
.EC-cal--ptitlewrap { flex: 1; display: flex; justify-content: center; align-items: center; gap: 2px; }
.EC-cal--ptitle { height: 30px; border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 13px; font-weight: 600; border-radius: 8px; padding: 0 8px; cursor: pointer; }
button.EC-cal--ptitle:hover { background: var(--sel); color: var(--accent); }
.EC-cal--ptitle.is-static { cursor: default; }
.EC-cal--pspacer { flex: 0 0 30px; }
/* gap edges of non-consecutive panels render «…» (set as the cell's number) in place of the spilled dates */
.EC-cal--day.is-gap { pointer-events: none; }
.EC-cal--day.is-gap .EC-cal--num { color: var(--muted); opacity: .55; letter-spacing: 1px; font-weight: 700; }
/* month/year drill: the currently-open month(s)/year(s) are marked distinctly from is-sel / is-today */
.EC-cal--mcell.is-open { background: var(--sel); color: var(--accent); font-weight: 700; box-shadow: inset 0 0 0 1px var(--accent); }
/* preset slot (T75/T76) — OWNER FEEDBACK B: relocated to the BOTTOM of the range dialog (the foot) and restyled
   as dashed TEXT-LINKS (no button box), a wrapped row above the <hr> + «Очистить диапазон». */
.EC-cal--presets { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0; padding: 0; border: 0; }
.EC-cal--preset { border: 0; border-bottom: 1px dashed var(--muted); background: transparent; color: var(--ink); font: inherit; font-size: 12px; padding: 1px 0; border-radius: 0; cursor: pointer; }
.EC-cal--preset:hover { color: var(--accent); border-bottom-color: var(--accent); background: transparent; }
.EC-cal--num { line-height: 1; }
.EC-cal--badge { position: absolute; top: 2px; right: 3px; font-size: 8px; line-height: 1; min-width: 6px; height: 6px; padding: 0; border-radius: 4px; background: var(--accent); color: #fff; display: flex; align-items: center; justify-content: center; }
.EC-cal--badge:not(:empty) { padding: 1px 3px; height: auto; font-size: 9px; }
/* loading: dim the grid + a thin pulsing bar; the draw never blocks on the async batch */
.EC-cal--grid.is-loading .EC-cal--days { opacity: .4; }
.EC-cal--grid.is-loading { position: relative; }
.EC-cal--grid.is-loading::after { content: ''; position: absolute; left: 0; right: 0; top: -2px; height: 2px; border-radius: 2px; background: var(--accent); animation: EC-cal-pulse 1s ease-in-out infinite; }
@keyframes EC-cal-pulse { 0%,100% { opacity: .25; } 50% { opacity: .9; } }
/* months / years drill grid */
.EC-cal--mgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding-top: 4px; }
.EC-cal--mcell { height: 46px; border: 1px solid transparent; background: transparent; color: var(--ink); font: inherit; border-radius: 8px; cursor: pointer; }
.EC-cal--mcell:hover { background: var(--sel); }
.EC-cal--mcell.is-today { border-color: var(--accent); color: var(--accent); font-weight: 700; box-shadow: inset 0 0 0 1px var(--accent); }
.EC-cal--mcell.is-sel, .EC-cal--mcell.is-sel:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
/* footer: today shortcut + optional time picker + confirm */
.EC-cal--foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
.EC-cal--today { border: 0; background: transparent; color: var(--accent); font: inherit; font-size: 12px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }
.EC-cal--today:hover { background: var(--sel); }
/* OWNER FEEDBACK B — range-mode foot: presets (dashed text-links) stacked over an <hr> + the accent
   «Очистить диапазон» clear action (mirrors the «Сегодня» accent style). */
.EC-cal--foot--range { flex-direction: column; align-items: stretch; gap: 8px; }
.EC-cal--foothr { width: 100%; height: 0; margin: 0; border: 0; border-top: 1px solid var(--line); }
.EC-cal--clear { align-self: flex-start; border: 0; background: transparent; color: var(--accent); font: inherit; font-size: 12px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }
.EC-cal--clear:hover { background: var(--sel); }
.EC-cal--time { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.EC-cal--tlbl { font-size: 12px; color: var(--muted); margin-right: 2px; }
.EC-cal--tin { width: 40px; text-align: center; font: inherit; padding: 5px 4px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--ink); -moz-appearance: textfield; }
.EC-cal--tin::-webkit-outer-spin-button, .EC-cal--tin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.EC-cal--tin:focus { outline: none; border-color: var(--accent); }
.EC-cal--tsep { color: var(--muted); }
.EC-cal--done { border: 0; background: var(--accent); color: #fff; font: inherit; font-size: 12px; padding: 6px 12px; border-radius: 6px; cursor: pointer; }
.EC-cal--withtime .EC-cal--today { margin-right: 0; }

/* ---- generic material-create dialog (data/materialDialog.tsx) ---- */
.EC-mcreate { min-width: 420px; }   /* wider — comfortable description editing */
.EC-mcreate--fields { display: flex; flex-direction: column; gap: 8px; }
.EC-mcreate textarea.EC-d--in, .EC-mcreate textarea.EC-input, .EC-sched--pop textarea.EC-d--in { min-height: 84px; }

/* ---- video block / youtube embed (core/blocks/site.ts#video) ---- */
.EC-video { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; border-radius: 10px; overflow: hidden; }
.EC-video--ifr { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.EC-video--empty { aspect-ratio: 16 / 9; display: flex; align-items: center; justify-content: center; background: var(--panel); border: 1px dashed var(--line); border-radius: 10px; color: var(--muted); font-size: 13px; }
.EC-video-block { margin: 12px 0; }

/* ---- YouTube field (data/youtubefield.tsx) — [ preview | title · link · meta ] ---- */
.EC-yt { display: grid; grid-template-columns: 140px 1fr; gap: 10px; align-items: start; }
.EC-yt--thumbcol { width: 140px; }
.EC-yt--thumbwrap { display: block; line-height: 0; }
.EC-yt--thumb { width: 140px; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; display: block; border: 1px solid var(--line); }
.EC-yt--ph { width: 140px; aspect-ratio: 16 / 9; border: 1px dashed var(--line); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 11px; }
.EC-yt--main { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.EC-yt--title { font-size: 13px; font-weight: 600; color: var(--ink); }
.EC-yt--title.is-ph { font-weight: 400; font-style: italic; color: var(--muted); }
.EC-yt--meta { font-size: 12px; color: var(--muted); }

/* ---- prompt dialog (data/popup.tsx — dirty-aware entry) ---- */
.EC-prompt { display: flex; flex-direction: column; gap: 8px; min-width: 240px; }
.EC-prompt .EC-field { margin: 0; }
.EC-prompt--bar { display: flex; gap: 6px; margin-top: 2px; }

/* ---- related materials picker (editor/pageBind.tsx — facets links) ---- */
.EC-rel { display: flex; flex-direction: column; gap: 5px; }
.EC-rel--chips { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.EC-rel--pop { display: flex; flex-direction: column; gap: 6px; min-width: 240px; }
.EC-rel--results { max-height: 220px; overflow: auto; display: flex; flex-direction: column; gap: 2px; }
.EC-rel--rit { padding: 6px 8px; border-radius: 6px; font-size: 13px; color: var(--ink); cursor: pointer; }
.EC-rel--rit:hover { background: var(--sel); color: var(--accent); }
.EC-rel--empty { padding: 6px 8px; font-size: 13px; color: var(--muted); font-style: italic; }

/* ---- block display-tags (editor/displayTags.tsx — facets composition) ---- */
.EC-dtags { display: flex; flex-direction: column; gap: 5px; margin-top: 8px; }
.EC-dtags--chips { display: flex; flex-wrap: wrap; gap: 5px; }
.EC-dtags--chip { font: inherit; font-size: 12px; line-height: 1.4; color: var(--muted); background: transparent; border: 1px solid var(--line); border-radius: 12px; padding: 2px 6px; cursor: pointer; }
.EC-dtags--chip:hover { border-color: var(--accent); color: var(--accent); }
.EC-dtags--chip.is-on { color: var(--accent); background: var(--sel); border-color: var(--accent); }
.EC-dtags--empty { font-size: 12px; color: var(--muted); font-style: italic; }

/* ---- article tags (editor/pageBind.tsx tagsField) — selected chips + searchable add ---- */
.EC-tags--search { position: relative; }
.EC-tags--inp { width: 100%; box-sizing: border-box; font: inherit; font-size: 13px; padding: 5px 9px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--ink); }
.EC-tags--inp:focus { outline: none; border-color: var(--accent); }
.EC-tags--results { position: absolute; z-index: 30; top: calc(100% + 3px); left: 0; right: 0; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.14); padding: 4px; }
.EC-tags--new { color: var(--accent); font-weight: 600; }

/* ---- compact image field (data/imagefield.tsx) ---- */
.EC-cimg { position: relative; width: 120px; height: 90px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
.EC-cimg--img { width: 100%; height: 100%; object-fit: cover; display: block; }
.EC-cimg--empty { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 26px; opacity: .35; }
.EC-cimg--bar { position: absolute; inset: auto 0 0 0; display: flex; gap: 2px; justify-content: center; padding: 4px; background: linear-gradient(transparent, rgba(0,0,0,.45)); opacity: 0; transition: opacity .12s; }
.EC-cimg:hover .EC-cimg--bar { opacity: 1; }
.EC-cimg--btn { font-size: 13px; line-height: 1; border: 0; background: rgba(0,0,0,.5); color: #fff; cursor: pointer; padding: 4px 6px; border-radius: 6px; }
.EC-cimg--btn:hover { background: var(--accent); }
.EC-cimg--cfg { display: flex; flex-direction: column; gap: 10px; width: 300px; }

/* ---- compact AUDIO field (data/audiofield.tsx) — the `audio` property widget ---- */
.EC-caud { display: flex; flex-direction: column; gap: 6px; border: 1px solid var(--line); border-radius: 8px; padding: 8px; background: var(--panel); }
.EC-caud--sel { display: flex; align-items: center; gap: 6px; min-width: 0; }
.EC-caud--icon { flex: 0 0 auto; font-size: 15px; }
.EC-caud--name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.EC-caud--player { width: 100%; height: 32px; }
.EC-caud--bar { display: flex; gap: 4px; }
.EC-caud--btn { font-size: 13px; line-height: 1; border: 1px solid var(--line); background: var(--panel); cursor: pointer; padding: 4px 8px; border-radius: 6px; }
.EC-caud--btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }

/* library picker: filename tile for non-image (audio/…) assets — no pixel thumbnail exists */
.EC-lib--fileicon { display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 4 / 3; font-size: 34px; background: var(--panel); }

/* ---- close-while-dirty: shake Save/Cancel to draw attention ---- */
@keyframes EC-pulse { 0%,100% { transform: translateX(0); } 15% { transform: translateX(-5px); } 30% { transform: translateX(5px); } 45% { transform: translateX(-4px); } 60% { transform: translateX(3px); } 75% { transform: translateX(-2px); } }
.EC-d--formbar.is-pulse { animation: EC-pulse .5s ease; }
.EC-d--formbar.is-pulse .EC-btn { border-color: var(--accent); box-shadow: 0 0 0 2px var(--sel); }

/* ---- ref field (data/reffield.tsx) ---- */
.EC-ref { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.EC-ref--chip { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: var(--accent); background: var(--sel); border: 1px solid var(--line); border-radius: 12px; padding: 2px 4px 2px 9px; }
.EC-ref--picker { width: 240px; }
.EC-ref--list { margin-top: 8px; max-height: 240px; overflow: auto; display: flex; flex-direction: column; gap: 2px; }
.EC-ref--it { padding: 6px 9px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.EC-ref--it:hover { background: var(--sel); color: var(--accent); }
.EC-ref--it.is-on { color: var(--accent); }
.EC-ref--it.is-on::after { content: ' ✓'; }

/* ---- field binding (record templates): bound chips, placeholders, context menu, page config ---- */
.EC-ctxmenu { min-width: 200px; }
.EC-menu--item.is-accent { color: #2faf5a; font-weight: 600; }
.EC-menu--item.is-disabled { color: var(--muted); cursor: default; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.EC-menu--item.is-disabled:hover { background: transparent; color: var(--muted); }
.EC-field-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 700; color: #2faf5a; background: color-mix(in srgb, #2faf5a 12%, transparent); border: 1px solid color-mix(in srgb, #2faf5a 32%, transparent); border-radius: 10px; padding: 1px 7px; vertical-align: middle; white-space: nowrap; }
.EC-field-chip::before { content: '⛓'; font-size: 10px; opacity: .85; }
.EC-field-chip--ph { border-style: dashed; }
/* editable material-slot bound field: contenteditable value + a green slot chip (the single source) */
.EC-fieldslot { display: inline-flex; align-items: baseline; gap: 6px; }
.EC-fieldslot--ed { outline: none; min-width: 1em; }
.EC-fieldslot--ed:empty:not(:focus)::before { content: attr(data-ph); color: var(--muted); }
.EC-field-chip--slot::before { content: '◧'; }   /* a slot (editable) vs the ⛓ model-bound chip */
.EC-fieldbound { display: inline-flex; align-items: center; gap: 6px; }
.EC-fieldbound--val { color: var(--ink); }
.EC-fieldbound--ph { opacity: .9; }
.EC-bound-img { position: relative; display: inline-block; max-width: 100%; }
.EC-bound-img img { display: block; max-width: 100%; max-height: var(--ec-imgh, none); height: auto; border-radius: 8px; outline: 2px solid color-mix(in srgb, #2faf5a 45%, transparent); outline-offset: 2px; }
.EC-bound-img .EC-field-chip { position: absolute; right: 8px; bottom: 8px; }
.EC-bound-img.is-empty { display: flex; width: 100%; min-height: 120px; height: var(--ec-imgh, 120px); border-radius: 10px; border: 2px solid color-mix(in srgb, #2faf5a 40%, transparent); background: repeating-linear-gradient(45deg, var(--sel), var(--sel) 10px, transparent 10px, transparent 20px); }
.EC-bound-img.is-empty::before { content: '⛓ связь установлена'; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #2faf5a; font-size: 13px; font-weight: 600; }
.EC-pagecfg { width:100%; display: flex; flex-direction: column; gap: 8px; }   /* tightened stacking (item 4) */
.EC-pagecfg--hint { font-size: 12px; color: var(--muted); line-height: 1.45; }
.EC-slots { display: flex; flex-direction: column; gap: 6px; }
.EC-slots--row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px; border: 1px solid var(--line); border-left: 3px solid var(--line); border-radius: 8px; }
.EC-slots--row.is-air { border-left-color: var(--accent); background: var(--sel); }
.EC-slots--row.is-repeat { border-left-color: #1f9c8b; border-left-style: dashed; opacity: .92; }
.EC-slots--dur { width: 64px; }
.EC-slots--fmt { width: 76px; }
.EC-slots--rep { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.EC-pagecfg--actions { display: flex; flex-direction: column; gap: 6px; }
.EC-pagecfg--actbtns { display: flex; flex-wrap: wrap; gap: 6px; }
.EC-pagecfg--sample { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.EC-rp--more { height: 1px; }
/* details-panel field binding affordance + bound row */
.EC-bindfield { position: relative; }
.EC-bindfield--attach { margin-top: 5px; font-size: 11px; font-weight: 600; color: #2faf5a; background: color-mix(in srgb, #2faf5a 10%, transparent); border: 1px solid color-mix(in srgb, #2faf5a 30%, transparent); border-radius: 8px; padding: 2px 8px; cursor: pointer; }
.EC-bindfield--attach:hover { background: color-mix(in srgb, #2faf5a 20%, transparent); }
.EC-bindfield--bound { display: inline-flex; align-items: center; gap: 8px; }
/* a bound MEDIA field in details (e.g. youtube→videoSource): chip on top, the resolved embed preview below */
.EC-bindfield--boundmedia { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; width: 100%; }
.EC-bindfield--boundmedia .EC-video { width: 100%; }
/* cssNumber / cssBox inputs (lengths, paddings, sizes) */
.EC-css--num { display: flex; gap: 6px; }
.EC-css--num .EC-css--n { width: 84px; }
.EC-css--u { width: auto; flex: 0 0 auto; padding-right: 4px; }
.EC-css--box { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.EC-css--boxrow { display: flex; gap: 6px; align-items: center; justify-content: center; }
.EC-css--side { width: 60px; text-align: center; }
.EC-css--boxc { display: flex; gap: 4px; align-items: center; }
.EC-css--link { border: 1px solid var(--line); background: transparent; border-radius: 6px; cursor: pointer; padding: 3px 7px; opacity: .45; line-height: 1; }
.EC-css--link.is-on { opacity: 1; border-color: var(--accent); }
/* List block source-aware config */
.EC-lcfg { display: flex; flex-direction: column; gap: 10px; }
.EC-lcfg--slot, .EC-lcfg--src { display: flex; flex-direction: column; gap: 10px; }
.EC-lcfg--src { border-top: 1px solid var(--line); padding-top: 10px; }
.EC-lcfg--hint { font-size: 12px; color: var(--muted); line-height: 1.4; }
.EC-lcfg--items { display: flex; flex-direction: column; gap: 8px; }
.EC-lcfg--item { border: 1px solid var(--line); border-radius: 8px; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.EC-lcfg--itemhd { display: flex; align-items: center; gap: 4px; }
.EC-lcfg--itemn { flex: 1; font-size: 12px; font-weight: 700; color: var(--muted); }
.EC-reflist--drag { cursor: grab; color: var(--muted); padding: 0 4px; user-select: none; flex: 0 0 auto; }
.EC-reflist--drag:active { cursor: grabbing; }
.EC-reflist--row.is-drag, .EC-lcfg--item.is-drag { opacity: .4; }
.EC-reflist--row.is-drop, .EC-lcfg--item.is-drop { box-shadow: inset 0 2px 0 var(--accent); }
/* publish/version actions moved into the History drawer */
.EC-drawer--actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 10px 12px; border-bottom: 1px solid var(--line); }
.EC-drawer--actions .EC-group--lbl { margin-left: 4px; }

/* ---- /data search highlight + derived match snippet ---- */
.EC-hl { background: color-mix(in srgb, var(--accent) 30%, transparent); border-radius: 3px; padding: 0 1px; color: var(--ink); }
.EC-d--snippet { font-size: 12px; color: var(--muted); margin-top: 4px; line-height: 1.4; }
.EC-d--snippet-lbl { text-transform: uppercase; letter-spacing: .03em; font-size: 10px; color: var(--accent); margin-right: 4px; }

/* ---- /data reverse-lookup: articles crediting a record ---- */
.EC-d--related { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 4px; }
.EC-d--relitem { font-size: 13px; color: var(--accent); text-decoration: none; padding: 2px 0; }
.EC-d--relitem:hover { text-decoration: underline; }

/* ---- Workflow configurator (base/Hooks.md) — «Сценарий» tab ---- */
.EC-wf--bar { display: flex; align-items: center; gap: 8px; margin: 8px 0 12px; }
.EC-wf--type { max-width: 220px; }
.EC-wf--status { font-size: 12px; color: var(--muted); }
.EC-wf--host { display: flex; flex-direction: column; gap: 14px; }
.EC-wf--graph { position: relative; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); overflow: auto; box-sizing: border-box; width: 100%; max-height: 70vh; min-height: 140px; padding: 0; }
.EC-wf--graph.is-empty { min-height: 80px; display: flex; align-items: center; justify-content: center; }
.EC-wf--canvas { position: relative; }
.EC-wf--svg { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
.EC-wf--edge { fill: none; stroke: var(--accent); stroke-width: 1.8; opacity: .85; }
.EC-wf--edge.is-clear { stroke-dasharray: 5 4; opacity: .55; }
.EC-wf--edge.is-time { stroke: var(--muted); stroke-width: 2.4; opacity: .9; }
.EC-wf--arrowhd { fill: var(--accent); }
/* kind colour + shape coding: фаза = coral pill, флаг = teal pill, вид = violet dashed */
.EC-wf--node { position: absolute; width: 140px; min-height: 48px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; border: 1.5px solid var(--line); border-radius: 10px; background: var(--canvas); box-shadow: 0 1px 3px rgba(0,0,0,.07); padding: 6px 8px; text-align: center; z-index: 1; cursor: grab; touch-action: none; }
.EC-wf--node:active { cursor: grabbing; }
.EC-wf--node.is-phase { background: rgba(240,119,108,.12); border-color: var(--accent); border-radius: 10px; }
.EC-wf--node.is-flag { border-color: #1f9c8b; background: rgba(31,156,139,.10); border-radius: 22px; }
.EC-wf--node.is-view { border-color: #7a6cf0; background: rgba(122,108,240,.08); border-style: dashed; }
.EC-wf--node.is-any { background: transparent; border-style: dotted; color: var(--muted); }
.EC-wf--nlabel { font-size: 13px; font-weight: 600; }
.EC-wf--ntag { font-size: 9px; text-transform: uppercase; letter-spacing: .05em; font-weight: 700; }
.EC-wf--node.is-phase .EC-wf--ntag { color: var(--accent); }
.EC-wf--node.is-flag .EC-wf--ntag { color: #1f9c8b; }
.EC-wf--node.is-view .EC-wf--ntag { color: #7a6cf0; }
.EC-wf--legend { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; }
.EC-wf--lg { display: inline-flex; align-items: center; gap: 5px; text-transform: uppercase; letter-spacing: .04em; font-weight: 700; color: var(--muted); }
.EC-wf--lg::before { content: ''; width: 13px; height: 13px; border: 1.5px solid var(--line); box-sizing: border-box; }
.EC-wf--lg.is-phase::before { border-color: var(--accent); background: rgba(240,119,108,.18); border-radius: 3px; }
.EC-wf--lg.is-flag::before { border-color: #1f9c8b; background: rgba(31,156,139,.18); border-radius: 999px; }
.EC-wf--lg.is-view::before { border-color: #7a6cf0; background: rgba(122,108,240,.14); border-style: dashed; border-radius: 3px; }
.EC-wf--lghint, .EC-wf--addhint { font-size: 11px; color: var(--muted); }
.EC-wf--echip.is-time { cursor: help; }
.EC-wf--info { max-width: 320px; font-size: 12.5px; line-height: 1.5; }
.EC-wf--info p { margin: 0 0 8px; }
.EC-wf--info p:last-child { margin-bottom: 0; }
.EC-wf--echip { position: absolute; transform: translate(-50%, -50%); transform-origin: center; font-size: 11px; line-height: 1.2; background: var(--canvas); border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; color: var(--ink); white-space: normal; max-width: 140px; text-align: center; box-shadow: 0 1px 2px rgba(0,0,0,.08); z-index: 2; opacity: .82; }
.EC-wf--echip:hover { opacity: 1; z-index: 6; }
.EC-wf--echip.is-loop { transform: none; }
.EC-wf--echip.is-time { background: rgba(255,255,255,.6); border: 0; box-shadow: none; color: var(--muted); opacity: 1; }
.EC-wf--echip.is-clickable { cursor: pointer; }
.EC-wf--echip.is-clickable:hover { border-color: var(--accent); color: var(--accent); background: var(--canvas); }
.EC-wf--addbar { display: flex; gap: 8px; }
.EC-wf--section { display: flex; flex-direction: column; gap: 6px; }
.EC-wf--card { display: flex; align-items: center; gap: 8px; border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; cursor: pointer; background: var(--canvas); }
.EC-wf--card:hover { border-color: var(--accent); }
.EC-wf--cardico { font-size: 14px; }
.EC-wf--cardtxt { font-size: 13px; }
/* inspector */
.EC-pop--wf { max-width: 620px; }
.EC-wf--edit { display: flex; flex-direction: column; gap: 8px; min-width: 460px; }
.EC-wf--cond, .EC-wf--acts { display: flex; flex-direction: column; gap: 6px; }
.EC-wf--arow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.EC-wf--arow > select { flex: 0 0 auto; max-width: 180px; }
.EC-wf--arow > .EC-wf--aparams { flex: 1 1 200px; }
.EC-wf--aparams { display: flex; gap: 6px; flex-wrap: wrap; }
.EC-wf--sm { min-width: 90px; flex: 1 1 110px; width: auto; }
.EC-wf--failhd { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-top: 6px; border-top: 1px solid var(--line); padding-top: 8px; }

/* ===== doc charts (ported from Formo docs — core/chart/) ===== */
.EC-blk--chart { margin: 8px 0; }
.doc-chart { margin: 18px 0 22px; --accent:#16b0a6; }   /* teal chart identity stays LOCAL; --line/--muted/--ink/--ink-soft now INHERIT the app theme (they used to shadow the globals → the chart never went dark) */
.doc-chart__scroll { padding: 4px; }
.doc-chart__canvas { position: relative; margin: 0 auto; }
.doc-chart__edges { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; }
.doc-chart__edge { fill: none; stroke: #b7c0cb; stroke-width: 1.8; }
.doc-chart__arrowhd { fill: #97a3b0; }
.doc-chart figcaption { text-align: center; font-size: 13.5px; color: var(--muted); margin-top: 12px; }
.doc-chart__node { position: absolute; border-radius: 11px; padding: 13px 15px; background: var(--ec-surface); border: 1.5px solid var(--line); box-shadow: 0 1px 3px rgba(20,25,35,.06); overflow: hidden; }
.doc-chart__node::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: #c9ced6; }
.doc-chart__t { font-weight: 700; font-size: 14.5px; line-height: 1.45; color: var(--ink); margin-bottom: 2px; }
.doc-chart__b { font-size: 12.8px; line-height: 1.46; color: var(--ink-soft); margin: 6px 0 0; }
.doc-chart__b code, .doc-chart__ex code { font-size: 11.5px; padding: 1px 4px; }
.doc-chart__ex { font-size: 12px; line-height: 1.4; color: var(--muted); margin: 7px 0 0; }
.doc-chart__ex span { font-weight: 600; color: var(--ink-soft); }
.doc-chart__elabel { position: absolute; transform: translate(-50%, -50%); background: var(--ec-surface); border: 1px solid var(--line); border-radius: 20px; font-size: 11.5px; color: var(--ink-soft); padding: 2px 9px; white-space: nowrap; box-shadow: 0 1px 2px rgba(20,25,35,.05); }
.cat-data    { background: #f3f8ff; border-color: #cfe0f7; } .cat-data::before    { background: #2f6fdb; }
.cat-schema  { background: #f0faf6; border-color: #c9ecdd; } .cat-schema::before  { background: #1f9d77; }
.cat-slot    { background: #fff7ee; border-color: #f3dcc0; } .cat-slot::before    { background: #e08a2b; }
.cat-content { background: #fff2f1; border-color: #f7d3cf; } .cat-content::before { background: #f0776c; }
.cat-block   { background: #f6f3fe; border-color: #ddd2f6; } .cat-block::before   { background: #7a5af0; }
.cat-editor  { background: #f1f9ff; border-color: #cce8f7; } .cat-editor::before  { background: #2b8fd6; }
.cat-process { background: #fffbef; border-color: #f0e4bd; } .cat-process::before { background: #d8a814; }
.cat-automation { background: #f2f2ff; border-color: #d6d6f7; } .cat-automation::before { background: #5a5af0; }
.cat-route   { background: #f3f6f9; border-color: #d8e0e8; } .cat-route::before   { background: #5b7a99; }
.cat-accent  { background: #e4f6f4; border-color: #bce6e1; } .cat-accent::before  { background: var(--accent); }
.cat-neutral { background: #f8f9fb; border-color: var(--line); } .cat-neutral::before { background: #aab2bc; }
/* dark: the light cat pastels would be light-on-light (the inherited --ink text now flips light) on a
   dark canvas. Collapse them to the app surface; the colored ::before bar still carries the category
   identity. Node/label bg + text/borders already flip via --ec-surface / --ink / --ink-soft / --line. */
[data-theme="dark"] .cat-data, [data-theme="dark"] .cat-schema, [data-theme="dark"] .cat-slot,
[data-theme="dark"] .cat-content, [data-theme="dark"] .cat-block, [data-theme="dark"] .cat-editor,
[data-theme="dark"] .cat-process, [data-theme="dark"] .cat-automation, [data-theme="dark"] .cat-route,
[data-theme="dark"] .cat-accent, [data-theme="dark"] .cat-neutral { background: var(--ec-surface); border-color: var(--ec-border); }

/* ===== list component (bullets / numbered) ===== */
.EC-blk--bullets { margin: 12px 0 16px; padding-left: 26px; }
ul.EC-blk--bullets { list-style: disc; }
ol.EC-blk--bullets { list-style: decimal; }
.EC-blk--bullets .EC-li, li.EC-li { margin: 5px 0; line-height: 1.55; }

/* server-rendered special blocks (docs-nav / pager) — editor placeholder only (real render is SSR) */
.EC-blk--srv { display: block; padding: 8px 12px; color: #8a929c; font-size: 13px; border: 1px dashed #d3d7de; border-radius: 8px; background: repeating-linear-gradient(45deg,#fafbfc,#fafbfc 10px,#f4f6f8 10px,#f4f6f8 20px); }

/* editor preview of the server-rendered docs blocks (docs-nav / pager) */
.EC-blk--docsprev .EC-docnav { display: flex; flex-direction: column; }
.EC-blk--docsprev .EC-docnav__link { display: flex; align-items: baseline; gap: 9px; text-decoration: none; color: #4b5560; font-size: 14px; padding: 6px 10px; border-radius: 8px; text-transform: none; }
.EC-blk--docsprev .EC-docnav__num { color: #8a929c; font-size: 12px; font-weight: 600; min-width: 18px; font-variant-numeric: tabular-nums; }
.EC-blk--docsprev .EC-docnav__item.is-active > .EC-docnav__link { background: #e4f6f4; color: #0f8f86; font-weight: 600; }
.EC-blk--docsprev .EC-docnav__sub { display: flex; flex-direction: column; margin: 2px 0 8px 23px; padding-left: 12px; border-left: 1px solid #e7e9ee; }
.EC-blk--docsprev .EC-docnav__sub a { text-decoration: none; color: #8a929c; font-size: 13px; padding: 3px 10px; }
.EC-blk--docsprev .EC-docpager__l { display: inline-flex; flex-direction: column; gap: 3px; text-decoration: none; border: 1px solid #e7e9ee; border-radius: 10px; padding: 10px 16px; }
.EC-blk--docsprev .EC-docpager__dir { font-size: 12px; color: #8a929c; }
.EC-blk--docsprev .EC-docpager__t { color: #1c1f23; font-weight: 600; font-size: 14px; }

/* transient article preview inside the content outlet (editor only) */
.EC-outlet--preview { padding: 4px 0; }
.EC-outlet--preview .EC-read--title { font-size: 26px; font-weight: 800; letter-spacing: -.3px; margin: 0 0 12px; }
.EC-outlet--preview .EC-read--body > * { max-width: 720px; }

/* ============================ Code block ============================ */
/* Syntax tokens (shared by SSR-baked spans + the editor overlay). Tuned for the light theme. */
.EC-hl-kw   { color: #b3389a; }            /* keywords */
.EC-hl-type { color: #2f7d8c; }            /* types */
.EC-hl-bi   { color: #2f7d8c; }            /* builtins */
.EC-hl-fn   { color: #6f42c1; }            /* function calls */
.EC-hl-str  { color: #2e9462; }            /* strings */
.EC-hl-num  { color: #c0791b; }            /* numbers */
.EC-hl-com  { color: var(--muted); font-style: italic; }
.EC-hl-op   { color: #8a929c; }            /* operators / punctuation */
.EC-hl-prep { color: #c0791b; }            /* GLSL preprocessor */
[data-theme="dark"] .EC-hl-kw   { color: #e57ac9; }
[data-theme="dark"] .EC-hl-type, [data-theme="dark"] .EC-hl-bi { color: #5fc9d8; }
[data-theme="dark"] .EC-hl-fn   { color: #b08cf0; }
[data-theme="dark"] .EC-hl-str  { color: #6fd99a; }
[data-theme="dark"] .EC-hl-num, [data-theme="dark"] .EC-hl-prep { color: #e0a85a; }

.EC-blk--code { margin: 8px 0; padding: 12px 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: auto; white-space: pre; }
.EC-blk--code, .EC-code--ov, .EC-code--ta, .EC-code { font: 13px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
.EC-blk--code > code { display: block; }

/* editor: a transparent <textarea> over a highlighted <pre> (same box, same metrics → perfect overlay) */
.EC-blk--codewrap { margin: 8px 0; }
.EC-code--lang { display: inline-block; margin-bottom: 6px; font-size: 12px; padding: 2px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--canvas); color: var(--muted); }
.EC-code--editor { position: relative; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.EC-code--ov, .EC-code--ta { margin: 0; padding: 12px 14px; border: 0; white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; tab-size: 2; }
.EC-code--ov { color: var(--ink); pointer-events: none; min-height: calc(1.55em + 24px); }
.EC-code--ov > code { display: block; }
.EC-code--ta { position: absolute; inset: 0; width: 100%; height: 100%; background: transparent; color: transparent; caret-color: var(--accent); resize: none; outline: none; overflow: hidden; }
.EC-code--ta::selection { background: var(--sel); }
.EC-code--editor:focus-within { border-color: var(--accent); }

/* ============================ Table block ============================ */
/* read-mode (SSR + editor preview) */
.EC-table { border-collapse: collapse; width: 100%; margin: 10px 0; font-size: 14px; }
.EC-table th, .EC-table td { border: 1px solid var(--line); padding: 7px 11px; text-align: left; vertical-align: top; }
.EC-table thead th { background: var(--panel); font-weight: 700; }
.EC-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--panel) 55%, transparent); }

/* editor: contenteditable grid with row/column handles + add/delete affordances */
.EC-blk--tablewrap { margin: 10px 0; padding-top: 22px; }   /* room for the column bar that floats above the top row */
.EC-etable--grid { display: flex; align-items: stretch; gap: 0; }
.EC-etable { border-collapse: collapse; font-size: 14px; }
.EC-etr { position: relative; }
.EC-etd { border: 1px solid var(--line); padding: 0; vertical-align: top; position: relative; min-width: 80px; }
.EC-eth { background: var(--panel); font-weight: 700; }
.EC-cell { padding: 7px 11px; outline: none; min-height: 1.55em; min-width: 60px; }
.EC-cell:focus { box-shadow: inset 0 0 0 2px var(--half-accent); border-radius: 2px; }

/* left gutter: row drag handle, shown on row hover */
.EC-etr--gutter { width: 0; padding: 0; border: 0; position: relative; }
.EC-etr--gutter > * { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); opacity: 0; transition: opacity .12s; }
.EC-rowhandle { cursor: grab; color: var(--muted); font-size: 11px; line-height: 1; user-select: none; padding: 2px; }
.EC-etr:hover .EC-etr--gutter > * { opacity: 1; }
/* right gutter: row delete, kept clear of the left drag handle */
.EC-etr--rgutter { width: 0; padding: 0; border: 0; position: relative; }
.EC-etr--rgutter > * { position: absolute; left: 6px; top: 50%; transform: translateY(-50%); opacity: 0; transition: opacity .12s; }
.EC-etr:hover .EC-etr--rgutter > * { opacity: 1; }

/* column bar (drag/align/delete) on the top-row cells, shown on cell hover */
.EC-colbar { position: absolute; top: -20px; left: 0; right: 0; height: 18px; display: flex; gap: 2px; justify-content: center; align-items: center; opacity: 0; transition: opacity .12s; }
.EC-etd:hover .EC-colbar, .EC-colbar:hover { opacity: 1; }
.EC-colhandle { cursor: grab; color: var(--muted); font-size: 13px; line-height: 1; user-select: none; padding: 0 4px; }

/* mini + add buttons */
.EC-minibtn, .EC-addbtn { border: 1px solid var(--line); background: var(--canvas); color: var(--muted); border-radius: 5px; font-size: 11px; line-height: 1; padding: 2px 5px; cursor: pointer; }
.EC-minibtn:hover, .EC-addbtn:hover { color: var(--ink); border-color: var(--accent); }
.EC-coldel:hover, .EC-rowdel:hover { color: var(--err); border-color: var(--err); }
.EC-etable--addcol { display: flex; align-items: center; padding-left: 4px; margin-left: 20px; opacity: 0; transition: opacity .12s; }
.EC-blk--tablewrap:hover .EC-etable--addcol { opacity: 1; }
.EC-etable--addrow { margin-top: 4px; opacity: 0; transition: opacity .12s; }
.EC-blk--tablewrap:hover .EC-etable--addrow { opacity: 1; }

/* vertical drop line for column drag (reuses the horizontal one's color) */
.EC-dropline--v { width: 2px; height: auto; }

/* ============ echo-button / echo-info-block (base/MainPage.md §2 templates) ============ */
.EC-btn2 { display:inline-flex; align-items:center; justify-content:center; gap:.4em; padding:.55em 1.1em; font:inherit; font-weight:600; text-decoration:none; cursor:pointer; border:1.5px solid transparent; line-height:1.2; transition:filter .12s; }
.EC-btn2:hover { filter:brightness(1.07); }
.EC-btn2--solid { background:var(--brand-red); color:var(--ec-accent-fg); }   /* DEFAULT = brand red (echofm #e13e39) */
.EC-btn2--dark { background:var(--ec-ink-strong); color:var(--ec-ink-strong-ct); }             /* black = unique / special buttons */
.EC-btn2--outline { background:transparent; color:var(--ec-ink-strong); border-color:var(--ec-ink-strong); }
.EC-btn2--ghost { background:transparent; color:var(--ec-ink-strong); }
.EC-btn2--r-none { border-radius:0; }
.EC-btn2--r-small { border-radius:4px; }
.EC-btn2--r-medium { border-radius:9px; }
.EC-btn2--r-large { border-radius:999px; }

.EC-info { display:flex; flex-direction:column; gap:.6em; padding:1em 1.1em; background:var(--ec-surface-2); border-radius:12px; }   /* overridden below by the Figma spec (#fff → --ec-surface) */
.EC-info--h { margin:0; font-size:1rem; font-weight:700; text-align: center;}
.EC-info--t { color:var(--ec-fg-secondary); font-size:.92rem; }
.EC-info--t:empty { display:none; }
.EC-info--acts { display:flex; flex-wrap:wrap; gap:.5em; justify-content: center}   /* buttons sit in a ROW (echofm app/merch are horizontal) */
.EC-info--acts > .EC-btn2 { flex:1 1 auto; }
/* the info-block GOVERNS its buttons' style/radius (one setting → every button); descendant selector wins
   over a child button's own variant by design (a standalone echo-button keeps using its own class). */
.EC-info--btns-solid   .EC-btn2 { background:var(--brand-red); color:var(--ec-accent-fg); border-color:transparent; }
.EC-info--btns-dark    .EC-btn2 { background:var(--ec-ink-strong); color:var(--ec-ink-strong-ct); border-color:transparent; }
.EC-info--btns-outline .EC-btn2 { background:transparent; color:var(--ec-ink-strong); border-color:var(--ec-ink-strong); }
.EC-info--btns-ghost   .EC-btn2 { background:transparent; color:var(--ec-ink-strong); border-color:transparent; }
.EC-info--r-none   .EC-btn2 { border-radius:0; }
.EC-info--r-small  .EC-btn2 { border-radius:4px; }
.EC-info--r-medium .EC-btn2 { border-radius:9px; }
.EC-info--r-large  .EC-btn2 { border-radius:999px; }

/* ============ center-column cards (base/MainPage.md §2: news / opinion / video / broadcast) ============ */
.EC-card { display:flex; flex-direction:column; gap:.5em; border-bottom:1px solid var(--ec-border-card); }
.EC-card--ttll { color:inherit; text-decoration:none; }
.EC-card--ttll:hover { color:var(--ec-red-deep); }
.EC-card .EC-feed--ttl { margin:0; font-size:1.18rem; line-height:1.25; font-weight:700; }
.EC-card--hero { width:100%; aspect-ratio:3/1; object-fit:cover; border-radius:10px; display:block; }
.EC-card--heroL { display:block; position:relative; }
.EC-card--body { display:flex; flex-direction:column; gap:.4em; }
.EC-card--lede, .EC-card .EC-feed--lede { margin:0; color:var(--ec-fg-secondary); font-size:.95rem; line-height:1.45; }
.EC-card .EC-feed--cat { font-size:.74rem; letter-spacing:.04em; text-transform:uppercase; color:var(--ec-fg-muted); }
/* gutter: section link · views · up/down vote (placeholders) */
.EC-card--gutter { display:flex; align-items:center; gap:1em; margin-top:.2em; font-size:.82rem; color:var(--ec-fg-muted); }
.EC-card--more { color:var(--ec-red-deep); text-decoration:none; font-weight:600; }
.EC-card--date { color:var(--ec-fg-muted); white-space:nowrap; }
.EC-card--votes { display:inline-flex; gap:.3em; }
.EC-card--vote { border:1px solid var(--ec-border-ctl); background:var(--ec-surface); color:var(--ec-fg-muted); padding:.15em .5em; height:1.9em; border-radius:5px; cursor:default; font-size:.72rem; line-height:1; white-space:nowrap; }
/* opinion card: author profile instead of a hero image */
.EC-card--barelink { display:block; text-decoration:none; color:inherit; }   /* person card (T95) — the whole avatar block links to the person page, no body/gutter */
.EC-card--author { display:flex; align-items:center; gap:.6em; }
.EC-card--avatar { width:44px; height:44px; border-radius:50%; background:#e7ebf0; color:var(--ec-fg-secondary); display:inline-flex; align-items:center; justify-content:center; font-weight:700; text-transform:uppercase; flex:none; object-fit:cover; overflow:hidden; }
img.EC-card--avatar { background:none; }
.EC-card--authmeta { display:flex; flex-direction:column; }
.EC-card--authname { font-weight:700; font-size:.95rem; }
.EC-card--authrole { font-size:.78rem; color:var(--ec-fg-muted); }
/* video poster play badge */
.EC-card--poster { position:relative; }
.EC-card--play { position:absolute; inset:0; margin:auto; width:3rem; height:3rem; border-radius:50%; background:rgba(0,0,0,.62); color:var(--ec-accent-fg); display:flex; align-items:center; justify-content:center; font-size:1.1rem; }
/* compact broadcast link */
.EC-card--bcast { display:flex; align-items:center; gap:.5em; padding:.5em 0; text-decoration:none; color:inherit; border-bottom:1px solid var(--ec-border-card); }
.EC-card--bcast:hover .EC-feed--ttl { color:var(--ec-red-deep); }
.EC-card--bcast .EC-feed--ttl { font-size:1rem; font-weight:600; }
.EC-card--bcdot { width:.55em; height:.55em; border-radius:50%; background:var(--ec-red-deep); flex:none; }

/* ============ horizontal scroller (base/MainPage.md §3c — Swiper-style loop/nav/dots, native scroll) ============ */
.EC-scroller { display:flex; flex-direction:column; gap:.7em; }
.EC-scroller--head { display:flex; align-items:center; justify-content:space-between; gap:1em; }
.EC-scroller--title { font-size:22px; line-height:1.2; font-weight:500; color:var(--ec-ink-strong); text-decoration:none; }
.EC-scroller--title:hover { color:var(--ec-accent); }   /* echofm coral on hover */
.EC-scroller--nav { display:flex; gap:.4em; flex:none; }
.EC-scroller--arr { width:2.1em; height:2.1em; border:1px solid var(--ec-border-ctl); background:var(--ec-surface); border-radius:50%; cursor:pointer; color:var(--ec-fg-secondary); font-size:1.1rem; line-height:1; display:flex; align-items:center; justify-content:center; }
.EC-scroller--arr:hover { border-color:var(--ec-ink-strong); color:var(--ec-ink-strong); }
.EC-scroller--track { display:flex; gap:16px; overflow-x:auto; scroll-snap-type:x mandatory; scrollbar-width:none; -ms-overflow-style:none; }
.EC-scroller--track::-webkit-scrollbar { display:none; }
.EC-scroller--track.EC-grabbing { cursor:grabbing; scroll-snap-type:none; }
.EC-scroller--slide { flex:0 0 auto; width:230px; scroll-snap-align:start; }
.EC-scroller--slide .EC-card { border-bottom:none; padding-bottom:0; }
/* title-only news slide */
.EC-card--title { display:block; font-size:1rem; font-weight:600; line-height:1.3; color:inherit; text-decoration:none; }
.EC-card--title:hover { color:var(--ec-red-deep); }
.EC-card--echero { width:100%; aspect-ratio:16/9; object-fit:cover; border-radius:10px; display:block; }
/* pagination dots */
.EC-scroller--dots { display:flex; gap:.4em; justify-content:center; margin-bottom: 8px }
.EC-scroller--dot { width:.55em; height:.55em; padding:0; border:none; border-radius:50%; background:#d6dae0; cursor:pointer; }
.EC-scroller--dot.is-on { background:var(--accent); }   /* active page = brand accent */
.EC-scroller--ph { color:var(--ec-fg-muted); font-size:.9rem; }

/* ============ «Выбор редакции» two-pane picker (base/MainPage.md §4) ============ */
.EC-ecp { padding:0; }
.EC-ecp--wrap { max-width:1100px; margin:0 auto; padding:1.2em 1.4em; }
.EC-ecp--top { display:flex; align-items:center; gap:1em; margin-bottom:1em; }
.EC-ecp--hint { color:var(--ec-fg-muted); font-size:.85rem; }
.EC-ecp--panes { display:grid; grid-template-columns:1fr 1fr; gap:1.4em; align-items:start; }
.EC-ecp--col, .EC-ecp--right { background:var(--ec-bg); border-radius:12px; padding:1em; }
.EC-ecp--chead, .EC-ecp--rhead { display:flex; align-items:center; justify-content:space-between; gap:1em; margin-bottom:.8em; }
.EC-ecp--chead h2, .EC-ecp--rhead h2 { margin:0; font-size:1.05rem; }
.EC-ecp--count { font-size:.8rem; color:var(--ec-fg-muted); }
.EC-ecp--search { max-width:55%; }
.EC-ecp--drop { display:flex; flex-direction:column; gap:.5em; min-height:80px; border-radius:8px; }
.EC-ecp--drop.is-over { outline:2px dashed var(--ec-red-deep); outline-offset:3px; }
.EC-ecp--empty { color:var(--ec-fg-muted); font-size:.9rem; padding:1.4em; text-align:center; }
.EC-ecp--pick { display:flex; gap:.6em; background:var(--ec-surface); border:1px solid var(--ec-border-card); border-radius:8px; padding:.6em; }
.EC-ecp--pick.is-hidden { opacity:.5; }
.EC-ecp--ord { display:flex; flex-direction:column; align-items:center; gap:.15em; flex:none; }
.EC-ecp--n { font-weight:700; color:var(--ec-fg-muted); font-size:.85rem; }
.EC-ecp--mv { width:1.7em; height:1.5em; border:1px solid var(--ec-border-ctl); background:var(--ec-surface); border-radius:5px; cursor:pointer; font-size:.7rem; color:var(--ec-fg-secondary); }
.EC-ecp--mv:disabled { opacity:.35; cursor:default; }
.EC-ecp--pbody { flex:1; min-width:0; display:flex; flex-direction:column; gap:.3em; }
.EC-ecp--ttl { font-weight:600; font-size:.95rem; line-height:1.25; }
.EC-ecp--meta { font-size:.74rem; color:var(--ec-fg-muted); }
.EC-ecp--desc { width:100%; resize:vertical; font:inherit; font-size:.85rem; border:1px solid #e2e6ea; border-radius:6px; padding:.4em; }
.EC-ecp--pacts { display:flex; flex-direction:column; gap:.3em; flex:none; }
.EC-ecp--icon { width:1.9em; height:1.9em; border:1px solid var(--ec-border-ctl); background:var(--ec-surface); border-radius:6px; cursor:pointer; font-size:.8rem; }
.EC-ecp--feed { display:flex; flex-direction:column; gap:.4em; max-height:70vh; overflow-y:auto; }
.EC-ecp--fitem { display:flex; align-items:center; gap:.6em; background:var(--ec-surface); border:1px solid var(--ec-border-card); border-radius:8px; padding:.5em .6em; cursor:grab; }
.EC-ecp--fitem.is-taken { opacity:.5; cursor:default; }
.EC-ecp--fbody { flex:1; min-width:0; }
.EC-ecp--add { width:2em; height:2em; border:1px solid var(--ec-red-deep); color:var(--ec-red-deep); background:var(--ec-surface); border-radius:6px; cursor:pointer; font-size:1rem; flex:none; }
.EC-ecp--add:disabled { border-color:#cdd3da; color:var(--ec-fg-muted); cursor:default; }
@media (max-width:760px) { .EC-ecp--panes { grid-template-columns:1fr; } }

/* ============ right-column + footer-region blocks (base/MainPage.md §5) ============ */
/* top banner */
.EC-banner { display:block; border-radius:12px; overflow:hidden; position:relative; text-decoration:none; }
.EC-banner img, .EC-banner .EC-img { width:100%; display:block; }
.EC-banner--cap { position:absolute; left:0; right:0; bottom:0; padding:.7em 1em; background:linear-gradient(transparent,rgba(0,0,0,.6)); color:var(--ec-accent-fg); font-weight:600; }
.EC-banner--cap:empty { display:none; }

/* now-playing (right column) */
.EC-now { display:flex; flex-direction:column; gap:.6em; background:#101216; color:var(--ec-accent-fg); border-radius:12px; padding:.8em; }
.EC-now--video { aspect-ratio:16/9; border-radius:8px; overflow:hidden; background:#000; }
.EC-now--video iframe, .EC-now--video .EC-video--ifr { width:100%; height:100%; border:0; }
.EC-now--bar { display:flex; align-items:center; gap:.6em; overflow:hidden; }
.EC-now--label { flex:none; font-size:.7rem; text-transform:uppercase; letter-spacing:.05em; color:var(--ec-accent); font-weight:700; }
.EC-now--marq { flex:1; overflow:hidden; white-space:nowrap; }
.EC-now--txt { display:inline-block; padding-left:100%; animation:ec-marq 14s linear infinite; }
@keyframes ec-marq { to { transform:translateX(-100%); } }
.EC-now--btns { display:flex; gap:.4em; }
.EC-now--btn { flex:1; padding:.5em; border:1px solid #2a2e36; background:#1b1e24; color:var(--ec-accent-fg); border-radius:7px; cursor:pointer; font:inherit; font-size:.82rem; }
.EC-now--btn:hover { background:#262b33; }
.EC-now--mute { flex:0 0 2.6em; }

/* next broadcasts */
.EC-nextb { display:flex; flex-direction:column; gap:.5em; }
.EC-nextb--h { margin:0; font-size:1rem; font-weight:700; }
.EC-nextb--list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.5em; }
.EC-nextb--all { color:var(--ec-red-deep); text-decoration:none; font-weight:600; font-size:.88rem; }

/* links grid (socials / footer links) + bottom bar */
.EC-lgrid { display:flex; flex-direction:column; gap:.6em; }
.EC-lgrid--h { margin:0; font-size:.95rem; font-weight:700; }
.EC-lgrid--h:empty { display:none; }
.EC-lgrid--items { display:grid; gap:.4em 1.2em; }
.EC-lgrid--it { color:var(--ec-fg-secondary); text-decoration:none; font-size:.9rem; }
.EC-lgrid--it:hover { color:var(--ec-red-deep); }
.EC-lbar2 { display:flex; flex-wrap:wrap; gap:1.2em; align-items:center; padding-top:.8em; border-top:1px solid var(--ec-border-card); color:var(--ec-fg-muted); font-size:.85rem; }
.EC-lbar2 .EC-lgrid--it { color:var(--ec-fg-muted); }

/* subscribe */
.EC-sub { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:1em; background:#f5f6f8; border-radius:12px; padding:1.2em; }
.EC-sub--h { margin:0 0 .2em; font-size:1.1rem; }
.EC-sub--t { margin:0; color:var(--ec-fg-secondary); font-size:.9rem; }
.EC-sub--form { display:flex; gap:.5em; flex:1; min-width:240px; max-width:420px; }
.EC-sub--in { flex:1; padding:.6em .8em; border:1px solid #d6dae0; border-radius:8px; font:inherit; }
.EC-sub--btn { padding:.6em 1.3em; border:none; background:var(--brand-red); color:var(--ec-accent-fg); border-radius:8px; cursor:pointer; font:inherit; font-weight:600; }
/* vertical sidebar menu (left column): a `list layout:stack` of menu-items — clean, no top-bar border */
.EC-list--stack { display:flex; flex-direction:column; gap:9px; }
.EC-list--stack .EC-nav--it { text-transform:none; font-size:16px; font-weight:600; letter-spacing:0; color:var(--ink); }
.EC-list--stack .EC-nav--it:hover { color:var(--brand-red); }

/* ============ main-page HOME layout (base/MainPage.md §1) — scoped to .EC-read--main so article pages are untouched ============ */
.EC-read--main .EC-cols { gap: 30px; align-items: flex-start; }
/* each column stacks its blocks vertically with consistent spacing */
.EC-read--main .EC-col { display: flex; flex-direction: column; gap: 24px; }
.EC-read--main .EC-col > .EC-blk { margin: 0; }
/* LEFT column: logo over a VERTICAL menu (the masthead/nav are top-bar styles otherwise) */
.EC-read--main .EC-col .EC-mast { flex-direction: column; align-items: flex-start; gap: 14px; border-bottom: none; padding: 0; margin: 0; }
.EC-read--main .EC-col .EC-nav, .EC-read--main .EC-col .EC-list--nav { flex-direction: column; align-items: flex-start; gap: 11px; border-bottom: none; padding: 0; margin: 0; }
.EC-read--main .EC-col .EC-nav--it { text-transform: none; font-size: 16px; letter-spacing: 0; }
/* RIGHT column sticks while you scroll the center */
.EC-read--main .EC-cols > .EC-col:nth-child(3) { position: sticky; top: 16px; }
/* footer region: separate it from the columns */
.EC-read--main > .EC-read--body > .EC-lgrid, .EC-read--main > .EC-read--body > .EC-sub, .EC-read--main > .EC-read--body > .EC-lbar2 { margin-top: 28px; }
.EC-read--main > .EC-read--body > .EC-cols { padding-bottom: 12px; border-bottom: 1px solid var(--ec-border-card); }
@media (max-width: 900px) { .EC-read--main .EC-cols { flex-wrap: wrap; } .EC-read--main .EC-cols > .EC-col:nth-child(3) { position: static; } }

/* slice (reusable template fragment) — editor placeholder (base/MainPage.md §1) */
.EC-slice--edit { padding:.7em 1em; border:1px dashed #c9ced6; border-radius:8px; background:#fafbfc; color:var(--ec-fg-muted); font-size:.85rem; }
.EC-slice--edit a { color:var(--accent); text-decoration:none; }

/* advanced per-block custom CSS field (details panel) */
.EC-d--adv { margin-top:10px; border-top:1px solid var(--line); padding-top:8px; }
.EC-d--advh { cursor:pointer; font-size:12px; color:var(--muted); user-select:none; }
.EC-d--adv .EC-d--code { margin-top:6px; width:100%; font-family:ui-monospace,SFMono-Regular,monospace; font-size:12px; }

/* ============ echofm MAIN-PAGE Figma spec (2026-06-24) — public reader only (.EC-read) ============ */
/* Forma DJR + #1a1a1a ink (echo never uses pure black). Admin keeps its own font/accent. */
.EC-read { font-family: "Forma DJR Cyrillic Micro", Arial, serif; color:var(--ec-ink-strong); }
.EC-read a { color:inherit; }

/* left-column menu: gap 13 · 20px · letter-spacing -1% */
.EC-read--main .EC-col .EC-list--stack { gap:8px; }
.EC-read--main .EC-col .EC-nav--it { font-size:20px; letter-spacing:-0.01em; text-transform:none; font-weight:500; color:var(--ec-ink-strong); }

/* echo-button: radius scale small=8 · medium=12 · large=16; brand red #e13e39, hover #f0776c */
.EC-btn2--r-none { border-radius:0; } .EC-btn2--r-small { border-radius:8px; } .EC-btn2--r-medium { border-radius:12px; } .EC-btn2--r-large { border-radius:16px; }
.EC-btn2--solid { background:var(--ec-brand-red); color:var(--ec-accent-fg) !important; }
.EC-btn2--solid:hover { background:var(--ec-accent); }
.EC-btn2--dark { background:var(--ec-ink-strong); }
.EC-read--main .EC-col > .EC-btn2 { font-size:20px; letter-spacing:0.01em; }   /* support button */

/* info blocks (app/merch): square top-left, 1px #dcdcdc, gap 15, buttons radius 12 max-width 150 */
.EC-info { background:var(--ec-surface); border:1px solid rgb(220,220,220); border-radius:0 12px 12px; padding:16px; gap:15px; }
.EC-info--h { font-size:16px; font-weight:700; line-height:1.2; letter-spacing:0.01em; }
.EC-info--acts { display:flex; flex-wrap:wrap; gap:10px; }
.EC-info--acts > .EC-btn2 { flex:1 1 auto; max-width:160px; border-radius:12px; }
.EC-info--btns-solid .EC-btn2 { background:var(--ec-brand-red); border-color:transparent; }
.EC-info--btns-solid .EC-btn2:hover { background:var(--ec-accent); }

/* cards (content blocks) */
.EC-card .EC-feed--ttl { font-size:26px; line-height:30px; letter-spacing:0.01em; font-weight:700; }
.EC-card--ttll, .EC-card--ttll:hover { color:var(--ec-ink-strong); }
.EC-card--hero, .EC-card--echero { border-radius:0; }   /* images are NOT rounded */
.EC-card .EC-feed--lede { font-size:20px; line-height:26px; letter-spacing:0.01em; color:var(--ec-ink-strong); }
.EC-card--gutter { display:flex; align-items:center; gap:14px; margin-top:6px; }
.EC-card--more { font-size:14px; font-weight:500; line-height:16px; letter-spacing:0.01em; color:var(--ec-ink-strong) !important; text-decoration:none; }
.EC-card--more:hover { color:var(--ec-brand-red) !important; }
.EC-card--date { font-size:14px; font-weight:400; line-height:16px; letter-spacing:0.01em; color:rgba(0,0,0,.5); white-space:nowrap; }
.EC-card--views { font-size:14px; color:rgba(0,0,0,.5); margin-left:auto; }
.EC-card--votes { display:inline-flex; gap:8px; }
.EC-card--vote { display:inline-flex; align-items:center; gap:5px; border:1px solid var(--ec-border-ctl); background:var(--ec-surface); color:rgba(0,0,0,.5); padding:4px 9px; border-radius:8px; cursor:default; font-size:14px; line-height:16px; }
.EC-card--vote::before { content:''; width:14px; height:14px; flex:none; background:currentColor; -webkit-mask:var(--thumb) center/contain no-repeat; mask:var(--thumb) center/contain no-repeat; }
.EC-card--down::before { transform:rotate(180deg); }
:root { --thumb:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M13.4 5.2C13.4 4.6 12.8 4.1 12.2 4.1H8.5L9.1 1.4C9.1 1.35 9.15 1.2 9.15 1.2C9.15 1 9 0.8 8.9 0.6125L8.26648 0L4.4 3.8C4.2 4.1 4.1 4.3 4.1 4.6V10.5C4.1 10.8 4.2 11.1 4.4 11.3C4.6 11.5 4.9 11.6 5.2 11.6H10.5C11 11.6 11.4 11.4 11.5 10.9L13.3 6.8C13.4 6.7 13.4 6.5 13.4 6.4V5.2ZM0.6 11.6H2.9V4.6H0.6V11.6Z'/%3E%3C/svg%3E"); }
/* T69 — the materials-list «live» icon: an external-link arrow (box + arrow out of the top-right corner),
   masked by .EC-art--live so it themes with the row. */
:root { --ec-live-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cline x1='10' y1='14' x2='21' y2='3'/%3E%3C/svg%3E"); }
/* OWNER FEATURE — the row's pencil (feather «edit-2») opening the editor; masked so it themes like --ec-live-ic. */
:root { --ec-edit-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z'/%3E%3C/svg%3E"); }
/* the detail-overlay «views» eye (feather «eye»), masked by .EC-mdo--cnt-v so it themes with the counters line. */
:root { --ec-views-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E"); }
/* OWNER FEEDBACK A — the materials-list date filter funnel icon (feather «filter»), masked by .EC-art--datefilter
   so it themes (grey inactive → accent active). Inert SVG namespace only — no external load. */
:root { --ec-filter-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3'/%3E%3C/svg%3E"); }
/* T85 — canonical material-TYPE icon tokens (base/MaterialsTable.md §2, core/materialTypeIcon.ts). One masked
   glyph per canonical type + a generic `other` fallback; feather/lucide monochrome, inert SVG namespace only
   (no external load), REFERENCED via .EC-tic--<type> so each is one asset that themes with the row colour like
   --ec-live-ic. Slugs/labels reconciled from KIND_RU / TEASERS / page-types (see the module header). */
:root { --ec-type-article-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cpolyline points='10 9 9 9 8 9'/%3E%3C/svg%3E"); }
:root { --ec-type-news-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2'/%3E%3Cpath d='M18 14h-8'/%3E%3Cpath d='M15 18h-5'/%3E%3Cpath d='M10 6h8v4h-8V6Z'/%3E%3C/svg%3E"); }
:root { --ec-type-opinion-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/%3E%3C/svg%3E"); }
:root { --ec-type-broadcast-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='2'/%3E%3Cpath d='M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14'/%3E%3C/svg%3E"); }
:root { --ec-type-podcast-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z'/%3E%3Cpath d='M19 10v2a7 7 0 0 1-14 0v-2'/%3E%3Cline x1='12' y1='19' x2='12' y2='23'/%3E%3Cline x1='8' y1='23' x2='16' y2='23'/%3E%3C/svg%3E"); }
:root { --ec-type-video-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='23 7 16 12 23 17 23 7'/%3E%3Crect x='1' y='5' width='15' height='14' rx='2' ry='2'/%3E%3C/svg%3E"); }
:root { --ec-type-poll-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='20' x2='18' y2='10'/%3E%3Cline x1='12' y1='20' x2='12' y2='4'/%3E%3Cline x1='6' y1='20' x2='6' y2='14'/%3E%3C/svg%3E"); }
:root { --ec-type-person-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E"); }
:root { --ec-type-event-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E"); }
:root { --ec-type-page-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z'/%3E%3Cpolyline points='13 2 13 9 20 9'/%3E%3C/svg%3E"); }
:root { --ec-type-other-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z'/%3E%3Cpolyline points='3.27 6.96 12 12.01 20.73 6.96'/%3E%3Cline x1='12' y1='22.08' x2='12' y2='12'/%3E%3C/svg%3E"); }

/* T83 — broadcast ATTACHMENT-presence icons (base/MaterialsTable.md §6). Same single-asset masked-SVG token
   discipline as the type icons above (colour comes from `background` on .EC-brow--att, so one def each, no
   theme fork). video = film, audio = waveform/soundwave, transcript = lined document. */
:root { --ec-att-video-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='23 7 16 12 23 17 23 7'/%3E%3Crect x='1' y='5' width='15' height='14' rx='2' ry='2'/%3E%3C/svg%3E"); }
:root { --ec-att-audio-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='3' y1='12' x2='3' y2='12'/%3E%3Cline x1='7' y1='8' x2='7' y2='16'/%3E%3Cline x1='11' y1='5' x2='11' y2='19'/%3E%3Cline x1='15' y1='8' x2='15' y2='16'/%3E%3Cline x1='19' y1='10' x2='19' y2='14'/%3E%3C/svg%3E"); }
:root { --ec-att-transcript-ic:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cline x1='10' y1='9' x2='8' y2='9'/%3E%3C/svg%3E"); }

/* opinion card = beige rounded block (Мнения) */
.EC-card--opinion { padding:16px; background:rgb(246,244,239); border-radius:16px; border-bottom:none; }

/* scroller slide card: news-like (image + title + description), no gutter (base/MainPage.md §3c) */
.EC-scroller--slide { width:260px; }
.EC-card--scroll { gap:8px; border-bottom:none; padding-bottom:0; }
.EC-card--scroll .EC-card--hero { aspect-ratio:16/9; width:100%; object-fit:cover; }
.EC-card--scroll .EC-feed--ttl { font-size:18px; line-height:22px; letter-spacing:0.01em; }
.EC-card--scroll .EC-feed--lede { font-size:15px; line-height:20px; letter-spacing:0.01em; color:var(--ec-ink-strong);
  display:-webkit-box; -webkit-line-clamp:3; line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }

/* ============ center-column separators + scroller title/dots (Figma 2026-06-24) ============ */
/* paginator: 10×10 dots, 8px apart */
.EC-scroller--dots { gap:8px; }
.EC-scroller--dot { width:10px; height:10px; }
/* scrolling-block title: 500 · 26/30 · +1% · 20px below */
.EC-scroller--title { font-size:26px; line-height:30px; font-weight:500; letter-spacing:0.01em; color:var(--ec-ink-strong); }
.EC-scroller--head { margin-bottom:20px; }
/* every element in the CENTER column separated by a thin rule (hr: 1px #808080, 0 above, 16 below) */
.EC-read--main .EC-cols > .EC-col:nth-child(2) { gap:0; }
.EC-read--main .EC-cols > .EC-col:nth-child(2) > .EC-blk:not(:last-child) { border-bottom:1px solid var(--ec-fg-faint); margin-bottom:16px; }
/* a STREAM separates its cards the same way; SCROLLER slides get NO per-card rule (only the block as a whole) */
.EC-read--main .EC-stream > .EC-card { border-bottom:1px solid var(--ec-fg-faint); margin-bottom:16px;  }
.EC-read--main .EC-stream > .EC-card:last-child { border-bottom:none; margin-bottom:0; }
.EC-scroller--track .EC-card, .EC-scroller--track .EC-card--scroll { border-bottom:none; margin-bottom:0; padding-bottom:0; }

/* ============ separators OVERRIDE — the hr lives BETWEEN siblings, not on the card (2026-06-24) ============ */
/* no card owns a border-bottom (opinion beige box included) */
.EC-read--main .EC-card, .EC-read--main .EC-stream > .EC-card { border-bottom:none; margin-bottom:0;  }
.EC-read--main .EC-cols > .EC-col:nth-child(2) > .EC-blk:not(:last-child) { border-bottom:none; margin-bottom:0; }
/* the hr = a top rule on every item AFTER the first, in the center column AND inside a stream (16px each side) */
.EC-read--main .EC-cols > .EC-col:nth-child(2) > .EC-blk:not(:first-child),
.EC-read--main .EC-stream > .EC-card:not(:first-child) { border-top:1px solid var(--ec-fg-faint);  }
/* scroller slides: never separated (only the scroller block as a whole) */
.EC-scroller--track .EC-card { border:none; margin:0; padding:0; }

/* ============ real <hr> separators (column / stream «separators» toggle) — replaces border approach ============ */
.EC-sep { height:1px; border:0;  background: rgba(0, 0, 0, 0.12); margin:16px 0; }
.EC-stream > .EC-sep:first-child, .EC-col > .EC-sep:first-child { display:none; }   /* never lead with a rule */
/* neutralize the earlier border-based separators (now done with <hr>) */
.EC-read--main .EC-cols > .EC-col:nth-child(2) > .EC-blk:not(:first-child),
.EC-read--main .EC-stream > .EC-card:not(:first-child) { border-top:0; margin-top:0;  }
.EC-read--main .EC-stream > .EC-card, .EC-read--main .EC-card { border-bottom:0; margin-bottom:0;  }

/* ============ paginator (base/MainPage.md): load-more + page bar ============ */
.EC-pager { display:flex; flex-direction:column; gap:16px; }
.EC-pager--items { display:flex; flex-direction:column; gap:0px; }
.EC-pager--more { align-self:center; padding:.7em 2em; border:1px solid var(--ec-ink-strong); background:var(--ec-surface); color:var(--ec-ink-strong); border-radius:8px; cursor:pointer; font:inherit; font-weight:600; font-size:15px; }
.EC-pager--more:hover { background:var(--ec-ink-strong); color:var(--ec-ink-strong-ct); }
.EC-pager--pages { display:flex; flex-wrap:wrap; gap:6px; justify-content:center; align-items:center; }
.EC-pager--p { min-width:32px; height:32px; padding:0 8px; border:1px solid var(--ec-border-ctl); background:var(--ec-surface); color:var(--ec-ink-strong); border-radius:8px; cursor:pointer; font:inherit; font-size:14px; }
.EC-pager--p:hover { border-color:var(--ec-ink-strong); }
.EC-pager--p.is-on { background:var(--ec-ink-strong); color:var(--ec-ink-strong-ct); border-color:var(--ec-ink-strong); }
.EC-pager--gap { color:var(--ec-fg-muted); padding:0 2px; }
.EC-pager--ph { color:var(--ec-fg-muted); font-size:.9rem; }

/* ── Search (base/Search.md): search box, /search results, instant overlay ── */
.EC-search { display:flex; align-items:center; gap:0; border:1px solid #dcdcdc; border-radius:8px; overflow:hidden; background:var(--ec-surface); }
.EC-search--in { flex:1; min-width:0; border:0; outline:0; padding:9px 12px; font:400 15px/1.2 Arial,sans-serif; background:transparent; }
.EC-search--go { border:0; background:transparent; padding:0 12px; font-size:18px; color:var(--ec-fg-faint); cursor:pointer; }
.EC-search--go:hover { color:var(--ec-brand-red); }
.EC-srch--group { margin-bottom:28px; }
/* results: each card capped at a readable 640px; wide screens flow the cards into multiple columns */
.EC-srch--list { display:grid; grid-template-columns:repeat(auto-fill, minmax(min(100%, 340px), 640px)); column-gap:36px; align-items:start; }
.EC-srch--gh { font:500 22px/26px "Forma DJR Cyrillic Micro",Arial,sans-serif; margin:0 0 12px; display:flex; align-items:baseline; gap:8px; }
.EC-srch--count { font:400 14px/16px Arial,sans-serif; color:var(--ec-fg-faint); }
.EC-srch--post { padding:12px 0; border-bottom:1px solid #eee; }
.EC-srch--card { display:flex; gap:12px; align-items:flex-start; padding:12px 0; border-bottom:1px solid #eee; color:var(--ec-ink-strong); text-decoration:none; }
.EC-srch--card:hover .EC-srch--ttl { color:var(--ec-brand-red); }
.EC-srch--cb { display:flex; flex-direction:column; min-width:0; }
.EC-srch--ttl { font:500 18px/22px Arial,sans-serif; margin:0; }
.EC-srch--sub { font:400 14px/18px Arial,sans-serif; color:var(--ec-fg-faint); margin-top:2px; }
.EC-srch--av { width:44px; height:44px; border-radius:50%; object-fit:cover; flex:0 0 auto; background:var(--ec-ink-strong); color:var(--ec-ink-strong-ct); display:flex; align-items:center; justify-content:center; font:600 18px/1 Arial,sans-serif; }
.EC-srch--tag { display:block; }
.EC-srch--tag .EC-srch--sub { display:block; margin-top:2px; }
.EC-srch mark { background:#fff1a8; color:inherit; border-radius:2px; padding:0 1px; }
.EC-srch--more { display:inline-block; margin-top:10px; color:var(--ec-brand-red); text-decoration:none; font-weight:500; }
.EC-srch--warm, .EC-srch--empty, .EC-srch--hint { color:var(--ec-fg-faint); padding:8px 0; }
.EC-srch-ov { position:fixed; z-index:50; background:var(--ec-surface); border:1px solid #dcdcdc; border-radius:10px; box-shadow:0 12px 40px rgba(0,0,0,.16); padding:16px 18px; max-height:78vh; overflow:auto; }
.EC-srch-ov .EC-srch--list { grid-template-columns:1fr; }
.EC-srch-ov .EC-srch--gh { font-size:18px; line-height:22px; margin-bottom:8px; }
.EC-srch-ov .EC-srch--group { margin-bottom:16px; }
.EC-srch-ov--foot { display:block; padding-top:6px; border-top:1px solid #eee; color:var(--ec-brand-red); text-decoration:none; font-weight:500; }
.EC-srch-ov--msg { color:var(--ec-fg-faint); padding:6px 0; }

/* ── Breadcrumb (base/Tagging.md): Эхо › Программы › {Program} › {Episode} ── */
.EC-crumb { font:400 13px/1.4 Arial,sans-serif; color:var(--ec-fg-faint); margin:0 0 14px; }
.EC-crumb--l { color:var(--ec-fg-faint); text-decoration:none; }
.EC-crumb--l:hover { color:var(--ec-brand-red); }
.EC-crumb--sep { margin:0 7px; color:#cfcfcf; }
.EC-crumb--cur { color:var(--ec-ink-strong); }

/* ── MainPage / reader widgets: dark-canvas overrides for bespoke Figma literals with NO matching
   token. The light rules above keep their literal values (byte-identical light); these ADD dark
   values so the widgets flip in the editor's dark canvas preview. The published reader is pinned
   light (server/render.ts), so these never reach the live site. See base/systems/dark-theme.md. ── */
[data-theme="dark"] .EC-sub { background: var(--ec-surface-2); }              /* #f5f6f8 */
[data-theme="dark"] .EC-card--avatar { background: var(--ec-border); }        /* #e7ebf0 */
[data-theme="dark"] .EC-scroller--dot { background: var(--ec-border); }       /* #d6dae0 */
[data-theme="dark"] .EC-sub--in,                                              /* #d6dae0 */
[data-theme="dark"] .EC-ecp--desc,                                            /* #e2e6ea */
[data-theme="dark"] .EC-ecp--add:disabled,                                    /* #cdd3da */
[data-theme="dark"] .EC-info,                                                 /* rgb(220,220,220) */
[data-theme="dark"] .EC-search,                                               /* #dcdcdc */
[data-theme="dark"] .EC-srch-ov { border-color: var(--ec-border-ctl); }       /* #dcdcdc */
[data-theme="dark"] .EC-srch--post,                                           /* #eee */
[data-theme="dark"] .EC-srch--card,                                           /* #eee */
[data-theme="dark"] .EC-srch-ov--foot { border-color: var(--ec-border); }     /* #eee */
[data-theme="dark"] .EC-crumb--sep { color: var(--ec-fg-muted); }             /* #cfcfcf */
[data-theme="dark"] .EC-card--date,                                           /* rgba(0,0,0,.5) */
[data-theme="dark"] .EC-card--views,                                          /* rgba(0,0,0,.5) */
[data-theme="dark"] .EC-card--vote { color: var(--ec-fg-muted); }             /* rgba(0,0,0,.5) */

/* Quick-insert bar — floating bottom-center row of the author's most-used blocks; one click inserts.
   For live/broadcast editing where hunting the palette costs seconds (base/BlockPalette.md). */
.EC-qbar { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); z-index: 40;
  display: flex; gap: 4px; padding: 5px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 6px 24px rgba(0,0,0,.16); max-width: 92vw; overflow-x: auto; }
.EC-qbar.is-hidden { display: none; }
.EC-qbar--btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border: none;
  background: transparent; color: var(--ink); border-radius: 8px; cursor: pointer; font: inherit;
  font-size: 13px; white-space: nowrap; }
.EC-qbar--btn:hover { background: var(--sel); color: var(--accent); }
.EC-qbar--ico { display: inline-block; width: 16px; text-align: center; opacity: .85; flex: none; }
.EC-qbar--lbl { font-size: 12px; }
@media (max-width: 900px) { .EC-qbar--lbl { display: none; } }

/* ── Text-check / async proofreading (base/TextCheck.md) ────────────────────────────────────────
   Highlights are a SEPARATE fixed overlay (never markup inside a field's contenteditable — that would
   be diffed into a bogus collab format op). Rects come from Range.getClientRects() in viewport space. */
.EC-tc-btn.is-busy { opacity: .7; pointer-events: none; }
.EC-tc-btn.is-muted { opacity: .45; pointer-events: none; }
.EC-tc-overlay { position: fixed; inset: 0; pointer-events: none; z-index: 900; }
.EC-tc-hl { position: fixed; border-radius: 2px; box-shadow: inset 0 -2px 0 currentColor; background: currentColor; opacity: .18; }
.EC-tc-hl--high { color: #e0245e; }
.EC-tc-hl--med  { color: #d98a00; }
.EC-tc-hl--low  { color: #2b8a3e; }
.EC-pop.EC-tc-pop { width: 380px; }
.EC-tc-sum { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.EC-tc-badge { font-size: 11px; padding: 2px 7px; border-radius: 10px; color: #fff; }
.EC-tc-badge.EC-tc-hl--high { background: #e0245e; opacity: 1; }
.EC-tc-badge.EC-tc-hl--med  { background: #d98a00; opacity: 1; }
.EC-tc-badge.EC-tc-hl--low  { background: #2b8a3e; opacity: 1; }
.EC-tc-list { display: flex; flex-direction: column; gap: 2px; max-height: 60vh; overflow: auto; }
.EC-tc-row { display: flex; align-items: baseline; gap: 6px; padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.EC-tc-row:hover { background: var(--hover); }
.EC-tc-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: currentColor; opacity: 1; align-self: center; box-shadow: none; }
.EC-tc-quote { color: var(--muted); text-decoration: line-through; }
.EC-tc-sugg { color: var(--ink); font-weight: 600; }
.EC-tc-arrow { color: var(--muted); }
.EC-tc-rule { margin-left: auto; font-size: 11px; color: var(--muted); opacity: .8; }
.EC-tc-empty { padding: 10px; color: var(--muted); font-size: 13px; }
.EC-tc-fpop { display: flex; flex-direction: column; gap: 8px; max-width: 320px; font-size: 13px; }
.EC-tc-fq { line-height: 1.4; }
.EC-tc-fmeta { font-size: 11px; color: var(--muted); }
.EC-tc-fbar { display: flex; gap: 6px; }

/* ---- material-card canvas widget + rich material picker (base/MainPage.md center column) ---- */
.EC-matcard--host { display: block; width: 100%; position: relative; }
/* dates overlay the card (no layout space, always visible); Изменить/Очистить live in a toolbar strip
   that surfaces when the block is SELECTED, mirroring the block-header toolbar (bottom:calc(100%-4px)). */
/* THE DATES OVERLAY — 📅 published / ✎ edited, filled by editor/blockRender.tsx#fillDatesFrom into
   `.EC-matcard--dates` inside this box. ON HOVER ONLY (owner, 2026-08-20): permanently-on chrome sat
   over every card's top-right corner. Same reveal vocabulary as the split/image grips below, and
   `opacity` (not `display`) so nothing about the card's layout depends on the pointer. */
.EC-matcard--chrome { position: absolute; top: 6px; right: 6px; display: flex; align-items: center; gap: 6px; z-index: 3; pointer-events: none; opacity: 0; transition: opacity .12s; }
.EC-matcard--host:hover .EC-matcard--chrome { opacity: 1; }
.EC-matcard--acts { position: absolute; bottom: calc(100% - 4px); right: 0; display: none; align-items: center; gap: 4px; z-index: 6; }
.EC-row.is-selected .EC-matcard--acts { display: flex; }
.EC-matcard--acts .EC-btn { box-shadow: 0 2px 12px rgba(0,0,0,.14); }
.EC-matcard--host [data-feed-list], .EC-matcard--host .EC-feed--portraititem, .EC-matcard--host .EC-feed--leaditem { width: 100%; max-width: none; }   /* the single card fills the block, not a narrow left segment */
.EC-matcard--pick { width: 100%; padding: 14px; border: 1px dashed var(--line); border-radius: 10px; color: var(--muted); background: transparent; cursor: pointer; font: inherit; font-size: 14px; }
.EC-matcard--pick:hover { border-color: var(--accent); color: var(--accent); background: var(--hover); }
/* THE IMAGE/TEXT DIVIDER GRIP (editor/imageGizmo.ts#attachCardSplit) — EDITOR CHROME, never published:
   it lives on `.EC-matcard--host`, a node the SSR path does not build. Geometry (left/top/height) comes
   from JS, which measures the real seam between the photo and `.EC-feed--portrbody`; only the look is here.
   Same reveal rule as the image grips: invisible until the card is hovered or its row selected. */
.EC-mc--split { cursor: ew-resize; border-radius: 6px; background: linear-gradient(90deg, transparent, rgba(0,0,0,.28), transparent); }
.EC-mc--split::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 2px; height: 34px; border-radius: 2px; background: rgba(255,255,255,.9); box-shadow: 0 0 0 1px rgba(0,0,0,.18); }
.EC-matcard--host:hover .EC-mc--split, .EC-row.is-selected .EC-mc--split { opacity: 1; pointer-events: auto; }
.EC-matcard--host.is-splitting .EC-mc--split { opacity: 1 !important; pointer-events: auto; }
.EC-matcard--host.is-splitting { cursor: ew-resize; user-select: none; }
.EC-app.is-preview .EC-mc--split { display: none !important; }
body.EC-isDragging .EC-mc--split { opacity: 0 !important; pointer-events: none !important; }
/* THE TOP IMAGE'S BOTTOM GRIP (editor/imageGizmo.ts#attachCardTopHeight) — the divider's rules on the other
   axis, and EDITOR CHROME in the same way: it lives on `.EC-matcard--host`, a node SSR never builds, so no
   page decision is being overridden and no doubled class is needed. Geometry comes from JS (it tracks the
   photo's real bottom edge); only the look is here. Same reveal rule as the divider. */
.EC-mc--imgh { cursor: ns-resize; border-radius: 6px; background: linear-gradient(180deg, transparent, rgba(0,0,0,.28), transparent); }
.EC-mc--imgh::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 34px; height: 2px; border-radius: 2px; background: rgba(255,255,255,.9); box-shadow: 0 0 0 1px rgba(0,0,0,.18); }
.EC-matcard--host:hover .EC-mc--imgh, .EC-row.is-selected .EC-mc--imgh { opacity: 1; pointer-events: auto; }
.EC-matcard--host.is-imgsizing .EC-mc--imgh { opacity: 1 !important; pointer-events: auto; }
.EC-matcard--host.is-imgsizing { cursor: ns-resize; user-select: none; }
.EC-app.is-preview .EC-mc--imgh { display: none !important; }
body.EC-isDragging .EC-mc--imgh { opacity: 0 !important; pointer-events: none !important; }

.EC-matcard--dates { font-size: 11px; background: color-mix(in srgb, var(--canvas) 82%, transparent); padding: 1px 6px; border-radius: 6px; color: var(--muted); white-space: nowrap; }

/* ================================================================================================
   material-card BANNER designer (base/MainPage.md · base/systems/feeds-aggregation.md)
   A social-post / banner: bg image + Echo logo + a gradient-overlay preset + a bottom-aligned content
   stack (section chip · hanging-punctuation headline · role line). ALL px are Figma @ the 1080×1080
   Instagram base, multiplied by --bscale (set inline per `size`) so one design scales to any embed size.
   NOTE: gradient geometry (g1/g2) + the site size dimensions are a FIRST PASS — expect visual tuning.

   FONT — the Figma uses "Forma DJR Cyrillic Display" (a LICENSED font, not vendored). If you have the
   files, drop them in playground/public/fonts/ and UNCOMMENT the @font-face below; until then the banner
   falls back to a Cyrillic-capable stack. (Sibling: .EC-read already uses "Forma DJR Cyrillic Micro".)
   @font-face {
     font-family: 'Forma DJR Cyrillic Display';
     src: url('/fonts/FormaDJRCyrillicDisplay-Regular.woff2') format('woff2');
     font-weight: 400 700; font-display: swap;
   }
   ================================================================================================ */
.EC-feed--matbanner { display: block; }
.EC-matbanner { --bscale: 0.556; position: relative; display: block; overflow: hidden; width: 100%;
  font-family: 'Forma DJR Cyrillic Display', 'Forma DJR Cyrillic Micro', 'Helvetica Neue', Arial, sans-serif;
  border-radius: calc(24px * var(--bscale)); background-color: #12151b; background-size: cover; background-position: center;
  text-decoration: none; color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.14); }

/* size presets: container width + aspect. --bscale is also set INLINE by renderMaterialCard (these are fallbacks). */
.EC-matbanner--sz-instagram { max-width: 1080px; aspect-ratio: 1 / 1; --bscale: 1; }
.EC-matbanner--sz-facebook  { max-width: 1200px; aspect-ratio: 1200 / 630; --bscale: 1.111; }
.EC-matbanner--sz-large     { max-width: 600px;  aspect-ratio: 1 / 1; --bscale: 0.556; }
.EC-matbanner--sz-medium    { max-width: 460px;  aspect-ratio: 1 / 1; --bscale: 0.426; }
.EC-matbanner--sz-small     { max-width: 340px;  aspect-ratio: 1 / 1; --bscale: 0.315; }

/* Echo logo, top-left (147×87 @1080), some inset. Scales with --bscale; ratio/size preserved across presets. Above the gradient. */
.EC-matbanner--logo { position: absolute; z-index: 3; top: calc(48px * var(--bscale)); left: calc(48px * var(--bscale));
  width: calc(147px * var(--bscale)); height: calc(87px * var(--bscale)); object-fit: contain; }

/* gradient overlay layer — z between the bg (0) and the content (2). The preset classes fill in the paint. */
.EC-matbanner--grad { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* g0 = none: no overlay. (Author should pick a gradient for legibility over bright images.) */
/* g1 «Красная подложка»: red→maroon fade [#E13E39 α00 → #590300 αff] reaching the BOTTOM (inset:0 → 100%);
       the α0 top keeps it visually below the logo. + the big Echo logo CENTERED (overlay blend), ~1097px wide (≈100px
       smaller than g2's 1197). */
.EC-matbanner--g1 .EC-matbanner--grad { background: linear-gradient(180deg, rgba(225,62,57,0) 12%, rgba(89,3,0,1) 100%); }
.EC-matbanner--g1 .EC-matbanner--grad::after { content: ''; position: absolute; top: calc(600px * var(--bscale)); left: 50%;
  transform: translateX(-50%); width: calc(1097px * var(--bscale)); height: calc(480px * var(--bscale));
  background: url('/logo.svg') no-repeat center / contain; mix-blend-mode: overlay; opacity: .45; }
/* g2 «Серая подложка»: the #252526 block is NOT a fixed top offset — it lives on the .EC-matbanner--lower wrapper so it
       starts at the TITLE's top, pulled UP 66px (margin/padding trick, below), and reaches the card bottom + edges.
       Here on the grad layer: the centered Echo logo (w=1197px, overlay blend) + a black fade [#000 α00 → #000 αf3]. */
.EC-matbanner--g2 .EC-matbanner--grad { background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.953) 100%); }
.EC-matbanner--g2 .EC-matbanner--grad::after { content: ''; position: absolute; top: calc(600px * var(--bscale)); left: 50%;
  transform: translateX(-50%); width: calc(1197px * var(--bscale)); height: calc(480px * var(--bscale));
  background: url('/logo.svg') no-repeat center / contain; mix-blend-mode: overlay; opacity: .32; }
/* g3 «Черный градиент» (added): a plain neutral bottom fade — the safe legibility default. */
.EC-matbanner--g3 .EC-matbanner--grad { background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,.75) 100%); }

/* content stack, bottom-aligned, above the gradient. Padding (the inset) = 48px @1080 on all sides. */
.EC-matbanner--content { position: relative; z-index: 2; display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 100%; box-sizing: border-box; padding: calc(48px * var(--bscale)); gap: calc(14px * var(--bscale)); }
.EC-matbanner--kicker { font-size: calc(30px * var(--bscale)); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #ffd24a; }
/* SECTION chip: white inline-block, red left bar, BLACK 66px text. NOT a full row → align-self start. */
.EC-matbanner--section { align-self: flex-start; display: inline-block; background: #fff; color: #000;
  border-left: calc(9px * var(--bscale)) solid red; font-size: calc(66px * var(--bscale)); font-weight: 700; line-height: 1.05;
  padding: calc(11px * var(--bscale)) calc(26px * var(--bscale)) 0 calc(27px * var(--bscale)); }
/* TITLE + role + cta wrapper — the g2 gray block keys off THIS box's top (= the title's top). */
.EC-matbanner--lower { display: flex; flex-direction: column; }
/* g2 gray block: start at title-top − 66px, full-bleed to the card's left/right/bottom edges (cancel the content's
   48px inset with negative margins, restore it as padding so text stays put). Extends downward to the bottom. */
.EC-matbanner--g2 .EC-matbanner--lower { background: #252526;
  margin: calc(-66px * var(--bscale)) calc(-48px * var(--bscale)) calc(-48px * var(--bscale));
  padding: calc(66px * var(--bscale)) calc(48px * var(--bscale)) calc(48px * var(--bscale)); }
/* TITLE: 46px @1080, weight 600, line-height 1.15. Hanging punctuation via inline text-indent (em → scales w/ --bscale). */
.EC-matbanner--ttl { margin: 0; font-size: calc(46px * var(--bscale)); line-height: 1.15; font-weight: 600; color: #fff;
  text-shadow: 0 1px calc(8px * var(--bscale)) rgba(0,0,0,.35); }
/* SPACE 131px @1080 between title and role. */
.EC-matbanner--role { margin-top: calc(131px * var(--bscale)); font-size: calc(34px * var(--bscale)); font-weight: 600;
  color: #f2f2f2; opacity: .92; }
/* editor: the role line is contenteditable in place. An empty one shows a placeholder + stays clickable. */
.EC-matbanner--role[data-role-edit] { outline: none; min-width: calc(60px * var(--bscale)); cursor: text; }
.EC-matbanner--role[data-role-edit]:empty::before { content: 'Роль…'; opacity: .5; }
.EC-matbanner--cta { align-self: flex-start; margin-top: calc(14px * var(--bscale)); padding: calc(14px * var(--bscale)) calc(28px * var(--bscale));
  border-radius: calc(12px * var(--bscale)); background: var(--accent, #d81a2a); color: #fff; font-size: calc(28px * var(--bscale)); font-weight: 700; }
.EC-matbanner:hover .EC-matbanner--cta { filter: brightness(1.08); }

/* details panel: the banner designer group (shown only for banner/hero) */
.EC-matcard--banner { display: block; }
.EC-matcard--bannerh { margin: 10px 0 4px; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); }
.EC-matcard--bnrow { display: flex; align-items: center; gap: 8px; }
.EC-matcard--bnrow > .EC-d--bool { flex: 0 0 auto; }
.EC-matcard--bnrow > .EC-d--in { flex: 1 1 auto; min-width: 0; }

.EC-pop.EC-mpick--pop { width: 640px; max-width: 94vw; }
.EC-mpick { display: flex; flex-direction: column; gap: 8px; }
.EC-mpick--bar { display: flex; flex-direction: column; gap: 6px; }
.EC-mpick--frow { display: flex; gap: 6px; flex-wrap: wrap; }
.EC-mpick--q { width: 100%; }
.EC-mpick--fin { flex: 1 1 80px; min-width: 70px; }
.EC-mpick--date { flex: 0 0 auto; width: 130px; }
.EC-mpick--titles { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--muted); cursor: pointer; white-space: nowrap; user-select: none; }
.EC-mpick--titles input { margin: 0; cursor: pointer; }
.EC-mpick--tabs { display: flex; gap: 4px; flex-wrap: wrap; }
.EC-mpick--tab { font: inherit; font-size: 12px; padding: 3px 9px; border-radius: 12px; border: 1px solid var(--line); background: transparent; color: var(--muted); cursor: pointer; }
.EC-mpick--tab:hover { color: var(--ink); }
.EC-mpick--tab.is-on { background: var(--sel); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.EC-mpick--list { max-height: 46vh; overflow: auto; display: flex; flex-direction: column; gap: 2px; }
.EC-mpick--row { display: flex; align-items: center; gap: 10px; padding: 6px; border-radius: 8px; cursor: pointer; }
.EC-mpick--row:hover { background: var(--hover); }
.EC-mpick--row.is-on { background: var(--sel); }
.EC-mpick--thumb { flex: 0 0 auto; width: 48px; height: 48px; object-fit: cover; border-radius: 6px; background: var(--hover); }
.EC-mpick--thumb-ph { display: flex; align-items: center; justify-content: center; font-size: 20px; }
.EC-mpick--meta { flex: 1 1 auto; min-width: 0; }
/* title + description fill the FULL row width (wrap up to 2 lines) instead of truncating to a short left segment */
.EC-mpick--ttl { font-size: 13px; font-weight: 600; color: var(--ink); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.EC-mpick--desc { font-size: 12px; color: var(--muted); margin-top: 2px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.EC-mpick--sub { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 3px; font-size: 11px; color: var(--muted); }
.EC-mpick--stat { white-space: nowrap; }
.EC-mpick--dates { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 2px; font-size: 11px; color: var(--muted); }
.EC-mpick--row { align-items: flex-start; }   /* top-align the thumb with the (now multi-line) meta */
.EC-mpick--kind { text-transform: lowercase; }
.EC-mpick--badge { padding: 1px 6px; border-radius: 8px; font-weight: 600; }
.EC-mpick--badge.is-draft { background: color-mix(in srgb, #b0b0b0 22%, transparent); color: #777; }
.EC-mpick--badge.is-sched { background: color-mix(in srgb, #e6a417 22%, transparent); color: #b47f0a; }
.EC-mpick--badge.is-pub { background: color-mix(in srgb, #2e9e4f 22%, transparent); color: #2e9e4f; }
.EC-mpick--cb { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line); background: var(--canvas); color: var(--muted); font-size: 14px; cursor: pointer; }
.EC-mpick--row.is-on .EC-mpick--cb { background: var(--accent); color: #fff; border-color: var(--accent); }
.EC-mpick--more { align-self: center; }
.EC-mpick--empty { padding: 12px; text-align: center; font-size: 13px; color: var(--muted); font-style: italic; }
.EC-mpick--foot { display: flex; justify-content: flex-end; padding-top: 6px; border-top: 1px solid var(--line); }

/* ============================================================================================
   WIREFRAME / "sketchy" VIEW MODE  (base/systems/editor.md · toolbar device seg `wireframe`)
   ------------------------------------------------------------------------------------------
   The device seg in the editor toolbar sets `.is-wireframe` on `.EC-canvas` (editor.tsx device
   hook). Everything below is scoped to that MODE CLASS — never to `.EC-canvas` — so the exact
   same wireframe render appears anywhere the class is added (e.g. add `is-wireframe` to a picker
   popup for a local preview). Goal: CONTENT / TEASER blocks flatten into a readable, list-like
   wireframe — text stays NORMAL-SIZED, media collapses to a flat placeholder, per-type outline
   keeps each block distinguishable, approximate height/proportion preserved. "Designed" blocks
   with NO override here (material-card banner `.EC-matbanner`, masthead, player, footer, poll…)
   keep their full rich render so proportions stay honest when one is placed. `donate` LEFT that
   list in `19807d1` — it has a compact wireframe strip now (201px → 51px).
   FIRST PASS — see base/systems/editor.md for the block coverage checklist / what's left.
   ============================================================================================ */
.is-wireframe {
  --wf-line: color-mix(in srgb, var(--muted) 60%, transparent);   /* placeholder outline */
  --wf-fill: color-mix(in srgb, var(--muted) 14%, transparent);   /* placeholder fill    */
  /* ── THE WIREFRAME SCALE — ONE knob, HALVED 2026-08-20 ────────────────────────────────────────
     Owner: «our wireframe blocks are looking better now, but they should be smaller vertically.
     two times». Halving the row-height TARGET (rowLayout.ts `WF_ROWS` 2→1) does not halve a row on
     its own: the rule is `max(cap, content)` and — MEASURED on /edit/center — every interesting row
     was already sitting on its CONTENT floor, which these numbers set. So the scale halves too, and
     it halves HERE so the wireframe cannot end up a mix of two scales.
     Was: --wf-rowh 48px · thumb 120×48 · item padding 6px 4px · gutter 12-14px.
     ⚠ The floor a CELL is capped to is what shrinks; the CONTENT is told to adapt (`min-height:0`,
     images `max-height:100%`, titles clamped at a LINE boundary). A pixel cap that slices a line of
     text is the `4dd99ea` bug and must never come back. */
  --wf-rowh:  24px;              /* ONE rundown line — the item floor AND the atomic-row cap        */
  --wf-rowh2: 48px;              /* …and the double allowance a text block may take                 */
  --wf-th-w:  64px;              /* the teaser thumb box, 64×24 — the 120×48 box halved             */
  --wf-th-h:  var(--wf-rowh);
  --wf-pad:   2px 4px;           /* an item's own padding                                           */
  --wf-gap:   8px;               /* the thumb ↔ text gutter                                         */
}
/* Any teaser/content image → a FLAT grey placeholder box (contrast(0) collapses the picture to a
   uniform tone regardless of the real src; the border makes it read as a wireframe slot). */
.is-wireframe .EC-feed--leadimg, .is-wireframe .EC-feed--cardimg, .is-wireframe .EC-feed--portraitimg,
.is-wireframe .EC-feed--podimg, .is-wireframe .EC-card--hero, .is-wireframe .EC-card--echero,
.is-wireframe .EC-card--avatar, .is-wireframe .EC-blk--image img, .is-wireframe .EC-blk--cover img,
.is-wireframe .EC-blk--gallery img {
  /* real thumbnail (no grayscale) so editors can recognise the material at a glance */
  border: 1px solid var(--wf-line); border-radius: 3px; box-shadow: none; object-fit: cover;
}

/* ---- center-column stream/scroller CARD → ONE rundown line: LEFT thumb (--wf-th-w x --wf-th-h) + title ---- */
.is-wireframe .EC-card {
  display: flex; flex-direction: row; align-items: center; gap: var(--wf-gap);
  min-height: var(--wf-rowh); padding: var(--wf-pad); border-bottom: 1px solid var(--wf-line);
  background: transparent; box-shadow: none;
}
.is-wireframe .EC-card--heroL, .is-wireframe .EC-card--hero, .is-wireframe .EC-card--echero {
  flex: 0 0 var(--wf-th-w); width: var(--wf-th-w) !important; height: var(--wf-th-h) !important; aspect-ratio: auto !important;
  object-fit: cover; margin: 0;
}
.is-wireframe .EC-card--play, .is-wireframe .EC-card--poster .EC-card--play { transform: scale(.5); }   /* keep the ▶ tag, shrunk */
.is-wireframe .EC-card--body { flex: 1 1 auto; gap: 2px; padding: 0; }
.is-wireframe .EC-card .EC-feed--ttl { font-size: 1rem; line-height: 1.3; font-weight: 600; }
.is-wireframe .EC-card--author { gap: 6px; }
.is-wireframe .EC-card--avatar { width: var(--wf-th-h); height: var(--wf-th-h); }
/* DROP the "designed detail" in list mode — lede, gutter (views/votes/date), author role, category kicker */
.is-wireframe .EC-card--lede, .is-wireframe .EC-card .EC-feed--lede, .is-wireframe .EC-card--gutter,
.is-wireframe .EC-card .EC-feed--cat, .is-wireframe .EC-card--authrole { display: none; }

/* ---- aggregation feed LAYOUTS (base/MainPage.md) ---- */
/* grid → single-column list of thumb-rows */
.is-wireframe .EC-feed--grid { display: flex; flex-direction: column; gap: 0; }
.is-wireframe .EC-feed--card {
  display: flex; flex-direction: row; align-items: center; gap: var(--wf-gap); min-height: var(--wf-rowh);
  padding: var(--wf-pad); border: none; border-bottom: 1px solid var(--wf-line); border-radius: 0; background: transparent;
}
.is-wireframe .EC-feed--cardimg { flex: 0 0 var(--wf-th-w); width: var(--wf-th-w) !important; height: var(--wf-th-h) !important; margin: 0; }
.is-wireframe .EC-feed--card .EC-feed--ttl { font-size: 1rem; font-weight: 600; }
/* lead → thumb-row, but keep the bigger title (it's the hero teaser) */
.is-wireframe .EC-feed--leaditem { display: flex; flex-direction: row; align-items: center; gap: var(--wf-gap); padding: 0; min-height: var(--wf-rowh); }
.is-wireframe .EC-feed--leadimg { flex: 0 0 var(--wf-th-w); width: var(--wf-th-w) !important; max-height: var(--wf-th-h) !important; height: var(--wf-th-h) !important; margin: 0; }
.is-wireframe .EC-feed--leaditem .EC-feed--ttl { font-size: 1.15rem; }
.is-wireframe .EC-feed--leaditem .EC-feed--lede { display: none; }
/* portrait → left thumb (--wf-th-*) + title (row) */
.is-wireframe .EC-feed--portraititem { grid-template-columns: var(--wf-th-w) 1fr; gap: var(--wf-gap); align-items: center; min-height: var(--wf-rowh); }
.is-wireframe .EC-feed--portraitimg { width: var(--wf-th-w) !important; height: var(--wf-th-h) !important; }
.is-wireframe .EC-feed--portraititem .EC-feed--ttl { font-size: 1.1rem; margin-left: 4px }
.is-wireframe .EC-feed--portraititem .EC-feed--lede { display: none; }
/* podcast → left thumb (--wf-th-*) row */
.is-wireframe .EC-feed--pod { align-items: center; min-height: var(--wf-rowh); padding: 0; }
.is-wireframe .EC-feed--podimg { width: var(--wf-th-w) !important; height: var(--wf-th-h) !important; }
.is-wireframe .EC-feed--pod .EC-feed--lede, .is-wireframe .EC-feed--pod .EC-feed--air { display: none; }
/* headlines → already a title list; just a subtle marker so the TYPE reads as a headline block */
.is-wireframe .EC-feed--li { padding: 2px 0 2px 8px; border-left: 2px solid var(--wf-line); border-bottom: 1px solid var(--wf-line); }
/* opinion → keep author + title, drop the excerpt */
.is-wireframe .EC-feed--op { border-left: 3px solid var(--wf-line); }
.is-wireframe .EC-feed--op .EC-feed--lede { display: none; }
/* breaking → timestamped title rows, drop the excerpt */
.is-wireframe .EC-feed--brk { padding: 2px 0; }
.is-wireframe .EC-feed--brk .EC-feed--lede { display: none; }
/* the not-yet-resolved editor placeholder stays, just quieter */
.is-wireframe .EC-feed--ph { background: transparent; }
/* the section <section> wrappers keep spacing but shed their own chrome so the rows carry the look */
.is-wireframe .EC-agg { margin: 0; }

/* ---- builtin CONTENT blocks ---- */
/* paragraph / heading: text is the content — keep it fully readable, just tint a faint gutter tick so a
   text block is visually a "block". Headings already differ by size (h1/h2/h3.EC-blk--text-block). */
.is-wireframe .EC-blk--text-block { border-left: 2px solid var(--wf-fill); padding-left: 8px; }
/* quote: neutralise the accent to the wireframe tone (still a left-rule + italic → distinguishable) */
.is-wireframe .EC-blk--quote { border-left-color: var(--wf-line); }
/* standalone image block: collapse to a modest placeholder strip + caption (top-aligned rule). NOTE: a
   pure image block has no text to sit beside, so it uses a short top strip rather than the --wf-th-* thumb. */
.is-wireframe .EC-blk--image { }
.is-wireframe .EC-blk--image img { max-height: var(--wf-rowh) !important; width: auto; }
/* image + text: the LEFT/RIGHT square becomes a --wf-th-* thumb beside normal text, row-aligned */
.is-wireframe .EC-blk--imageParagraph { align-items: center; gap: var(--wf-gap); }
.is-wireframe .EC-blk--imageParagraph .EC-blk--imgWrap { flex: 0 0 var(--wf-th-w); }
.is-wireframe .EC-blk--imageParagraph .EC-blk--imgWrap img { height: var(--wf-th-h) !important; width: var(--wf-th-w) !important; object-fit: cover; }
/* list / bullets: keep readable, already list-like */
.is-wireframe .EC-blk--bullets { }
/* cover: flatten the overlay to a plain block (drop the bg-image drama), keep the text readable */
.is-wireframe .EC-blk--cover { min-height: var(--wf-rowh); }
/* gallery: cap the thumbnails so a big grid reads as a compact placeholder strip */
.is-wireframe .EC-blk--gallery img { max-height: var(--wf-rowh) !important; }

/* ============================================================================================
   WIREFRAME — EDITOR CANVAS TABLE ROWS  (base/systems/editor.md · steps 1+2, 2026-07-28)
   ------------------------------------------------------------------------------------------
   WHY THE FIRST PASS DIDN'T "LAND": the rules above restyle each block's INNER widget classes
   (.EC-feed--*, .EC-card, .EC-blk--*) — which the editor DOES render (blockView.tsx:
   .EC-row > .EC-bv--body > widget) — so for the handful of covered types they applied. BUT
   nothing compacted the editor ROW itself (.EC-row / .EC-bv--body / .EC-typeico kept their full
   padding + height), AND ~30 registered block types (material-card banner/hero, masthead, player,
   banner, links-bar/-grid, tile-grid, subscribe, slice, echo-button/-info, logo, menu,
   program-header, search-box/-results, people, person-card, newsletter-cta, video, code, table,
   chart, embed, divider, now-playing, columns/section, outlet, + every author-created type) had
   NO wireframe rule at all → they rendered at full height. Net effect: the canvas never read as a
   dense table, which is exactly the "I don't see the overload per block" report.
   THE FIX (below): compact the ROW for EVERY block into an aligned table row — a persistent left
   type-glyph column (.EC-typeico, driven by each block def's `icon`) + the block's essence, capped
   to a spreadsheet-like `--wf-rowh` (24px since 2026-08-20) so many materials fit on one screen. RICH/"designed" blocks and
   multi-item CONTAINERS opt OUT of the cap (:not(:has(…))) so their proportions stay honest and
   their already-compacted child rows carry the look. Keyed off `data-block-type` (stamped in
   blockView.renderBody) where per-type polish helps. Scoped to `.is-wireframe` (never `.EC-canvas`)
   so the same table render is reusable in any host that adds the class (e.g. a future picker popup).
   ============================================================================================ */
/* --wf-rowh / --wf-rowh2 are declared with the rest of THE WIREFRAME SCALE at the top of this
   section (one authority — a second declaration here would silently win by order). */

/* the ROW becomes a table row: a faint baseline rule + a reserved left glyph column, tight radius.
   Tighter insertion gaps so the stack reads as contiguous rows, not spaced cards. */
.is-wireframe .EC-row { border-bottom: 1px solid var(--wf-line); border-radius: 0; }
.is-wireframe .EC-row.is-selected { border-bottom-color: var(--half-accent); }
.is-wireframe .EC-bv--body { padding: 0; }   /* left pad reserves the type-glyph column */
.is-wireframe .EC-gap { height: 2px; margin: 0; background: #d4d5d8; }   /* bespoke light gap rule */
[data-theme="dark"] .is-wireframe .EC-gap { background: var(--ec-border); }

/* persistent type indicator: pull .EC-typeico INTO the row as a fixed left column and keep it visible
   (it normally fades out on hover/select) so the glyph column stays stable like a spreadsheet gutter. */
.is-wireframe .EC-typeico {
  display:none
}
.is-wireframe .EC-row:hover > .EC-typeico, .is-wireframe .EC-row.is-selected > .EC-typeico { opacity: .75; }

/* HEIGHT CAP — every ATOMIC block collapses to one spreadsheet row. Opt OUT: (1) rich/"designed"
   blocks (honest proportions), (2) multi-item feed CONTAINERS, and (3) any row that itself nests
   further .EC-row's (columns / section / list / ref) — those carry the look through their children.
   Keyed on the block's rendered inner class, so material-card is dynamic (banner cardView → .EC-matbanner
   = rich = full; portrait teaser → .EC-feed--portraititem = compact). */
.is-wireframe .EC-row:not(:has(
    .EC-row,
    .EC-matbanner, .EC-mast, .EC-player, .EC-poll, .EC-don, .EC-footer, .EC-proghdr, .EC-now--video,
    .EC-agg, .EC-feed, .EC-stream, .EC-scroller, .EC-nextb, .EC-people,
    .EC-blk--chart, .EC-blk--table, .EC-blk--code
  )) > .EC-bv--body {
  max-height: var(--wf-rowh); overflow: hidden;
}
/* the SELECTED atomic block expands so its full content stays readable/editable in place */
.is-wireframe .EC-row.is-selected > .EC-bv--body { max-height: none; overflow: visible; }

/* HEIGHT CONSTRAINT (2026-07-28): every material template fits ONE row (`--wf-rowh`). Text-heavy blocks
   whose first real paragraph needs room may take a DOUBLE row (`--wf-rowh2`) — never more. Keyed on the
   stamped data-block-type so a paragraph/heading/quote/list gets the taller allowance while every
   teaser/card stays a single spreadsheet row. Custom "designed" widgets (banner/masthead/poll/…)
   still opt out above and keep honest proportions. */
.is-wireframe .EC-row[data-block-type="paragraph"] > .EC-bv--body,
.is-wireframe .EC-row[data-block-type="quote"] > .EC-bv--body,
.is-wireframe .EC-row[data-block-type="bullets"] > .EC-bv--body,
.is-wireframe .EC-row[data-block-type="imageParagraph"] > .EC-bv--body {
  max-height: var(--wf-rowh2); overflow: hidden;
}
/* feed-item rows inside a container also obey the single-row cap (scroller/stream cards, lead/portrait/pod) */
.is-wireframe .EC-card, .is-wireframe .EC-feed--card, .is-wireframe .EC-feed--leaditem,
.is-wireframe .EC-feed--portraititem, .is-wireframe .EC-feed--pod { max-height: var(--wf-rowh); overflow: hidden; }

/* TYPOGRAPHY: compact table type — titles 12px/normal, secondary meta 10px/italic. Overrides the
   per-layout 1rem/600 title rules above so every row reads at the same dense scale. */
.is-wireframe .EC-feed--ttl, .is-wireframe .EC-card .EC-feed--ttl {
  font-size: 12px !important; font-weight: normal; line-height: 1.25;
}
.is-wireframe .EC-feed--lede, .is-wireframe .EC-feed--cat, .is-wireframe .EC-feed--air,
.is-wireframe .EC-card--gutter, .is-wireframe .EC-card--authrole, .is-wireframe .EC-feed--meta {
  font-size: 10px; font-style: italic;
}
.is-wireframe .EC-matcard--dates.EC-d--muted {
    display: flex;
    flex-direction: column;
    background: transparent;
    align-items: flex-end;
    padding: 0 2px;
}
.is-wireframe .EC-handle {
    background: var(--ec-surface);
    box-shadow: -4px 0px 11px 0px #00000047;
    top: 5px;
    position: relative;
    cursor: grabbing;
}
/* the material-card dates overlay sits at the bottom corner of the tight row */
.is-wireframe .EC-matcard--chrome { bottom: 0; right: 2px; top: auto; }
/* category chip pinned top-right of the row, bold — reads as the section tag in the table.
   The item rows carry position:relative so the absolute chip anchors to its own row. */
.is-wireframe .EC-feed--card, .is-wireframe .EC-feed--leaditem, .is-wireframe .EC-feed--portraititem,
.is-wireframe .EC-feed--pod, .is-wireframe .EC-feed--li, .is-wireframe .EC-feed--op,
.is-wireframe .EC-feed--brk { position: relative; }
.is-wireframe .EC-feed--cat { position: absolute; right: 4px; top: 0; float: right; font-weight: 600; }
.is-wireframe .EC-matcard--acts {
    background: var(--ec-surface);
    bottom: 100%;
}
.is-wireframe .EC-feed--podbody, .is-wireframe .EC-feed--ttl {
    max-width: 550px;
}
/* per-type polish so the capped rows read clean: drop stacked margins / heavy chrome, keep the
   title or first line on the visible line. */
.is-wireframe .EC-blk--text-block, .is-wireframe .EC-blk--quote { margin: 0; }
.is-wireframe .EC-blk--quote cite { display: none; }
.is-wireframe .EC-blk--divider { padding: 0; }
.is-wireframe .EC-blk--divider hr { margin: 0; }
.is-wireframe .EC-blk--embed { padding: 4px 8px; min-height: 0; }
.is-wireframe .EC-btn2, .is-wireframe .EC-info, .is-wireframe .EC-lgrid, .is-wireframe .EC-lbar2,
.is-wireframe .EC-slice, .is-wireframe .EC-sub, .is-wireframe .EC-cta, .is-wireframe .EC-logo,
.is-wireframe .EC-search, .is-wireframe .EC-srch, .is-wireframe .EC-banner, .is-wireframe .EC-now,
.is-wireframe .EC-tile-grid, .is-wireframe .EC-lgrid--items {
  box-shadow: none; border: none; background: transparent;
}
.is-wireframe .EC-banner, .is-wireframe .EC-now, .is-wireframe .EC-info, .is-wireframe .EC-cta { min-height: 0; padding: 0; }
/* container blocks (columns/section/list) keep a thin frame so the group reads as a group, but shed padding */
.is-wireframe .EC-blk--section, .is-wireframe .EC-col { min-height: 0; padding: 4px; }

/* ============================================================================================
   WIREFRAME — «TWO TIMES SMALLER» (owner, 2026-08-20)  ·  base/WireframeRow.md §5
   ------------------------------------------------------------------------------------------
   The row-height rule is `track = clamp(min, max(capTrack, contentTrack), authoredTrack)`, so
   halving the CAP (rowLayout.ts `WF_ROWS` 2→1) moves nothing on a row that already sits on its
   CONTENT floor — MEASURED, that was every interesting row on /edit/center. The scale at the top
   of this section halves the teaser geometry; the rules below take out the remaining tall things,
   which are all CHROME rather than copy: an aspect-ratio media box, a 26px-padded drop zone, a
   26px section heading, a 2-line title where a rundown wants one.

   ⚠ AND NOT ONE OF THEM IS A PIXEL CAP OVER TEXT. That is `4dd99ea` — a `max-height` slicing a
   line in half under `overflow:hidden`. Where copy has to be shortened it is shortened at a LINE
   boundary (`-webkit-line-clamp`, which ellipses) or by making the line itself divide the cap
   exactly (`line-height: calc(cap / n)`), never by cutting through one.
   ============================================================================================ */

/* TITLES: ONE line in a rundown. A clamp cuts at a line boundary and says so with an ellipsis, so
   this SHRINKS the content floor without ever slicing a glyph — the scroller card was 41px (two
   14px lines + 12px of padding) and is 17px. `.EC-card--ttll` is the <a> inside the heading, and
   the clamp has to be on the box that holds the text. */
.is-wireframe .EC-feed--ttl, .is-wireframe .EC-card .EC-feed--ttl {
  display: -webkit-box; -webkit-line-clamp: 1; line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.4 !important;
}
/* ⚠ 1.4, AND `!important`, AND BOTH ARE MEASURED FACTS, NOT TASTE.
   · 1.4 — a `-webkit-line-clamp` box is exactly `line-height` tall, so a line box tighter than the
     font's own ascender+descender clips the GLYPHS. At 1.15/12px the FIRST card of a stream lost the
     top of its Cyrillic caps against the cell's `overflow:hidden` (seen at 3x zoom; the 2nd and 3rd
     cards had the previous card's box to bleed into, which is why it read as "only sometimes").
     The clamp must cut between LINES, never through one — that is `4dd99ea`.
   · !important — `core/blocks/layout.ts` publishes `.EC-gridrow>.EC-nest--list>.EC-nest--item
     .EC-feed--ttl{line-height:1.15}` at (0,4,0) AND is injected after this file, so a canvas rule
     loses twice over (handoff §3.1, the trap that has now cost seven bugs). The neighbouring
     `font-size: 12px !important` is here for the same reason. */

/* HEADINGS collapse to one rundown line too. Without this an h1 block (a ~38px line) lands under
   the 24px atomic-row cap below and is sliced — the exact shape of `4dd99ea`. */
.is-wireframe .EC-row > .EC-bv--body h1.EC-blk--text-block,
.is-wireframe .EC-row > .EC-bv--body h2.EC-blk--text-block,
.is-wireframe .EC-row > .EC-bv--body h3.EC-blk--text-block {
  font-size: 14px; line-height: var(--wf-rowh); margin: 0;
}
/* …and a text block's LINE divides its cap exactly (3 × 16px = --wf-rowh2), so the cap can only
   ever land between lines. */
.is-wireframe .EC-row[data-block-type="paragraph"] .EC-blk--text-block,
.is-wireframe .EC-row[data-block-type="quote"] .EC-blk--quote,
.is-wireframe .EC-row[data-block-type="bullets"] .EC-blk--bullets {
  line-height: calc(var(--wf-rowh2) / 3);
}

/* MEDIA BOXES lose their aspect ratio — a 16:9 frame is 224px at column width and it is the single
   tallest thing on the canvas. In a rundown it is a placeholder that sizes to whatever the editor
   puts inside it, so nothing (the YouTube picker and its URL input included) can be clipped. */
.is-wireframe .EC-now--video { aspect-ratio: auto !important; }
.is-wireframe .EC-now { gap: 4px; }
.is-wireframe .EC-now--btns .EC-now--btn { padding: 2px 6px; font-size: 10px; border-radius: 4px; }
.is-wireframe .EC-yt { gap: 8px; }
.is-wireframe .EC-yt--thumbcol, .is-wireframe .EC-yt--thumb { width: 80px; }
.is-wireframe .EC-yt--main { gap: 2px; }
.is-wireframe .EC-yt--title { font-size: 11px; }
.is-wireframe .EC-yt .EC-input { padding: 2px 6px; font-size: 11px; }

/* THE EMPTY-IMAGE DROP ZONE — editor chrome, 26px of padding, 106px tall inside a banner. */
.is-wireframe .EC-img2--drop { padding: 5px; font-size: 11px; border-radius: 4px; }

/* SCROLLER chrome: a 26px/30px section heading with a 20px margin, and 10px dots. */
.is-wireframe .EC-scroller { gap: 2px; }
.is-wireframe .EC-scroller--head { margin-bottom: 2px; }
.is-wireframe .EC-scroller--title { font-size: 13px; line-height: 1.2; }
.is-wireframe .EC-scroller--nav .EC-scroller--arr { width: 18px; height: 18px; font-size: 11px; padding: 0; line-height: 1; }
.is-wireframe .EC-scroller--dots { margin-bottom: 2px; gap: 4px; }
.is-wireframe .EC-scroller--dot { width: 5px; height: 5px; }
/* the hairline between stream / pager items is a rundown rule, not a 32px block separator */
.is-wireframe .EC-sep { margin: 0; }

/* ============================================================================================
   WIREFRAME — NO IMAGE DRAGGER  (owner, 2026-08-20: «with no image dragger in wireframe»)
   ------------------------------------------------------------------------------------------
   The crop/size/divider gizmos are PRECISION tools — a 12px grip on the seam of a photo, a pan
   that writes a normalised crop rect. At a 24px rundown line there is nothing to aim at and they
   sit exactly where the structural gestures (≡ move, 🗑 delete, which STAY) need the pointer.
   TWO PLACES, the pattern `1aa8cbc` set for the resize grips: hidden here, AND refused in the
   mousedown/wheel handlers (editor/imageGizmo.ts) — a hidden-but-live handler still captures a
   gesture, and a stray drag here would write `imgCrop`/`imgShare` at a size where the author
   cannot see what they are doing.
   ============================================================================================ */
.is-wireframe .EC-ig--gr { display: none !important; }        /* every grip: gh / gw / corner / split / imgh */
.is-wireframe .EC-ig--panwrap, .is-wireframe .EC-ig--tag { display: none !important; }


/* Gallery ADD TILE — EDITOR-ONLY chrome (base/GalleryWizard.md). Lives here, NOT in the gallery
   BlockType's `css:`, because BlockType.css ships verbatim into every published page AND into the
   frozen contract artifact the backend consumes — 2.2 KB of rules no reader can ever trigger.
   Sibling of .EC-nest--add, the other editor-only slot chrome.
   REVEAL MUST STAY `opacity` — display:none / visibility:hidden drop the buttons out of the tab
   order and make the tile mouse-only. Pinned by test/blockType.test.ts.

   (Nothing on a public page ever emits .EC-galadd, so these rules are inert there.) A dashed trailing
   tile that is the LAST child of the slot list, so it inherits whatever the current data-layout does
   to a cell and always sits after the last photo. REVEALED ON HOVER — and on :focus-within, because a
   hover-only affordance is a mouse-only affordance: the reveal is opacity, never display/visibility,
   so its three real buttons stay in the tab order and tabbing to them fades the tile in. An EMPTY
   gallery keeps it visible (:only-child) — there is nothing else to hover — and so does a SELECTED
   gallery (.EC-row.is-selected, the editor's BlockView), which is what keeps the tile on screen while
   the block palette it just opened covers it. Space is reserved at all times, so revealing never
   reflows. */
.EC-blk--gallery .EC-galadd{position:relative;display:flex;flex-direction:column;align-items:stretch;justify-content:center;gap:6px;min-height:88px;padding:8px;box-sizing:border-box;opacity:0;transition:opacity .12s ease}
.EC-blk--gallery:hover .EC-galadd,.EC-blk--gallery:focus-within .EC-galadd,.EC-row.is-selected .EC-galadd,.EC-blk--gallery .EC-galadd:only-child{opacity:1}
.EC-blk--gallery[data-layout="grid"] .EC-galadd{aspect-ratio:1}
.EC-galadd--main{display:flex;flex:1 1 auto;flex-direction:column;align-items:center;justify-content:center;gap:4px;width:100%;min-height:56px;padding:10px;border:2px dashed #c3c7d4;border-radius:8px;background:transparent;color:#8a90a3;font:inherit;font-size:.9em;cursor:pointer}
.EC-galadd--main:hover{border-color:#4a6cf7;color:#4a6cf7;background:rgba(74,108,247,.05)}
.EC-galadd--plus{font-size:20px;line-height:1}
.EC-galadd--acts{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}
.EC-galadd--chip{padding:2px 9px;border:1px solid #d5d8e2;border-radius:11px;background:#fff;color:#5a6072;font:inherit;font-size:.78em;line-height:1.7;cursor:pointer;white-space:nowrap}
.EC-galadd--chip:hover{border-color:#4a6cf7;color:#4a6cf7}
.EC-galadd--main:focus-visible,.EC-galadd--chip:focus-visible{outline:2px solid #4a6cf7;outline-offset:2px}
.EC-blk--gallery .EC-galadd{transition:none}

/* CANVAS MULTI-SELECTION over slot items (base/GalleryWizard.md slice B — editor/slotSelect.ts).
   EDITOR-ONLY, same reasoning as the add tile above: .is-msel / .EC-marquee are painted by the
   selection controller and no compiled template ever emits them, so they must NOT go into any
   BlockType `css:` field (which ships into every published page and the frozen contract).
   The ring is an outline, not a border — a border would resize the tile and reflow the grid. */
.EC-nest--item.is-msel{outline:2px solid #4a6cf7;outline-offset:1px;border-radius:4px}
/* SELECTION IS A BORDER, NOT A FILL — the same call the owner made for `.EC-row` (styles.css:617),
   applied to the row model's multi-select. The blue wash was an ::after painting
   rgba(74,108,247,.10) over the whole item; the 2px outline above carries the state alone. */
.EC-nest--item.is-msel{position:relative}
/* The slot list is ONE tab stop (roving selection) so the whole feature is keyboard-reachable; show
   that focus, but only from the keyboard. */
.EC-nest--list:focus{outline:none}
.EC-nest--list:focus-visible{outline:2px dashed #9aa3bd;outline-offset:3px}
/* The marquee rectangle is body-level + fixed, so it is never clipped by the canvas's overflow. */
.EC-marquee{position:fixed;z-index:2500;border:1px solid #4a6cf7;background:rgba(74,108,247,.12);pointer-events:none}
body.EC-isMarquee{user-select:none}
/* The bulk-config popup is a normal popup-stack entry; only its width + the mixed-value tag are ours. */
.EC-pop--bulk{width:290px}
.EC-bulk--n{display:block;margin-bottom:8px;color:#8a90a3;font-size:.8em}
.EC-bulk--mixed{margin-left:6px;color:#b98900;font-size:.85em;font-style:italic}

/* GALLERY LAYOUT SWITCHER + «как на сайте» PREVIEW (base/GalleryImplementation.md Slice 3 —
   editor/gallerySwitchBar.tsx). EDITOR-ONLY, same reasoning as the add tile and the multi-select ring
   above: nothing here is emitted by the compiled gallery template, so it must never move into the
   gallery BlockType's `css:` field (which ships verbatim into every published page AND the frozen
   contract). Sits ABOVE the slot list as a sibling — a bar, not an overlay, so it never needs the
   hover-reveal dance the add tile uses. */
.EC-galsw{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}
.EC-galsw--chips{display:flex;flex-wrap:wrap;gap:6px}
.EC-galsw--chip{padding:3px 10px;border:1px solid #d5d8e2;border-radius:12px;background:#fff;color:#5a6072;font:inherit;font-size:.8em;line-height:1.7;cursor:pointer;white-space:nowrap}
.EC-galsw--chip:hover:not(:disabled){border-color:#4a6cf7;color:#4a6cf7}
.EC-galsw--chip.is-on{border-color:#4a6cf7;background:#4a6cf7;color:#fff}
.EC-galsw--chip:disabled{opacity:.45;cursor:default}
.EC-galsw--prev{padding:3px 10px;border:1px solid #d5d8e2;border-radius:12px;background:#fff;color:#5a6072;font:inherit;font-size:.8em;line-height:1.7;cursor:pointer;white-space:nowrap}
.EC-galsw--prev:hover:not(:disabled){border-color:#4a6cf7;color:#4a6cf7}
.EC-galsw--prev.is-on{border-color:#22a06b;background:#22a06b;color:#fff}
.EC-galsw--prev:disabled{opacity:.4;cursor:not-allowed}
.EC-galsw--chip:focus-visible,.EC-galsw--prev:focus-visible{outline:2px solid #4a6cf7;outline-offset:2px}

/* Settings → «+ Пригласить стажёра». An ACTION, not a list row: accent, inline-block so the dashed rule
   hugs the label instead of spanning the panel, and a dashed underline that goes solid on hover — the
   affordance reads as clickable without competing with the primary buttons. Stays a <button>. */
.EC-set--act{display:inline-block;width:auto;padding:2px 0;border:0;background:none;
  color:var(--accent,#c2461f);border-bottom:1px dashed currentColor;cursor:pointer;font:inherit}
.EC-set--act:hover{border-bottom-style:solid}
.EC-set--act:focus-visible{outline:2px solid var(--accent,#c2461f);outline-offset:3px;border-bottom-color:transparent}

/* Gallery items on the DETAILS PANEL: the row list is hidden (see editor/widgets.tsx#galleryItemsField).
   Kept in the DOM, not removed, because it is still the file-drop target — but zero-height and out of the
   tab order so it cannot be reached or read. The canvas is the item surface. */
.EC-reflist--hidden{display:block;height:0;overflow:hidden;visibility:hidden}

/* Drop indicator turned 90° for a horizontally-laid container (gallery rows/grid/carousel). The base
   .EC-dropline is a horizontal rule between stacked blocks; between side-by-side tiles the insertion point
   is a vertical seam, and showing a horizontal bar there points at the wrong gap entirely. */
.EC-dropline--v{width:2px!important}

/* A «скоро» layout chip: listed so the roadmap reads, but never selectable. Matches the disabled
   <option> in the details panel — the two surfaces read the same enum and must not disagree. */
.EC-galsw--chip.is-soon{opacity:.45;cursor:not-allowed;text-decoration:line-through}

/* ===== GRID CHILD width control (base/GridContainer.md §4) — EDITOR-ONLY chrome, so it lives here and
   NOT in the block type's `css:` (which ships verbatim onto every published page). A segmented row of
   width tiers + the slice-2 pin, shown in the details header only when the parent is a grid-container. */
.EC-gw{display:flex;flex-direction:column;gap:4px;margin-top:8px}
.EC-gw--row{display:flex;gap:4px;align-items:center}
.EC-gw--b{flex:1;min-width:0;padding:4px 0;border:1px solid #d4d4dd;border-radius:5px;background:#fff;cursor:pointer;font:inherit;font-size:12px;line-height:1.2;color:#333}
.EC-gw--b:hover{border-color:#8a8ab0}
.EC-gw--b.is-on{background:#2d2d3a;border-color:#2d2d3a;color:#fff}
.EC-gw--hv{flex:1;min-width:0;text-align:center;font-size:12px;line-height:1.2;color:#333}
.EC-gw--pin{padding:4px 6px;border:1px solid #d4d4dd;border-radius:5px;background:#fff;font:inherit;font-size:12px;cursor:not-allowed;opacity:.45}
/* The canvas slot: the grid lives on .EC-nest--list (the drag container), one DOM layer deeper than
   SSR's .EC-grid--i wrappers — the block's own css: establishes the tracks for BOTH via dual selectors.
   Only the authoring affordances belong here. */
.EC-grid--edit>.EC-nest--list{min-height:32px}
.EC-grid--edit>.EC-nest--list>.EC-nest--item{min-width:0}

/* ── GRID TILES on the canvas (G1, base/GridMigration.md §6b) ─────────────────────────────────────────
   A tile's height IS `h * rowH`, so the canvas grid gets a HARD row track (the published stylesheet uses
   minmax(rowH,auto) instead — a page must never overlap or clip its own content) and each tile CLIPS.
   Without the clip the four uncapped container types (.EC-stream/.EC-scroller/.EC-agg/.EC-feed) spill
   out of their cell and the canvas stops reading as a layout at all.

   ⚠ KNOWN GAP, deliberately shipped. Clipping can visually truncate a headline, and the experiment's
   product stance is «never trim text» (experiments/grid-constructor/HANDOFF.md §0 and INVARIANT 5).
   The resolution (owner, 2026-08-19) is NOT to un-clip: the BROWSER measures the title at authoring
   time, persists a discrete size token (e.g. fontsize=xs) that SSR renders like any other setting, and
   marks the block RED when even the smallest size will not fit — a visible authoring protest instead of
   a silent shrink or a silent clip. Until that lands an author can produce a tile that truncates a
   headline: a known trade-off, see base/GridMigration.md §7b. Do not "fix" it by measuring on the
   server — that is the SSR/editor drift gridPack.ts:5-8 exists to prevent. */
.EC-grid--edit.EC-grid>.EC-nest--list{grid-auto-rows:var(--ec-grid-rowh,48px)}   /* same doubling, same reason */
.EC-grid--edit>.EC-nest--list>.EC-nest--item{overflow:hidden}

/* ── THE DRAG PLACEHOLDER (G2, base/GridMigration.md §2b) ─────────────────────────────────────────────
   While a tile is being dragged it stays in the flow as a DASHED GHOST in the slot it would drop into,
   and its neighbours FLIP-glide around it (editor/grid/gridDrag.ts + gridFlip.ts) — the experiment's
   `.dragging` (app.js#startMove). Editor chrome, so it lives HERE and never in the block's published
   `css:` (which is linted + shipped to readers). The whole gesture is DOM-only; the model is written
   once, on pointerup. */
.EC-grid--edit>.EC-nest--list>.EC-nest--item.EC-grid--ghost{
  outline:2px dashed #8a8ab0;outline-offset:-2px;border-radius:4px;
  background:rgba(138,138,176,.08);opacity:.5;
}
.EC-grid--edit>.EC-nest--list>.EC-nest--item.EC-grid--ghost *{pointer-events:none}

/* ── ROWS on the canvas (base/GridMigration.md §7f, slices R0 + R1) ───────────────────────────────────
   A row is a bounded cell space, `cols` wide × `max(h)` tall, and its children sit at AUTHORED x/y/w/h.
   The canvas track is HARD (the published stylesheet uses minmax(rowH,auto) — a page must never clip
   its own content) because the drop rule reads the pointer's CELL off it: a track that grew with its
   content would make the Y math a lie. Editor chrome only — never the block's published `css:`. */
/* ⚠ CLASS DOUBLED for specificity, same trap as align-items above. The block's PUBLISHED css sets
   `.EC-gridrow>.EC-nest--list{grid-auto-rows:minmax(rowH,auto)}` (core/blocks/layout.ts) so a page block
   taller than its cell still renders — right for a reader. The canvas carries BOTH classes, and at equal
   (0,2,0) the later-injected block css won: squeezing a card's text grew its track and the tile escaped
   the grid (owner, 2026-08-20). (0,3,0) pins the canvas to a HARD track — the tile's overflow:hidden
   then clips, which is the mockup surface behaving as designed. */
.EC-gridrow--edit.EC-gridrow>.EC-nest--list{min-height:32px;grid-auto-rows:var(--ec-grid-rowh,48px)}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{min-width:0;overflow:hidden}
/* ⚠ THE ONE RULE THAT MAKES `h` VISIBLE AT ALL (owner, 2026-08-19: «BR resize changes width but not
   height»). The row block's PUBLISHED css sets `align-items:start` on the very same list
   (core/blocks/layout.ts — a page must never stretch a block past its own content), and a start-aligned
   grid item is CONTENT-height: its `grid-row: y+1 / span h` reserved four tracks and then drew 24px.
   So `h` had no visual effect whatsoever, and the same flattening silently erased every zero-content
   overlay this list holds — the free-space hatch (.EC-rw--gap) and the drop preview (.EC-rw--drop) were
   both 0px tall, which is why a within-row drag ALSO read as «it snaps» rather than «it follows».
   The canvas is a MOCKUP surface where a tile's box IS its cell, so here it STRETCHES.
   Written with the class doubled — specificity (0,3,0) — so it wins over the block's own (0,2,0) rule
   no matter which stylesheet the browser saw last. */
.EC-gridrow--edit.EC-gridrow>.EC-nest--list{align-items:stretch}
/* And an IDLE box, so «my block got taller» is legible before anything is hovered or selected. A row
   child is the one block whose extent is authored, so its extent has to be visible at rest. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{box-shadow:inset 0 0 0 1px rgba(138,138,176,.26);border-radius:4px}
/* …AND THE CONTENT FOLLOWS THE CELL (owner, 2026-08-19: «blocks do not scale vertically as in the
   experiment»). Stretching the WRAPPER alone gives a correctly-sized cell with a content-height block
   sitting at the top of it — the cell resized, the block did not. The chain from the cell down to the
   block is four levels deep (`.EC-nest--item > .EC-row > .EC-bv--body > the block`), and every one of
   them has to pass the height on or it stops there. Flex does that in one direction with no measurement.

   `min-height`, not `height`, on the block itself: a block taller than its cell must still RENDER (the
   item's own `overflow:hidden` decides what is visible, exactly as before) rather than being squashed —
   «never trim text» is a product stance about SSR, but silently crushing a block on the canvas would
   mislead the author about what they authored. Editor chrome only; the page is untouched. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{display:flex;flex-direction:column}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row>.EC-bv--body{flex:1 1 auto;min-height:0}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row>.EC-bv--body>*{min-height:100%}
/* ⚠ …AND THROUGH THE `display:contents` HOST. Measured in a real browser, 2026-08-20 (owner, in devtools:
   «.EC-bv--body is the full cell, while 'EC-blk EC-agg EC-agg--lead EC-matcard' block inside is smaller»):
   some blocks are wrapped in a bare `.EC-blkhost` that is `display:contents`, and a `display:contents` box
   GENERATES NO BOX — so the rule above lands on it and is discarded, and the real block below it never
   hears about the cell at all. The chain reported .EC-bv--body 348px, section 256px, with the min-height
   sitting inert on a 0×0 node in between. Reach through it; harmless where no such host exists. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row>.EC-bv--body>.EC-blkhost>*{min-height:100%}
/* …and the article-flow margin comes off, for the reason the published rule states (core/blocks/layout.ts):
   a row cell is an absolute box, the grid's row-gap is the spacing, and `.EC-agg`'s 28px bottom margin
   otherwise hangs below the block inside its own cell. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row>.EC-bv--body>*,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-row>.EC-bv--body>.EC-blkhost>*{margin-block:0}
/* The WRAPPER holding rows is a plain vertical list, so it must drop the tile track and the tile clip
   it applies to a free-2-D grid's children — a row is as tall as its own tallest block. `is-rows` is
   set by editor/grid/gridLayout.ts, which is the only place that knows what the slot currently holds. */
.EC-grid--edit>.EC-nest--list.is-rows{grid-auto-rows:auto}
.EC-grid--edit>.EC-nest--list.is-rows>.EC-nest--item{overflow:visible}

/* ── PUBLISHED ROW-CELL GALLERY — clip to the authored cell (T54, base/GridMigration.md §7f handoff §3.1) ─
   A gallery's own `css:` (core/blocks/builtin.ts) sizes it for an ARTICLE COLUMN (--ec-gal-h ≈ a 480px
   slot), so a gallery dropped into a row cell renders at its NATURAL height (~2500px) and grows the row
   instead of clipping to the authored cell. The CANVAS twin of this fix already ships in the gallery
   block's `editorCss` — kept out of `css:` because that field is FROZEN v1 contract surface
   (scripts/export-registry.ts copies it into contract/block-registry.json). This file is NOT contract
   surface and is <link>ed on every published page (server/render.ts), so the same rules land here without
   moving a single golden byte. Scoped to `.EC-gridrow > .EC-gridrow--i` (the SSR row cell) — a gallery in
   an article column is untouched, and the standalone gallery golden HTML is unchanged.
   The published cell is CONTENT-sized (the row block's css sets align-items:start + grid-auto-rows:
   minmax(rowH,auto)), so height:100% would degrade to auto and the gallery would grow the row again — the
   same case the editorCss documents for the read-only preview. So the height is DERIVED from the same row
   numbers the cell carries: --ec-cell-h tracks plus the row-gaps between them. */
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery{
  --ec-gal-cellh:max(var(--ec-grid-rowh,48px),calc(var(--ec-cell-h,4)*var(--ec-grid-rowh,48px) + (var(--ec-cell-h,4) - 1)*var(--ec-grid-rowgap,12px)));
  --ec-gal-gap:var(--ec-grid-gap,12px);
  --ec-gal-tile:clamp(72px,calc(.55*var(--ec-gal-cellh)),240px);
  height:var(--ec-gal-cellh);max-height:var(--ec-gal-cellh);min-height:0;overflow:hidden;margin-inline:0;
}
/* Список: the feed grid's shape (auto-fit tracks + a shared gap) over a FIXED-HEIGHT box — grid-auto-rows:1fr
   makes the tiles DIVIDE the cell, so more photos mean smaller photos, never a taller block. */
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery[data-layout="stacked"]{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--ec-gal-tile),100%),1fr));
  grid-auto-rows:1fr;gap:var(--ec-gal-gap);height:var(--ec-gal-cellh);max-height:var(--ec-gal-cellh);min-height:0;align-content:stretch;
}
/* Карусель keeps its native scroll-snap strip; the tiles just stretch to the cell, not to a 480px slot. */
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery[data-layout="carousel"]{
  height:var(--ec-gal-cellh);max-height:var(--ec-gal-cellh);min-height:0;align-items:stretch;
}
/* the tile → its figure → the image fill the cell track and clip rather than overflow. !important for the
   reason the block's own rules already document: renderImage writes the asset's real width/height/aspect
   INLINE (core/blocks/widgets.tsx#scalingStyle) and inline beats any selector; cover on a uniform tile is
   the feed card's photo treatment. */
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery>*{min-width:0;min-height:0;overflow:hidden}
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery figure{display:flex;flex-direction:column;min-height:0;height:100%;margin:0}
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery img{flex:1 1 auto;width:100%!important;height:100%!important;min-height:0;max-height:none!important;aspect-ratio:auto!important;object-fit:cover!important;object-position:center;border-radius:4px}
.EC-gridrow>.EC-gridrow--i .EC-blk--gallery figcaption{flex:0 0 auto;font-size:max(9px,calc(11px*var(--ec-rw-scale,1)));line-height:1.25;margin-top:calc(var(--ec-rw-kick,1)*3px);max-height:calc(var(--ec-rw-kick,1)*2.4em);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE DROP PREVIEW (R1). Both outcomes of a drop are legal, so both are SHOWN: the cell the block would
   land in, drawn with the very `grid-column`/`grid-row` the drop will write, or — when the row has no
   free w×h rectangle — the existing .EC-dropline on the edge a new row would appear at. The whole
   gesture is DOM-only; the model is written once, on pointerup. */
.EC-rw--drop{pointer-events:none;border-radius:4px;outline:2px solid var(--accent);outline-offset:-2px;background:color-mix(in srgb,var(--accent) 12%,transparent)}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--ghost{outline:2px dashed #8a8ab0;outline-offset:-2px;border-radius:4px;background:rgba(138,138,176,.08);opacity:.5}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--ghost *{pointer-events:none}

/* ── THE GAP AFFORDANCE (R3, base/GridMigration.md §7f «Gaps — advisory, not enforced») ───────────────
   *"here is a free space and editor should feel it with something if they do not want to have gaps"*.
   Under-full rows are LEGAL, so the hole is DRAWN, never closed: one inert hatched rectangle per free
   cell rectangle (core/blocks/rowModel.ts#freeRects), stamped with the very grid placement a block
   dropped there would get.

   ⚠ EDITOR CHROME. It lives here and NOT in the `row` block's published `css:` — the PAGE shows a plain
   gap, the canvas shows the hole. And it only DRAWS: no auto-fill (that would resize blocks the author
   never touched) and no auto-pull from the neighbouring row (the non-local effect the row model exists
   to delete). `pointer-events:none` also keeps it out of the drag hit-test, which reads
   `.EC-nest--item` children only. */
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap{
  pointer-events:none;border-radius:4px;border:1px dashed #c8c8d6;
  background:repeating-linear-gradient(45deg,rgba(138,138,176,.07) 0 6px,transparent 6px 12px);
}

/* ── THE SIZE HANDLE (R2) ────────────────────────────────────────────────────────────────────────────
   A block's share of its row, dragged from the bottom-right corner. The gesture is DOM-only: the tile
   re-stamps its own `grid-column`/`grid-row` from `planResize` on every frame and the model is written
   ONCE, on mouseup (editor/row/rowResize.ts). `.is-spill` = releasing here moves the block to a row of
   its own, because the row has no free rectangle that size — shown before the button comes up. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{position:relative}
/* TWO corners now, mirroring the experiment 1:1 (style.css:134-137): a 16×16 hit box inset 3px with a
   9×9 chevron of 2px strokes, `opacity:0 → 1` on tile hover over .12s. BR grows from the authored
   origin; TL grows with the BOTTOM-RIGHT corner ANCHORED (core/blocks/gridGeom.ts#resizeTL). */
.EC-rw--rz{position:absolute;width:16px;height:16px;z-index:3;cursor:nwse-resize;opacity:0;transition:opacity .12s}
.EC-rw--rz::before{content:'';position:absolute;width:9px;height:9px;border-style:solid;border-color:#8a8ab0;border-width:0}
.EC-rw--rz{right:3px;bottom:3px}
.EC-rw--rz::before{right:2px;bottom:2px;border-right-width:2px;border-bottom-width:2px;border-bottom-right-radius:2px}
.EC-rw--rz.EC-rw--tl{right:auto;bottom:auto;left:3px;top:3px}
.EC-rw--rz.EC-rw--tl::before{right:auto;bottom:auto;left:2px;top:2px;border-width:2px 0 0 2px;border-radius:2px 0 0 0}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item:hover>.EC-rw--rz{opacity:.85}
.EC-rw--rz:hover{opacity:1 !important}
.EC-rw--rz:hover::before{border-color:var(--accent)}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--rzing{outline:2px solid var(--accent);outline-offset:-2px;border-radius:4px;z-index:2}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--rzing.is-spill{outline-style:dashed}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--rzing>.EC-rw--rz{opacity:1}
/* MID-GESTURE the hatches are stale — they are drawn from the MODEL and the model has not been written
   yet (and must not be). Fade them for the duration instead of drawing a lie. */
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap{transition:opacity .12s}
.EC-gridrow--edit>.EC-nest--list.EC-rw--rzlive>.EC-rw--gap{opacity:0}

/* ── THE CORNER GIZMO CLUSTER (C1, base/EditorChrome.md §2 + §3d) ────────────────────────────────────
   A block inside a row had NO idle affordance: `.EC-typeico` and `.EC-gutter` are display:none for
   `.EC-nest--item` (see :582/:607) and `.EC-bv--header` is laid out ~26px ABOVE the box, where the
   item's `overflow:hidden` cuts it. The answer is the grid-constructor experiment's, not a portal:
   EVERY GIZMO LIVES INSIDE THE TILE (experiments/grid-constructor/src/style.css:108 `.tile{overflow:hidden}`
   with each control inset 3–4px at :129/:135/:146). Nothing pokes out, so nothing clips — and the item's
   clip may KEEP clipping CONTENT, which it must (a scroller in a 2-unit tile needs to be told where its
   cell ends).

   Three corners, no fourth: ≡ move top-right, 🗑 delete bottom-left, ⤡ resize bottom-right (above, R2).
   Top-centre stays EMPTY — the experiment's 📌 pin has nothing to toggle in a row and its ⤴ select-parent
   belongs to prefabs, not rows (§3b).

   ⚠ EDITOR CHROME. Here and never in a block's published `css:`, which ships verbatim into every page and
   into the frozen contract (R1). Geometry mirrors the experiment; colours are ours. */
.EC-rw--gz{
  position:absolute;z-index:4;display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.92);box-shadow:0 1px 3px rgba(0,0,0,.2);
  opacity:0;transition:opacity .12s;user-select:none;
}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item:hover>.EC-rw--gz{opacity:1}
/* ≡ MOVE — top-right, the experiment's `.thandle` (:129-131): 18px tall, three 2×11 bars, 2px apart. */
.EC-rw--mv{top:4px;right:4px;height:18px;padding:0 4px;gap:2px;border-radius:4px;cursor:grab}
.EC-rw--mv>i{width:2px;height:11px;border-radius:1px;background:#5b5b6b}
.EC-rw--mv:hover{box-shadow:0 1px 4px rgba(0,0,0,.3)}
.EC-rw--mv:hover>i{background:var(--accent)}
.EC-rw--mv:active{cursor:grabbing}
/* 🗑 DELETE — bottom-left, the experiment's `.celltrash` (:146-148): 20×20, r5, red on hover. */
.EC-rw--del{left:3px;bottom:3px;width:20px;height:20px;border-radius:5px;font-size:11px;line-height:1;cursor:pointer}
.EC-rw--del:hover{background:#cc2b2b;box-shadow:0 1px 4px rgba(204,43,43,.4)}
/* Chrome is not a drop target and must not answer the pointer while a drag is in flight. Carries the FULL
   path deliberately: the `:hover` rule above scores (0,5,0), so the short `body.EC-isDragging .EC-rw--gz`
   this started as (0,2,1) lost to it and left the cluster lit under the cursor for the whole gesture. */
body.EC-isDragging .EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-rw--gz{opacity:0;pointer-events:none}

/* WIREFRAME, scoped OUT of the grid (R6 — the wireframe block above is load-bearing for ARTICLES and is
   never edited; these rules are additive and later in the file, so they win on equal specificity).
   Its 48px max-height cap fights the grid track (a capped body inside a 3-row tile leaves 2 rows blank),
   and its table-row chrome turns tiles into spreadsheet rows. Both are wrong inside a grid. */
.is-wireframe .EC-grid--edit .EC-row>.EC-bv--body{max-height:none;overflow:visible}
.is-wireframe .EC-grid--edit .EC-card, .is-wireframe .EC-grid--edit .EC-feed--card,
.is-wireframe .EC-grid--edit .EC-feed--leaditem, .is-wireframe .EC-grid--edit .EC-feed--portraititem,
.is-wireframe .EC-grid--edit .EC-feed--pod{max-height:none}
.is-wireframe .EC-grid--edit .EC-row{border-bottom:none}
.is-wireframe .EC-grid--edit .EC-gap{height:0;background:transparent}

/* ⚠ THE SAME ESCAPE HATCH FOR A ROW — the block above is keyed on `.EC-grid--edit`, and S5'/S6 seeded
   the region body's rows at the TOP LEVEL with no `grid-container` wrapper (server/seedSteps.ts). So on
   `center` and `home-center` — both `region-center`, which AUTO-ARMS wireframe (editor/editor.tsx) —
   nothing matched: every block in a row was capped at 48px by the wireframe rule AND clipped by
   `.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{overflow:hidden}` below. That is the «not usable»
   report (owner, 2026-08-19; base/EditorChrome.md §1d).
   A tile's height IS its cell — `h * rowH` — so a 48px cap is simply the wrong constraint here. The cell
   keeps clipping (a scroller in a 2-unit tile must be told where its cell ends); only the CAP is lifted. */
.is-wireframe .EC-gridrow--edit .EC-row>.EC-bv--body{max-height:none;overflow:visible}
.is-wireframe .EC-gridrow--edit .EC-card, .is-wireframe .EC-gridrow--edit .EC-feed--card,
.is-wireframe .EC-gridrow--edit .EC-feed--leaditem, .is-wireframe .EC-gridrow--edit .EC-feed--portraititem,
.is-wireframe .EC-gridrow--edit .EC-feed--pod{max-height:none}
.is-wireframe .EC-gridrow--edit .EC-row{border-bottom:none}
.is-wireframe .EC-gridrow--edit .EC-gap{height:0;background:transparent}

/* ── CMS home declutter (base/CmsHome.md) ─────────────────────────────────────────────────────────────────
   One appended block: the public-site link above the wordmark, the «Контент»/«Шаблоны» caption, and the
   «Скрытые» toggle riding the existing bar row. Nothing above is restructured. */
.EC-home--brand { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.EC-home--site { font-size: 11px; font-weight: 600; color: var(--muted); text-decoration: none; }
.EC-home--site:hover, .EC-home--site:focus-visible { color: var(--accent); }
/* caption cell of .EC-home--bar; the tab strip (flex:1 1 auto) still pushes search + toggle to the right */
.EC-home--cap { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; }
.EC-home--bar > .EC-btn { flex: 0 0 auto; }

/* Settings ⚙ → «Шаблоны» / «Данные» as an INLINE SUBMENU STRIP rather than stacked rows (owner, 2026-08-19).
   They are navigation to other sections, not entries in a list, so they read as a compact horizontal group
   directly under the profile. `.EC-set--list` is kept on the container purely for its `:empty` collapse. */
.EC-set--inline { flex-direction: row; flex-wrap: wrap; gap: 6px; padding: 2px 0 1px; }
.EC-set--chip { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px;
  background: var(--sel); color: var(--ink); text-decoration: none; font-size: 12px; line-height: 1.5;
  white-space: nowrap; flex: 0 0 auto; }
.EC-set--chip:hover, .EC-set--chip:focus-visible { background: var(--hover); }

/* ── IN-ROW GROUP MOVE + SHARED-SIDE DRAG (base/GridMigration.md §7f R11(6)/(7)) ──────────────────────
   ONE appended block, editor chrome only — nothing here reaches a block's published `css:`.

   1. `.EC-rw--gmv` — the members of a marquee selection while the group is being dragged. The move is a
      rigid translation (core/blocks/rowModel.ts#planGroupMove) and every frame is DOM-only, so this is
      the only thing on screen that says «these are the blocks in flight».

   2. `.EC-rw--edge` — a draggable SHARED SIDE. Two blocks share one iff they abut and their spans on
      the other axis are equal, so the handle can be an ordinary child of the row's grid, placed with
      the very `grid-column`/`grid-row` the blocks use: no pixel geometry in JS and no hit-test
      tolerance — the browser decides which side the pointer meant. It straddles the track line, which
      is why it is nudged back by half its own width plus half the gutter.
      Geometry + feel are the experiment's 1:1 (experiments/grid-constructor/src/style.css:356-360): a
      12px hit box, a hairline down the middle, thickening to the accent colour on hover. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item.EC-rw--gmv{outline:2px solid var(--accent);outline-offset:-2px;border-radius:4px;z-index:2;opacity:.85}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--edge{z-index:5;transition:background .1s}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--edge.is-v{
  cursor:ew-resize;width:12px;justify-self:start;margin-left:calc(-6px - var(--ec-grid-gap,12px)/2);
  background:linear-gradient(90deg,transparent calc(50% - .5px),rgba(138,138,176,.5) calc(50% - .5px) calc(50% + .5px),transparent calc(50% + .5px));
}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--edge.is-h{
  cursor:ns-resize;height:12px;align-self:start;margin-top:calc(-6px - var(--ec-grid-rowgap,12px)/2);
  background:linear-gradient(180deg,transparent calc(50% - .5px),rgba(138,138,176,.5) calc(50% - .5px) calc(50% + .5px),transparent calc(50% + .5px));
}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--edge.is-v:hover{
  background:linear-gradient(90deg,transparent calc(50% - 1px),var(--accent) calc(50% - 1px) calc(50% + 1px),transparent calc(50% + 1px));
}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--edge.is-h:hover{
  background:linear-gradient(180deg,transparent calc(50% - 1px),var(--accent) calc(50% - 1px) calc(50% + 1px),transparent calc(50% + 1px));
}

/* ── THE CONTENT FILLS ITS CELL, AND SCALES WITH IT (owner, 2026-08-19) ───────────────────────────────
   *"content inside a block does not scale with the block's dimensions — a big tile is a big empty box
   with a small card in the corner"*.

   The earlier pass made the CELL grow and then the block's BOX grow (the `.EC-nest--item > .EC-row >
   .EC-bv--body > *` flex chain above). It stopped there: the block's own INNER markup — an aggregate
   section wrapping a feed list wrapping ONE card — is content-height all the way down, so a 12×6 cell
   drew a 200px card at the top and 200px of nothing under it.

   TWO MECHANISMS, and the split is deliberate:

     1. HEIGHT PASSES DOWN, structurally. Every wrapper between the block box and the card gets
        `height:100%` / `flex:1`, so the card is as tall as the cell. No numbers, no measurement.
     2. TYPE SCALES, from the AUTHORED CELL. `--ec-cell-w` / `--ec-cell-h` are stamped on the item by
        editor/row/rowLayout.ts#stampCellVars — the same `w`/`h` the `grid-row` beside them transcribes,
        NOT a measured pixel box — and `--ec-rw-scale` is a pure CSS function of the height. So the
        stylesheet is the only thing that decides what a size looks like, JS never reads back layout,
        and the resize preview scales live because the vars are re-stamped every frame.

   ⚠ SCALE IS APPLIED TO TEASER/CARD TYPE ONLY (`.EC-feed--ttl`, `--lede`, `--cat`, `.EC-card--ttll`),
   never to a paragraph/heading block: those carry the author's real text at the size they authored, and
   growing their cell must not silently restyle them. Editor chrome — nothing here is a block's
   published `css:`, so the PAGE is untouched.                                                       */
/* 1. the chain: block box → aggregate/feed section → feed list → the card itself.
   ⚠ SCOPED TO CONTAINER BLOCKS, never `> *`. A blanket `display:flex` on the block box would reach a
   paragraph/heading too, and a flex container blockifies its inline children — `hello <b>world</b>`
   would become two stacked flex items. The block box already gets its height from the `min-height:100%`
   rule in the R2 chain above; only the containers below need to PASS it on. */
/* ⚠ `.EC-matcard--host` and `--teaser` are IN THIS CHAIN and were missing from it. They are the two
   canvas-only wrappers a material-card's teaser hangs from (editor/blockRender.tsx#materialCardWidget), and
   both were `display:block; flex:0 1 auto` — so the section grew with the cell and then handed its child
   nothing. Measured 12x6: section 348, host 256. Every link, or the chain stops at the first one missing. */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-agg,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-matcard--host,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-matcard--teaser,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item [data-feed-list]{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item [data-feed-list]>*{flex:1 1 auto;min-height:0}
/* the teaser cards themselves — every layout a row child can hold */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraititem,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--leaditem,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--card,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--pod,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-card{height:100%;min-height:0;max-height:none;align-items:stretch}
/* the image is the part that must GROW, not repeat: a cover crop of the whole media column */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraitimg,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--leadimg,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--cardimg,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--podimg,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-card--hero{height:100%;min-height:0;object-fit:cover}
/* …and the media column itself is a SHARE of the cell rather than a fixed 280px, so a wide tile gives
   the picture more room instead of stretching the text column alone */
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraititem{grid-template-columns:var(--ec-imgshare,minmax(0,38%)) minmax(0,1fr)}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraititem.EC-feed--imgtop,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraititem.EC-feed--noimg{grid-template-columns:minmax(0,1fr)}
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portrbody,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-card--body,
.EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--podbody{min-height:0;display:flex;flex-direction:column;justify-content:center;gap:4px}
/* 2. TYPE + PART GATING moved OUT of this file (owner, 2026-08-20). They used to live here, canvas-only,
   which is exactly why the canvas and the published page could disagree about what a big tile looks like.
   Both `--ec-rw-scale` and the part gates are now ONE definition in the `row` block's published `css:`
   (core/blocks/layout.ts) written with the DUAL selectors, so the editor item and the SSR item read the
   same rule off the same `--ec-cell-w/h`. SSR emits those vars from core/blocks/rowModel.ts#rowItemStyle;
   the editor stamps them in editor/row/rowLayout.ts#stampCellVars. Do not re-add a copy here: block css
   is injected AFTER this file, so a copy would silently win on the canvas and nowhere else. */

/* ── A ROW READS AS A ROW IN WIREFRAME (owner, 2026-08-19) ────────────────────────────────────────────
   *"wireframe already renders material-card well — reuse that; what is missing is the ROW itself"*. So
   nothing below touches a block's wireframe render: the row gains a FRAME and a label, and its children
   keep the flattened card look they already have.

   CSS-only, and it must stay geometrically inert: the drop rule reads the pointer's cell off the list's
   own box (editor/row/rowLayout.ts#rowColsOf + rowStepOf), so padding or a border on `.EC-nest--list`
   would silently shift every unit of the pointer→cell mapping. `outline` and `box-shadow` do not take
   part in layout, which is why the frame is drawn with them; the label is absolutely positioned, and an
   absolutely-positioned child of a grid is out of flow, so it creates no track either. */
.is-wireframe .EC-gridrow--edit{position:relative}
.is-wireframe .EC-gridrow--edit>.EC-nest--list{
  outline:1px dashed var(--wf-line);outline-offset:4px;border-radius:3px;
  background:var(--wf-fill);
}
.is-wireframe .EC-gridrow--edit::before{
  content:'РЯД';position:absolute;top:-6px;left:2px;z-index:1;pointer-events:none;
  padding:0 4px;border-radius:2px;background:var(--canvas,#fff);
  font-size:9px;font-weight:600;letter-spacing:.08em;line-height:12px;color:var(--muted);
}
/* each cell tenant reads as a wireframe box rather than as a loose block: a flat panel on the row's
   fill, with the same dashed vocabulary as the free-space hatch it sits beside. */
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item{
  box-shadow:inset 0 0 0 1px var(--wf-line);border-radius:3px;background:var(--canvas,#fff);
}
/* the row's own hole hatch is already the right vocabulary — just quiet it so the FRAME reads first */
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-rw--gap{background:none;border-color:var(--wf-line)}

/* ── A WIREFRAME ROW IS 2 UNITS — OR THE HEIGHT OF ITS CONTENT (owner, 2026-08-20) ────────────────────
   *«a row is 2 rows tall in wireframe, whatever its blocks say»*, and then, against the first build of
   it, *«rows are still taking more vertical space than content inside»*. Wireframe is a structural
   RUNDOWN, not a scale model, so a row's height stops being the `h` its blocks authored and becomes the
   SMALLER of two things: two authored units, or what its schematic content actually measures — never
   more than the authored track either way. The height is stamped per row as `--ec-grid-rowh` by
   editor/row/rowLayout.ts#stampWireframeRowH — the var, because it is the one authority the grid track
   AND the pointer→cell math both read, so ≡ MOVE keeps landing where the pointer is. The tightened row
   gap is stamped from there too (`--ec-grid-rowgap`), which is why no gap rule appears below.

   ⚠ THIS IS NOT A RE-RUN OF THE CLIPPING `4dd99ea` CURED — and the content-fit half above is exactly
   what keeps it from being one. There a 48px `max-height` crushed CONTENT inside a still-tall cell and
   the item's `overflow:hidden` then cut it. Here the CELL is the compact thing, the content is told to
   ADAPT to it (the flattened teaser drops its `--wf-rowh` floor, its placeholder image is capped by the
   cell) — and where the content still does not fit, the CELL gives way rather than the content: a row whose
   blocks need more than the target gets what they need, and one that does not fit the authored track
   either is left exactly as authored. `4dd99ea`'s `max-height:none` escape hatch above is untouched.

   `max-height`, never `height`, on the images: the row block's PUBLISHED css sets their `height` at the
   same specificity and is injected AFTER this sheet (the specificity trap, handoff §3.1). A different
   property cannot lose that race — and `.EC-feed--crop`'s three row layouts keep the `--wf-th-*` look
   they have whenever the cell is tall enough for it. */
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-card,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--card,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--leaditem,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraititem,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--pod{min-height:0}
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--cardimg,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--leadimg,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--portraitimg,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--podimg,
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item .EC-feed--crop{max-height:100%;min-height:0}
/* ⚠ NO SIZE GRIPS IN WIREFRAME (owner: they *«are not working there and produce incorrect result»*).
   The cell on screen is the CAPPED one, so a gesture that wrote `h` against it would author a size the
   author cannot see. Both corners go; the ≡ move and 🗑 delete gizmos (editor/row/rowGizmo.ts) stay.
   editor/row/rowResize.ts#rzDown refuses the same gesture in JS — display:none is the visible half. */
.is-wireframe .EC-gridrow--edit>.EC-nest--list>.EC-nest--item>.EC-rw--rz{display:none}

/* ── THE CHROME LAYER (C5, base/EditorChrome.md §4) ───────────────────────────────────────────────────
   `.EC-bv--header` — the selected block's toolbar (type switcher · ⋯ · ⚙) — is a child of `.EC-row` and
   is laid out ~26px ABOVE the block's box (`bottom:calc(100% - 4px)`, :615). Inside a ROW that is under
   `.EC-gridrow--edit>.EC-nest--list>.EC-nest--item{overflow:hidden}` (:2516), so the whole toolbar is
   cut away and the block's ⚙/⋯/type-switch are unreachable. The clip is LOAD-BEARING for CONTENT (R3 —
   a scroller in a 2-unit tile must be told where its cell ends), so the toolbar moves out instead.

   `editor/chromeLayer.ts` borrows the LIVE header node into this layer while its block is selected and
   gives it straight back afterwards. Only for a block under a clipper: outside a row nothing is
   portalled and plain article authoring is byte-identical to before.

   ⚠ EDITOR CHROME, here and never in a block's published `css:` — that ships verbatim into every page
   and into the frozen contract (R1). No block template is touched, so `contract/render-golden.json`
   is unchanged by this slice. */
.EC-canvasWrap{position:relative}   /* the layer's offset parent IS the scroll container — see below */
/* THE WHOLE POINT: an absolutely-positioned child of the SCROLL CONTAINER scrolls with the content for
   free, so coordinates are computed once in the container's own scroll space and a scroll event does
   ZERO work (0 reads, 0 writes, 0 allocations per frame). A `position:fixed` layer on <body> would need
   a rAF reposition every scroll frame. It is also immune to a future canvas `transform`, which would
   silently break every fixed overlay at once (R5). `pointer-events:none` so the layer itself never eats
   a click meant for the canvas; the toolbar re-enables them for itself. */
.EC-chrome{position:absolute;inset:0;pointer-events:none;z-index:6}
/* The borrowed toolbar. It is no longer a child of `.EC-row`, so :617's `.EC-row.is-selected >` rule no
   longer reaches it — display and the vertical anchor are restated here, and `left/top` are written by
   the controller in the host's scroll space. `bottom:auto` retires :615's above-the-box offset: the
   layer positions from the top edge, and chromeRect() already did the "above, or flipped inside when
   there is no room" decision that :541 hand-rolls for the canvas's first row. */
.EC-chrome>.EC-bv--header.EC-chrome--on{display:flex;position:absolute;bottom:auto;pointer-events:auto}
/* Drag in flight → hide the layer outright. This is also the FLIP fix: during a glide
   `getBoundingClientRect()` reports the TRANSFORMED box, so a live overlay would chase the animation
   and land wrong. Nothing to reposition while nothing is legible. */
body.EC-isDragging .EC-chrome{display:none}

/* ── THE CARD'S OWN FRAMING (`imgCrop`, core/blocks/aggregation.ts) ───────────────────────────────────
   The window a material-card teaser's photo is panned/zoomed inside. The markup is emitted by
   core/blocks/feedRender.ts#img ONLY when a crop is stored (`<span class="EC-feed--crop"><img …></span>`),
   so an untouched card never gets this box and stays byte-for-byte as before. Geometry rides four custom
   properties set by the SAME code path in SSR and in the drag (editor/imageGizmo.ts#attachCardCrop →
   panStyle): blow the photo up to 100/w × 100/h of the window and slide it by -x/w, -y/h, so the crop
   rect exactly fills the window. `!important` + last-in-file so the wireframe rules that hard-set an
   image's box (`.is-wireframe .EC-feed--portraitimg{width:120px!important}`) cannot strand the photo. */
.EC-feed--crop { display: block; position: relative; overflow: hidden; width: 100%; height: 200px; border-radius: 10px; }
.EC-feed--leaditem > .EC-feed--crop { height: var(--ec-imgh, 320px); margin-bottom: 10px; }
.EC-feed--crop > .EC-feed--portraitimg,
.EC-feed--crop > .EC-feed--leadimg,
.EC-feed--crop > .EC-feed--podimg {
  position: absolute !important; max-width: none !important;
  width: var(--ec-cw, 100%) !important; height: var(--ec-ch, 100%) !important;
  left: var(--ec-cl, 0); top: var(--ec-ct, 0); border-radius: 0;
}
/* inside a grid row the media column is height-driven (the rules above give the img `height:100%`): it is
   the WINDOW that must fill the cell now, since the img's height is a percentage OF the window. */
.EC-gridrow--edit > .EC-nest--list > .EC-nest--item .EC-feed--crop { height: 100%; min-height: 0; }
/* ⚠ THE WINDOW IS SIZED BY THE SAME RULES AS THE PHOTO, `!important` INCLUDED. Wrapping an uncropped photo
   on the first drag (editor/imageGizmo.ts#attachCardCrop) must not change what is on screen. The <img>'s
   wireframe box is `!important` (:2226, so the row rules above cannot strand it); without the same weight
   here the (0,4,0) row rule beat this (0,2,0) one and the WINDOW switched to a percentage of the cell the
   instant it appeared — a 3x2 tile's photo jumped 48px -> 89px, a 12x6 tile's fell to 34px, and a top image
   (whose grid row has nothing else to give it a height) collapsed to 0 and vanished under the cursor.
   Measured in a real /edit canvas, 2026-08-20. Symmetry with the img rule is the invariant: any rule that
   hard-sets `.EC-feed--portraitimg`'s box must set `.EC-feed--crop`'s to match, at the same weight. */
.is-wireframe .EC-feed--crop { width: var(--wf-th-w) !important; height: var(--wf-th-h) !important; }
/* EDITOR ONLY — `cursor:grab` where a grab does something. `.EC-matcard--host` is a node the SSR path never
   builds and `is-croppable` is set by attachCardCrop (called only when ctx.editable), so no page decision
   is being overridden here and no doubled class is needed. */
.EC-matcard--host.is-croppable img.EC-feed--portraitimg { cursor: grab; }
.EC-matcard--host.is-cropping { user-select: none; }
.EC-matcard--host.is-cropping img.EC-feed--portraitimg { cursor: grabbing; }

/* ── A HOLE IS AN INVITATION (owner, 2026-08-20: «when hovering empty space lets give ability to click on
   it and show material choose dialog») ──────────────────────────────────────────────────────────────
   The hatch above still takes NO pointer events — that is deliberate and load-bearing, because the row
   background is where a MARQUEE starts (editor/slotSelect.ts bails unless the pointer's target IS the
   list) and the row already has one dead zone over it (the shared-side handles). So the hover state is
   DELEGATED: editor/row/rowGapAdd.ts hit-tests the drawn rectangles off the list's own pointermove and
   toggles `.is-hot` here, plus `.EC-rw--gaphot` on the LIST so the cursor can speak without the hatch
   ever becoming an event target.

   ⚠ EDITOR CHROME, like the hatch it decorates: `.EC-rw--gap` is built only by `paintGaps`, which runs
   only for an editable row slot, so nothing in this block can ever reach a published page. No doubled
   class is needed — the row block's published `css:` knows nothing about these two class names. */
.EC-gridrow--edit>.EC-nest--list.EC-rw--gaphot{cursor:pointer}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap{
  display:flex;align-items:center;justify-content:center;overflow:hidden;
  transition:opacity .12s,background-color .12s,border-color .12s;
}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap.is-hot{
  border-color:var(--accent);border-style:solid;
  background:color-mix(in srgb,var(--accent) 10%,transparent);
}
/* The label rides the hole, so it is exactly as big as the block that would land there. Faint at rest —
   the hatch alone never said the space was actionable — and legible under the pointer. */
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap::after{
  content:'＋ материал';
  font:600 11px/1 system-ui,sans-serif;letter-spacing:.02em;color:#8a8ab0;
  padding:4px 8px;border-radius:99px;background:rgba(255,255,255,.72);
  opacity:0;transition:opacity .12s;white-space:nowrap;max-width:100%;overflow:hidden;
}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap:not(.is-hot)::after{opacity:0}
.EC-gridrow--edit>.EC-nest--list>.EC-rw--gap.is-hot::after{
  opacity:1;color:var(--accent);background:rgba(255,255,255,.92);box-shadow:0 1px 3px rgba(0,0,0,.14);
}
/* Mid-gesture the hatches fade (`EC-rw--rzlive`, above) — their label must go with them, and a stale
   `.is-hot` must never survive into a drag. rowGapAdd clears the class; this is the belt to that braces. */
.EC-gridrow--edit>.EC-nest--list.EC-rw--rzlive>.EC-rw--gap.is-hot{background:none;border-style:dashed;border-color:#c8c8d6}
