/* ============================================================
   TOKENS
   ============================================================ */
/* The colour values come from the OppAlerts theme on oppalerts.com
   (wp-content/themes/oa/style.css) and from the AI Search Visibility
   reports (AI-Search-Visibility/static/style.css). The two files share
   one palette, so the application now reads as the same brand.

   Three blocks set the colours:
     1. :root                          the light values.
     2. @media (prefers-color-scheme: dark) :root:not([data-theme="light"])
                                       the dark values, used when the
                                       operating system asks for dark and
                                       the reader has not chosen light.
     3. :root[data-theme="dark"] / [data-theme="light"]
                                       the reader's own choice. script.js
                                       writes the attribute and stores it
                                       in localStorage. An attribute
                                       selector on :root outranks a bare
                                       :root inside a media query, so the
                                       choice always wins.
   Only colours change between the blocks. Every size, radius, font and
   spacing value is set once, in :root, and is the same in both.
   ============================================================ */
:root {
	color-scheme: light;

	/* Five surface steps, one meaning each. Read them as depth: the frame
	   is the bottom layer, the canvas sits on the frame, a card sits on
	   the canvas. In light the ramp runs toward white, in dark it runs
	   away from black, so the same rule holds in both:
	     --bg-bar    the frame: .topbar, .page-head, .filters. The
	                 darkest value in dark, and the anchor the rest of
	                 the ramp steps up from.
	     --bg-sunk   the canvas .main scrolls, and inset wells inside a
	                 card (tracks, code blocks, opened rows)
	     --bg-head   .card-head, the section heading strip. It is DARKER
	                 than the table header below it: a section title
	                 outranks the column labels inside it, so it must not
	                 be the lighter of the two.
	     --bg-rail   table headers and row hover
	     --bg        the content surface: card bodies, table rows,
	                 inputs, menus. The lightest value in dark.
	     --bg-hover  a pointer is over it. Always the step with the most
	                 contrast against the surface it covers, so it is the
	                 darkest value in light and the lightest in dark.
	   In dark the ramp runs
	     bar #050709 < canvas #080b0d < head #0a0f12 < th #0d1216 < rows #10171a
	   so every nested level is one step up from the one holding it. */
	/* THE NEUTRALS ARE GITHUB LIGHT'S, 2026-08-31 (Ben): the grey scale of
	   the github-light theme block further down, which carries more
	   contrast than the old warm greys, with the OppAlerts teal kept for
	   every accent, status and chart colour. The old values are in git. */
	--bg-bar:        #ffffff;
	--bg-sunk:       #f0f3f6;
	--bg-head:       #eaeef2;
	--bg-rail:       #f6f8fa;
	--bg:            #ffffff;
	--bg-hover:      #e3e8ed;

	--line:          #d0d7de;
	--line-strong:   #afb8c1;
	--line-faint:    #eaeef2;

	/* the hairline of the grid drawn behind the page head */
	--grid-line:     #eaeef2;

	--ink:           #1f2328;
	--ink-2:         #59636e;
	--ink-3:         #8c959f;

	--accent:        #2f6f68;
	--accent-ink:    #1d4842;
	--accent-tint:   #e6efec;
	--accent-tint-2: #cfe0db;
	/* text and icons drawn ON a solid --accent fill */
	--on-accent:     #ffffff;

	/* The brand gradient from oppalerts.com. Used once, on the mark in
	   the top bar. */
	--grad-teal:     linear-gradient(135deg, #0e3e3a 0%, #2f9e8f 100%);

	/* link-type wash, used behind the site name on link_gap_newsidx.php. Pastel on
	   purpose: enough colour to scan a column by, not enough to fight the
	   text. Priority order is followed, UGC, sponsored, nofollow. */

	--pos:           #155a32;
	--neg:           #a8483c;
	--warn:          #8a5417;

	/* Each status colour has a filled pair: -soft is the background,
	   -soft-line is the border on it. They replace the hard-coded pastel
	   hexes that were spread through this file. */
	--pos-soft:       #dcf3e6;
	--pos-soft-line:  #c3e6d2;
	--neg-soft:       #f5e9e7;
	--neg-soft-line:  #ecd3cf;
	--warn-soft:      #fbf0dc;
	--warn-soft-line: #f0dfba;

	/* Chart and data colours, from the reports palette. Semantic roles:
	   teal (--accent) = score, blue = counts, green = organic search,
	   amber = warm status, purple = AI search. */
	--blue:          #2e639c;
	--green:         #5c8433;
	--amber:         #ac6f26;
	--purple:        #7c4fa0;
	--purple-soft:   #eee3fa;
	--blue-soft:     #e4ecf6;
	--green-soft:    #e9f1e0;
	--amber-soft:    #f7ecdc;
	--slate:         #6b7f8a;

	/* Six-step teal ramp for heat cells and coverage columns. Step 0 is
	   the empty cell; step 5 is a full accent fill. */
	--heat-0:        #f2f3f2;
	--heat-0-line:   #e6e8e6;
	--heat-0-ink:    var(--ink-2);
	--heat-1:        #e6efec;
	--heat-1-line:   #d6e4e0;
	--heat-1-ink:    var(--accent-ink);
	--heat-2:        #bfdbd5;
	--heat-2-line:   #aecfc8;
	--heat-2-ink:    #0f2e2a;
	--heat-3:        #6fa89f;
	--heat-3-line:   #5f9b91;
	--heat-3-ink:    #ffffff;
	--heat-4:        #2f6f68;
	--heat-4-line:   #2f6f68;
	--heat-4-ink:    #ffffff;

	/* the one permitted shadow: floating overlays only */
	/* the one permitted shadow, on floating overlays and cards */
	--shadow-card:   0 1px 2px rgba(20, 24, 26, .04), 0 2px 6px -2px rgba(20, 24, 26, .05);
	--pop:           0 6px 16px -8px rgba(20, 24, 26, .22);
	--pop-kinds:     0 0 0 1px rgba(20, 24, 26, .04), 0 2px 6px rgba(20, 24, 26, .10), 0 10px 22px -8px rgba(20, 24, 26, .28);

	--r:  2px;
	--r-lg: 4px;

	--topbar-h: 44px;

	--f-ui:   'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
	--f-mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
}

/* The dark values. Written once, in a custom property list that both the
   media query and the [data-theme="dark"] rule apply. CSS has no way to
   share a declaration block between two selectors without repeating it,
   so the block below is repeated exactly once. Change both together. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		color-scheme: dark;

		--bg-bar:        #050709;
		--bg-sunk:       #080b0d;
		--bg-head:       #0a0f12;
		--bg-rail:       #0d1216;
		--bg:            #10171a;
		--bg-hover:      #171f23;

		--line:          #1e262b;
		--line-strong:   #333e44;
		--line-faint:    #141b1f;
		
		--grid-line:     rgba(255, 255, 255, .045);

		--ink:           #e8eceb;
		--ink-2:         #9aa5a3;
		--ink-3:         #6b7674;

		--accent:        #5cc2b3;
		--accent-ink:    #7fdcc8;
		--accent-tint:   rgba(92, 194, 179, .16);
		--accent-tint-2: rgba(92, 194, 179, .30);
		--on-accent:     #05201c;

		--grad-teal:     linear-gradient(135deg, #17554e 0%, #3fbba8 100%);

		--pos:           #6fd695;
		--neg:           #d07567;
		--warn:          #e0a35c;

		--pos-soft:       rgba(30, 132, 73, .22);
		--pos-soft-line:  rgba(111, 214, 149, .32);
		--neg-soft:       rgba(208, 117, 103, .18);
		--neg-soft-line:  rgba(208, 117, 103, .34);
		--warn-soft:      rgba(202, 111, 30, .18);
		--warn-soft-line: rgba(224, 163, 92, .32);

		--blue:          #5d9ee8;
		--green:         #97c24e;
		--amber:         #de9c42;
		--purple:        #b98be8;
		--purple-soft:   rgba(108, 52, 131, .28);
		--blue-soft:     rgba(93, 158, 232, .18);
		--green-soft:    rgba(151, 194, 78, .18);
		--amber-soft:    rgba(222, 156, 66, .18);
		--slate:         #8ea0aa;

		--heat-0:        #1b2224;
		--heat-0-line:   #2b3436;
		--heat-0-ink:    var(--ink-2);
		--heat-1:        rgba(92, 194, 179, .14);
		--heat-1-line:   rgba(92, 194, 179, .26);
		--heat-1-ink:    #7fdcc8;
		--heat-2:        rgba(92, 194, 179, .30);
		--heat-2-line:   rgba(92, 194, 179, .42);
		--heat-2-ink:    #e7f7f4;
		--heat-3:        #2c675f;
		--heat-3-line:   #35786f;
		--heat-3-ink:    #ffffff;
		--heat-4:        #3f9c8d;
		--heat-4-line:   #3f9c8d;
		--heat-4-ink:    #ffffff;

		--pop:           0 6px 18px -8px rgba(0, 0, 0, .60);
		--pop-kinds:     0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .40), 0 10px 22px -8px rgba(0, 0, 0, .75);
	}
}

/* The reader chose dark. Same values as the media query above. */
:root[data-theme="dark"] {
	color-scheme: dark;

	--bg-bar:        #050709;
	--bg-sunk:       #080b0d;
	--bg-head:       #0a0f12;
	--bg-rail:       #0d1216;
	--bg:            #10171a;
	--bg-hover:      #171f23;

	--line:          #1e262b;
	--line-strong:   #333e44;
	--line-faint:    #141b1f;
	
	--grid-line:     rgba(255, 255, 255, .045);

	--ink:           #e8eceb;
	--ink-2:         #9aa5a3;
	--ink-3:         #6b7674;

	--accent:        #5cc2b3;
	--accent-ink:    #7fdcc8;
	--accent-tint:   rgba(92, 194, 179, .16);
	--accent-tint-2: rgba(92, 194, 179, .30);
	--on-accent:     #05201c;

	--grad-teal:     linear-gradient(135deg, #17554e 0%, #3fbba8 100%);

	--pos:           #6fd695;
	--neg:           #d07567;
	--warn:          #e0a35c;

	--pos-soft:       rgba(30, 132, 73, .22);
	--pos-soft-line:  rgba(111, 214, 149, .32);
	--neg-soft:       rgba(208, 117, 103, .18);
	--neg-soft-line:  rgba(208, 117, 103, .34);
	--warn-soft:      rgba(202, 111, 30, .18);
	--warn-soft-line: rgba(224, 163, 92, .32);

	--blue:          #5d9ee8;
	--green:         #97c24e;
	--amber:         #de9c42;
	--purple:        #b98be8;
	--purple-soft:   rgba(108, 52, 131, .28);
	--blue-soft:     rgba(93, 158, 232, .18);
	--green-soft:    rgba(151, 194, 78, .18);
	--amber-soft:    rgba(222, 156, 66, .18);
	--slate:         #8ea0aa;

	--heat-0:        #1b2224;
	--heat-0-line:   #2b3436;
	--heat-0-ink:    var(--ink-2);
	--heat-1:        rgba(92, 194, 179, .14);
	--heat-1-line:   rgba(92, 194, 179, .26);
	--heat-1-ink:    #7fdcc8;
	--heat-2:        rgba(92, 194, 179, .30);
	--heat-2-line:   rgba(92, 194, 179, .42);
	--heat-2-ink:    #e7f7f4;
	--heat-3:        #2c675f;
	--heat-3-line:   #35786f;
	--heat-3-ink:    #ffffff;
	--heat-4:        #3f9c8d;
	--heat-4-line:   #3f9c8d;
	--heat-4-ink:    #ffffff;

	--pop:           0 6px 18px -8px rgba(0, 0, 0, .60);
	--pop-kinds:     0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .40), 0 10px 22px -8px rgba(0, 0, 0, .75);
}

/* The reader chose light. The :root block at the top already holds these
   values, so this rule only has to stop the media query from applying,
   which the :not([data-theme="light"]) in it already does. It is written
   out so that `color-scheme` is correct when the operating system asks
   for dark and the reader asked for light. */
:root[data-theme="light"] { color-scheme: light; }

/* ============================================================
   THE OTHER SIX THEMES
   ============================================================
   One :root[data-theme="<name>"] block each, and each block sets
   the SAME 58 variables in the SAME order as the two blocks above.
   Nothing else in this file names a colour, so a theme is these 58
   lines and no other change.

   To add a theme: copy one block, rename it, change the 58 values,
   then add one <button class="theme-option" data-theme="<name>"> to
   templates/header.php and one name to THEMES in www/script.js.

   The values come from the AI Search Visibility reports
   (oppalerts.com/AI-Search-Visibility/static/style.css), mapped onto
   this application's token names. The report file has four surfaces
   and this one has six, so --bg-bar, --bg-rail and --bg-hover are
   steps of that ramp and are not in the report file.
   ============================================================ */

/* Nord Dark. Polar Night surfaces, Frost accents. nordtheme.com */
:root[data-theme="nord-dark"] {
	color-scheme: dark;

	--bg-bar:         #242933;
	--bg-sunk:        #2e3440;
	--bg-head:        #333a46;
	--bg-rail:        #373e4b;
	--bg:             #3b4252;
	--bg-hover:       #454d5e;
	--line:           #4c566a;
	--line-strong:    #5b6779;
	--line-faint:     #3f4757;
	--grid-line:      rgba(236, 239, 244, .06);
	--ink:            #eceff4;
	--ink-2:          #d8dee9;
	--ink-3:          #8792a6;
	--accent:         #88c0d0;
	--accent-ink:     #8fbcbb;
	--accent-tint:    rgba(136, 192, 208, .16);
	--accent-tint-2:  rgba(136, 192, 208, .30);
	--on-accent:      #20262f;
	--grad-teal:      linear-gradient(135deg, #4c6a92 0%, #88c0d0 100%);
	--pos:            #a3be8c;
	--neg:            #bf616a;
	--warn:           #ebcb8b;
	--pos-soft:       rgba(163, 190, 140, .20);
	--pos-soft-line:  rgba(163, 190, 140, .32);
	--neg-soft:       rgba(191, 97, 106, .20);
	--neg-soft-line:  rgba(191, 97, 106, .32);
	--warn-soft:      rgba(235, 203, 139, .18);
	--warn-soft-line: rgba(235, 203, 139, .30);
	--blue:           #5e81ac;
	--green:          #a3be8c;
	--amber:          #ebcb8b;
	--purple:         #b48ead;
	--purple-soft:    rgba(180, 142, 173, .22);
	--blue-soft:      rgba(94, 129, 172, .22);
	--green-soft:     rgba(163, 190, 140, .18);
	--amber-soft:     rgba(235, 203, 139, .16);
	--slate:          #8792a6;
	--heat-0:         #3b4252;
	--heat-0-line:    #4c566a;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         rgba(136, 192, 208, .14);
	--heat-1-line:    rgba(136, 192, 208, .26);
	--heat-1-ink:     #88c0d0;
	--heat-2:         rgba(136, 192, 208, .30);
	--heat-2-line:    rgba(136, 192, 208, .42);
	--heat-2-ink:     #eaf6f8;
	--heat-3:         #5e81ac;
	--heat-3-line:    #6b8db6;
	--heat-3-ink:     #ffffff;
	--heat-4:         #4c6a92;
	--heat-4-line:    #4c6a92;
	--heat-4-ink:     #ffffff;
	--pop:            0 6px 18px -8px rgba(0, 0, 0, .60);
	--pop-kinds:      0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .40), 0 10px 22px -8px rgba(0, 0, 0, .75);
}

/* Nord Light. Snow Storm surfaces, Frost accents. */
:root[data-theme="nord-light"] {
	color-scheme: light;

	--bg-bar:         #eceff4;
	--bg-sunk:        #e5e9f0;
	--bg-head:        #e1e6ee;
	--bg-rail:        #e8ecf3;
	--bg:             #eceff4;
	--bg-hover:       #d8dee9;
	--line:           #d8dee9;
	--line-strong:    #b8c2d4;
	--line-faint:     #e3e7ef;
	--grid-line:      rgba(46, 52, 64, .06);
	--ink:            #2e3440;
	--ink-2:          #4c566a;
	--ink-3:          #7b88a1;
	--accent:         #5e81ac;
	--accent-ink:     #3c5a80;
	--accent-tint:    rgba(94, 129, 172, .14);
	--accent-tint-2:  rgba(94, 129, 172, .26);
	--on-accent:      #ffffff;
	--grad-teal:      linear-gradient(135deg, #3c5a80 0%, #5e81ac 100%);
	--pos:            #4f7040;
	--neg:            #a03b45;
	--warn:           #8a6a2f;
	--pos-soft:       #dde7d5;
	--pos-soft-line:  #c9d8be;
	--neg-soft:       #f2dfe1;
	--neg-soft-line:  #e6c8cb;
	--warn-soft:      #f2e9d8;
	--warn-soft-line: #e3d6bb;
	--blue:           #4a7e7d;
	--green:          #5b7b46;
	--amber:          #92702f;
	--purple:         #8a627f;
	--purple-soft:    #ebe0e8;
	--blue-soft:      #dee9e9;
	--green-soft:     #e4ebdd;
	--amber-soft:     #f0e7d5;
	--slate:          #7b88a1;
	--heat-0:         #e1e6ee;
	--heat-0-line:    #d3dae6;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         rgba(94, 129, 172, .12);
	--heat-1-line:    rgba(94, 129, 172, .24);
	--heat-1-ink:     #3c5a80;
	--heat-2:         rgba(94, 129, 172, .28);
	--heat-2-line:    rgba(94, 129, 172, .40);
	--heat-2-ink:     #2e4763;
	--heat-3:         #81a1c1;
	--heat-3-line:    #6f92b7;
	--heat-3-ink:     #ffffff;
	--heat-4:         #5e81ac;
	--heat-4-line:    #5e81ac;
	--heat-4-ink:     #ffffff;
	--pop:            0 6px 16px -8px rgba(20, 24, 26, .22);
	--pop-kinds:      0 0 0 1px rgba(20, 24, 26, .04), 0 2px 6px rgba(20, 24, 26, .10), 0 10px 22px -8px rgba(20, 24, 26, .28);
}

/* GitHub Light. Primer light surfaces, blue accent. */
:root[data-theme="github-light"] {
	color-scheme: light;

	--bg-bar:         #ffffff;
	--bg-sunk:        #f0f3f6;
	--bg-head:        #eaeef2;
	--bg-rail:        #f6f8fa;
	--bg:             #ffffff;
	--bg-hover:       #e3e8ed;
	--line:           #d0d7de;
	--line-strong:    #afb8c1;
	--line-faint:     #eaeef2;
	--grid-line:      #eaeef2;
	--ink:            #1f2328;
	--ink-2:          #59636e;
	--ink-3:          #8c959f;
	--accent:         #0969da;
	--accent-ink:     #0550ae;
	--accent-tint:    #ddf4ff;
	--accent-tint-2:  #b6e3ff;
	--on-accent:      #ffffff;
	--grad-teal:      linear-gradient(135deg, #0550ae 0%, #0969da 100%);
	--pos:            #1a7f37;
	--neg:            #cf222e;
	--warn:           #9a6700;
	--pos-soft:       #dafbe1;
	--pos-soft-line:  #aceebb;
	--neg-soft:       #ffebe9;
	--neg-soft-line:  #ffcecb;
	--warn-soft:      #fff8c5;
	--warn-soft-line: #f0e4a8;
	--blue:           #0891a6;
	--green:          #1a7f37;
	--amber:          #9a6700;
	--purple:         #8250df;
	--purple-soft:    #fbefff;
	--blue-soft:      #ddf4f8;
	--green-soft:     #dafbe1;
	--amber-soft:     #fff8c5;
	--slate:          #8c959f;
	--heat-0:         #f6f8fa;
	--heat-0-line:    #e6ebf0;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         #ddf4ff;
	--heat-1-line:    #c4e9ff;
	--heat-1-ink:     #0550ae;
	--heat-2:         #b6e3ff;
	--heat-2-line:    #99d6ff;
	--heat-2-ink:     #033d8b;
	--heat-3:         #54aeff;
	--heat-3-line:    #3e9ff5;
	--heat-3-ink:     #ffffff;
	--heat-4:         #0969da;
	--heat-4-line:    #0969da;
	--heat-4-ink:     #ffffff;
	--pop:            0 6px 16px -8px rgba(20, 24, 26, .22);
	--pop-kinds:      0 0 0 1px rgba(20, 24, 26, .04), 0 2px 6px rgba(20, 24, 26, .10), 0 10px 22px -8px rgba(20, 24, 26, .28);
}

/* GitHub Dark. Primer dark surfaces and hues. */
:root[data-theme="github-dark"] {
	color-scheme: dark;

	--bg-bar:         #010409;
	--bg-sunk:        #0d1117;
	--bg-head:        #11161d;
	--bg-rail:        #141a21;
	--bg:             #161b22;
	--bg-hover:       #1f262e;
	--line:           #30363d;
	--line-strong:    #484f58;
	--line-faint:     #21262d;
	--grid-line:      rgba(230, 237, 243, .05);
	--ink:            #e6edf3;
	--ink-2:          #8b949e;
	--ink-3:          #6e7681;
	--accent:         #58a6ff;
	--accent-ink:     #79c0ff;
	--accent-tint:    rgba(88, 166, 255, .15);
	--accent-tint-2:  rgba(88, 166, 255, .28);
	--on-accent:      #0d1117;
	--grad-teal:      linear-gradient(135deg, #1f6feb 0%, #58a6ff 100%);
	--pos:            #3fb950;
	--neg:            #f85149;
	--warn:           #d29922;
	--pos-soft:       rgba(63, 185, 80, .15);
	--pos-soft-line:  rgba(63, 185, 80, .30);
	--neg-soft:       rgba(248, 81, 73, .15);
	--neg-soft-line:  rgba(248, 81, 73, .32);
	--warn-soft:      rgba(210, 153, 34, .15);
	--warn-soft-line: rgba(210, 153, 34, .30);
	--blue:           #39c5cf;
	--green:          #3fb950;
	--amber:          #d29922;
	--purple:         #bc8cff;
	--purple-soft:    rgba(188, 140, 255, .16);
	--blue-soft:      rgba(57, 197, 207, .16);
	--green-soft:     rgba(63, 185, 80, .15);
	--amber-soft:     rgba(210, 153, 34, .15);
	--slate:          #8b949e;
	--heat-0:         #161b22;
	--heat-0-line:    #30363d;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         rgba(88, 166, 255, .15);
	--heat-1-line:    rgba(88, 166, 255, .28);
	--heat-1-ink:     #58a6ff;
	--heat-2:         rgba(88, 166, 255, .35);
	--heat-2-line:    rgba(88, 166, 255, .48);
	--heat-2-ink:     #cae8ff;
	--heat-3:         #388bfd;
	--heat-3-line:    #4e97fd;
	--heat-3-ink:     #ffffff;
	--heat-4:         #1f6feb;
	--heat-4-line:    #1f6feb;
	--heat-4-ink:     #ffffff;
	--pop:            0 6px 18px -8px rgba(0, 0, 0, .60);
	--pop-kinds:      0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .40), 0 10px 22px -8px rgba(0, 0, 0, .75);
}

/* Slate Light. Neutral graphite, the least colour of the eight. */
:root[data-theme="slate-light"] {
	color-scheme: light;

	--bg-bar:         #ffffff;
	--bg-sunk:        #f2f3f4;
	--bg-head:        #eeeff1;
	--bg-rail:        #f7f7f8;
	--bg:             #ffffff;
	--bg-hover:       #e5e7ea;
	--line:           #dee0e3;
	--line-strong:    #c4c7cc;
	--line-faint:     #ebeced;
	--grid-line:      #edeef0;
	--ink:            #1c1e21;
	--ink-2:          #5b5f66;
	--ink-3:          #8a8f96;
	--accent:         #475569;
	--accent-ink:     #334155;
	--accent-tint:    #e7e9ec;
	--accent-tint-2:  #cfd4da;
	--on-accent:      #ffffff;
	--grad-teal:      linear-gradient(135deg, #334155 0%, #5b6b80 100%);
	--pos:            #2f5d3f;
	--neg:            #9b3b3b;
	--warn:           #8a6a2f;
	--pos-soft:       #dce7df;
	--pos-soft-line:  #c4d6c9;
	--neg-soft:       #f1dede;
	--neg-soft-line:  #e2c4c4;
	--warn-soft:      #f2e9d8;
	--warn-soft-line: #e3d6bb;
	--blue:           #527399;
	--green:          #5f824b;
	--amber:          #96703b;
	--purple:         #7d5e96;
	--purple-soft:    #e6e1ec;
	--blue-soft:      #e2e8ef;
	--green-soft:     #e6ebe0;
	--amber-soft:     #f1e9dc;
	--slate:          #8a8f96;
	--heat-0:         #eeeff1;
	--heat-0-line:    #e0e2e5;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         #e4e7ea;
	--heat-1-line:    #d3d7dd;
	--heat-1-ink:     #475569;
	--heat-2:         #c6ccd4;
	--heat-2-line:    #b4bcc7;
	--heat-2-ink:     #1c2733;
	--heat-3:         #8b96a3;
	--heat-3-line:    #7c8896;
	--heat-3-ink:     #ffffff;
	--heat-4:         #5b6b80;
	--heat-4-line:    #5b6b80;
	--heat-4-ink:     #ffffff;
	--pop:            0 6px 16px -8px rgba(20, 24, 26, .22);
	--pop-kinds:      0 0 0 1px rgba(20, 24, 26, .04), 0 2px 6px rgba(20, 24, 26, .10), 0 10px 22px -8px rgba(20, 24, 26, .28);
}

/* Slate Dark. Deep graphite, the least colour of the eight. */
:root[data-theme="slate-dark"] {
	color-scheme: dark;

	--bg-bar:         #0b1120;
	--bg-sunk:        #0f172a;
	--bg-head:        #182234;
	--bg-rail:        #1a2437;
	--bg:             #1e293b;
	--bg-hover:       #29354a;
	--line:           #334155;
	--line-strong:    #475569;
	--line-faint:     #26324a;
	--grid-line:      rgba(226, 232, 240, .05);
	--ink:            #e2e8f0;
	--ink-2:          #94a3b8;
	--ink-3:          #64748b;
	--accent:         #94a3b8;
	--accent-ink:     #cbd5e1;
	--accent-tint:    rgba(148, 163, 184, .16);
	--accent-tint-2:  rgba(148, 163, 184, .30);
	--on-accent:      #0f172a;
	--grad-teal:      linear-gradient(135deg, #475569 0%, #94a3b8 100%);
	--pos:            #8fb871;
	--neg:            #d98a82;
	--warn:           #cd9a50;
	--pos-soft:       rgba(143, 184, 113, .18);
	--pos-soft-line:  rgba(143, 184, 113, .32);
	--neg-soft:       rgba(217, 138, 130, .18);
	--neg-soft-line:  rgba(217, 138, 130, .32);
	--warn-soft:      rgba(205, 154, 80, .16);
	--warn-soft-line: rgba(205, 154, 80, .30);
	--blue:           #6fa3dc;
	--green:          #8fb871;
	--amber:          #cd9a50;
	--purple:         #b18cd1;
	--purple-soft:    rgba(177, 140, 209, .20);
	--blue-soft:      rgba(111, 163, 220, .18);
	--green-soft:     rgba(143, 184, 113, .18);
	--amber-soft:     rgba(205, 154, 80, .16);
	--slate:          #94a3b8;
	--heat-0:         #1e293b;
	--heat-0-line:    #334155;
	--heat-0-ink:     var(--ink-2);
	--heat-1:         rgba(148, 163, 184, .14);
	--heat-1-line:    rgba(148, 163, 184, .26);
	--heat-1-ink:     #94a3b8;
	--heat-2:         rgba(148, 163, 184, .30);
	--heat-2-line:    rgba(148, 163, 184, .42);
	--heat-2-ink:     #e2e8f0;
	--heat-3:         #64748b;
	--heat-3-line:    #74839a;
	--heat-3-ink:     #ffffff;
	--heat-4:         #94a3b8;
	--heat-4-line:    #94a3b8;
	--heat-4-ink:     #0f172a;
	--pop:            0 6px 18px -8px rgba(0, 0, 0, .60);
	--pop-kinds:      0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .40), 0 10px 22px -8px rgba(0, 0, 0, .75);
}

/* pseudo-elements need this too; a bare `*` does not match them */
*, *::before, *::after { box-sizing: border-box; }

html, body { margin: 0; padding: 0; height: 100%; }

body {
	background: var(--bg-sunk);
	color: var(--ink);
	font-family: var(--f-ui);
	font-size: 13px;
	line-height: 1.45;
	-webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 1px;
	border-radius: var(--r);
}

.vh {
	position: absolute;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

.mono { font-family: var(--f-mono); font-variant-numeric: tabular-nums; }

/* ============================================================
   SHELL
   ============================================================ */
/* One column. The left rail was removed on 2026-08-23: the navigation is
   in the top bar, and the pages are mostly wide tables that need the
   width. See templates/header.php. */
.app {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: var(--topbar-h) 1fr;
	grid-template-areas:
		"topbar"
		"main";
	height: 100vh;
	background: var(--bg-sunk);
}

/* The brand is the first item inside .topbar, not a grid area of its own. */
.brand {
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 0 10px 0 2px;
	flex: none;
	overflow: hidden;
}
.brand-mark {
	flex: none;
	width: 22px; height: 22px;
	border-radius: var(--r);
	background: var(--grad-teal);
	display: grid;
	place-items: center;
	color: #ffffff;
	font-family: var(--f-mono);
	font-size: 12px;
	font-weight: 600;
}
/* The notification badge on the corner of the mark. It is part of the logo
   and not a live count: oppalerts.com draws the same red circle holding a 9
   in wp-content/themes/oa/header.php. Ben, 2026-08-30.

   #E5484D is the theme's own --color-notify and is a LITERAL here on
   purpose, the same way .brand-mark above sets color: #ffffff. Both are
   brand colours that must read the same in all eight themes, so neither is
   a theme token. Nothing else in this file may hold a hex.

   The 2px ring is --bg-bar, the topbar's own surface, so the badge reads as
   a cutout in every theme rather than as a ring of one fixed colour. */
.brand-mark { position: relative; }
.brand-notify {
	position: absolute;
	top: -5px; right: -5px;
	width: 13px; height: 13px;
	border-radius: 50%;
	background: #E5484D;
	color: #ffffff;
	font-family: var(--f-ui);
	font-size: 8px;
	font-weight: 700;
	line-height: 1;
	display: grid;
	place-items: center;
	border: 2px solid var(--bg-bar);
}
.brand-name { font-weight: 600; font-size: 13.5px; letter-spacing: -0.01em; white-space: nowrap; }

/* ---- topbar: left group / centered search / right group ---- */
.topbar {
	grid-area: topbar;
	position: relative;
	z-index: 30;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 12px;
	border-bottom: 1px solid var(--line);
	background: var(--bg-bar);
}
.tb-right  { display: flex; align-items: center; gap: 6px; flex: 1 1 0; min-width: 0; justify-content: flex-end; }

.icon-btn {
	flex: none;
	width: 28px; height: 28px;
	display: grid;
	place-items: center;
	border: 1px solid transparent;
	border-radius: var(--r);
	background: transparent;
	color: var(--ink-2);
	cursor: pointer;
	position: relative;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--ink); }
.icon-btn .pip {
	position: absolute;
	top: 4px; right: 4px;
	width: 5px; height: 5px;
	border-radius: 50%;
	background: var(--neg);
	border: 1px solid var(--bg);
}

.search { position: relative; width: 100%; }
.search svg { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: var(--ink-3); pointer-events: none; }
.search input {
	width: 100%;
	height: 28px;
	padding: 0 56px 0 28px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	font: inherit;
	color: var(--ink);
}
.search input::placeholder { color: var(--ink-3); }
.search input:focus { background: var(--bg); border-color: var(--line-strong); outline: none; box-shadow: 0 0 0 3px var(--accent-tint); }
.search kbd {
	position: absolute;
	right: 6px; top: 50%;
	transform: translateY(-50%);
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-3);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 1px 4px;
	background: var(--bg);
}

/* ============================================================
   MENUS (the only JS-backed component)
   ============================================================ */
.menu { position: relative; display: inline-flex; min-width: 0; }
.menu-pop {
	display: none;
	position: absolute;
	top: calc(100% + 5px);
	left: 0;
	z-index: 60;
	min-width: 200px;
	padding: 5px;
	background: var(--bg);
	border: 1px solid var(--line-strong);
	border-radius: var(--r-lg);
	box-shadow: var(--pop);
}
.menu-pop.to-right { left: auto; right: 0; }
.menu-pop.wide { min-width: 240px; }
.menu.is-open .menu-pop { display: block; }

.menu-label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 6px 8px 4px;
}
.menu-item {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	padding: 6px 8px;
	border: 0;
	border-radius: var(--r);
	background: transparent;
	color: var(--ink-2);
	font-size: 12.5px;
	text-align: left;
	cursor: pointer;
}
.menu-item:hover { background: var(--bg-hover); color: var(--ink); }
.menu-item[aria-selected="true"] { background: var(--accent-tint); color: var(--accent-ink); font-weight: 500; }
.menu-item .sub { display: block; font-size: 11px; color: var(--ink-3); font-weight: 400; }
.menu-item .tick { margin-left: auto; color: var(--accent); visibility: hidden; flex: none; }
.menu-item[aria-selected="true"] .tick { visibility: visible; }
.menu-item .kbd { margin-left: auto; font-family: var(--f-mono); font-size: 10px; color: var(--ink-3); }
.menu-item.danger { color: var(--neg); }
.menu-item.danger:hover { background: var(--neg-soft); }
/* A row that stays visible but cannot be used, e.g. "New campaign" once the
   account is at its limit. It keeps its place and its explanation. */
.menu-item.is-off, .menu-item.is-off:hover { background: transparent; color: var(--ink-3); cursor: default; }
.menu-div { height: 1px; background: var(--line); margin: 4px 0; }
.menu-head {
	padding: 8px;
	border-bottom: 1px solid var(--line);
	margin: -5px -5px 5px;
	background: var(--bg-rail);
	border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.menu-head b { display: block; font-size: 12.5px; }
.menu-head span { font-family: var(--f-mono); font-size: 10.5px; color: var(--ink-3); }

/* ============================================================
   LEFT NAV - REMOVED 2026-08-23
   ============================================================ */
/* The rail (.nav, .nav-item, .nav-label, .nav-count, .nav-collapse)
   and the #navtoggle collapse control were deleted here. The page
   links had already moved to the top bar, so the rail held nothing
   but the account menu, and it took 232px from pages that are mostly
   wide tables. The account menu is now in .tb-right. See
   templates/header.php. */

/* ============================================================
   MAIN + VIEW SWITCHING
   ============================================================ */
.main {
	grid-area: main;
	overflow-y: auto;
	background: var(--bg-sunk);
	position: relative;
	display: grid;
	/* The implicit column is minmax(0, 1fr), not auto. Without the 0 floor
	   the column is held open by the widest min-content inside it, which is
	   the section tabs row: ten tabs that cannot wrap, ~1200px. That set the
	   minimum width of every page in the application. With the floor at 0 the
	   tabs use their own overflow-x and scroll, and a page is only as wide as
	   its content needs. */
	grid-template-columns: minmax(0, 1fr);
}

.page-head {
	/* The fine grid, built the way .bg-grid-fine is built on
	   oppalerts.com: two hairline layers, and the fade is an opaque
	   gradient painted OVER them that ends in the surface colour. No
	   mask-image, so it cannot bleed past this element's own edges.
	   The grid is strongest at the top of the head and is gone by the
	   bottom, where the content begins. */
	background-color: var(--bg-bar);
	background-image:
		linear-gradient(to bottom, transparent 0%, transparent 35%, var(--bg-bar) 100%),
		linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
		linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
	background-size: 100% 100%, 40px 40px, 40px 40px;
	background-repeat: no-repeat, repeat, repeat;
	border-bottom: 1px solid var(--line);
	padding: 14px 20px 0;
	position: sticky;
	top: 0;
	z-index: 5;
}

.crumbs {
	display: flex;
	align-items: center;
	gap: 6px;
	font-family: var(--f-mono);
	font-size: 10.5px;
	color: var(--ink-3);
	margin-bottom: 6px;
}
.crumbs .sep { color: var(--line-strong); }
.crumbs .cur { color: var(--ink-2); }

/* THE HEAD IS THE SAME HEIGHT ON EVERY PAGE, whatever the title and the
   description say. Ben, 2026-08-28. It is one line of title plus two lines
   of description, and both are reserved whether or not the words fill them:
   a head that grows by a line when the description wraps moves the whole
   table under it, and moving furniture between two pages of one report
   reads as a fault.
   Title up to 50% of the row, description 33%. Anything longer is cut with
   an ellipsis rather than wrapped into another line. */
.page-title-row { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.page-title-row > div { flex: 1 1 100%; min-width: 0; }
h1 { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.15; }
/* Only the head's own h1 is held to one line. A page body may print an h1
   of its own, and .view > h1:first-child hides that one anyway. */
.page-title-row h1 {
	max-width: 50%;
	height: 1.15em;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	/* The gap between the breadcrumb and the title. Ben, 2026-08-28: the
	   .page-kicker line that used to sit between them was removed, and the
	   title needs a little air under the breadcrumb. */
	margin-top: 6px;
}
.page-sub {
	margin: 5px 0 0;
	color: var(--ink-2);
	font-size: 13px;
	line-height: 1.4;
	max-width: 33%;
	/* Two lines, always. The element is printed even when the page sets no
	   description, so the height does not move. */
	height: 2.8em;
	overflow: hidden;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
}
/* The campaign and persona above the title, from header.php. It is printed
   on every page, holding a non-breaking space where there is no campaign to
   name, so the head is the same height everywhere. Its height is fixed for
   the same reason. */
.page-kicker {
	font-size: 11.5px;
	color: var(--ink-3);
	margin: 0 0 3px;
	letter-spacing: 0.01em;
	height: 1.3em;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	max-width: 50%;
}
.page-kicker .sep { color: var(--line-strong); }

.btn {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 29px;
	padding: 0 11px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg);
	cursor: pointer;
	font-weight: 500;
	white-space: nowrap;
}
.btn:hover { background: var(--bg-hover); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); }
.btn-quiet { border-color: transparent; color: var(--ink-2); }
.btn-quiet:hover { border-color: var(--line); color: var(--ink); }
.btn-sm { height: 25px; padding: 0 8px; font-size: 12px; }
.btn-danger { color: var(--neg); border-color: var(--neg-soft-line); }
.btn-danger:hover { background: var(--neg-soft); }

/* .btn-neg and .btn-warn, the two attention buttons, are NOT here. They are
   in the legacy form-control section further down, beside `.view button`,
   because that selector is `.view button` = one class and one element and
   would outrank a bare `.btn-neg` wherever this file put it. The same trap
   is already recorded there for `.btn-primary`. Ben, 2026-08-30. */

/* view tabs */
/* One row of real tabs. It does not scroll: it holds two or three
   tabs, never the whole navigation column. See templates/header.php. */
.section-tabs { display: flex; align-items: flex-end; gap: 3px; margin-top: 12px; }
.section-tabs label {
	padding: 7px 10px 8px;
	color: var(--ink-2);
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;
	font-weight: 500;
	cursor: pointer;
	white-space: nowrap;
	display: flex;
	align-items: center;
	gap: 6px;
}
.section-tabs label:hover { color: var(--ink); }
.section-tabs .tabcount {
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-3);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 0 4px;
	line-height: 15px;
}

.head-block { display: none; }
.view { display: none; }

#v-ov:checked ~ .page-head #h-ov,
#v-pr:checked ~ .page-head #h-pr,
#v-ci:checked ~ .page-head #h-ci,
#v-co:checked ~ .page-head #h-co,
#v-se:checked ~ .page-head #h-se,
#v-al:checked ~ .page-head #h-al { display: block; }

#v-ov:checked ~ .page-head .section-tabs label[for="v-ov"],
#v-pr:checked ~ .page-head .section-tabs label[for="v-pr"],
#v-ci:checked ~ .page-head .section-tabs label[for="v-ci"],
#v-co:checked ~ .page-head .section-tabs label[for="v-co"],
#v-se:checked ~ .page-head .section-tabs label[for="v-se"],
#v-al:checked ~ .page-head .section-tabs label[for="v-al"] {
	color: var(--ink);
	border-bottom-color: var(--accent);
}
#v-ov:focus-visible ~ .page-head .section-tabs label[for="v-ov"],
#v-pr:focus-visible ~ .page-head .section-tabs label[for="v-pr"],
#v-ci:focus-visible ~ .page-head .section-tabs label[for="v-ci"],
#v-co:focus-visible ~ .page-head .section-tabs label[for="v-co"],
#v-se:focus-visible ~ .page-head .section-tabs label[for="v-se"],
#v-al:focus-visible ~ .page-head .section-tabs label[for="v-al"] {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

#v-ov:checked ~ .filters #f-ov,
#v-pr:checked ~ .filters #f-pr,
#v-ci:checked ~ .filters #f-ci,
#v-co:checked ~ .filters #f-co,
#v-se:checked ~ .filters #f-se,
#v-al:checked ~ .filters #f-al { display: flex; }

#v-ov:checked ~ .views #w-ov,
#v-pr:checked ~ .views #w-pr,
#v-ci:checked ~ .views #w-ci,
#v-co:checked ~ .views #w-co,
#v-se:checked ~ .views #w-se,
#v-al:checked ~ .views #w-al { display: grid; }

/* filter bar */
.filters { position: relative; z-index: 6; background: var(--bg-bar); border-bottom: 1px solid var(--line); }
.pill {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 25px;
	padding: 0 8px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink-2);
	cursor: pointer;
	font-size: 12px;
	white-space: nowrap;
}
.pill:hover { background: var(--bg-hover); }
.pill.is-on { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); font-weight: 500; }
.pill .x { color: var(--ink-3); font-size: 13px; line-height: 1; }
.pill .caret { color: var(--ink-3); }

/* content grid */
.view {
	padding: 14px 20px 40px;
	gap: 12px;
	grid-template-columns: repeat(12, minmax(0, 1fr));
	align-content: start;
}

/* ============================================================
   BANNER
   ============================================================ */
.banner {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 9px 12px;
	background: var(--accent-tint);
	border: 1px solid var(--accent-tint-2);
	border-radius: var(--r);
	color: var(--accent-ink);
	flex-wrap: wrap;
}
.banner strong { font-weight: 600; }
.banner .meter { width: 148px; height: 4px; border-radius: var(--r); background: var(--bg); overflow: hidden; border: 1px solid var(--accent-tint-2); }
.banner .meter i { display: block; height: 100%; width: 68%; background: var(--accent); }

/* ============================================================
   CARDS
   ============================================================ */
.card {
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-card);
	display: flex;
	flex-direction: column;
	min-width: 0;
	position: relative;
}
.card-head {
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 8px 14px;
	background: var(--bg-head);
	border-bottom: 1px solid var(--line);
	border-radius: var(--r-lg) var(--r-lg) 0 0;
	min-height: 34px;
	flex-wrap: wrap;
}
/* The menu group at the RIGHT end of a card head. Ben, 2026-08-30: the
   menus sit against the right edge, the reader's own text on the left of
   the group and the source on the right, so the source is always in the
   same place whatever the prompt says.

   margin-left: auto pushes the whole group right; .card-head is flex and
   wraps, so on a narrow card the group drops to its own line rather than
   squeezing the title. */
.card-menus {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
	max-width: 100%;
}
/* A menu whose options are the READER'S OWN TEXT: a tracked prompt or a
   tracked keyword. FIXED WIDTH, because one long prompt would otherwise
   make the menu as wide as the card and push the source menu off the row.
   The closed state ellipsizes; the option list itself cannot be clipped by
   CSS, which is why the label is also cut in PHP. */
.card-menus .menu-text {
	/* BOTH the basis and the width, and neither grow nor shrink. .control
	   above is width: 100%, so a flex child that only sets a basis takes
	   its parent's whole width instead of 320px. */
	flex: 0 0 320px;
	width: 320px;
	max-width: 100%;
	text-overflow: ellipsis;
	overflow: hidden;
	white-space: nowrap;
}
/* The source menu sizes to its own longest label. width: auto is required
   for the same reason: .control sets width: 100%. */
.card-menus .menu-src { flex: 0 0 auto; width: auto; }

.card-title {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	font-weight: 600;
	font-size: 13.5px;
	line-height: 1.3;
	letter-spacing: -0.01em;
	color: var(--ink);
}
.card-title::before {
	content: "";
	width: 6px; height: 6px;
	border-radius: 1px;
	background: var(--grad-teal);
	flex: none;
}
/* For a row of several small cards side by side, where the dot on every
   one of them reads as noise rather than as the section marker it is on
   a lone card. The color drops a step too, to --ink-2: full --ink is the
   same weight as the card ABOVE these sit inside, and these are a level
   under it, not its equal. */
.card-title.is-plain::before { content: none; }
.card-title.is-plain { color: var(--ink-2); }
.card-note { font-family: var(--f-mono); font-size: 10.5px; color: var(--ink-3); letter-spacing: 0.02em; }
.card-body { padding: 12px; }
.card-body.flush { padding: 0; }
.card-foot {
	padding: 8px 12px;
	border-top: 1px solid var(--line);
	display: flex;
	align-items: center;
	gap: 8px;
	color: var(--ink-3);
	font-size: 12px;
	background: var(--bg-rail);
	border-radius: 0 0 var(--r-lg) var(--r-lg);
	flex-wrap: wrap;
}

/* ---- KPI ---- */
.kpi { padding: 12px; display: flex; flex-direction: column; gap: 8px; }
.kpi-label { font-family: var(--f-mono); font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3); }
.kpi-row { display: flex; align-items: flex-end; gap: 8px; }
.kpi-value { font-family: var(--f-mono); font-size: 25px; font-weight: 500; letter-spacing: -0.03em; line-height: 1; }
.kpi-unit { font-size: 14px; color: var(--ink-3); }

.delta { display: inline-flex; align-items: center; gap: 3px; font-family: var(--f-mono); font-size: 11px; font-weight: 500; }
.delta.up { color: var(--pos); }
.delta.down { color: var(--neg); }
.delta.flat { color: var(--ink-3); }

/* ---- mini stat strip ---- */
.statstrip { display: flex; flex-wrap: wrap; }
.statstrip > div {
	flex: 1 1 130px;
	padding: 10px 12px;
	border-right: 1px solid var(--line-faint);
	border-bottom: 1px solid var(--line-faint);
}
.statstrip b { display: block; font-family: var(--f-mono); font-size: 17px; font-weight: 500; letter-spacing: -0.02em; }
.statstrip span { display: block; font-family: var(--f-mono); font-size: 9.5px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3); margin-top: 2px; }

/* ---- legend ---- */
.legend { display: flex; gap: 12px; flex-wrap: wrap; }
.legend span { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-2); }
.legend i { width: 9px; height: 2px; border-radius: 1px; display: inline-block; }

/* ---- horizontal bars ---- */
.bars { display: flex; flex-direction: column; gap: 9px; }
.dot { width: 6px; height: 6px; border-radius: 50%; flex: none; display: inline-block; }
/* an inline span cannot take width or height, so fills must be blocks */

/* ---- stacked bar ---- */
.stack { display: flex; height: 10px; border: 1px solid var(--line-faint); border-radius: var(--r); overflow: hidden; background: var(--bg-sunk); }
.stack i { display: block; height: 100%; }

/* ---- column chart ---- */
.cols { display: flex; align-items: stretch; gap: 8px; height: 132px; }
.cols .c { flex: 1 1 0; display: flex; flex-direction: column; justify-content: flex-end; gap: 4px; min-width: 0; }
.cols .cv { font-family: var(--f-mono); font-size: 10.5px; text-align: center; color: var(--ink-2); }
.cols .cb { display: block; background: var(--accent); border-radius: var(--r) var(--r) 0 0; }
.cols .cb.tint { background: var(--accent-tint-2); }
.cols .cl {
	font-family: var(--f-mono);
	font-size: 9px;
	letter-spacing: 0.04em;
	text-align: center;
	color: var(--ink-3);
	border-top: 1px solid var(--line);
	padding-top: 5px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ---- coverage matrix ---- */

.matrix {
	display: grid;
	grid-template-columns: minmax(160px, 1.5fr) repeat(6, minmax(28px, 1fr));
	gap: 2px;
	font-size: 12px;
}
.cell {
	height: 22px;
	border-radius: var(--r);
	border: 1px solid var(--line-faint);
	background: var(--bg-sunk);
	display: grid;
	place-items: center;
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-3);
}
.c1 { background: var(--heat-1); border-color: var(--heat-1-line); color: var(--heat-1-ink); }

.swatch { width: 11px; height: 11px; border-radius: var(--r); border: 1px solid var(--line-faint); display: inline-block; }

/* ============================================================
   TABLE
   ============================================================ */
.tbl-scroll { overflow: auto; max-height: 430px; }
.tbl-scroll.tall { max-height: 620px; }
table { width: 100%; border-collapse: collapse; }

thead th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--bg-rail);
	border-bottom: 1px solid var(--line);
	text-align: left;
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 8px 12px;
	white-space: nowrap;
}
thead th.num, tbody td.num { text-align: right; }

tbody td { padding: 9px 12px; border-bottom: 1px solid var(--line-faint); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--bg-rail); }

.t-primary { font-weight: 500; }
.t-sub { color: var(--ink-3); font-size: 11.5px; margin-top: 1px; }

.tag {
	display: inline-block;
	padding: 1px 6px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	font-family: var(--f-mono);
	font-size: 10px;
	letter-spacing: 0.03em;
	color: var(--ink-2);
	white-space: nowrap;
}
.tag.pos { background: var(--pos-soft); border-color: var(--pos-soft-line); color: var(--pos); }
.tag.neg { background: var(--neg-soft); border-color: var(--neg-soft-line); color: var(--neg); }
.tag.warn { background: var(--warn-soft); border-color: var(--warn-soft-line); color: var(--warn); }
.tag.acc { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }

/* entity classification tags */
.ent {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 1px 6px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-2);
	white-space: nowrap;
}
.ent::before { content: ""; width: 5px; height: 5px; border-radius: 1px; background: var(--line-strong); flex: none; }
.ent.owned { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }
.ent.owned::before { background: var(--accent); }
.ent.competitor { background: var(--neg-soft); border-color: var(--neg-soft-line); color: var(--neg); }
.ent.competitor::before { background: var(--neg); }
.ent.partner { background: var(--pos-soft); border-color: var(--pos-soft-line); color: var(--pos); }
.ent.partner::before { background: var(--pos); }
.ent.publisher { background: var(--warn-soft); border-color: var(--warn-soft-line); color: var(--warn); }
.ent.publisher::before { background: var(--warn); }
.ent.unmapped { border-style: dashed; color: var(--ink-3); }

input[type="checkbox"].box {
	appearance: none;
	-webkit-appearance: none;
	width: 14px; height: 14px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	display: inline-grid;
	place-content: center;
	cursor: pointer;
	vertical-align: -2px;
	margin: 0;
	flex: none;
}
input[type="checkbox"].box::after {
	content: "";
	width: 7px; height: 4px;
	border-left: 1.5px solid var(--bg);
	border-bottom: 1.5px solid var(--bg);
	transform: rotate(-45deg) translate(0.5px, -0.5px);
	opacity: 0;
}
input[type="checkbox"].box:checked { background: var(--accent); border-color: var(--accent); }
input[type="checkbox"].box:checked::after { opacity: 1; }

.pager { display: flex; align-items: center; gap: 6px; width: 100%; flex-wrap: wrap; }

/* ============================================================
   LISTS / FEEDS
   ============================================================ */
.feed { display: flex; flex-direction: column; }

.rows > div { display: flex; align-items: center; gap: 9px; padding: 8px 12px; border-bottom: 1px solid var(--line-faint); }
.rows > div:last-child { border-bottom: 0; }
.rows > div:hover { background: var(--bg-rail); }
.rows .r-main { min-width: 0; flex: 1 1 auto; }
.rows .r-main b { font-weight: 500; display: block; }
.rows .r-main span { color: var(--ink-3); font-size: 11.5px; }

/* ============================================================
   FORMS + SETTINGS
   ============================================================ */

.field { margin-bottom: 14px; }
.field:last-child { margin-bottom: 0; }
/* ONE LABEL, THE SAME LABEL AS THE SEARCH FORM. Ben, 2026-08-31: the
   configure forms (campaign_edit, campaign_new, the tracked-item add
   boxes, the Help form) named their fields in 12.5px body text while the
   index search form named its fields in small mono capitals, so the two
   halves of the application did not look related. This is the search
   form's `.idx-field > label` rule, written once for both. */
.field > label, .legend-label {
	display: block;
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	margin-bottom: 5px;
}
.hint { color: var(--ink-3); font-size: 11.5px; margin-top: 5px; }

.control {
	width: 100%;
	height: 30px;
	padding: 0 9px;
	/* --line-strong, the search form's box edge, not --line: a box a
	   reader types in has to be visible on the card it sits on.
	   Ben, 2026-08-31. */
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	font: inherit;
	color: var(--ink);
}
textarea.control { height: auto; min-height: 78px; padding: 8px 9px; resize: vertical; font-family: var(--f-mono); font-size: 11.5px; line-height: 1.6; }
.control:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
/* A locked field on campaign_edit.php. disabled, not readonly: it takes
   no focus and no click, and its text is printed as text, not as an input.
   The background is --bg-rail, NOT --bg-sunk: in the dark themes --bg-sunk
   is near black and the locked description boxes read as holes beside the
   inputs. Ben, 2026-08-31: a form box matches the inputs; the muted text
   and the flat border carry the locked state. */
/* The second selector: the legacy ".view input[type=text]" block further
   down is 0-2-1 and outranked the bare ".control:disabled" (0-2-0), so a
   locked INPUT kept --bg while the textareas moved. ".view .control:disabled"
   is 0-3-0 and wins inside .view; the bare one still covers everything
   else. Found by the 2026-08-31 audit of entry 2. */
.control:disabled,
.view .control:disabled {
	background: var(--bg-rail);
	border-color: var(--line);
	color: var(--ink-2);
	cursor: default;
	pointer-events: none;
	opacity: 1;
	-webkit-text-fill-color: var(--ink-2);
}
/* One row, label on the left and description on the right: the campaign
   and each persona on campaign_edit.php. Ben, 2026-08-27. */
.row-ld { display: grid; grid-template-columns: minmax(200px, 300px) 1fr; gap: 14px; align-items: start; }
.row-ld .field { margin: 0; }
.row-ld > div > .field + .field { margin-top: 12px; }
@media (max-width: 720px) { .row-ld { grid-template-columns: 1fr; } }
.control::placeholder { color: var(--ink-3); }
select.control {
	appearance: none;
	-webkit-appearance: none;
	background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%), linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
	background-position: calc(100% - 15px) 13px, calc(100% - 10px) 13px;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	padding-right: 28px;
}
/* No fixed height. A 12px font with the body's 1.45 line-height needs a
   17.4px line box, and a 24px box with borders trimmed the descenders of
   g, j, p, q and y. Padding plus min-height lets the box grow with the
   text instead, so it cannot clip whatever container it sits in. */
select.control.sm {
	height: auto;
	min-height: 26px;
	line-height: 1.45;
	padding-top: 3px;
	padding-bottom: 3px;
	font-size: 12px;
	background-position: calc(100% - 14px) 11px, calc(100% - 9px) 11px;
}

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* A FORM ON THE LEFT AND A TABLE ON THE RIGHT. Ben, 2026-08-30: an even
   split squeezed the Opportunities list, whose six columns need the room,
   while the form beside it had space to spare. 40 / 60. It collapses to one
   column at the same two widths .grid-2 does, because it IS a .grid-2. */
.grid-2.grid-2-40-60 { grid-template-columns: 40% 60%; }

.checks { display: grid; gap: 7px; }
.check { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
.check span { line-height: 1.35; }
.check .sub { display: block; color: var(--ink-3); font-size: 11.5px; }

/* toggle: 30x17 outer, 15px inner box, 13px knob, 1px gap on every side */
input[type="checkbox"].switch {
	appearance: none;
	-webkit-appearance: none;
	flex: none;
	width: 30px; height: 17px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg-sunk);
	position: relative;
	cursor: pointer;
	margin: 0;
	transition: background .12s ease, border-color .12s ease;
}
input[type="checkbox"].switch::after {
	content: "";
	position: absolute;
	top: 1px;
	left: 1px;
	width: 13px;
	height: 13px;
	border-radius: 1px;
	background: var(--bg);
	border: 1px solid var(--line-strong);
	transition: transform .12s ease, border-color .12s ease;
}
input[type="checkbox"].switch:checked { background: var(--accent); border-color: var(--accent); }
input[type="checkbox"].switch:checked::after { transform: translateX(13px); border-color: var(--accent); }

input[type="range"] { width: 100%; appearance: none; -webkit-appearance: none; height: 18px; background: transparent; margin: 0; }
input[type="range"]::-webkit-slider-runnable-track { height: 4px; background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--r); }
input[type="range"]::-webkit-slider-thumb {
	appearance: none; -webkit-appearance: none;
	width: 12px; height: 16px; margin-top: -7px;
	border-radius: var(--r); background: var(--bg);
	border: 1px solid var(--line-strong);
	box-shadow: inset 0 0 0 1px var(--bg);
	cursor: pointer;
}
input[type="range"]::-moz-range-track { height: 4px; background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--r); }
input[type="range"]::-moz-range-thumb { width: 11px; height: 15px; border-radius: var(--r); background: var(--bg); border: 1px solid var(--line-strong); cursor: pointer; }

.tokens {
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
	align-items: center;
	padding: 5px 6px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg);
	min-height: 30px;
}
.token {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 2px 4px 2px 7px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	font-size: 11.5px;
}
.token.acc { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }
.token button { border: 0; background: transparent; color: var(--ink-3); cursor: pointer; padding: 0 2px; line-height: 1; font-size: 13px; }
.token button:hover { color: var(--neg); }
.tokens input { border: 0; outline: none; flex: 1 1 90px; min-width: 90px; height: 20px; font: inherit; background: transparent; }

.form-foot {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px 12px;
	border-top: 1px solid var(--line);
	background: var(--bg-rail);
	border-radius: 0 0 var(--r-lg) var(--r-lg);
	flex-wrap: wrap;
}
.saved { font-family: var(--f-mono); font-size: 10.5px; color: var(--ink-3); }

.usage { display: flex; flex-direction: column; gap: 5px; }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 1180px) {
}
@media (max-width: 900px) {
}
/* TWO TABLES SIDE BY SIDE NEED THE WIDTH. Ben, 2026-08-28: collapse to one
   column sooner. At 1180px each half is about 560px, which is a page title,
   a url and four numbers, and that is the floor of what reads. Below it the
   two cards stack and each one gets the whole width. */
@media (max-width: 1180px) {
	.grid-2, .grid-2.grid-2-40-60 { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {
	.hide-sm { display: none !important; }
	.grid-2, .grid-2.grid-2-40-60, .grid-3 { grid-template-columns: 1fr; }
	.view, .filterset, .page-head { padding-left: 12px; padding-right: 12px; }
	.matrix { grid-template-columns: minmax(100px, 1fr) repeat(6, minmax(24px, 1fr)); font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
	* { transition: none !important; animation: none !important; }
}

/* ============================================================
   V2 EDITS
   Everything below this banner was added during the rework of
   oppalerts-app.1.html into app.html. Nothing above it changed.
   ============================================================ */

/* ---- v2: marker for new work --------------------------------
   Used only where new cards sit beside kept v1 cards, so the two
   can be told apart at a glance. Views that are entirely new
   carry no marker, because all of their content is new.        */
.card.v2 { border-color: var(--line-strong); }

/* ---- v2: view switching for 33 views ------------------------
   The view radios sit before .app in the document, so every
   selector is `#v-xx:checked ~ .app ...`. The four lists near the
   top of MAIN + VIEW SWITCHING are superseded and now inert.
   The `.nav-item[for=...]` selectors below are inert as well: they
   marked the current view in the left rail, which was removed on
   2026-08-23.                                                   */
#v-ov:checked ~ .app #h-ov, #v-pl:checked ~ .app #h-pl, #v-al:checked ~ .app #h-al,
#v-tl:checked ~ .app #h-tl, #v-pe:checked ~ .app #h-pe, #v-ai:checked ~ .app #h-ai,
#v-an:checked ~ .app #h-an, #v-pr:checked ~ .app #h-pr, #v-ci:checked ~ .app #h-ci,
#v-co:checked ~ .app #h-co, #v-og:checked ~ .app #h-og, #v-au:checked ~ .app #h-au,
#v-sa:checked ~ .app #h-sa, #v-at:checked ~ .app #h-at, #v-ls:checked ~ .app #h-ls,
#v-rp:checked ~ .app #h-rp, #v-mo:checked ~ .app #h-mo, #v-sq:checked ~ .app #h-sq,
#v-rd:checked ~ .app #h-rd, #v-fo:checked ~ .app #h-fo, #v-vi:checked ~ .app #h-vi,
#v-ct:checked ~ .app #h-ct, #v-op:checked ~ .app #h-op, #v-pt:checked ~ .app #h-pt,
#v-sp:checked ~ .app #h-sp, #v-px:checked ~ .app #h-px, #v-mm:checked ~ .app #h-mm,
#v-bm:checked ~ .app #h-bm, #v-lk:checked ~ .app #h-lk, #v-lo:checked ~ .app #h-lo,
#v-ec:checked ~ .app #h-ec, #v-se:checked ~ .app #h-se, #v-np:checked ~ .app #h-np { display: block; }

#v-ov:checked ~ .app #f-ov, #v-pl:checked ~ .app #f-pl, #v-al:checked ~ .app #f-al,
#v-tl:checked ~ .app #f-tl, #v-pe:checked ~ .app #f-pe, #v-ai:checked ~ .app #f-ai,
#v-an:checked ~ .app #f-an, #v-pr:checked ~ .app #f-pr, #v-ci:checked ~ .app #f-ci,
#v-co:checked ~ .app #f-co, #v-og:checked ~ .app #f-og, #v-au:checked ~ .app #f-au,
#v-sa:checked ~ .app #f-sa, #v-at:checked ~ .app #f-at, #v-ls:checked ~ .app #f-ls,
#v-rp:checked ~ .app #f-rp, #v-mo:checked ~ .app #f-mo, #v-sq:checked ~ .app #f-sq,
#v-rd:checked ~ .app #f-rd, #v-fo:checked ~ .app #f-fo, #v-vi:checked ~ .app #f-vi,
#v-ct:checked ~ .app #f-ct, #v-op:checked ~ .app #f-op, #v-pt:checked ~ .app #f-pt,
#v-sp:checked ~ .app #f-sp, #v-px:checked ~ .app #f-px, #v-mm:checked ~ .app #f-mm,
#v-bm:checked ~ .app #f-bm, #v-lk:checked ~ .app #f-lk, #v-lo:checked ~ .app #f-lo,
#v-ec:checked ~ .app #f-ec, #v-se:checked ~ .app #f-se, #v-np:checked ~ .app #f-np { display: flex; }

#v-ov:checked ~ .app #w-ov, #v-pl:checked ~ .app #w-pl, #v-al:checked ~ .app #w-al,
#v-tl:checked ~ .app #w-tl, #v-pe:checked ~ .app #w-pe, #v-ai:checked ~ .app #w-ai,
#v-an:checked ~ .app #w-an, #v-pr:checked ~ .app #w-pr, #v-ci:checked ~ .app #w-ci,
#v-co:checked ~ .app #w-co, #v-og:checked ~ .app #w-og, #v-au:checked ~ .app #w-au,
#v-sa:checked ~ .app #w-sa, #v-at:checked ~ .app #w-at, #v-ls:checked ~ .app #w-ls,
#v-rp:checked ~ .app #w-rp, #v-mo:checked ~ .app #w-mo, #v-sq:checked ~ .app #w-sq,
#v-rd:checked ~ .app #w-rd, #v-fo:checked ~ .app #w-fo, #v-vi:checked ~ .app #w-vi,
#v-ct:checked ~ .app #w-ct, #v-op:checked ~ .app #w-op, #v-pt:checked ~ .app #w-pt,
#v-sp:checked ~ .app #w-sp, #v-px:checked ~ .app #w-px, #v-mm:checked ~ .app #w-mm,
#v-bm:checked ~ .app #w-bm, #v-lk:checked ~ .app #w-lk, #v-lo:checked ~ .app #w-lo,
#v-ec:checked ~ .app #w-ec, #v-se:checked ~ .app #w-se, #v-np:checked ~ .app #w-np { display: grid; }

#v-ov:checked ~ .app .section-tabs label[for="v-ov"],
#v-pl:checked ~ .app .section-tabs label[for="v-pl"],
#v-al:checked ~ .app .section-tabs label[for="v-al"],
#v-tl:checked ~ .app .section-tabs label[for="v-tl"],
#v-pe:checked ~ .app .section-tabs label[for="v-pe"],
#v-ai:checked ~ .app .section-tabs label[for="v-ai"],
#v-an:checked ~ .app .section-tabs label[for="v-an"],
#v-pr:checked ~ .app .section-tabs label[for="v-pr"],
#v-ci:checked ~ .app .section-tabs label[for="v-ci"],
#v-co:checked ~ .app .section-tabs label[for="v-co"],
#v-og:checked ~ .app .section-tabs label[for="v-og"],
#v-au:checked ~ .app .section-tabs label[for="v-au"],
#v-sa:checked ~ .app .section-tabs label[for="v-sa"],
#v-at:checked ~ .app .section-tabs label[for="v-at"],
#v-ls:checked ~ .app .section-tabs label[for="v-ls"],
#v-rp:checked ~ .app .section-tabs label[for="v-rp"],
#v-mo:checked ~ .app .section-tabs label[for="v-mo"],
#v-sq:checked ~ .app .section-tabs label[for="v-sq"],
#v-rd:checked ~ .app .section-tabs label[for="v-rd"],
#v-fo:checked ~ .app .section-tabs label[for="v-fo"],
#v-vi:checked ~ .app .section-tabs label[for="v-vi"],
#v-ct:checked ~ .app .section-tabs label[for="v-ct"],
#v-op:checked ~ .app .section-tabs label[for="v-op"],
#v-pt:checked ~ .app .section-tabs label[for="v-pt"],
#v-sp:checked ~ .app .section-tabs label[for="v-sp"],
#v-px:checked ~ .app .section-tabs label[for="v-px"],
#v-mm:checked ~ .app .section-tabs label[for="v-mm"],
#v-bm:checked ~ .app .section-tabs label[for="v-bm"],
#v-lk:checked ~ .app .section-tabs label[for="v-lk"],
#v-lo:checked ~ .app .section-tabs label[for="v-lo"],
#v-ec:checked ~ .app .section-tabs label[for="v-ec"],
#v-se:checked ~ .app .section-tabs label[for="v-se"],
#v-np:checked ~ .app .section-tabs label[for="v-np"] {
	color: var(--ink);
	border-bottom-color: var(--accent);
}

/* the left nav can now show the active view, which it could not
   when the radios lived inside .main */
#v-ov:checked ~ .app .nav-item[for="v-ov"],
#v-pl:checked ~ .app .nav-item[for="v-pl"],
#v-al:checked ~ .app .nav-item[for="v-al"],
#v-tl:checked ~ .app .nav-item[for="v-tl"],
#v-pe:checked ~ .app .nav-item[for="v-pe"],
#v-ai:checked ~ .app .nav-item[for="v-ai"],
#v-an:checked ~ .app .nav-item[for="v-an"],
#v-pr:checked ~ .app .nav-item[for="v-pr"],
#v-ci:checked ~ .app .nav-item[for="v-ci"],
#v-co:checked ~ .app .nav-item[for="v-co"],
#v-og:checked ~ .app .nav-item[for="v-og"],
#v-au:checked ~ .app .nav-item[for="v-au"],
#v-sa:checked ~ .app .nav-item[for="v-sa"],
#v-at:checked ~ .app .nav-item[for="v-at"],
#v-ls:checked ~ .app .nav-item[for="v-ls"],
#v-rp:checked ~ .app .nav-item[for="v-rp"],
#v-mo:checked ~ .app .nav-item[for="v-mo"],
#v-sq:checked ~ .app .nav-item[for="v-sq"],
#v-rd:checked ~ .app .nav-item[for="v-rd"],
#v-fo:checked ~ .app .nav-item[for="v-fo"],
#v-vi:checked ~ .app .nav-item[for="v-vi"],
#v-ct:checked ~ .app .nav-item[for="v-ct"],
#v-op:checked ~ .app .nav-item[for="v-op"],
#v-pt:checked ~ .app .nav-item[for="v-pt"],
#v-sp:checked ~ .app .nav-item[for="v-sp"],
#v-px:checked ~ .app .nav-item[for="v-px"],
#v-mm:checked ~ .app .nav-item[for="v-mm"],
#v-bm:checked ~ .app .nav-item[for="v-bm"],
#v-lk:checked ~ .app .nav-item[for="v-lk"],
#v-lo:checked ~ .app .nav-item[for="v-lo"],
#v-ec:checked ~ .app .nav-item[for="v-ec"],
#v-se:checked ~ .app .nav-item[for="v-se"],
#v-np:checked ~ .app .nav-item[for="v-np"] {
	background: var(--accent-tint);
	color: var(--accent-ink);
	font-weight: 500;
}
#v-ov:checked ~ .app .nav-item[for="v-ov"] svg,
#v-pl:checked ~ .app .nav-item[for="v-pl"] svg,
#v-al:checked ~ .app .nav-item[for="v-al"] svg,
#v-tl:checked ~ .app .nav-item[for="v-tl"] svg,
#v-pe:checked ~ .app .nav-item[for="v-pe"] svg,
#v-ai:checked ~ .app .nav-item[for="v-ai"] svg,
#v-an:checked ~ .app .nav-item[for="v-an"] svg,
#v-pr:checked ~ .app .nav-item[for="v-pr"] svg,
#v-ci:checked ~ .app .nav-item[for="v-ci"] svg,
#v-co:checked ~ .app .nav-item[for="v-co"] svg,
#v-og:checked ~ .app .nav-item[for="v-og"] svg,
#v-au:checked ~ .app .nav-item[for="v-au"] svg,
#v-sa:checked ~ .app .nav-item[for="v-sa"] svg,
#v-at:checked ~ .app .nav-item[for="v-at"] svg,
#v-ls:checked ~ .app .nav-item[for="v-ls"] svg,
#v-rp:checked ~ .app .nav-item[for="v-rp"] svg,
#v-mo:checked ~ .app .nav-item[for="v-mo"] svg,
#v-sq:checked ~ .app .nav-item[for="v-sq"] svg,
#v-rd:checked ~ .app .nav-item[for="v-rd"] svg,
#v-fo:checked ~ .app .nav-item[for="v-fo"] svg,
#v-vi:checked ~ .app .nav-item[for="v-vi"] svg,
#v-ct:checked ~ .app .nav-item[for="v-ct"] svg,
#v-op:checked ~ .app .nav-item[for="v-op"] svg,
#v-pt:checked ~ .app .nav-item[for="v-pt"] svg,
#v-sp:checked ~ .app .nav-item[for="v-sp"] svg,
#v-px:checked ~ .app .nav-item[for="v-px"] svg,
#v-mm:checked ~ .app .nav-item[for="v-mm"] svg,
#v-bm:checked ~ .app .nav-item[for="v-bm"] svg,
#v-lk:checked ~ .app .nav-item[for="v-lk"] svg,
#v-lo:checked ~ .app .nav-item[for="v-lo"] svg,
#v-ec:checked ~ .app .nav-item[for="v-ec"] svg,
#v-se:checked ~ .app .nav-item[for="v-se"] svg,
#v-np:checked ~ .app .nav-item[for="v-np"] svg { color: var(--accent); }

#v-ov:focus-visible ~ .app .section-tabs label[for="v-ov"],
#v-pl:focus-visible ~ .app .section-tabs label[for="v-pl"],
#v-al:focus-visible ~ .app .section-tabs label[for="v-al"],
#v-tl:focus-visible ~ .app .section-tabs label[for="v-tl"],
#v-pe:focus-visible ~ .app .section-tabs label[for="v-pe"],
#v-ai:focus-visible ~ .app .section-tabs label[for="v-ai"],
#v-an:focus-visible ~ .app .section-tabs label[for="v-an"],
#v-pr:focus-visible ~ .app .section-tabs label[for="v-pr"],
#v-ci:focus-visible ~ .app .section-tabs label[for="v-ci"],
#v-co:focus-visible ~ .app .section-tabs label[for="v-co"],
#v-og:focus-visible ~ .app .section-tabs label[for="v-og"],
#v-au:focus-visible ~ .app .section-tabs label[for="v-au"],
#v-sa:focus-visible ~ .app .section-tabs label[for="v-sa"],
#v-at:focus-visible ~ .app .section-tabs label[for="v-at"],
#v-ls:focus-visible ~ .app .section-tabs label[for="v-ls"],
#v-rp:focus-visible ~ .app .section-tabs label[for="v-rp"],
#v-mo:focus-visible ~ .app .section-tabs label[for="v-mo"],
#v-sq:focus-visible ~ .app .section-tabs label[for="v-sq"],
#v-rd:focus-visible ~ .app .section-tabs label[for="v-rd"],
#v-fo:focus-visible ~ .app .section-tabs label[for="v-fo"],
#v-vi:focus-visible ~ .app .section-tabs label[for="v-vi"],
#v-ct:focus-visible ~ .app .section-tabs label[for="v-ct"],
#v-op:focus-visible ~ .app .section-tabs label[for="v-op"],
#v-pt:focus-visible ~ .app .section-tabs label[for="v-pt"],
#v-sp:focus-visible ~ .app .section-tabs label[for="v-sp"],
#v-px:focus-visible ~ .app .section-tabs label[for="v-px"],
#v-mm:focus-visible ~ .app .section-tabs label[for="v-mm"],
#v-bm:focus-visible ~ .app .section-tabs label[for="v-bm"],
#v-lk:focus-visible ~ .app .section-tabs label[for="v-lk"],
#v-lo:focus-visible ~ .app .section-tabs label[for="v-lo"],
#v-ec:focus-visible ~ .app .section-tabs label[for="v-ec"],
#v-se:focus-visible ~ .app .section-tabs label[for="v-se"],
#v-np:focus-visible ~ .app .section-tabs label[for="v-np"] {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

/* ---- v2: tab groups -----------------------------------------
   Thirty three tabs in one strip would be unreadable. The left nav
   picks the section; the strip shows only that section's views.
   Each group is a flex row revealed by its own views.          */
	#v-ov:checked ~ .app .tg-cmd, #v-pl:checked ~ .app .tg-cmd, #v-al:checked ~ .app .tg-cmd,
	#v-tl:checked ~ .app .tg-cmd,
	#v-pe:checked ~ .app .tg-ai, #v-ai:checked ~ .app .tg-ai, #v-an:checked ~ .app .tg-ai,
	#v-pr:checked ~ .app .tg-ai, #v-ci:checked ~ .app .tg-ai, #v-co:checked ~ .app .tg-ai,
	#v-og:checked ~ .app .tg-search, #v-au:checked ~ .app .tg-search, #v-sa:checked ~ .app .tg-search,
	#v-at:checked ~ .app .tg-search,
	#v-ls:checked ~ .app .tg-listen, #v-rp:checked ~ .app .tg-listen, #v-mo:checked ~ .app .tg-listen,
	#v-sq:checked ~ .app .tg-listen,
	#v-rd:checked ~ .app .tg-comm, #v-fo:checked ~ .app .tg-comm, #v-vi:checked ~ .app .tg-comm,
	#v-ct:checked ~ .app .tg-grow, #v-op:checked ~ .app .tg-grow, #v-pt:checked ~ .app .tg-grow,
	#v-sp:checked ~ .app .tg-grow, #v-px:checked ~ .app .tg-grow,
	#v-mm:checked ~ .app .tg-rsch, #v-bm:checked ~ .app .tg-rsch, #v-lk:checked ~ .app .tg-rsch,
	#v-lo:checked ~ .app .tg-mod, #v-ec:checked ~ .app .tg-mod,
	#v-se:checked ~ .app .tg-proj, #v-np:checked ~ .app .tg-proj { display: flex; }

.section-tabs .tabsec {
	display: flex;
	align-items: center;
	font-family: var(--f-mono);
	font-size: 9.5px;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 7px 12px 8px 0;
	margin-right: 4px;
	border-right: 1px solid var(--line);
	white-space: nowrap;
}

/* ---- v2: settings sections 9 to 12 -------------------------- */

/* ---- v2: section rule inside a long view --------------------
   Long views need breathing room. A labeled hairline groups the
   cards below it without adding another level of card nesting. */

/* ---- v2: extra column spans --------------------------------- */

/* ---- v2: matrix column counts -------------------------------
   The base matrix is fixed at six engines. These variants cover
   personas, models, and prompt groups.                          */
.matrix.m4  { grid-template-columns: minmax(150px, 1.5fr) repeat(4,  minmax(34px, 1fr)); }
.matrix.m5  { grid-template-columns: minmax(150px, 1.5fr) repeat(5,  minmax(32px, 1fr)); }
.matrix.m7  { grid-template-columns: minmax(150px, 1.5fr) repeat(7,  minmax(28px, 1fr)); }
.matrix.m8  { grid-template-columns: minmax(150px, 1.4fr) repeat(8,  minmax(26px, 1fr)); }
.matrix.m9  { grid-template-columns: minmax(146px, 1.4fr) repeat(9,  minmax(25px, 1fr)); }
.matrix.m11 { grid-template-columns: minmax(140px, 1.3fr) repeat(11, minmax(23px, 1fr)); }
.matrix.m13 { grid-template-columns: minmax(136px, 1.2fr) repeat(13, minmax(21px, 1fr)); }

/* heat ramp, for matrices whose cells are counts rather than ranks */
.cell.h0 { color: var(--ink-3); }
.cell.h1 { background: var(--heat-0); border-color: var(--heat-0-line); color: var(--heat-0-ink); }
.cell.h2 { background: var(--heat-1); border-color: var(--heat-1-line); color: var(--heat-1-ink); }
.cell.h3 { background: var(--heat-2); border-color: var(--heat-2-line); color: var(--heat-2-ink); }
.cell.h4 { background: var(--heat-3); border-color: var(--heat-3-line); color: var(--heat-3-ink); }
.cell.h5 { background: var(--heat-4); border-color: var(--heat-4-line); color: var(--heat-4-ink); }
.cell.hn { background: var(--neg-soft); border-color: var(--neg-soft-line); color: var(--neg); }
.cell.hw { background: var(--warn-soft); border-color: var(--warn-soft-line); color: var(--warn); }

/* ---- v2: bar row with a trailing delta column ---------------- */

/* ---- v2: funnel ---------------------------------------------
   For staged loss: retrieved, cited, absorbed, named. Each stage
   is a share of the stage above it, so widths always descend.  */
.funnel { display: flex; flex-direction: column; gap: 5px; }

/* ---- v2: diverging bar, for anything with two directions ----- */

/* ---- v2: inline bar inside a table cell ---------------------- */

/* ---- v2: dot scale, for model agreement in a dense cell ------ */

/* ---- v2: tiles, for category grids that act as a work queue -- */
/* Hairlines come from the tiles themselves rather than a 1px grid gap
   over a coloured backing. A partial last row would otherwise show the
   backing colour as a grey block. */
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(152px, 1fr)); gap: 0; border-top: 1px solid var(--line-faint); }

/* ---- v2: persona strip --------------------------------------
   Persona is the axis the built reports are organised around, so
   it gets a first-class picker rather than only a filter menu.  */

/* ---- v2: callout, used for method and confidence notes ------
   The product's argument is that honesty about variance is the
   differentiator, so the caveats need a real component.        */
.callout { display: flex; gap: 9px; padding: 9px 11px; border: 1px solid var(--line); border-left-width: 2px; border-left-color: var(--line-strong); border-radius: var(--r); background: var(--bg-sunk); color: var(--ink-2); font-size: 12px; }
.callout svg { flex: none; margin-top: 1px; color: var(--ink-3); }
.callout b { font-weight: 600; color: var(--ink); }
.callout.acc { background: var(--accent-tint); border-color: var(--accent-tint-2); border-left-color: var(--accent); color: var(--accent-ink); }
.callout.acc b, .callout.acc svg { color: var(--accent-ink); }
.callout.warn { background: var(--warn-soft); border-color: var(--warn-soft-line); border-left-color: var(--warn); color: var(--warn); }
.callout.warn b, .callout.warn svg { color: var(--warn); }
.callout.neg { background: var(--neg-soft); border-color: var(--neg-soft-line); border-left-color: var(--neg); color: var(--neg); }
.callout.neg b, .callout.neg svg { color: var(--neg); }

/* ---- v2: owner chip -----------------------------------------
   This is a tool for a whole team, so work items name an owner. */
.who { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-2); white-space: nowrap; }
.who i { width: 17px; height: 17px; border-radius: var(--r); background: var(--bg-sunk); border: 1px solid var(--line); display: grid; place-items: center; font-family: var(--f-mono); font-size: 8.5px; font-style: normal; color: var(--ink-2); flex: none; }
.who i.acc { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }
.who.none i { border-style: dashed; color: var(--ink-3); }

/* ---- v2: priority chip --------------------------------------- */
.pri { display: inline-grid; place-items: center; min-width: 27px; height: 19px; padding: 0 5px; border: 1px solid var(--line); border-radius: var(--r); background: var(--bg-sunk); font-family: var(--f-mono); font-size: 10.5px; font-weight: 500; color: var(--ink-2); }
.pri.p1 { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.pri.p2 { background: var(--accent-tint-2); border-color: var(--heat-2-line); color: var(--accent-ink); }
.pri.p3 { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }

/* ---- v2: two more entity kinds ------------------------------
   The config model covers people as well as domains: your own
   experts and spokespeople, and the ones competitors put forward.
   They stay neutral so the five domain colors keep their meaning. */
.ent.expert::before { background: var(--ink-2); border-radius: 50%; }
.ent.person::before { background: var(--ink-3); border-radius: 50%; }

/* ---- v2: dense engine stack, for thirteen models ------------- */

/* ---- v2: stacking fix ---------------------------------------
   .filters shipped at z-index 6, above the sticky .page-head at 5,
   so while you scrolled past it the filter bar painted over the
   title, the crumbs, and the tab strip. The sticky head has to win.

   Filter menus are unaffected. .menu-pop sits inside the stacking
   context .filters creates, and any positive z-index still paints
   above the cards below, which are position: relative with z-index
   auto. Menus also open downward, away from the head. */
.filters { z-index: 4; }

/* ---- v2: small odds and ends -------------------------------- */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.tbl-scroll.short { max-height: 268px; }
.card-body.tight { padding: 9px 12px; }
.nowrap { white-space: nowrap; }
.tag.sm { font-size: 9px; padding: 0 4px; }
.rows.tight > div { padding: 6px 12px; }
.card-note.wrap { white-space: normal; }
.mono-sm { font-family: var(--f-mono); font-size: 10.5px; color: var(--ink-3); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.q { font-family: var(--f-mono); font-size: 11px; color: var(--ink-2); }

/* A matrix is one flat grid of cells, so the column count can never
   change responsively, because the cells would re-flow out of their rows.
   Narrow screens get a minimum width and a horizontal scroller. */
@media (max-width: 860px) {
	.grid-4 { grid-template-columns: 1fr; }
	.matrix.m4  { min-width: 380px; }
	.matrix.m5  { min-width: 420px; }
	.matrix.m7  { min-width: 480px; }
	.matrix.m8  { min-width: 500px; }
	.matrix.m9  { min-width: 520px; }
	.matrix.m11 { min-width: 560px; }
	.matrix.m13 { min-width: 600px; }
}
/* ============================================================
   V2 SETTINGS SCOPE
   A settings screen is used at speed, so nothing that matters may
   be behind a dropdown. Personas are a permanent third column, on
   the far left, always laid out and always one click away.
   Column 1 is what you are editing. Column 2 is which section.
   Column 3 is the panel.
   ============================================================ */

/* the scope strip at the top of the body says what you are editing,
   so it is never a guess */

/* a persona edit is visibly a persona edit, all the way down */

/* sections that only exist at campaign level cannot be clicked while a
   persona is selected, so an invalid combination is unreachable */

@media (max-width: 1100px) {
}
@media (max-width: 900px) {
}

/* ---- v2: campaign level sections, seen from a persona ---------

/* ============================================================
   ACCOUNT MENU (top bar, right end)
   ============================================================ */
/* The account menu was at the foot of the left rail and opened
   upward. The rail was removed on 2026-08-23; the menu is now the
   last item in .tb-right and opens downward and to the right.
   .nav-account, .nav-menu and .na-id were deleted with the rail. */

/* `1fr` is minmax(auto, 1fr), so the row's minimum is its content, and
   the row would grow past the viewport. Pinning it to minmax(0, 1fr)
   keeps .main at the viewport height. */
.app { grid-template-rows: var(--topbar-h) minmax(0, 1fr); }

/* ---- theme picker ---- */
/* The palette button in the top bar and the list of themes under it. It
   replaced the two-state light / dark button on 2026-08-29. The menu opens
   on hover and on keyboard focus, and it sits flush under the button so a
   pointer never crosses a gap and loses :hover. The same shape as the
   picker on oppalerts.com/AI-Search-Visibility/. */
.theme-picker { position: relative; display: flex; align-items: center; flex: none; }

.theme-picker-btn {
	display: flex;
	align-items: center;
	gap: 5px;
	height: 28px;
	padding: 0 7px;
	border: 1px solid transparent;
	border-radius: var(--r);
	background: transparent;
	color: var(--ink-2);
	font-family: var(--f-ui);
	font-size: 12px;
	font-weight: 500;
	cursor: pointer;
	white-space: nowrap;
}
.theme-picker:hover .theme-picker-btn,
.theme-picker:focus-within .theme-picker-btn {
	background: var(--bg-hover);
	color: var(--ink);
}
.theme-picker .chevron { transition: transform .15s ease; flex: none; }
.theme-picker:hover .chevron,
.theme-picker:focus-within .chevron { transform: rotate(180deg); }

.theme-picker-menu {
	display: none;
	position: absolute;
	top: 100%; right: 0;
	min-width: 168px;
	padding: 4px;
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	box-shadow: var(--pop);
	z-index: 400;
}
.theme-picker:hover .theme-picker-menu,
.theme-picker:focus-within .theme-picker-menu { display: block; }

.theme-option {
	display: block;
	width: 100%;
	padding: 6px 9px;
	border: 0;
	border-radius: var(--r);
	background: none;
	color: var(--ink-2);
	font-family: var(--f-ui);
	font-size: 12.5px;
	text-align: left;
	cursor: pointer;
}
.theme-option:hover { background: var(--bg-hover); color: var(--ink); }
.theme-option.on { color: var(--accent-ink); font-weight: 600; }

.theme-picker-sep { height: 1px; margin: 4px 2px; background: var(--line); }

.acct-btn {
	display: flex;
	align-items: center;
	gap: 4px;
	height: 28px;
	padding: 0 6px;
	border: 1px solid transparent;
	border-radius: var(--r);
	background: transparent;
	color: var(--ink-2);
	cursor: pointer;
	flex: none;
}
.acct-btn:hover { background: var(--bg-hover); color: var(--ink); }
.menu.is-open .acct-btn { background: var(--bg-hover); border-color: var(--line); color: var(--ink); }
.acct-btn .caret { color: var(--ink-3); flex: none; }

/* upward variant of the popover */
.menu-pop.to-top { top: auto; bottom: calc(100% + 5px); }

/* the usage row: its own section at the top of the menu */

/* ---- v2: the tab strip scrolled sideways ---------------------
   `overflow-x: auto` and the rules that hid its scrollbars were
   removed on 2026-08-23. The strip holds two or three tabs now,
   and the active tab has to paint over the .page-head bottom
   border, which a clipping overflow would have cut off. */

/* ---- v2: only .main scrolls ----------------------------------
   .main's overscroll-behavior was `auto`, so reaching its top or
   bottom chained the scroll out to the document and rubber-banded
   the whole page, taking the sticky .page-head with it. That is
   the nudge. `none` stops the chaining and the bounce.

   The document is pinned as well. .app is exactly 100vh and every
   region carries its own scroller, so the root never needs to
   scroll and should not be able to. */
html, body { overflow: hidden; overscroll-behavior: none; }
.main { overscroll-behavior: none; }

/* ---- v2: the hero sits outside the scroller ------------------
   .main is now two rows: the page head, then one scrolling panel
   holding the filter bar and the view. The scrollbar starts below
   the hero instead of running alongside it, and the head and the
   filter bar can no longer overlap at all, which is what the
   earlier .filters z-index fix was working around. */
.main { display: grid; grid-template-rows: auto minmax(0, 1fr); overflow: hidden; }
.page-head { position: relative; }
.main-scroll {
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: none;
}

/* ============================================================
   PHP MULTI-PAGE ADAPTATION (app2)
   The design mock drives its views with hidden radios in one
   file. Here every view is its own PHP page: the single
   head-block / filterset / view on a page renders
   unconditionally, and nav items are real links carrying
   .is-active. Everything below either re-enables what the
   radio selectors gated, or restyles app2's pre-migration
   class vocabulary with the design tokens so every existing
   page adopts the aesthetic without markup changes.
   ============================================================ */
.head-block { display: block; }
.view { display: block; }

/* the head-block already renders the page title.
   THE SECOND SELECTOR IS THE ONE THAT WORKS, from 2026-08-31.
   templates/header.php prints the empty <div id="api-fail"> as the FIRST
   child of .view (2026-08-29), so the page's own <h1> stopped being
   :first-child and the title printed twice on all 33 pages that carry
   one: once in the page head, once over the table. On
   report_search_domains.php the title stood over every tab's table,
   which is where Ben saw it. */
.view > h1:first-child,
.view > #api-fail + h1 { display: none; }

a.nav-item, a.menu-item { text-decoration: none; color: inherit; }

/* ---- legacy tables: pages emit <table class="grid"> with bare
       <tr><th> rows (no thead), so the design's thead-scoped
       header styling is re-declared for them here ---- */
table.grid th {
	background: var(--bg-rail);
	border-bottom: 1px solid var(--line);
	text-align: left;
	font-family: var(--f-mono);
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 9px 14px;
	white-space: nowrap;
}
table.grid td { padding: 10px 14px; border-bottom: 1px solid var(--line-faint); vertical-align: top; font-size: 13px; }

/* ── A TABLE FITS ITS CARD. Ben, 2026-08-28 ───────────────────────────────
   Two cards side by side, each holding a table with a page column and three
   or four number columns, made the table scroll sideways inside its card:
   measured +50px at a 1600px window and +350px at 1000px. Three causes, all
   fixed here and nowhere else, so every table on every page gets it.

   1. A grid child is min-width:auto, so the table's own minimum width
      pushed the column wider than 1fr. Every .grid-* child is min-width:0
      below.
   2. A number column carried the same 14px side padding as a text column,
      and a `#` column of two digits is not a text column.
   3. A url and a long title could not wrap, so the page column had a
      minimum width of the longest url in the table. */
.grid-2 > *, .grid-3 > *, .grid-4 > * { min-width: 0; }
.tbl-scroll { max-width: 100%; }

/* Numbers are narrow, right-aligned and never wrap. 14px of side padding on
   a 3-character column is most of the column. */
table.grid th.num, table.grid td.num {
	padding-left: 6px;
	padding-right: 10px;
	white-space: nowrap;
	width: 1%;
}
/* The row number is the narrowest column on the page. */
table.grid th:first-child.num, table.grid td:first-child.num {
	padding-left: 10px;
	padding-right: 4px;
}
/* The text column takes what is left and WRAPS, url and all. Without
   overflow-wrap a 120-character url is one unbreakable word. */
table.grid td:not(.num) { overflow-wrap: anywhere; }
table.grid td a { color: var(--accent); font-weight: 500; }
table.grid td a:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
table.grid tr:last-child td { border-bottom: 0; }
table.grid tr:hover td { background: var(--bg-rail); }
table.grid { background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg); }
table.grid .num { text-align: right; font-family: var(--f-mono); font-variant-numeric: tabular-nums; }

/* ── A DAY GRID: one column per day, two rows per subject ─────────────────
   The Dashboard's "Opportunities found, by day" table. Ben, 2026-08-30.
   Two differences from every other table.grid, and both are about reading a
   wide grid of small numbers:

   1. A DAY COLUMN IS CENTRED, head and cell. A right-aligned two-digit
      head over a three-digit number does not sit over its own column, and
      fourteen of them read as a drift.
   2. THE TWO ROWS OF ONE SUBJECT ARE ONE BLOCK. The first has no bottom
      border and the second sits on --bg-sunk, one step off the card, so a
      pair reads as a pair without a rule between them. */
table.grid.tbl-days th.num, table.grid.tbl-days td.num {
	text-align: center;
	padding-left: 8px;
	padding-right: 8px;
}
table.grid.tbl-days tr.is-first td { border-bottom: 0; }
/* --bg-rail, not --bg-sunk: the sunk surface is near black in the dark
   themes and the second row of each pair read as a hole. Ben, 2026-08-31. */
table.grid.tbl-days tr.is-second td { background: var(--bg-rail); }
/* The index name sits at the RIGHT of its own column, against the first
   number, so the two rows of a pair start at the same place and the
   opportunity name is the only thing in the first column. */
table.grid.tbl-days th.src, table.grid.tbl-days td.src {
	text-align: right;
	white-space: nowrap;
	width: 1%;
	padding-left: 10px;
	padding-right: 12px;
	color: var(--ink-3);
	font-size: 11.5px;
}

/* ---- legacy banners / bars / boxes ---- */
/* The legacy full-width banner. :not(.tag) because .tag.warn is a small
   pill and this rule's padding, margin and font-size were overriding it:
   both are one class, and this one is later in the file, so it won for
   every property .tag.warn does not name itself. */
.warn:not(.tag) {
	margin: 0 0 14px;
	padding: 9px 12px;
	background: var(--warn-soft);
	border: 1px solid var(--warn-soft-line);
	border-radius: var(--r-lg);
	font-size: 12.5px;
	color: var(--warn);
}
.ok {
	margin: 0 0 14px;
	padding: 9px 12px;
	background: var(--accent-tint);
	border: 1px solid var(--accent-tint-2);
	border-radius: var(--r-lg);
	font-size: 12.5px;
	color: var(--pos);
}
/* A plain statement of fact. Not a warning and not a success: the reader
   is being told what the page is working with, e.g. which taxonomies are
   loaded and which are still being built. */
.note {
	margin: 0 0 14px;
	padding: 9px 12px;
	background: var(--bg-sunk);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	font-size: 12.5px;
	color: var(--ink-2);
	line-height: 1.55;
}
.muted { color: var(--ink-3); }
.small { font-size: 11.5px; }
.filterbar {
	margin: 0 0 14px;
	padding: 8px 12px;
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	display: flex;
	gap: 12px;
	align-items: center;
	flex-wrap: wrap;
	font-size: 12.5px;
}
/* The filter bar of monitor_links.php and link_gap_newsidx.php. Each control is a
   .fb-group: a small upper-case label above the control itself, so the bar
   reads as named fields. */
.filterbar { align-items: flex-end; }
.fb-group { display: flex; flex-direction: column; gap: 4px; }
.fb-label {
	/* The same label as `.idx-field > label` and `.field > label`: mono,
	   9.5px, uppercase. It was the UI font, which was the one thing left
	   telling the reader this bar came from somewhere else. Ben,
	   2026-08-31. */
	font-family: var(--f-mono);
	font-size: 9.5px;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	font-weight: 500;
}
.fb-boxes {
	display: flex;
	align-items: center;
	gap: 10px;
	height: 24px;
}
/* The link pages lay the same controls out as a grid: two columns, two
   rows, and the Search button on a third row across both columns. */
.filterbar.fb-grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 10px 24px;
	align-items: start;
}
.filterbar.fb-grid .fb-span { grid-column: 1 / -1; }
.filterbar.fb-grid .fb-act { align-items: flex-start; }
.filterbar.fb-grid .fb-boxes { height: auto; flex-wrap: wrap; row-gap: 4px; }
.filterbar.fb-grid input[type="text"].control { width: 100%; }
/* THE CONTROLS ARE THE SEARCH FORM'S CONTROLS: 28px, the --line-strong
   edge, a number centred in its box. Ben, 2026-08-31. */
.filterbar.fb-grid select.control.sm,
.filterbar.fb-grid input.control {
	height: 28px;
	min-height: 28px;
	padding-top: 0;
	padding-bottom: 0;
	line-height: normal;
	border-color: var(--line-strong);
}
.filterbar.fb-grid select.control.sm {
	width: auto;
	align-self: flex-start;
	min-width: 160px;
	background-position: calc(100% - 14px) 12px, calc(100% - 9px) 12px;
}
.filterbar.fb-grid input[type=number].control {
	width: 120px;
	text-align: center;
	font-family: var(--f-mono);
	font-size: 11.5px;
}
.filterbar.fb-grid input.control:focus,
.filterbar.fb-grid select.control:focus {
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-tint);
}
/* The head and foot rows of the filter box run the full width of it, so
   they are pulled out to the box's own padding. */
.filterbar.fb-grid .fb-head,
.filterbar.fb-grid .fb-foot {
	margin: 0 -12px;
	padding: 0 12px;
}
.filterbar.fb-grid .fb-head {
	margin-top: -8px;
	padding-top: 7px;
	padding-bottom: 8px;
	border-bottom: 1px solid var(--line);
	background: var(--bg-sunk);
	border-radius: var(--r-lg) var(--r-lg) 0 0;
	font-weight: 500;
	color: var(--ink);
	font-size: 13px;
}
.filterbar.fb-grid .fb-foot {
	margin-bottom: -8px;
	padding-top: 9px;
	padding-bottom: 8px;
	border-top: 1px solid var(--line);
	background: var(--bg-sunk);
	border-radius: 0 0 var(--r-lg) var(--r-lg);
}
/* link_gap_newsidx.php: the domains the report ran against, under the form. */
/* `.view button` further down this file is more specific than
   `.btn-primary`, so the Search button needs its own rule to keep the
   accent colour. Same reason as `.nx-form button.btn-primary`. */
.filterbar button.btn-primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-accent);
}
.filterbar button.btn-primary:hover {
	background: var(--accent-ink);
	border-color: var(--accent-ink);
}
@media (max-width: 720px) {
	.filterbar.fb-grid { grid-template-columns: 1fr; }
}
.chk {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	white-space: nowrap;
}
.chk input { margin: 0; }
/* link_gap_newsidx.php: the site cell carries the link-type wash. A site with more
   than one type gets one stripe per type, in priority order. */
.chk-lt { padding: 1px 7px 1px 5px; border-radius: var(--r); }
/* link_gap_newsidx.php result card: a filled head, so it reads as a header and the
   colour key sits on the same ground as the filter box above it. */
.lg-card > .card-head { background: var(--bg-head); }
/* Counts that are read as a rank, not as a quantity, sit centred. Beats
   both .num rules above, which right-align every numeric cell. */
table.grid thead th.ctr,
table.grid tbody td.ctr { text-align: center; }
/* The site name is clipped rather than allowed to set the table's width.
/* link_gap_newsidx.php: the opened row, and the pages inside it. */
tr.is-open > td { background: var(--bg-sunk); }
pre.raw {
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	padding: 10px 12px;
	overflow-x: auto;
	white-space: pre-wrap;
	word-break: break-word;
	font-family: var(--f-mono);
	font-size: 11px;
}
.pill {
	display: inline-block;
	padding: 1px 6px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-2);
}
/* ---- legacy bare form controls (pages predate .btn / .field) ---- */
.view h2 { font-size: 14px; font-weight: 600; margin: 24px 0 8px; letter-spacing: -0.005em; }
/* `background-color`, NOT the `background` SHORTHAND. Ben, 2026-08-31.
   `select.control` draws its caret as two background gradients, and this
   block is the same specificity and later in the file, so the shorthand
   reset the image and EVERY select inside .view lost its arrow: the
   Relationship select on Organizations, Combine, Type and Sort by on
   Advanced Search, and the three on Latest Industry News. */
.view input[type=text], .view input[type=search], .view input[type=number],
.view textarea, .view select {
	font: 12.5px/1.4 var(--f-ui);
	padding: 5px 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background-color: var(--bg);
	color: var(--ink);
}
.view input[type=number] { width: 70px; font-family: var(--f-mono); }
.view textarea { width: 100%; }
.view button {
	font: 12px/1.4 var(--f-ui);
	font-weight: 500;
	padding: 5px 12px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink);
	cursor: pointer;
}
.view button:hover { background: var(--bg-hover); }
.view button.danger { color: var(--neg); border-color: var(--neg); }
/* THE PRIMARY BUTTON IS FILLED, ON EVERY PAGE. Ben, 2026-08-31: Save on
   campaign_edit, Add organization, Create campaign and Score this writing
   were all painted back to --bg by `.view button` above, so the one button
   that starts the work looked like the boxes beside it. `button.btn-primary`
   ties with `.view button` on specificity and sits after it, which is the
   same fix .btn-neg and .btn-warn below already use. */
button.btn-primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-accent);
	font-weight: 600;
}
button.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); }

/* TWO ATTENTION BUTTONS. Ben, 2026-08-30: a button that says something is
   wrong has to look it. `.btn-danger` in the button block is an outline and
   reads as quiet, which is right for Archive and wrong for this.

   THEY ARE HERE, AND THEY NAME THE ELEMENT, for the reason the comment
   above `.nx-form button.btn-primary` gives: `.view button` is a class and
   an element, so it outranks a bare `.btn-neg` and would repaint it with
   --bg and --ink whatever the button block said. `button.btn-neg` ties on
   specificity and sits after it, so it wins. Measured over CDP in all eight
   themes on 2026-08-30; the bare form did not apply at all.

   They are the SOFT FILL plus the FULL-STRENGTH border and text, which is
   the .ent and .tag pattern already in this file. Not a solid fill of
   --neg or --warn: those are TEXT colours and are 22 to 47 percent
   lightness in the light themes, so a solid block of one is muddy.
   README.design.md section 2, "Status and data colours are text, never
   fills".

   .btn-neg is WRONG: a number that is present and was counted from a query
   that is no longer run. .btn-warn is MISSING: a number that was never
   counted. Each one matches the callout it stands beside, so the colour
   says which of the two it is before the sentence is read. */
button.btn-neg {
	background: var(--neg-soft);
	border-color: var(--neg);
	color: var(--neg);
	font-weight: 600;
}
button.btn-neg:hover { background: var(--neg); color: var(--bg); }
button.btn-warn {
	background: var(--warn-soft);
	border-color: var(--warn);
	color: var(--warn);
	font-weight: 600;
}
button.btn-warn:hover { background: var(--warn); color: var(--bg); }

form.inline { display: inline; }

/* ---- JSON syntax highlighting (Schema / JSON-LD report) ---- */
pre.raw.json { line-height: 1.55; }
.j-key { color: var(--accent-ink); font-weight: 500; }
.j-str { color: var(--amber); }
.j-num { color: var(--blue); }
.j-lit { color: var(--neg); font-weight: 500; }

/* ---- multi-page section tabs: links instead of the mock's labels ---- */
/* A tab has a top, a left and a right border and NO bottom border at
   all: border-bottom-width is 0, so there is nothing to paint there.
   Painting a bottom border in the panel colour instead is wrong. It is
   invisible only while the panel colour is what the reader sees, and a
   colour-changing extension such as Dark Reader recolours it into a
   visible line under the tab. Ben, 2026-08-23.
   The 8px bottom padding replaces the 1px the border used to take, so
   the tab keeps its height.
   The active tab carries the background of .main (--bg-sunk), which is
   the panel under the strip, and margin-bottom: -1px pulls it down one
   pixel. An element paints its own background and border before its
   descendants, so the tab's background covers the .page-head bottom
   border and the tab and the panel join. */
.section-tabs a {
	padding: 6px 14px 8px;
	color: var(--ink-2);
	font-weight: 500;
	font-size: 12px;
	cursor: pointer;
	white-space: nowrap;
	display: flex;
	align-items: center;
	gap: 6px;
	text-decoration: none;
	border: 1px solid transparent;
	border-bottom: 0;
	border-radius: var(--r) var(--r) 0 0;
	margin-bottom: -1px;
	background: transparent;
}
.section-tabs a:hover { color: var(--ink); background: var(--bg-hover); }
.section-tabs a.is-on,
.section-tabs a.is-on:hover {
	color: var(--ink);
	background: var(--bg-sunk);
	border-color: var(--line);
}

/* ---- viewport-fit table scroller + pagination foot ---- */
/* The page section stays put; only the table scrolls. Height = viewport
   minus the shell (topbar + hero + card chrome), clamped sensibly. */
.tbl-scroll.fit { max-height: calc(100vh - var(--topbar-h) - 265px); min-height: 260px; }
.card-foot {
	display: flex;
	gap: 4px;
	flex-wrap: wrap;
	padding: 8px 12px;
	border-top: 1px solid var(--line);
	background: var(--bg-rail);
}
.page-link {
	font-family: var(--f-mono);
	font-size: 10.5px;
	padding: 2px 8px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	color: var(--ink-2);
	text-decoration: none;
}
.page-link:hover { background: var(--bg-hover); }
.page-link.is-on { background: var(--accent-tint); border-color: var(--accent-tint-2); color: var(--accent-ink); }

/* ---- sortable table headers ---- */
.sort-th { color: inherit; text-decoration: none; white-space: nowrap; }
.sort-th:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.sort-th.is-on { color: var(--accent-ink); font-weight: 600; }

/* ---------------------------------------------------------------------------
   Per-result-type result tables (templates/result_types.inc.php).
   Every page that shows search results renders ONE TABLE PER TYPE, never
   merged. Organic and AI Overview are the two that get read, so they get twice
   the height; the rest are capped short so a page with eight types stays
   scannable (Ben, 2026-08-07).
   --------------------------------------------------------------------------- */
.tbl-scroll.vh50 { max-height: 50vh; }
.tbl-scroll.vh25 { max-height: 25vh; }

/* ============================================================
   V3 TOP NAVIGATION
   The navigation moved from the left rail into the top bar. The
   map of sections, columns and pages is in
   templates/nav.inc.php; the plan is in todo.design.md.
   The rail rules above stay in this file, unused, until Ben
   decides what the left side holds.
   ============================================================ */

/* The rail was removed on 2026-08-23. The top bar holds the brand
   mark, the section buttons and .tb-right. */
.tb-right { flex: 0 0 auto; }

/* ---- section buttons ---- */
.topnav {
	display: flex;
	align-items: stretch;
	gap: 1px;
	flex: 1 1 auto;
	min-width: 0;
	height: 100%;
}
.topnav-sec { position: relative; display: flex; align-items: stretch; }
/* Admin sits at the right end of the bar, beside the theme button. The
   class is set in header.php on the section named Admin, not on the last
   child: a customer has no Admin section, and their last section stays
   with the others. Ben, 2026-08-27. */
.topnav-sec.is-admin { margin-left: auto; }
.topnav-sec.is-admin .sec-ico { margin-right: 0; }
.topnav-btn {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	height: 100%;
	padding: 0 10px;
	border: 0;
	border-bottom: 2px solid transparent;
	background: transparent;
	color: var(--ink-2);
	font-weight: 500;
	white-space: nowrap;
	cursor: pointer;
}
/* The open tab keeps the hover look for as long as its panel is open, even
   after the mouse has left the button and moved down into the panel. The
   selector is the SECTION, not the button, because .mega is inside
   .topnav-sec and the mouse is still over the section the whole time.
   Without this the button goes flat the moment the pointer enters the
   panel, and the only lit tab left is the current page's, which reads as
   though the wrong menu is open.

   It sets background and color only. It must not set border-bottom-color:
   .topnav-sec:hover .topnav-btn outranks .topnav-btn.is-on, so it would
   take the accent underline off the current page's tab. */
.topnav-btn:hover,
.topnav-sec:hover .topnav-btn,
.topnav-sec:focus-within .topnav-btn { background: var(--bg-hover); color: var(--ink); }
.topnav-btn .caret { color: var(--ink-3); flex: none; }
.topnav-btn.is-on { color: var(--ink); border-bottom-color: var(--accent); }

/* ---- the drop-down panel ----
   It opens on hover and on keyboard focus, so it needs no
   JavaScript. --mega-cols is set per section in header.php. */
.mega {
	display: none;
	position: absolute;
	top: 100%;
	left: 0;
	z-index: 40;
	width: max-content;
	max-width: calc(100vw - 20px);
	padding: 12px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-lg);
	background: var(--bg);
	box-shadow: var(--pop);
	grid-template-columns: repeat(var(--mega-cols, 1), minmax(170px, auto));
	gap: 0 10px;
}
/* Every section opens rightwards from its own button, except the last
   two. Those sit hard against the right of the bar, so their panels open
   leftwards and cannot run past the window edge. The rule counts rendered
   sections, so it follows the reader: for an admin the last two are
   Performance and Admin, for a customer they are Outreach and Performance,
   because NavVisibleSections() drops Admin entirely.
   Content opens rightwards with the rest: its panel is two columns and
   is not expected to grow (Ben, 2026-08-11). */
.topnav-sec:nth-last-child(-n+2) .mega { left: auto; right: 0; }
.topnav-sec:hover .mega,
.topnav-sec:focus-within .mega { display: grid; }

.mega-col { min-width: 0; }
.mega-col + .mega-col { border-left: 1px solid var(--line-faint); padding-left: 10px; }
.mega-label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 2px 8px 7px;
	white-space: nowrap;
}
.mega-item {
	display: flex;
	align-items: center;
	gap: 8px;
	min-height: 25px;
	padding: 4px 8px;
	border-radius: var(--r);
	color: var(--ink-2);
	line-height: 1.25;
	cursor: pointer;
}
.mega-item:hover { background: var(--bg-hover); color: var(--ink); }
.mega-item.is-active { background: var(--accent-tint); color: var(--accent-ink); font-weight: 500; }
.mega-item.is-soon, .mega-item.is-soon:hover { background: transparent; color: var(--ink-3); cursor: default; }
/* ---- the third menu column: the kinds under one group ----
   A group row in Opportunities names a group, not a query. The kinds under it
   open on hover, in a panel of their own, to the right of the column the row
   sits in.

   THE ROW IS NOT A LINK, 2026-08-28. It is a <span>, not an <a>: a group has
   no query of its own, so clicking it only opened a page that said to come
   back here and pick a kind. It keeps the look and loses the pointer.

   THE ROW KEEPS ITS HOVER WHILE THE POINTER IS IN ITS KINDS. Ben, 2026-08-28:
   moving across into the third column dropped the highlight off the row it
   came from, so there was nothing left saying which group the list belonged
   to. .mega-kinds sits inside .mega-row, so hovering the panel is hovering
   the row.

   The panel is positioned against .mega-row, so it lines up with its own row
   and not with the top of the menu. A group can hold 20 kinds, so the panel
   scrolls at 60vh rather than running off the bottom of the window. */
.mega-row { position: relative; }
.mega-item.has-kinds { cursor: default; }
.mega-row:hover > .mega-item,
.mega-row:focus-within > .mega-item { background: var(--bg-hover); color: var(--ink); }
.mega-more { margin-left: auto; opacity: 0.5; flex: none; }
.mega-item.has-kinds:hover .mega-more,
.mega-row:hover > .mega-item .mega-more { opacity: 1; }
.mega-kinds {
	display: none;
	position: absolute;
	top: -6px;
	/* Overlaps the row by 6px. There must be NO gap between the row and this
	   panel: a gap is outside .mega-row, so crossing it drops the hover and
	   the whole menu closes. Ben, 2026-08-25. */
	left: calc(100% - 6px);
	z-index: 41;
	min-width: 200px;
	max-height: 60vh;
	overflow-y: auto;
	padding: 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-lg);
	/* --bg-rail, one step darker than the .mega behind it, and --pop-kinds,
	   a shadow on every side with more of it below, so the kinds panel reads
	   as its own panel and not as part of the menu it overlaps. Ben, 2026-08-27. */
	background: var(--bg-rail);
	box-shadow: var(--pop-kinds);
}
.mega-row:hover > .mega-kinds,
.mega-row:focus-within > .mega-kinds { display: block; }
/* The last two sections open leftwards, so their kind panels do too. The
   same 6px overlap, mirrored. */
.topnav-sec:nth-last-child(-n+2) .mega-kinds {
	left: auto;
	right: calc(100% - 6px);
}
.mega-item i {
	margin-left: auto;
	font-family: var(--f-mono);
	font-style: normal;
	font-size: 9px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 0 3px;
	line-height: 14px;
}
.mega-none { padding: 4px 8px 6px; color: var(--ink-3); white-space: nowrap; }

/* ---- breadcrumb menus: campaign and persona ----
   A button, not a label. The caret and the dotted underline say
   the reader can change it. */
.crumbs { flex-wrap: wrap; row-gap: 2px; }
.crumb-btn {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 20px;
	max-width: 230px;
	padding: 0 5px;
	border: 0;
	border-radius: var(--r);
	background: transparent;
	font-family: var(--f-mono);
	font-size: 10.5px;
	color: var(--ink-2);
	cursor: pointer;
}
.crumb-btn .label {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	border-bottom: 1px dotted var(--line-strong);
}
.crumb-btn .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.crumb-btn .caret { color: var(--ink-3); flex: none; }
.crumb-btn:hover { background: var(--bg-hover); color: var(--ink); }
.crumb-btn:hover .label { border-bottom-color: var(--ink-3); }
.menu.is-open .crumb-btn { background: var(--bg-hover); color: var(--ink); }
.crumb-menu .menu-pop { font-family: var(--f-ui); font-size: 13px; }

/* A tab for a page that is not built yet. */
.section-tabs a.is-soon { color: var(--ink-3); cursor: default; }
.section-tabs a.is-soon:hover { color: var(--ink-3); }

/* The How-To tab and the Help tab sit at the right end of the strip. The
   first of the two carries .tab-right and the auto margin pushes both of
   them over, so the page's own tabs keep the left end. */
.section-tabs a.tab-right { margin-left: auto; }

/* ---- the How-To and Help views ----
   templates/docs.inc.php prints these in place of the page body.
   README.how_to.md holds the rules these follow. The three that decide
   everything below, Ben 2026-08-29:

   1. ONE COLUMN, 960px at most, centred, narrowing on its own below that.
   2. THE TEXT IS HALF AS LARGE AGAIN AS THE APPLICATION'S. The application
      is 13px because a report is read by scanning. A How-To is read by
      somebody who is stuck, so it is 17.5px and 1.7 line height. Every
      size in this block is that ratio, and nothing here is a new colour or
      a new component.
   3. THE RECREATED INTERFACE IS AT ITS REAL SIZE. .doc-demo puts the font
      size back to 13px and holds the page's own markup, so what the reader
      sees in the How-To is what they see on the page. */
.doc { max-width: 960px; margin: 0 auto; padding: 4px 0 48px; font-size: 17.5px; }

/* A section is a card, the same card as everywhere else in the
   application. .doc-lead is the sentence under the section title. */
.doc-sec { margin-bottom: 22px; }
.doc-sec:last-child { margin-bottom: 0; }
.doc-sec > .card-head { min-height: 44px; padding: 10px 18px; }
.doc-sec > .card-head .card-title { font-size: 17.5px; }
.doc-sec > .card-head .card-note { font-size: 12px; }
.doc-body { padding: 18px 22px 22px; }

.doc-body > :first-child { margin-top: 0; }
.doc-body > :last-child { margin-bottom: 0; }
.doc-body h3 {
	margin: 26px 0 8px;
	font-size: 19px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--ink);
}
.doc-body p { margin: 0 0 14px; color: var(--ink-2); line-height: 1.7; }
.doc-body ul, .doc-body ol { margin: 0 0 14px; padding-left: 26px; color: var(--ink-2); }
.doc-body li { margin-bottom: 9px; line-height: 1.65; }
.doc-body li:last-child { margin-bottom: 0; }
.doc-body b, .doc-body strong { color: var(--ink); font-weight: 600; }
/* Scoped to the article's own prose. A bare `.doc-body a` also matched
   every link inside a .doc-demo, and it sits later in this file than
   `.section-tabs a`, so the recreated tab strip drew its tabs in the accent
   colour instead of the colour the page draws them in. A demo is the page's
   own markup and every component rule in it has to win. */
.doc-body p a, .doc-body li a { color: var(--accent-ink); }
.doc-body code {
	font-family: var(--f-mono);
	font-size: 0.86em;
	background: var(--bg-sunk);
	border: 1px solid var(--line-faint);
	border-radius: var(--r);
	padding: 1px 5px;
}
/* The one place a How-To raises its voice. Same .callout as the rest of
   the application, at the How-To's own size. */
.doc-body .callout { margin: 0 0 14px; font-size: 15.5px; line-height: 1.6; padding: 12px 14px; }
.doc-body .callout:last-child { margin-bottom: 0; }

/* ---- the recreated interface ----
   The page's own markup, at the page's own size, so the reader recognises
   it when they get there. It is wrapped in `inert` so nothing in it takes
   a click or a keystroke, and it holds no <form>, so nothing can be
   posted from a How-To. */
.doc-demo {
	margin: 0 0 18px;
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	background: var(--bg-sunk);
	font-size: 13px;
	line-height: 1.45;
}
.doc-demo-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 12px;
	border-bottom: 1px solid var(--line);
	font-family: var(--f-mono);
	font-size: 10.5px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
}
.doc-demo-body { padding: 14px; min-width: 0; }
/* A card inside the frame is the page's card, so it keeps its own border
   and drops the shadow: it is not floating, it is being pointed at. */
.doc-demo-body .card { box-shadow: none; min-width: 0; }
.doc-demo-body p, .doc-demo-body li { font-size: 13px; line-height: 1.45; }

/* A DEMO IS ALWAYS IN A NARROW COLUMN, whatever the window is.
   .doc is 960px at most, so a demo frame is about 890px wide on a 1600px
   screen as much as on a 1000px one. The result card stacks its two columns
   at a 1180px WINDOW, which never fires here, so the right column's 140px
   tables pushed the card wider than the frame and the description and the
   url overflowed. Ben, 2026-08-29. Stack it inside a demo always. */
.doc-demo-body .idx-hit,
.doc-demo-body .idx-hit-3 { grid-template-columns: 1fr; }
.doc-demo-body .idx-facts { flex-wrap: wrap; }
.doc-demo-body .idx-hit-main,
.doc-demo-body .idx-hit-side,
.doc-demo-body .idx-title-col { min-width: 0; }
.doc-demo-body .nx-snip,
.doc-demo-body .idx-url { overflow-wrap: anywhere; }

/* The same reason: a two-column layout copied from a page assumes the
   page's width. In a demo frame both halves go full width. */
.doc-demo-body .grid-2,
.doc-demo-body .grid-3,
.doc-demo-body .grid-4,
.doc-demo-body .co-layout,
.doc-demo-body .co-grid { grid-template-columns: 1fr; display: grid; }
.doc-demo-body .grid-2 > *,
.doc-demo-body .grid-3 > *,
.doc-demo-body .grid-4 > * { min-width: 0; }

/* The backstop. A table copied from a page can still be wider than 890px;
   it scrolls inside its own frame and never widens the article. */
.doc-demo-body > table,
.doc-demo-body .card-body > table { display: block; overflow-x: auto; }

/* ---- the Help view ----
   The Help view is NOT a How-To. Ben, 2026-08-29: it has to look like it is
   exactly part of the application, and the How-To is the one exception to
   that. So it is built from .card, .card-head, .card-body, .field,
   .control, .hint and .form-foot, the same markup as the forms on
   campaign_edit.php, and it has no styles of its own. .doc-plain is the
   only class it adds: the same 960px column at the application's own
   13px, so none of the .doc sizing above reaches it. */
.doc-plain { font-size: 13px; }

/* ============================================================
   THE .nx-form / .nx-field FORM ROWS
   The index and lookup forms: Site Info, Link Graph, Backlinks
   DB and the index search form. The .nx-hit result block that
   opened this section is retired (IdxHitCardHtml() draws every
   result since 2026-08-28); its rules went with the 2026-08-30
   style.css cleanup.
   ============================================================ */
.nx-form .row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.nx-form .row:last-child { margin-bottom: 0; }
.nx-field { display: flex; flex-direction: column; gap: 3px; flex: 1 1 200px; min-width: 0; }
.nx-field.quarter { flex: 1 1 calc(25% - 8px); }
.nx-field.fifth   { flex: 1 1 calc(20% - 8px); }

/* One half of the query row: the text box, then its two rank floors.
   Left half is domain-level, right half is host-level. */
.nx-col { flex: 1 1 340px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
/* .nx-field carries flex: 1 1 200px for the horizontal rows. Inside a
   column that 200px is a HEIGHT, which opened a large empty gap between
   the text box and its two rank floors. */
.nx-col > .nx-field { flex: 0 0 auto; }
.nx-col .nx-sub { display: flex; gap: 10px; }
.nx-col .nx-sub .nx-field { flex: 1 1 0; }

/* input[type=date] is not in the legacy .view control list further up
   this file, so without this it renders as the browser default and looks
   nothing like the boxes beside it. */
.view input[type=date] {
	font: 12.5px/1.4 var(--f-ui);
	padding: 4px 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink);
	height: 26px;
	width: 100%;
	min-width: 0;
	font-family: var(--f-mono);
	font-size: 11.5px;
}
.view input[type=date]::-webkit-calendar-picker-indicator {
	opacity: 0.45;
	cursor: pointer;
	padding: 0;
	margin-left: 2px;
}
.view input[type=date]:hover::-webkit-calendar-picker-indicator { opacity: 0.8; }
.view input[type=date]::-webkit-datetime-edit { color: var(--ink); }
.view input[type=date]:invalid::-webkit-datetime-edit { color: var(--ink-3); }
.view input[type=date]:focus {
	border-color: var(--accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--accent-tint);
}

/* THE 40vh BOX IS GONE, 2026-08-28. Ben: the result list on Brand Mentions,
   Competitor Mentions, Link Mentions, Industry News, Industry Monitor and
   News Search scrolled inside a 40vh box of its own, while the same result
   cards on the Opportunities and Content Landscape pages flow down the page.
   Two scrollbars for one list, and the two halves of the application did not
   match. The list flows now, everywhere. The class is kept because seven
   pages name it and it is still the hook for the result list. */
.nx-results { }
.nx-field > label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
}
.nx-field input, .nx-field select { width: 100%; }
/* ── THE LOOKUP FORMS FOLLOW THE SEARCH FORM, 2026-08-31 (Ben) ────────
   Site Info, Backlinks DB and Link Graph are built from .nx-form, which
   is a third form vocabulary. It is not replaced; it is given the search
   form's measurements, so the three sets of pages read as one product.
   Same 28px control height, same --line-strong edge, same caret on a
   select, and a button that is a button rather than a bar the width of
   its column. */
.nx-form .row { align-items: end; gap: 10px 12px; }
/* ── THE ROW PACKS LEFT AND THE BUTTON IS PART OF IT. Ben, 2026-08-31:
   "it's weird having the input all the way on the left and then the button
   all the way on the right", and "there's a huge gap between show per
   field and the full record". Both were the same rule: a .nx-field grew to
   fill the row (flex: 1 1 200px, and 20 or 25 percent for .fifth and
   .quarter), so three fields and a button were spread across 1,370px. A
   field is the width of its own control now, the row packs to the left,
   and the button follows the last field. The textarea fields are outside
   these rows, or inside a .nx-col, so neither is touched. */
.nx-form .row > .nx-field,
.nx-form .row > .nx-field.quarter,
.nx-form .row > .nx-field.fifth { flex: 0 1 auto; }

.nx-field { gap: 4px; }
.nx-form input[type=text], .nx-form input[type=search],
.nx-form input[type=number], .nx-form select {
	height: 28px;
	padding: 0 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background-color: var(--bg);
	color: var(--ink);
	font: 12.5px/1.4 var(--f-ui);
}
/* A SELECT IS THE WIDTH OF ITS LONGEST OPTION, not of its column: the
   Sort by box on Link Graph: Multi-Site was 690px wide for 26 characters
   of text. */
/* EVERY BOX IS THE WIDTH OF WHAT IT HOLDS, not of the row. A host box is
   320px, a two or three digit box is 120px, and a select is as wide as its
   longest option. Inside a .nx-col, which is a half of the row, a box
   takes the whole half. */
.nx-form select { width: auto; max-width: 100%; min-width: 0; }
.nx-form input[type=text], .nx-form input[type=search] {
	width: 320px;
	max-width: 100%;
}
.nx-form input[type=number] { width: 120px; }
.nx-form .nx-col input, .nx-form .nx-col select { width: 100%; }
/* The caret, drawn the way select.control draws it: these are bare
   <select> elements, so the legacy `.view select` block is all they had
   and its `background:` shorthand left them with the browser's own. */
.nx-form select {
	appearance: none;
	-webkit-appearance: none;
	padding-right: 26px;
	background-image:
		linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
		linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
	background-position: calc(100% - 14px) 12px, calc(100% - 9px) 12px;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
}
.nx-form input:focus, .nx-form select:focus, .nx-form textarea:focus {
	border-color: var(--accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--accent-tint);
}
/* A TICK BOX SITS ON THE CONTROL LINE and is the styleguide's own
   .check-chip, so it is the same tick box as the ones on the two Rank
   Tracking list pages. Site Info: Site is the one lookup form that has
   one. */
.nx-form .check-chip { height: 28px; align-self: flex-start; }
/* A chip is a <label>, so the `.nx-field > label` rule above would set it
   in small mono capitals. It is a control, not a field name. */
.nx-field > label.check-chip {
	font-family: var(--f-ui);
	font-size: 12px;
	letter-spacing: normal;
	text-transform: none;
	color: var(--ink-2);
}
/* A BUTTON IS THE WIDTH OF ITS WORDS. It sits in a .nx-field, which is a
   stretched flex column, so Look up and Search were bars 690px wide. */
.nx-form button {
	align-self: flex-start;
	width: auto;
	height: 28px;
	padding: 0 16px;
	font-size: 12px;
	font-weight: 500;
	border-radius: var(--r);
}
.nx-form .nx-field > button { align-self: flex-end; }
.nx-simhead { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 3px; }
.nx-simhead > label { margin-right: auto; }
.nx-simopt { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.nx-simopt label { white-space: nowrap; }
.nx-simopt input[type=number], .nx-simopt .f-in { width: 62px; }
/* The min-similarity and threshold boxes in the similarity head line.
   Written AFTER the .idx-field-text .f-in width:100% rule cannot reach
   here, and more specific than it, so the two little boxes stay little. */
.idx-search .idx-field-text .nx-simhead .f-in { width: 62px; }
/* The Search and Clear buttons of the shared advanced form sit side by
   side at the right end of their row. */
.idx-search .idx-field-go { margin-left: auto; align-self: flex-end; flex-direction: row; gap: 6px; }
.nx-form textarea { width: 100%; min-height: 74px; resize: vertical; }
/* The filled primary button is `button.btn-primary` in the button block
   now, for every form in the application, so this file no longer restates
   it per form. Ben, 2026-08-31. */
.nx-form button.btn-primary { padding: 0 16px; }

.nx-meta {
	display: flex;
	align-items: center;
	gap: 7px;
	flex-wrap: wrap;
	font-size: 11.5px;
	color: var(--ink-3);
	margin: 3px 0 6px;
}
.nx-meta b { color: var(--ink-2); font-weight: 600; }
.nx-snip { margin: 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.nx-sim { color: var(--pos); font-weight: 600; font-family: var(--f-mono); }
.nx-actions { display: flex; gap: 12px; align-items: center; margin-top: 7px; flex-wrap: wrap; }
.nx-actions a, .nx-actions summary {
	font-family: var(--f-mono);
	font-size: 10px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	cursor: pointer;
}
.nx-actions a:hover, .nx-actions summary:hover { color: var(--accent-ink); }
.nx-actions details { display: inline; }
.nx-drop { margin-top: 8px; }
.nx-drop pre {
	background: var(--bg-sunk);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 9px;
	font-size: 11px;
	line-height: 1.45;
	max-height: 340px;
	overflow: auto;
	margin: 0;
	white-space: pre-wrap;
	word-break: break-word;
}
.nx-dup { padding: 6px 0; border-bottom: 1px solid var(--line-faint); }
.nx-dup:last-child { border-bottom: 0; }
.nx-dup a { font-size: 12.5px; color: var(--accent-ink); }
.nx-dup a:hover { text-decoration: underline; }
.nx-dup-meta { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
/* A duplicate row holds a title and a url, and a url is one unbreakable
   word, so the box scrolled sideways by up to 87px at every window width.
   Ben, 2026-08-28: it wraps. */
.nx-dupbody { background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--r); padding: 2px 10px; margin-top: 6px; max-height: 240px; overflow-y: auto; overflow-x: hidden; }
.nx-dup, .nx-dup a, .nx-dup-meta { overflow-wrap: anywhere; }
/* news_similar.php: the "same article" box is the main list of its card,
   so it is taller than a fold-out inside one result. */
.nx-scroll { max-height: 380px; margin-top: 0; }

/* ============================================================
   INDEX SEARCH FORM (IdxHit/search.inc.php) - the one search
   form of the News Index and the Prospect Index.
   ============================================================
   TWO FORMS, ONE LOOK. Ben, 2026-08-31: the simple form
   (IdxBasicSearchFormHtml, on every opportunity type and
   content type page) and the advanced form
   (IdxAdvancedSearchFormHtml, on Advanced Search) were two
   different designs on the same screen. They are one design
   now, and every rule below names both.

   What the design is, in four rules:

   1. THE FORM IS A CARD. --bg, the card surface, on the --bg-sunk
      canvas, so the form is a thing sitting on the page rather
      than a piece of the page. It was --bg-sunk, the same colour
      as what is behind it, and it had no edge to see.
   2. EVERY CONTROL IS --idx-h TALL. An input, a select, a date
      box and a button are all 28px, so a row lines up along its
      bottom whatever it holds. The Search button used to be
      25px and sat above the boxes beside it.
   3. A LABEL IS THE SAME LABEL EVERYWHERE. Mono, 9.5px,
      uppercase. The advanced form printed its similarity label
      as plain 13px body text and nothing else on either form
      looked like that.
   4. A NUMBER IS CENTRED UNDER ITS LABEL. A box that holds two
      or three digits is narrower than the words above it, so the
      column centres it and the digits are centred inside it.
      Ben: "the small numeric inputs aren't centered above their
      headers".
   ============================================================ */
.idx-search,
.idx-basic {
	--idx-h: 28px;
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	padding: 11px 13px 12px;
	margin-bottom: 12px;
}
/* THE SIMPLE FORM IS STICKY, so the reader changes a number without
   scrolling back up. z-index 3 sits over the sticky column labels
   (thead th is 2) and under the page head (5). The shadow is what
   separates it from the results sliding under it. */
.idx-basic {
	position: sticky;
	top: 0;
	z-index: 3;
	box-shadow: var(--shadow-card);
}
/* A ROW ENDS FLAT. align-items: end puts every control on the same
   bottom line, whatever the label above it wraps to. */
.idx-search-row {
	--idx-h: 28px;
	display: flex;
	flex-wrap: wrap;
	align-items: end;
	gap: 10px 12px;
}
.idx-search-row + .idx-search-row,
.idx-field-full + .idx-search-row { margin-top: 10px; }
.idx-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.idx-field > label,
.idx-search .nx-simhead > label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	white-space: nowrap;
}
.idx-field-wide { flex: 1 1 230px; max-width: 320px; }
.idx-field-wide select { width: 100%; }
/* The text boxes take the width the row has left. The shared .f-in
   width below is a number's width. */
.idx-search-row .idx-field-text { flex: 2 1 260px; max-width: none; }
.idx-search-row .idx-field-text .f-in { width: 100%; }
/* The similarity textarea and its head line take the whole row. */
.idx-field-full { flex: 1 1 100%; }
/* A NUMBER FIELD: the box is centred under the label and the digits
   are centred in the box. */
.idx-field-sm { flex: 0 0 auto; align-items: center; }
.idx-field-sm .f-in { width: 66px; }
/* A MIN AND MAX PAIR, and a group of counts, centre their label over the
   boxes for the same reason: the label is wider than what it labels. */
.idx-field-pair > label,
.idx-field-grp > label { text-align: center; }
/* A group of four counts under one label, so "Article links out" is said
   once instead of four times. The inner labels are the rel types. */
.idx-field-grp .f-pair { gap: 6px; }
.idx-field-grp .idx-field > label {
	font-size: 9px;
	letter-spacing: 0.04em;
	text-transform: lowercase;
	text-align: center;
}
.idx-field-grp .f-in { width: 52px; }
/* THE BUTTONS SIT AT THE END OF THE ROW, ON THE ROW'S BOTTOM LINE.
   Both forms, from 2026-08-31: only the advanced form had this, so the
   simple form's Search button floated at the top of its row. */
.idx-search-row .idx-field-go {
	flex: 0 0 auto;
	margin-left: auto;
	flex-direction: row;
	align-items: center;
	gap: 8px;
}
.idx-search-row .f-pair { display: flex; gap: 6px; }
.idx-search-row .f-in {
	font-family: var(--f-mono);
	font-size: 11.5px;
	height: var(--idx-h);
	padding: 0 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink);
	width: 66px;
	min-width: 0;
}
.idx-search-row .f-num { text-align: center; }
.idx-search-row .f-in::placeholder { color: var(--ink-3); }
.idx-search-row .f-in:focus {
	border-color: var(--accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--accent-tint);
}
/* A date box is an input of its own type and the legacy `.view input`
   rules never reached it, so it is sized here with the rest. */
.idx-search-row input[type=date] {
	width: auto;
	font-size: 11.5px;
	padding: 0 6px;
}
/* The selects: the same height, border and type size as an input, and
   wide enough for the longest option they hold.
   THE CARET IS DRAWN AGAIN HERE. `select.control` paints it as two
   background gradients, and the legacy `.view select` block further up
   this file sets `background: var(--bg)`, a SHORTHAND, at the same
   specificity and later in the file, so every select inside .view lost
   its arrow. Naming the image here, on a more specific selector, puts it
   back on the form's own selects. Ben, 2026-08-31. */
.idx-search-row select.control.sm {
	width: auto;
	min-width: 128px;
	height: var(--idx-h);
	min-height: var(--idx-h);
	padding: 0 26px 0 8px;
	font-size: 12px;
	border-color: var(--line-strong);
	background-color: var(--bg);
	background-image:
		linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
		linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
	background-position: calc(100% - 14px) 12px, calc(100% - 9px) 12px;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
}
/* The similarity text box. A bare <textarea>, so the legacy
   `.view textarea` rule is what it had. */
.idx-search textarea {
	width: 100%;
	min-height: 68px;
	resize: vertical;
	font: 12.5px/1.5 var(--f-ui);
	padding: 7px 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink);
}
.idx-search textarea:focus {
	border-color: var(--accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--accent-tint);
}
/* EVERY BUTTON IN THE FORM IS THE HEIGHT OF AN INPUT, AND SEARCH IS
   THE ONE THAT IS FILLED. Ben, 2026-08-31: "the search button isn't
   even a different colour, it blends in with all the form fields".
   The colour has to be restated with the element named, because
   `.view button` further up this file is a class plus an element and
   outranks a bare `.btn-primary`. The same trap is recorded there. */
.idx-search-row .btn { height: var(--idx-h, 28px); font-size: 12px; }
.idx-search-row button.btn-primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-accent);
	padding: 0 16px;
	font-weight: 600;
}
.idx-search-row button.btn-primary:hover {
	background: var(--accent-ink);
	border-color: var(--accent-ink);
}
.idx-search-row button.btn:not(.btn-primary) {
	border-color: var(--line-strong);
	color: var(--ink-2);
}
.idx-search-row button.btn:not(.btn-primary):hover {
	background: var(--bg-hover);
	color: var(--ink);
}
/* The head line of the similarity box: its label on the left, the
   minimum and the dedupe box on the right. The two options keep sentence
   case, because each one is a sentence with a control inside it, and they
   are set in the small quiet size the rest of the form's helper text uses. */
.idx-search .nx-simhead { align-items: center; gap: 6px 16px; margin-bottom: 5px; }
.idx-search .nx-simopt { gap: 7px; font-size: 11.5px; color: var(--ink-2); }
.idx-search .nx-simopt input[type=checkbox] { margin: 0; }
/* The tick is the application's own teal, not the browser's blue. It is
   set for every box on every page, so one form cannot tick in a different
   colour from the next. Ben, 2026-08-31. */
.view input[type=checkbox], .view input[type=radio],
.idx-search input[type=checkbox],
.idx-basic input[type=checkbox] { accent-color: var(--accent); }
/* A SET OF TICK BOXES UNDER ONE LABEL, in a search-form row: the sources
   of the add box and the providers and days of the ticked-rows card on the
   two Rank Tracking list pages. `.check-chip` is the styleguide's own tick
   box; the set wraps rather than pushing the row wider. Ben, 2026-08-31. */
.chip-field { flex: 0 1 auto; min-width: 0; }
.chip-set { display: flex; flex-wrap: wrap; gap: 6px; }
.chip-set .check-chip { height: var(--idx-h, 28px); }
/* A second button group in the same row, beside the fields rather than at
   the end of the row. */
.idx-field-go.idx-go-inline { margin-left: 0; }
/* The filter row of the two Rank Tracking list pages is the search form's
   row, so it takes the search form's card. */
.track-filters { margin-top: 12px; }
/* THE TICKED-ROWS CARD IS TWO COLUMNS: the typed filter on the left third,
   the actions on the right two thirds. Ben, 2026-08-31. Under 1000px they
   stack, the filter first. */
.tl-cols {
	display: grid;
	grid-template-columns: 1fr 2fr;
	gap: 16px;
	align-items: start;
}
.tl-filter { min-width: 0; border-right: 1px solid var(--line); padding-right: 16px; }
.tl-filter .f-in { width: 100%; }
.tl-actions { min-width: 0; }
@media (max-width: 1000px) {
	.tl-cols { grid-template-columns: 1fr; }
	.tl-filter { border-right: 0; padding-right: 0; border-bottom: 1px solid var(--line); padding-bottom: 12px; }
}

/* A FORM INSIDE A CARD SITS ON THE RECESSED SURFACE. The card is --bg on
   the --bg-sunk canvas, so a filter panel inside it takes --bg-sunk and
   the depth still reads in the right order: canvas, card, panel. The
   Opportunities tab of campaign_edit is the one place this happens. */
.card .idx-search { background: var(--bg-sunk); }
.idx-search .idx-field-text .nx-simhead .f-in { width: 58px; }
/* The fold-out. The hairline above it is what separates the fields
   that are always shown from the ones that are not. */
.idx-search-adv { margin-top: 11px; border-top: 1px solid var(--line); padding-top: 9px; }
.idx-search-adv > summary {
	font-family: var(--f-mono);
	font-size: 10px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	cursor: pointer;
	width: fit-content;
}
.idx-search-adv > summary:hover { color: var(--accent-ink); }
.idx-search-adv[open] > summary { margin-bottom: 10px; }
.idx-search-note {
	margin: 9px 0 0;
	font-size: 11px;
	line-height: 1.45;
	color: var(--ink-3);
}

/* ============================================================
   SITE INFO (siteinfo.inc.php) - the panels of Site Info: Site
   ============================================================
   Ben, 2026-08-31: the page was one full-width table per field,
   in the order the record held them, with 65 hosts at the top.
   It is five named sections now, two cards to a row, each card
   scrolling inside itself. `.si-grid-1` is the row for a card
   too wide to share: the two nested tables, Locations and Hosts.
   ============================================================ */
.si-head {
	font-family: var(--f-mono);
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	margin: 18px 0 8px;
}
.si-head:first-of-type { margin-top: 4px; }
.si-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 12px;
	margin-bottom: 12px;
	/* THE CARDS OF A ROW ARE THE SAME HEIGHT. Ben, 2026-08-31: Partner pages
	   holds one line, and beside a six-line card it left a hole of page
	   background. The card fills the row instead, and its scroll area grows
	   with it. */
	align-items: stretch;
}
.si-grid-1 { grid-template-columns: minmax(0, 1fr); }
.si-card {
	margin: 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.si-card > .tbl-scroll { flex: 1 1 auto; }
/* AN ODD LAST CARD TAKES THE WHOLE ROW, for the same reason: three cards in
   two columns left the third one beside half a row of background. */
.si-grid > .si-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* SOCIAL ACCOUNTS: THE ACCOUNT IS CUT, THE OTHER THREE COLUMNS ARE NOT.
   Ben, 2026-09-01: a long account name squeezed Service and Type to nothing
   as the window narrowed. Service, Type and Pages take the width of their
   own words; the account takes what is left and ends in an ellipsis, with
   the whole value in its title. `max-width: 0` with `width: 100%` is what
   makes a cell of an auto-layout table clip instead of push. */
.si-accounts th, .si-accounts td { white-space: nowrap; }
.si-accounts th:nth-child(2), .si-accounts td:nth-child(2) { width: 72px; }
.si-accounts td.si-acct {
	width: 100%;
	max-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}
@media (max-width: 1180px) { .si-grid { grid-template-columns: minmax(0, 1fr); } }

/* ============================================================
   INDEX HIT CARD (IdxHit/) — the shared News Index / Prospect
   Index result card. Used by every "News Index" / "Prospect
   Index" tab across Landscape, Monitoring and Prospects.
   Left ~2/3 is the result itself; right ~1/3 is a slot for two
   short vertical mention lists (brand / competitor).
   ============================================================ */
.idx-hit {
	display: grid;
	/* 75/25. The right column holds three narrow tables and was wider than
	   it needs to be at a third. Ben, 2026-08-24. */
	grid-template-columns: 3fr 1fr;
	gap: 16px;
	/* start, not the stretch a grid uses by default: with stretch, opening
	   the Details panel made the left column tall, and the right column's
	   table stretched with it, spreading three rows over 300px. */
	align-items: start;
	border-top: 1px solid var(--line-faint);
	padding: 12px 0;
}
.idx-hit:first-child { border-top: 0; }
/* Three columns when the caller passes extra_html. Ben, 2026-08-28, for Brand
   Link Gap. The third column was 1fr and is half of that now: it holds a
   count and a few short host names, so it needs about half what the figure
   tables beside it need. column-gap is wider than the card's own 16px,
   because the middle column's tables run the full width of their column and
   sat hard against the box. */
.idx-hit-3 {
	grid-template-columns: 3fr 1fr 0.5fr;
	column-gap: 24px;
}
.idx-hit-main  { min-width: 0; }
.idx-hit-side  { min-width: 0; }

/* A SEARCH RESULT CARD STACKS UNDER 1180px. Ben, 2026-08-28. The card is
   3fr 1fr, and the right column holds the Rank/PR/PRoT and the Links out
   tables, whose cells do not wrap. Under about 1180px that column is 93px
   against the 140px those tables need, so the card scrolled sideways.
   Stacked, the two tables get the whole width.
   THIS BLOCK SITS AFTER .idx-hit ON PURPOSE: a media query adds no
   specificity, so the plain .idx-hit rule above would win from further down
   the file. */
@media (max-width: 1180px) {
	.idx-hit, .idx-hit-3 { grid-template-columns: 1fr; }
	.idx-facts { flex-wrap: wrap; }
	/* .idx-det-tbl is left alone: it is table-layout: fixed with
	   overflow: hidden and an ellipsis, and the whole value is in its
	   title="", so it truncates rather than pushing the card wider. */
}
.idx-hit-extra { min-width: 0; }
@media (max-width: 900px) {
	.idx-hit, .idx-hit-3 { grid-template-columns: 1fr; }
}

/* The competitor box on Brand Link Gap. .ent carries the relationship colour
   already, so the rows here look the same as the ones in the figure tables. */
.gap-box { font-size: 11.5px; }
.gap-box .gap-n {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	display: block;
	margin-bottom: 4px;
}
/* A pill in this box wraps. .ent is nowrap everywhere else, which is right in
   a table cell and wrong in a column this narrow: a long host name would run
   out of the card. */
.gap-box .ent {
	display: flex;
	margin: 2px 0;
	white-space: normal;
	overflow-wrap: anywhere;
}

.idx-title-row { display: flex; align-items: flex-start; gap: 8px; }
.idx-favicon { width: 16px; height: 16px; margin-top: 2px; flex: none; border-radius: 2px; }
.idx-title-col { min-width: 0; flex: 1 1 auto; }
.idx-title-col .t { font-size: 14px; font-weight: 600; color: var(--accent-ink); word-break: break-word; }
.idx-title-col .t:hover { text-decoration: underline; }
/* The URL and the host name are links, with the colour they had as plain
   text and no hover state of their own. Ben, 2026-08-24: anywhere a URL
   or a host name is printed, it opens. */
a.idx-url { display: block; font-family: var(--f-mono); font-size: 10.5px; color: var(--ink-3); word-break: break-all; margin-top: 1px; }
a.idx-url:hover { color: var(--ink-3); text-decoration: none; }
.idx-hit .nx-meta a.idx-host { color: var(--ink-2); text-decoration: none; }
.idx-hit .nx-meta a.idx-host:hover { color: var(--ink-2); text-decoration: none; }

.idx-sim-date { flex: none; text-align: right; }
.idx-sim-date .idx-date { display: block; font-size: 10.5px; color: var(--ink-3); margin-top: 2px; white-space: nowrap; }
/* Two measures, side by side and coloured apart. Similarity is the vector
   distance; relevance is the advanced query's full-text weight. */
.idx-scores { display: block; white-space: nowrap; }
.idx-rel { color: var(--blue); font-weight: 600; font-family: var(--f-mono); }
.idx-sep { color: var(--ink-3); margin: 0 5px; }

/* The row under the snippet: the Details fold-out on the left, the two
   similarity searches on the right. The links are positioned rather than
   laid out beside the fold-out, so the panel keeps the full card width. */
.idx-actions-row { position: relative; min-height: 15px; margin-top: 7px; }
.idx-hit-links {
	position: absolute; top: 0; right: 0;
	display: flex; align-items: center;
	font-family: var(--f-mono); font-size: 10px;
	letter-spacing: 0.06em; text-transform: uppercase;
}
.idx-hit-links a { color: var(--ink-3); }
.idx-hit-links a:hover { color: var(--accent-ink); }
.idx-hit-links .idx-sep { margin: 0 6px; }

/* A "?" beside a label. Hover or focus shows data-help at once: a native
   title="" waits about a second before it shows. campaign_edit.php. */
.help {
	display: inline-flex; align-items: center; justify-content: center;
	width: 15px; height: 15px; margin-left: 3px; border-radius: 50%;
	border: 1px solid var(--line-strong); color: var(--ink-3);
	font-size: 10px; font-weight: 600; line-height: 1; cursor: default;
	position: relative; vertical-align: 1px;
}
.help:hover, .help:focus { color: var(--accent-ink); border-color: var(--accent); outline: none; }
.help::after {
	content: attr(data-help);
	display: none;
	position: absolute; z-index: 5; left: 0; top: 100%; margin-top: 5px;
	width: 300px; padding: 7px 9px;
	background: var(--bg); color: var(--ink-2); border: 1px solid var(--line);
	border-radius: var(--r-lg); box-shadow: var(--shadow-card);
	font-size: 12px; font-weight: 400; line-height: 1.45; white-space: normal;
	text-align: left;
}
.help:hover::after, .help:focus::after { display: block; }

/* Hover popovers, zero delay: a native title="" tooltip has a browser
   delay, so the account/email/phone list is a CSS-only popover shown on
   :hover/:focus instead. */
.idx-hoverwrap { position: relative; display: inline-flex; }
.idx-hovericon {
	display: inline-flex; align-items: center; justify-content: center;
	width: 20px; height: 20px; border-radius: var(--r);
	color: var(--ink-3); cursor: default;
}
.idx-hoverwrap:hover .idx-hovericon, .idx-hoverwrap:focus-within .idx-hovericon {
	color: var(--accent-ink); background: var(--accent-tint);
}
.idx-hoverpop {
	display: none;
	position: absolute; z-index: 5; top: 100%; right: 0; margin-top: 4px;
	min-width: 180px; max-width: 260px;
	background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg);
	box-shadow: var(--shadow-card); padding: 6px 0;
}
.idx-hoverwrap:hover .idx-hoverpop, .idx-hoverwrap:focus-within .idx-hoverpop { display: block; }
.idx-hoverpop a, .idx-hoverpop span {
	display: block; padding: 4px 10px; font-size: 12px; color: var(--ink-2);
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.idx-hoverpop a { color: var(--accent-ink); }
.idx-hoverpop a:hover { background: var(--bg-hover); }
/* The service name at the top of the list, so an unfamiliar glyph is
   never the only thing naming the account. */
.idx-hoverpop .idx-hoverpop-head {
	font-family: var(--f-mono); font-size: 9.5px; font-weight: 600;
	letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3);
	border-bottom: 1px solid var(--line-faint); padding-bottom: 4px;
	margin-bottom: 2px;
}

.idx-social { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }

/* The right column's figure tables: PageRank beside the outbound link
   counts. They were a stack of headings and loose chips until
   2026-08-24, which made every result about 230px tall and left the
   left column mostly empty. Two tables side by side hold the same six
   numbers in four rows. */
.idx-facts { display: flex; gap: 8px; align-items: flex-start; margin-bottom: 8px; }
.idx-fact-tbl {
	border-collapse: collapse;
	font-size: 11.5px;
	flex: 1 1 0;
	min-width: 0;
	table-layout: auto;
}
.idx-fact-tbl th,
.idx-fact-tbl td {
	border: 1px solid var(--line-faint);
	padding: 2px 6px;
	text-align: left;
	white-space: nowrap;
}
/* NOT STICKY, 2026-08-28. The global `thead th` rule makes every column label
   in the application sticky, which is right for a report table of hundreds of
   rows and wrong here: these are the two-row Rank/PR/PRoT and Links out tables
   inside one search result card. A sticky element with no scrolling ancestor
   sticks to the WINDOW, so those labels lifted off their card and slid down
   the page as the reader scrolled. Ben reported it on the Opportunities pages
   and on Brand Mentions, 2026-08-28. The same applies to .idx-det-tbl in the
   Details panel below. */
.idx-fact-tbl thead th,
.idx-det-tbl thead th {
	position: static;
}
.idx-fact-tbl thead th {
	background: var(--bg-rail);
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
}
.idx-fact-tbl tbody th {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	background: var(--bg-rail);
	width: 1%;
	vertical-align: top;
}
.idx-fact-tbl tbody td { color: var(--ink-2); }
/* Centred, not right aligned: these columns are two or three digits wide
   and read better centred under their label. Ben, 2026-08-24. */
.idx-fact-tbl .num {
	text-align: center;
	font-family: var(--f-mono);
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}
.idx-fact-tbl thead .num { color: var(--ink-3); }
/* A zero is printed, not dropped: it says the page was read and had none.
   It recedes so the numbers that are not zero carry the row. */
.idx-fact-tbl tr.is-zero td { color: var(--ink-3); }
.idx-fact-tbl tr.is-zero .num { color: var(--ink-3); }

/* The wide mentions/contact table under the two figure tables. */
.idx-fact-wide { width: 100%; }
.idx-fact-wide td { white-space: normal; }
.idx-org {
	display: inline-block;
	margin: 0 6px 0 0;
	color: var(--ink-2);
}
.idx-org + .idx-org { border-left: 1px solid var(--line); padding-left: 6px; }
.idx-none { color: var(--ink-3); font-size: 11.5px; }

/* The expandable "Details" panel: everything the card summarizes, in
   full, in a small grid. Replaces a raw JSON dump — nothing here is
   meant to be read as a payload, only as a list a reader can act on. */
.idx-details { margin-top: 6px; }
.idx-details > summary {
	display: inline-block;
	font-family: var(--f-mono);
	font-size: 10px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	cursor: pointer;
}
.idx-details > summary:hover { color: var(--accent-ink); }
.idx-details[open] > summary { margin-bottom: 4px; }
/* display:inline-block drops the browser's own disclosure triangle, so
   the open/closed mark is drawn here instead. */
.idx-details > summary::marker,
.idx-details > summary::-webkit-details-marker { display: none; content: ""; }
.idx-details > summary::before { content: "\25B8\00A0"; }
.idx-details[open] > summary::before { content: "\25BE\00A0"; }
.idx-details-panel {
	margin-top: 6px;
	padding: 10px 12px;
	background: var(--bg-sunk);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
}
/* A minimum width per column, so all five sit on one row and a long value
   is cut rather than wrapping the column. */
.idx-details-grid {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: 10px 14px;
}
/* The links-out row: always three columns, one per page section, so
   article, nav and UGC line up under each other and can be compared. */
.idx-details-grid.is-sections {
	grid-template-columns: repeat(3, 1fr);
	margin-top: 10px;
	padding-top: 10px;
	border-top: 1px solid var(--line);
}
@media (max-width: 900px) {
	.idx-details-grid.is-sections { grid-template-columns: 1fr; }
}
.idx-details-col { min-width: 0; }
.idx-details-col h4 {
	margin: 0 0 5px; font-family: var(--f-mono); font-size: 10px;
	letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3);
	font-weight: 600;
}
.idx-details-col h4 b {
	color: var(--ink-2); font-weight: 600;
}
/* Every Details column is a table in a scrolling box: one page carried
   302 outbound links, and the panel must not grow to hold them. The BOX
   scrolls, never the table: a table set to display:block stops honouring
   width:100%, which floats the count column in mid-panel. */
.idx-det-scroll { max-height: 176px; overflow-y: auto; }
/* table-layout: fixed so a long address is cut with an ellipsis instead
   of wrapping the column to three lines. text-overflow only works on a
   cell whose width is settled, which auto layout never settles. Every
   truncated value carries the whole string in title="". */
.idx-det-tbl {
	width: 100%;
	table-layout: fixed;
	border-collapse: collapse;
	font-size: 11.5px;
}
.idx-det-tbl th,
.idx-det-tbl td {
	border-bottom: 1px solid var(--line-faint);
	padding: 3px 6px 3px 0;
	text-align: left;
	color: var(--ink-2);
	vertical-align: top;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.idx-det-tbl tbody th {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	width: 62px;
}
.idx-det-tbl tr:last-child th,
.idx-det-tbl tr:last-child td { border-bottom: 0; }
/* The count column, and the total above it, share one width and one
   padding so the numbers line up under each other. They did not before:
   the header cell carried its own padding. Ben, 2026-08-24. */
.idx-det-tbl .num {
	width: 42px;
	text-align: right;
	font-family: var(--f-mono);
	font-variant-numeric: tabular-nums;
	color: var(--ink);
	padding-right: 0;
}
.idx-det-tbl a { color: var(--accent-ink); }
.idx-det-tbl a:hover { text-decoration: underline; }
.idx-det-tbl thead th {
	background: var(--bg-rail);
	border-bottom: 1px solid var(--line);
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 2px 6px 2px 0;
	position: sticky;
	top: 0;
}
.idx-det-tbl thead th.num { color: var(--ink); padding-right: 0; }
/* The rel flags beside an outbound host. */
.idx-relflag {
	display: inline-block;
	margin-left: 5px;
	padding: 0 4px;
	border: 1px solid var(--warn-soft-line);
	border-radius: var(--r);
	background: var(--warn-soft);
	color: var(--warn);
	font-family: var(--f-mono);
	font-size: 9px;
}

/* ============================================================
   CONTENT OPTIMIZER (content_optimizer.php)
   ============================================================ */
/* The writing box and the mirror behind it.
   .view textarea further up this file sets its own font and padding and
   is more specific than a bare .co-text, so both boxes are written at
   the same higher specificity here. script.js copies the computed
   metrics across on top of this, which is what actually guarantees the
   two never drift: any rule that beats this one still gets copied. */
.co-editor { position: relative; }
.co-editor .co-text,
.co-editor .co-mirror {
	width: 100%;
	margin: 0;
	padding: 8px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	font: 13px/1.55 var(--f-ui);
	letter-spacing: normal;
	white-space: pre-wrap;
	overflow-wrap: break-word;
	word-break: normal;
	tab-size: 4;
	box-sizing: border-box;
}
.co-editor .co-text {
	position: relative;
	display: block;
	/* The row is a fixed height, so the box fills it exactly. No resize
	   handle: dragging it would push the card past its own grid row. */
	height: 100%;
	resize: none;
	background: transparent;
	overflow-y: auto;
}
/* Behind the box, exactly the same size, never interactive. It carries
   the colour; the textarea above carries the caret and the text. */
.co-editor .co-mirror {
	position: absolute;
	top: 0;
	left: 0;
	height: 100%;
	border-color: transparent;
	color: transparent;
	background: var(--bg);
	overflow: hidden;
	pointer-events: none;
	user-select: none;
}
.co-editor .co-mirror mark {
	background: var(--neg-soft);
	color: transparent;
	border-radius: 2px;
}
/* Over the limit, the box itself says so. Without this the only red is
   below the fold, and a focused box shows the ordinary green focus ring
   while its content is refused. */
.co-editor.is-over .co-text { border-color: var(--neg); }
.co-editor.is-over .co-text:focus-visible { outline-color: var(--neg); }

/* A disabled submit has to look disabled. This rule was lost once when
   the block above it was rewritten, and the button stayed green while
   refusing to do anything. */
.co-form button.btn-primary:disabled,
.co-form button.btn-primary:disabled:hover {
	background: var(--bg-sunk);
	border-color: var(--line);
	color: var(--ink-3);
	cursor: not-allowed;
}
.co-blocked {
	margin-left: 10px;
	font-size: 12px;
	color: var(--neg);
}
.co-blocked[hidden] { display: none; }
/* The category picker. It always opens straight down from the box and
   fills its width, so it never wanders the way a <datalist> did. */
.co-combo { position: relative; }
.co-combo input[type=text] { width: 100%; }
.co-pop {
	display: none;
	position: absolute;
	top: calc(100% + 3px);
	left: 0;
	right: 0;
	z-index: 50;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-lg);
	background: var(--bg);
	box-shadow: var(--pop);
	overflow: hidden;
}
.co-combo.is-open .co-pop { display: block; }
/* The parent of whatever row is at the top of the list, held there while
   the reader scrolls. Two categories deep in a tree can read almost the
   same and mean different things, so the row alone is not enough. */
.co-crumb {
	border-bottom: 1px solid var(--line);
	background: var(--accent-tint);
	color: var(--accent-ink);
	/* The same padding and line height as a row below it, so a one-line
	   path is exactly one row tall. It grows to a second line only for
	   the 3.4% of paths that need more. Three lines is the cap, which
	   fits the longest path in the ten taxonomies, 291 characters. */
	padding: 5px 8px;
	font-size: 11.5px;
	line-height: 1.35;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
	word-break: break-word;
}
.co-crumb:empty { display: none; }

/* About twelve rows before it scrolls. Every category stays reachable
   by scrolling; typing narrows the same list. Every row is one line of
   the same height, which is what lets the parent line above be found by
   arithmetic instead of by measuring. */
.co-opts { max-height: 320px; overflow-y: auto; overscroll-behavior: contain; }
.co-opt {
	padding: 5px 8px;
	font-size: 12.5px;
	line-height: 1.35;
	color: var(--ink-2);
	cursor: pointer;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.co-opt:hover { background: var(--accent-tint); color: var(--accent-ink); }
/* Two ways to print one row. Browsing shows the leaf, indented, because the
   parent is the row above. Searching shows the whole path, because the rows
   that survive come from unrelated branches and the indent would lie about
   which one each belongs to. */
.co-path { display: none; }
.co-opts.is-filtered .co-leaf { display: none; }
.co-opts.is-filtered .co-path { display: inline; }
.co-anc { color: var(--ink-3); }
.co-opt:hover .co-anc { color: inherit; opacity: 0.75; }
.co-count {
	border-top: 1px solid var(--line);
	background: var(--bg-sunk);
	padding: 4px 8px;
	font-family: var(--f-mono);
	font-size: 10px;
	color: var(--ink-3);
}

/* Loading a different taxonomy. The whole form is put beyond use with
   pointer-events rather than the disabled attribute, because a disabled
   control is left out of the request and the taxonomy itself is one of the
   values being submitted. */
.co-form.is-loading { pointer-events: none; }
.co-form.is-loading .co-text,
.co-form.is-loading select,
.co-form.is-loading button { opacity: 0.5; }
.co-pick.is-loading .co-combo::after {
	content: '';
	position: absolute;
	top: 50%;
	right: 10px;
	width: 13px;
	height: 13px;
	margin-top: -7px;
	border: 2px solid var(--line-strong);
	border-top-color: var(--accent);
	border-radius: 50%;
	animation: co-spin 0.6s linear infinite;
	pointer-events: none;
}
.co-pick.is-loading [data-combo-input] {
	color: var(--ink-3);
	padding-right: 30px;
	background: var(--bg-sunk);
}
@keyframes co-spin { to { transform: rotate(360deg); } }
/* A reader who has asked not to see motion gets a still ring, which still
   says the box is busy. */
@media (prefers-reduced-motion: reduce) {
	.co-pick.is-loading .co-combo::after { animation: none; }
}

.co-pick { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.co-pick .nx-field { flex: 0 0 220px; }
.co-pick .co-target { flex: 1 1 340px; }
.co-pick .co-topn { flex: 0 0 130px; }
.co-form button.btn-primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-accent);
	padding: 0 14px;
}
.co-form button.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); }

/* The suggested phrases. They are a menu to choose from, so they are
   laid out as a field of chips and not as a numbered list: a list
   invites someone to work down it and use them all. */
.co-form [data-charcount].is-over { color: var(--neg); }
/* ── Writing on the left, phrases on the right ────────────────────────────
   Two columns. The LEFT column is one stack: the writing box (a fixed
   height) above the category box (its own height). The RIGHT column holds
   the phrase panel, which fills the left column exactly, top to bottom.

       ┌─ .co-left ─────────────────────────────┬── .co-side ──┐
       │ Your writing            --co-box-h     │ Phrases      │
       ├────────────────────────────────────────┤ fills the    │
       │ The category you are writing for       │ whole column │
       └────────────────────────────────────────┴──────────────┘

   THE ONE THING THAT MATTERS: the phrase card is taken out of flow with
   position:absolute, so its 87 rows contribute NOTHING to how tall
   anything is. The column height is decided by the left stack alone, and
   the card is then stretched to it by inset:0.

   Every earlier version failed on this single point. Content-sized boxes
   cannot be made equal after the fact: whichever is taller wins, and the
   list is always taller. Spanning two grid rows fails the same way, because
   grid hands a spanning item's content to the rows it spans - measured, it
   pushed the column to 2,562px and showed all 87 rows.

   The list then scrolls inside whatever height it was given: flex:1 with
   min-height:0, which is what lets a flex child be shorter than its
   content. No JavaScript, nothing measured, nothing to keep in sync. */
:root { --co-box-h: 420px; }

.co-layout {
	display: grid;
	grid-template-columns: 2fr 1fr;
	gap: 12px;
	align-items: stretch;
	/* The four summary boxes come next and must not touch this. */
	margin-bottom: 12px;
}

.co-left {
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 12px;
}
.co-write { height: var(--co-box-h); flex: 0 0 auto; min-height: 0; }
.co-write > .card-body { flex: 1 1 auto; min-height: 0; }
.co-write .co-editor   { height: 100%; }

.co-side { min-width: 0; position: relative; }
.co-side > .card {
	position: absolute;
	inset: 0;
	overflow: hidden;
}

@media (max-width: 1080px) {
	/* One column. Out of flow no longer helps, so the panel takes a plain
	   height of its own and stacks under the writing. */
	.co-layout { grid-template-columns: 1fr; }
	.co-side { position: static; }
	.co-side > .card { position: static; height: var(--co-box-h); }
}

.co-side-note {
	padding: 8px 12px;
	border-bottom: 1px solid var(--line);
	background: var(--bg-sunk);
	color: var(--ink-3);
	font-size: 11.5px;
	line-height: 1.45;
	flex: 0 0 auto;
}

/* Whole-pixel rows, so a row is never sliced into a stray strip by the
   scroll edge. Padding plus a 1.35 line-height gave 27.875px and moved
   again when the web font loaded. */
/* The Content Optimizer prints the top ten of a taxonomy and then, when the
   category the reader picked is not in that ten, one more row with its real
   rank. The dashed line says the row below it is not the eleventh: it is a
   different measure. Ben, 2026-08-28. */
.co-row-gap {
	border-top: 1px dashed var(--line-strong);
	margin-top: 6px;
	padding-top: 6px;
}

:root { --co-row-h: 28px; }
.co-phrases {
	list-style: none;
	margin: 0;
	padding: 0;
	overflow-y: auto;
	flex: 1 1 auto;
	min-height: 0;
	counter-reset: co-p;
}
.co-phrase {
	display: flex;
	align-items: center;
	gap: 4px;
	height: var(--co-row-h);
	padding: 0 12px;
	font-size: 12.5px;
	color: var(--ink-2);
	border-bottom: 1px solid var(--line-faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.co-phrase:hover { background: var(--bg-hover); color: var(--ink); }
.co-phrase::before {
	counter-increment: co-p;
	content: counter(co-p);
	flex: 0 0 22px;
	color: var(--ink-3);
	font-family: var(--f-mono);
	font-size: 10px;
}
.co-phrases.is-used .co-phrase { color: var(--ink-3); }

.co-side-foot { display: block; padding: 0; flex: 0 0 auto; }
.co-used > summary {
	padding: 8px 12px;
	cursor: pointer;
	font-size: 11.5px;
	color: var(--ink-3);
}
.co-used > summary:hover { color: var(--ink); }
.co-used .co-phrases { max-height: 150px; border-top: 1px solid var(--line); }

/* One card per category picked, side by side and always the same width.
   auto-fit, not auto-fill: with fewer than four cards, auto-fill would
   leave the row short and left-aligned, empty tracks and all; auto-fit
   collapses the empty tracks so one or two cards stretch to fill the
   row on their own. Either way every card is the same 1fr width. */
.co-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	gap: 14px;
}
.co-block { min-width: 0; }
.co-row {
	display: flex;
	gap: 8px;
	align-items: baseline;
	padding: 3px 0;
	border-top: 1px solid var(--line-faint);
	font-size: 12.5px;
}
.co-row:first-of-type { border-top: 0; }
.co-row.is-hit { font-weight: 600; color: var(--accent-ink); }
.co-score {
	flex: 0 0 46px;
	font-family: var(--f-mono);
	font-size: 11px;
	color: var(--ink-3);
	text-align: right;
}
.co-row.is-hit .co-score { color: var(--accent); }
.co-label { flex: 1 1 auto; min-width: 0; color: var(--ink-2); line-height: 1.35; }
.co-row.is-hit .co-label { color: var(--accent-ink); }

/* Narrow windows: the section row scrolls sideways, so the panel
   cannot be clipped by it. It becomes a fixed full-width sheet. */
@media (max-width: 1100px) {
	.topnav { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
	.topnav::-webkit-scrollbar { height: 0; display: none; }
	.mega,
	.topnav-sec:nth-last-child(-n+3) .mega {
		position: fixed;
		top: var(--topbar-h);
		left: 8px;
		right: 8px;
		width: auto;
		max-width: none;
	}
}

/* ============================================================
   STYLE GUIDE COMPONENTS (2026-08-23)
   Everything below is shared by every report page and is shown,
   with sample data, on 0.styleguide.php. Build a new page from
   these classes rather than adding page-local rules.
   ============================================================ */

/* ---- chart layout ----
   .chart-grid is the default: two charts to a row, one on a narrow
   window. .chart-full is one chart across the card. The height is set
   on the .echart itself, because ECharts measures its container. */
.chart-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
	gap: 1px;
	background: var(--line-faint);
}
.chart-cell { background: var(--bg); padding: 12px 14px 14px; min-width: 0; }
.chart-full { background: var(--bg); padding: 12px 14px 14px; }
.chart-label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	margin-bottom: 2px;
}
.chart-cap { font-size: 11.5px; color: var(--ink-3); margin: 6px 0 0; }
.echart { width: 100%; min-width: 0; }

/* The row of selects that drives a comparison chart. Two on the model
   comparison, four on the four-way. It sits between the section head and
   the chart, on the sunk surface so it reads as a control, not as data. */
.chart-controls {
	display: flex;
	align-items: flex-end;
	gap: 10px;
	flex-wrap: wrap;
	padding: 9px 14px;
	background: var(--bg-sunk);
	border-bottom: 1px solid var(--line);
}
.chart-controls .cc-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.chart-controls label {
	font-family: var(--f-mono);
	font-size: 9.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink-3);
}

/* A row of checkboxes that redraws the chart or table under it. */
.check-row { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.check-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 24px;
	padding: 0 9px 0 7px;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg);
	color: var(--ink-2);
	font-size: 12px;
	cursor: pointer;
	white-space: nowrap;
}
.check-chip:hover { background: var(--bg-hover); color: var(--ink); }
.check-chip input { margin: 0; accent-color: var(--accent); }
.check-chip .swatch { width: 8px; height: 8px; border-radius: 1px; flex: none; }
.check-chip:has(input:checked) {
	border-color: var(--accent-tint-2);
	background: var(--accent-tint);
	color: var(--accent-ink);
}

/* ---- sticky column labels ----
   .tbl-scroll is the element that scrolls, so the labels stick to its
   top, not the window's. Without the background the rows show through. */
.tbl-scroll table.grid thead th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--bg-rail);
}
/* Half the report pages emit a bare <tr><th> with no <thead>, which the
   browser parses into <tbody>. Sticking those too, so the rule covers every
   table.grid in the application rather than the 21 that use <thead>. */
.tbl-scroll table.grid > tbody > tr:first-child > th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--bg-rail);
}

/* When a filter row follows the label row it sticks directly under it.
   The offset must equal the label row's height. */
.tbl-scroll table.grid thead tr.filter-row th {
	top: 32px;
	padding: 5px 8px 6px;
	background: var(--bg-rail);
	border-bottom: 1px solid var(--line);
}
.filter-row .f-in {
	display: block;
	width: 100%;
	min-width: 0;
	font: inherit;
	font-size: 12px;
	color: var(--ink);
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r);
	padding: 2px 5px;
}
.filter-row .f-in::placeholder { color: var(--ink-3); text-transform: none; letter-spacing: normal; }
.filter-row .f-in:focus { outline: none; border-color: var(--accent); }
.filter-row .f-num { font-family: var(--f-mono); text-align: right; }
.filter-row .f-pair { display: flex; gap: 4px; }

/* ---- number treatment ----
   One rule for every figure a reader compares across rows: mono, tabular,
   right aligned. .metric is the one number a table is sorted by, so it
   carries the weight; .metric-2 is a supporting figure. */
.metric {
	font-family: var(--f-mono);
	font-variant-numeric: tabular-nums;
	font-size: 13px;
	font-weight: 600;
	color: var(--ink);
	white-space: nowrap;
}
.metric-2 {
	font-family: var(--f-mono);
	font-variant-numeric: tabular-nums;
	font-size: 12.5px;
	color: var(--ink-2);
	white-space: nowrap;
}
.metric-nil { color: var(--ink-3); font-weight: 400; }
/* The share bar that sits under a metric in the same cell. A fixed width
   with margin-left auto, so in a right-aligned .num cell it lines up under
   the number instead of stretching the whole column. */
.metric-bar {
	display: block;
	width: 54px;
	height: 3px;
	margin: 3px 0 0 auto;
	border-radius: 1px;
	background: var(--accent-tint);
	overflow: hidden;
}
.metric-bar i { display: block; height: 100%; background: var(--accent); }
/* a change against the previous period */
.delta { font-family: var(--f-mono); font-size: 11.5px; white-space: nowrap; }
.delta.up { color: var(--pos); }
.delta.down { color: var(--neg); }
.delta.flat { color: var(--ink-3); }

/* ---- report table ----
   The loose, presentational table used to summarise a finding. Not for
   500 rows: that is table.grid. Rows breathe, the first column carries
   the subject, and there is no card chrome around it. */
table.report { width: 100%; border-collapse: collapse; }
table.report th {
	text-align: left;
	font-family: var(--f-mono);
	font-size: 9.5px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 0 14px 8px;
	border-bottom: 1px solid var(--line);
}
table.report td {
	padding: 12px 14px;
	border-bottom: 1px solid var(--line-faint);
	vertical-align: top;
	font-size: 13px;
}
table.report tr:last-child td { border-bottom: 0; }
table.report td:first-child { font-weight: 600; color: var(--ink); }
table.report .r-note { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-3); font-weight: 400; }
table.report th.num, table.report td.num { text-align: right; }

/* ---- empty state ----
   Every report page needs one: what is missing, why, and the one link
   that fixes it. A blank card reads as a fault. */
.empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 7px;
	padding: 34px 20px 38px;
	text-align: center;
}
.empty .e-mark {
	width: 30px; height: 30px;
	display: grid;
	place-items: center;
	border: 1px solid var(--line);
	border-radius: var(--r);
	background: var(--bg-sunk);
	color: var(--ink-3);
	margin-bottom: 3px;
}
.empty .e-title { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.empty .e-why { font-size: 12.5px; color: var(--ink-2); max-width: 46ch; }
.empty .e-act { margin-top: 6px; }
/* the same thing inside a table, so the columns stay put */
table.grid tr.empty-row td { padding: 0; border-bottom: 0; }
/* .empty used on its own, not inside a card, still reads as a panel */
.view > .empty { background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg); }

/* ---- style guide page chrome ----
   0.styleguide.php only. Not used by report pages. */
.sg-swatches {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
	gap: 10px;
	padding: 12px 14px;
	border-bottom: 1px solid var(--line);
}
.sg-swatches:last-child { border-bottom: 0; }
.sg-swatch { min-width: 0; }
.sg-chip { height: 30px; border: 1px solid var(--line); border-radius: var(--r); margin-bottom: 6px; }
/* A text colour is shown as text on the surface it is used on, never as a
   filled block. See the note in 0.styleguide.php. */
.sg-chip-text {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	font-size: 13px;
	font-weight: 600;
}
.sg-chip-text b { font-family: var(--f-mono); font-size: 12px; font-weight: 600; }
.sg-name { font-family: var(--f-mono); font-size: 10.5px; color: var(--ink); }
.sg-val { font-family: var(--f-mono); font-size: 10px; color: var(--ink-3); }
.sg-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding: 12px 14px; }
.sg-note { padding: 10px 14px 14px; font-size: 12.5px; color: var(--ink-2); max-width: 84ch; }
.sg-note code { font-family: var(--f-mono); font-size: 11.5px; background: var(--bg-hover); padding: 0 4px; border-radius: var(--r); }
.sg-series { display: flex; gap: 0; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.sg-series i { display: block; height: 26px; flex: 1 1 0; }

/* ============================================================
   VISUAL LAYER (2026-08-23)
   Section icons, the figures beside a page title, and the rank
   treatment for a ranked table. All three are on 0.styleguide.php.
   ============================================================ */

/* ---- section icons ----
   Inline SVG in the top bar and the breadcrumb, from NavSectionIcon() in
   templates/nav.inc.php. --sec is the section's hue, set inline on the
   element; it is used by the icon and by the tab underline and by nothing
   else. Knowing where you are is the whole job. */
/* The box is fixed and every path inside it shares a centre, so the icon
   needs no per-icon nudging. display:block stops the inline baseline from
   adding a descender gap under it. See NavSectionIcon() in nav.inc.php.
   Every icon is one size: width and height both 14, viewBox 0 0 16 16,
   ink from 2 to 14 on both axes. No rule here may size or move one icon. */
.sec-ico { flex: none; display: block; }
.topnav-btn .sec-ico { margin-right: 3px; color: var(--ink-3); }
/* .topnav-sec:hover as well as .topnav-btn:hover: the icon keeps the
   section colour while the pointer is down in the open .mega. Ben, 2026-08-27. */
.topnav-btn:hover .sec-ico,
.topnav-sec:hover .topnav-btn .sec-ico,
.topnav-sec:focus-within .topnav-btn .sec-ico { color: var(--sec, var(--accent)); }
.topnav-btn.is-on .sec-ico { color: var(--sec, var(--accent)); }
.topnav-btn.is-on { border-bottom-color: var(--sec, var(--accent)); }
.crumb-sec { display: inline-flex; align-items: center; gap: 3px; }
.crumb-sec .sec-ico { color: var(--sec, var(--accent)); position: relative; top: -1px; }

/* ---- the figures beside a page title ----
   Two to four numbers that answer the page before the tables do. Mono and
   large for the value, mono and small for the label, which is the same
   pairing every column heading uses. */
.stat-strip {
	display: flex;
	align-items: stretch;
	gap: 0;
	margin: 0 0 12px;
	align-self: flex-start;
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	background: var(--bg);
	overflow: hidden;
}
.stat-cell {
	display: flex;
	flex-direction: column;
	gap: 1px;
	padding: 7px 14px 8px;
	min-width: 84px;
}
.stat-cell + .stat-cell { border-left: 1px solid var(--line-faint); }
.stat-val {
	font-family: var(--f-mono);
	font-variant-numeric: tabular-nums;
	font-size: 17px;
	font-weight: 600;
	line-height: 1.15;
	color: var(--ink);
}
.stat-lab {
	font-family: var(--f-mono);
	font-size: 9px;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--ink-3);
	white-space: nowrap;
}
/* a figure that is a change carries the delta colours */
.stat-val.up { color: var(--pos); }
.stat-val.down { color: var(--neg); }

/* ---- rank ----
   The first column of a ranked table. Rows 1 to 3 are filled so the top of
   a long list has a shape; everything below is a plain number. */
.rank {
	display: inline-grid;
	place-items: center;
	min-width: 20px;
	height: 18px;
	padding: 0 5px;
	border-radius: var(--r);
	font-family: var(--f-mono);
	font-size: 11px;
	font-variant-numeric: tabular-nums;
	color: var(--ink-3);
}
.rank.r1 { background: var(--accent); color: var(--on-accent); font-weight: 600; }
.rank.r2 { background: var(--accent-tint-2); color: var(--accent-ink); font-weight: 600; }
.rank.r3 { background: var(--accent-tint); color: var(--accent-ink); font-weight: 500; }

/* ---- a state mark on a tab ----
   $page_tab_marks in templates/header.php prints one after a tab's label.
   campaign_edit.php uses two on Market Segments while a campaign is not
   locked: a warning triangle when something is still missing, and a hollow
   ring when everything is there and it is waiting to be locked. A locked
   campaign carries neither. Ben, 2026-08-29. */
.section-tabs a .tab-mark { flex: none; margin-left: 1px; }
.section-tabs a .tab-mark.is-todo  { color: var(--warn); }
.section-tabs a .tab-mark.is-ready { color: var(--accent); }

/* ---- stacked blocks inside a view need a gap ----
   .view is display:block, so the `gap` declared on it does nothing and two
   forms or two cards printed one after the other touched. Ben, 2026-08-29,
   on the Market Segments tab of campaign_edit.php, where three cards sat
   squashed together.

   12px, the same as the gap .view already declares for its grid children,
   so a stacked page and a grid page space the same. Only DIRECT children
   are matched, so a card inside a grid or inside another card is untouched
   and nothing that already sets its own margin doubles up. */
.view > form + form,
.view > form + .card,
.view > .card + form,
.view > .card + .card { margin-top: 12px; }

/* ---- the AI answer tool tabs (ai_answer_tabs.inc.php) ----
   The ChatGPT / Gemini / Google AI Mode strip on AI Answer Sources and AI
   Answer Ads. Moved here from an inline block in the fragment, 2026-08-30. */
.ai-tabs { display: flex; align-items: center; gap: 6px; margin: 0 0 12px; flex-wrap: wrap; }
.ai-tabs .ai-counts { margin-left: auto; }
.ai-scroll { max-height: 420px; overflow-y: auto; }

/* ---- the HTMX data block and its spinner ----
   A page draws its layout and one .api-block per piece of data. The block
   holds the spinner, htmx replaces the whole of it with the answer, so
   nothing has to be hidden on the first load. todo.htmx.md.

   .htmx-indicator is htmx's own class, for a block that RELOADS: htmx puts
   .htmx-request on the element making the request, and the rule below is
   what reveals the spinner inside it. */
.spin-wrap { display: flex; align-items: center; justify-content: center; padding: 28px 0; }
.spin { display: inline-block; width: 18px; height: 18px; border: 2px solid var(--line-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.htmx-indicator { opacity: 0; transition: opacity 120ms; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

/* A BLOCK THAT RELOADS LOOKS LIKE A BLOCK THAT LOADS FOR THE FIRST TIME.
   Ben, 2026-08-31. A tab, a filter, a sort or a page number inside a
   fragment fetches www/api.php and swaps the answer into the
   .api-wrap div ApiBlockWrap() prints. htmx swaps only when the answer
   lands, so the old table stood there for the whole wait and the reader
   was given no sign that anything was happening. Every one of those
   helpers names its wrap in hx-indicator, htmx puts .htmx-request on it
   for the length of the request, and these two rules take the old answer
   off the screen and put the same spinner in its place. The spinner is
   the .spin ring, written again because a pseudo-element cannot carry
   that class. */
.api-wrap.htmx-request > * { display: none; }
.api-wrap.htmx-request::after { content: ""; display: block; width: 18px; height: 18px; margin: 28px auto; border: 2px solid var(--line-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.7s linear infinite; }

/* The fixed area a 5xx body is swapped into, printed empty at the top of
   every page body by templates/header.php. Every ApiBlock() carries
   hx-status:5xx="swap:innerHTML target:#api-fail". While it holds nothing
   it takes no space; the moment htmx swaps a body in, it shows as a warn
   callout. It is printed with nothing between its tags, or :empty would
   never match. */
#api-fail { display: block; margin-bottom: 12px; }
#api-fail:empty { display: none; }

/* A browser set to reduce motion gets a still ring rather than nothing, so
   the block still says it is waiting. */
@media (prefers-reduced-motion: reduce) {
	.spin { animation: none; }
	.api-wrap.htmx-request::after { animation: none; }
}

/* ============================================================
   THE TOOLTIP
   ============================================================ */
/* ONE element, on document.body, made and positioned by www/script.js.
   Tip() in lib.php writes the data-tip attribute a source element carries,
   and it is the only thing that does.

   position: fixed is the point. Every data table sits inside .tbl-scroll,
   which is overflow: auto, so a bubble drawn inside its own parent would be
   cut off on the first and the last column. This one is taken out of every
   scroller and every card.

   z-index 500 is above the theme picker at 400, which is the highest
   anything else in this file reaches.

   pointer-events: none, so the bubble cannot take the pointer off the
   element it describes and make itself blink.

   It shows with no delay and no animation. That is why title="" is not the
   mechanism: the browser waits about a second before it draws one. */
.oa-tip {
	position: fixed;
	z-index: 500;
	max-width: 320px;
	padding: 6px 9px;
	background: var(--bg);
	color: var(--ink);
	border: 1px solid var(--line-strong);
	border-radius: var(--r);
	box-shadow: var(--pop);
	font-family: var(--f-ui);
	font-size: 12px;
	font-weight: 400;
	line-height: 1.45;
	letter-spacing: 0;
	text-align: left;
	text-transform: none;
	white-space: normal;
	pointer-events: none;
}

/* The element carries the hidden attribute while nothing is shown. The rule
   above sets no display, so the browser's own [hidden] rule still applies;
   this one is written out so a later display declaration cannot take it
   away by accident. */
.oa-tip[hidden] { display: none; }

