/*
Theme Name: TheoTrade Archive
Theme URI: https://archive.theotrade.com
Author: TheoTrade
Description: Trading-blog theme for the TheoTrade archive rebuild, in a dark and a light colour scheme with a header toggle (the visitor's choice is remembered, otherwise the OS preference wins). Classic theme, single stylesheet, with tiny vanilla JS enhancements: a conditional facade enqueued on singular views (Vimeo click-to-load) and an unconditional header script enqueued on every page (mobile nav toggle plus the desktop search disclosure). Content renders exclusively through the standard editor (post_content).
Version: 0.1.0
Requires at least: 6.6
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: theotrade-archive
*/

/* ---------------------------------------------------------------------------
   Design tokens: ALIASES ONLY. Every colour value and the font stack live in
   theme.json (settings.color.palette / settings.typography.fontFamilies), so
   nothing here holds a value and the front end cannot drift from the editor.
   Audience skews 60-90: base 18px, line-height 1.6+, high contrast, 44px+
   click targets, always-visible focus.

   Declared on `body`, not `:root`, on purpose. WP 7.0.2 emits presets on
   :root (WP_Theme_JSON::ROOT_CSS_PROPERTIES_SELECTOR, ':root' since 6.6),
   verified by reading the rendered global-styles-inline-css. `body` is a
   descendant of :root, so these aliases resolve whether core emits presets on
   :root or on body, which older versions did. No rule in this file uses a
   token outside body (html only carries scroll-behavior). var() resolves at
   computed-value time, so load order relative to global-styles is irrelevant.

   --radius stays a literal deliberately: it has no home in theme.json v3
   (settings.custom only), has two consumers (.card, .tile), never reaches the
   editor, and every other radius in this file is already a literal.
--------------------------------------------------------------------------- */
body {
	--bg: var(--wp--preset--color--bg);
	--bg-2: var(--wp--preset--color--bg-2);
	--card: var(--wp--preset--color--card);
	--card-2: var(--wp--preset--color--card-2);
	--line: var(--wp--preset--color--line);
	--line-2: var(--wp--preset--color--line-2);
	--ink: var(--wp--preset--color--ink);
	--muted: var(--wp--preset--color--muted);
	--faint: var(--wp--preset--color--faint);
	--accent: var(--wp--preset--color--accent);     /* trading green */
	--accent-2: var(--wp--preset--color--accent-2); /* gold */
	--danger: var(--wp--preset--color--danger);

	/* --link is accent-as-FOREGROUND, and it exists only because of the light
	   theme. The palette green (#16c784) is designed against a near-black page:
	   on the light theme's white card it measures 2.2:1, and it is the colour of
	   every body link (.entry-content a), every card and tile title on hover and
	   every focus-visible ring. For an audience skewing 60-90 this file promises high
	   contrast, and 2.2:1 fails both the 4.5:1 text bar and the 3:1 non-text bar
	   a focus ring has to clear. So foreground uses --link and BACKGROUND uses
	   --accent: the brand green still fills the skip link, the card placeholder
	   mark, the play button, the current pagination chip and the blockquote rule
	   in both themes, unchanged, while text and focus rings darken to #0a7f52
	   (measured 5.04:1 on card, 4.65:1 on page) in light. In dark the two are the
	   same colour, so nothing shifts. */
	--link: var(--wp--custom--dark--link, #16c784);

	/* Two values that were literals until the light theme needed a second set.
	   Both carry a fallback because an unresolved var() here is not a wrong
	   colour but a broken masthead (no background at all over scrolling content)
	   and a missing dropdown shadow. */
	--header-bg: var(--wp--custom--dark--header-bg, rgba(11, 15, 20, .88));
	--shadow-lg: var(--wp--custom--dark--shadow-lg, 0 18px 40px rgba(0, 0, 0, .45));
	--radius: 14px;
	/* settings.layout.contentSize. The fallback keeps .wrap/.article bounded if
	   theme.json ever loses its layout block: an unresolved var() would compute
	   to max-width: none and silently full-bleed 21k pages. Colours get no
	   fallback on purpose, because a broken colour is obvious on first paint. */
	/* 1180 -> 1210 on 2026-09-12 for the 9th menu item ("TOS Links", Jeff's
	   request: members ask for the thinkorswim shared links daily). Arithmetic in
	   the Responsive section. Widened globally rather than on the header alone so
	   the masthead and the content column stay on the same left edge.
	   The number below is only the FALLBACK. theme.json's layout.contentSize is
	   what WordPress actually emits as --wp--style--global--content-size, so it
	   has to move with it: changing this line alone is inert, which is exactly the
	   silent failure the note further down warns about. Both were changed. */
	--maxw: var(--wp--style--global--content-size, 1210px);
	--font: var(--wp--preset--font-family--system);
	/* OP2 two-column split. Single source of truth: theme.json
	   settings.custom.op2Split. WP emits it as --wp--custom--op-2-split, quoted
	   verbatim from the rendered global-styles-inline-css; _wp_to_kebab_case
	   turns op2Split, op2-split AND op2split all into op-2-split, so the
	   plausible-looking --wp--custom--op2-split does not exist in any spelling.
	   The fallback is mandatory here, unlike the colours: an unresolved var()
	   makes grid-template-columns invalid at computed-value time, the property
	   falls back to none, and all 704 column pages render as a single column —
	   visually identical to the bug this token exists to fix. Same class of
	   silent catastrophe as --maxw, hence the same treatment. The fallback also
	   covers a NON-iframed editor canvas, where add_editor_style rewrites body
	   to .editor-styles-wrapper and global styles may land on another element.
	   58% is a choice, not a restoration: no .op2-col in the archive carries a
	   width or style attribute (0 of 704), the original OP2 ratios did not
	   survive the scrape. */
	--op2-split: var(--wp--custom--op-2-split, 58%);
}

/* Light theme ------------------------------------------------------------
   Re-aliases the same tokens to the light values in theme.json
   (settings.custom.light), so this block still holds no colour value and the
   dark palette stays the single source of truth for the editor.

   Selector is `html[data-theme] body`, one attribute above the plain `body`
   block, so it wins on specificity (0,1,1) vs (0,0,1) regardless of source
   order. The attribute lands on <html> from the head script in nav.js before
   first paint, which is what keeps the wrong theme from flashing.

   Only the neutrals and --link change. --accent, --accent-2 and --danger are
   deliberately identical in both themes: they are brand colours, they are used
   as fills rather than as text (see --link above), and re-tinting them per
   theme is how a two-theme palette starts drifting into two designs.

   color-scheme is set here and on the dark side below so the UA styles the
   things CSS cannot reach: form controls, scrollbars and the canvas behind an
   overscroll bounce. Without it a light page keeps dark scrollbars.
------------------------------------------------------------------------- */
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

html[data-theme="light"] body {
	--bg: var(--wp--custom--light--bg, #f4f6f9);
	--bg-2: var(--wp--custom--light--bg-raised, #ffffff);
	--card: var(--wp--custom--light--card, #ffffff);
	--card-2: var(--wp--custom--light--card-raised, #f8fafc);
	--line: var(--wp--custom--light--line, #dde3ea);
	--line-2: var(--wp--custom--light--line-strong, #c0cad8);
	--ink: var(--wp--custom--light--ink, #0f172a);
	--muted: var(--wp--custom--light--muted, #41506a);
	--faint: var(--wp--custom--light--faint, #5b6b83);
	--link: var(--wp--custom--light--link, #0a7f52);
	--header-bg: var(--wp--custom--light--header-bg, rgba(255, 255, 255, .90));
	--shadow-lg: var(--wp--custom--light--shadow-lg, 0 18px 40px rgba(15, 23, 42, .14));
}

/* The three accent tints baked into gradients and overlays assume a dark page
   underneath. On the light theme the green wash over a white card reads as a
   dirty smear, so the tint is dropped and the card keeps its own surface. The
   play-button scrim stays, because it sits over a video poster, not over the
   page. */
html[data-theme="light"] .card .thumb .ph,
html[data-theme="light"] .embed.no-poster {
	background: linear-gradient(135deg, var(--card-2), var(--bg-2));
}

/* Base ------------------------------------------------------------------- */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
	margin: 0;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--font);
	font-size: 1.125rem; /* 18px */
	line-height: 1.65;
	-webkit-font-smoothing: antialiased;
	/* Sticky footer. The footer used to sit wherever the content happened to
	   end, leaving dead space beneath it: measured 226px on a 1272px viewport
	   on analyze-tab-overview, and the archive is full of pages that short.
	   A flex column here plus a growing .site-main pins the footer to the
	   bottom with no height arithmetic, which is still the right shape even
	   though the masthead is now a fixed 70px at every width above 860px (the
	   search disclosure took the form out of the header row): below 860px the
	   header is multi-row, and with JS disabled the form is in the row again.
	   100dvh, not 100vh: on mobile the dynamic unit avoids making the
	   page scroll by exactly the height of the browser chrome.
	   Safe for the skip-link, the only other body child: it is
	   position:absolute while hidden and position:fixed when focused, so it is
	   never in flow and never becomes a flex item. */
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
}
/* Takes the slack so the footer lands at the bottom. flex-shrink 0 so long
   content is never compressed instead of scrolling. */
.site-main { flex: 1 0 auto; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; height: auto; display: block; }
a:focus-visible,
button:focus-visible,
input:focus-visible {
	outline: 3px solid var(--link);
	outline-offset: 2px;
	border-radius: 4px;
}
.video-facade:focus-visible {
	outline: 3px solid var(--link);
	outline-offset: 2px;
	border-radius: 4px;
}
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 22px; }

/* Accessibility utilities ------------------------------------------------ */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}
.skip-link:focus {
	position: fixed;
	top: 10px;
	left: 10px;
	z-index: 100;
	width: auto;
	height: auto;
	clip-path: none;
	background: var(--accent);
	color: #04140d;
	font-weight: 800;
	padding: 12px 18px;
	border-radius: 8px;
	/* The global focus-visible ring (outline: 3px solid var(--link)) is
	   invisible against this same-colour accent background, so give the
	   skip-link's own focus ring a distinct colour already in the file
	   (the dark ink used for on-accent text/icons above). */
	outline: 3px solid #04140d;
	outline-offset: 2px;
}

/* #content is the skip-link + tabindex="-1" target (header.php); clear the
   sticky masthead so focused content isn't hidden underneath it. The 70px is
   exact above 860px now that the header search sits in an absolutely
   positioned panel instead of wrapping to a second row; before this feature
   the masthead was 131px here and this value silently under-cleared it. */
#content {
	scroll-margin-top: calc(70px + 16px);
}

/* Header ----------------------------------------------------------------- */
.site-header {
	position: sticky;
	top: 0;
	z-index: 50;
	background: var(--header-bg);
	backdrop-filter: blur(10px);
	border-bottom: 1px solid var(--line);
}
/* position: relative has zero layout effect here and is what makes .hd — not
   .site-header — the containing block for .search-panel. .site-header spans the
   full viewport, so above 1224px a panel pinned to its right edge would drift
   away from both the content column and the button it belongs to. */
/* gap is the MINIMUM spacing here — space-between distributes any leftover, so
   the visual gaps stay wider than this whenever the row has room. 20px stopped
   fitting when the menu grew its 8th item ("TheoTrade Live", ~137px): logo 88
   + nav ~797 + 44 search + 44 theme + ~85 logout = ~1058 content, plus 4 gaps.
   4x20 = 1138+ > the 1136px content box, so .logout-link wrapped onto a second
   row; 4x10 = ~1098 fits with ~38px of slack for space-between to spread. */
.hd { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 70px; flex-wrap: wrap; padding-top: 8px; padding-bottom: 8px; }
/* 6px -> 2px alongside the 50px logo. .hd is border-box with min-height 70px and
   2 x 8px of its own padding, so the row has exactly 54px of content box: at 6px
   the 50px logo made a 62px block and the masthead grew to 78px, breaking the
   "70px at every width above 860px" invariant the Responsive section relies on.
   Measured in the browser at 6/4/2/0px: 78 / 74 / 70 / 70. */
.logo { display: flex; align-items: center; padding: 2px 0; }

/* The real TheoTrade artwork replaced a green "T" tile plus the site name in
   text. Sized by height so the masthead's 70px row governs, with width: auto so
   either aspect ratio (600x273 wordmark, 300x230 mark) follows on its own and
   the intrinsic width/height attributes still reserve the box before the PNG
   lands. */
/* 40px -> 50px on 2026-09-12 (Andrii). width:auto means the wordmark's 600x273
   ratio turns those 10px of height into 22px of row width, which is why the
   Responsive arithmetic below counts the logo at 110px, not 88px. Still clears
   the 70px masthead: 50 + the 2 x 6px .logo padding = 62px. */
.logo img { display: block; height: 50px; width: auto; }

/* Below 560px the wordmark gives way to the mark alone: that is the width where
   the row also carries the menu button and the theme toggle, and 560px is a
   breakpoint this file already uses (.article padding) rather than a new one.
   The mark is set slightly shorter because its artwork is nearly square, so at
   an equal height it reads bigger than the wordmark does. */
/* Scoped through .logo on purpose. `.logo img` above is (0,1,1) because of the
   type selector, so a bare `.logo-mark` at (0,1,0) LOSES to it and both images
   render side by side (measured: a 140px-wide logo box with the mark stuck to
   the wordmark). Every switch below therefore carries the parent class. */
.logo .logo-mark { display: none; }
@media (max-width: 560px) {
	.logo .logo-full { display: none; }
	.logo .logo-mark { display: block; height: 34px; }
}

/* Both PNGs are the WHITE artwork, which is correct on the dark page and almost
   invisible on the light one. The artwork is monochrome, so inverting is a
   lossless way to get the dark variant without shipping a second pair of files
   (measured: un-inverted white on the light page is unreadable; inverted it is
   clean black). If a coloured logo ever replaces these, this is the line that
   has to become a real second asset. */
html[data-theme="light"] .logo img { filter: invert(1); }

/* Primary menu (wp_nav_menu markup; dropdowns are CSS-only hover/focus) --- */
/* gap 4 -> 3 with the 9th item: 8 gaps x 1px is only 8px, but the row needed
   every one of them to keep a usable slack. See the Responsive section. */
.nav ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 3px; flex-wrap: wrap; }
.nav a {
	display: inline-block;
	/* 14px side padding stopped fitting alongside the 8th menu item — see the
	   .hd gap note below; 8 links x 2 sides x 4px buys the row 64px back.
	   10px -> 8px on 2026-09-12 for the 9th item: 9 x 2 x 2px = 36px more. */
	padding: 12px 8px;
	border-radius: 8px;
	color: var(--muted);
	font-size: 1rem;
	font-weight: 600;
	white-space: nowrap;
}
.nav a:hover { color: var(--ink); background: var(--card); }
.nav li { position: relative; }
.nav .sub-menu {
	display: none;
	position: absolute;
	top: 100%;
	left: 0;
	z-index: 60;
	min-width: 260px;
	padding: 8px;
	background: var(--bg-2);
	border: 1px solid var(--line);
	border-radius: 12px;
	box-shadow: var(--shadow-lg);
	flex-direction: column;
}
.nav li:hover > .sub-menu,
.nav li:focus-within > .sub-menu { display: flex; }
.nav .sub-menu a { white-space: normal; width: 100%; }

/* Active item: colour cue plus a non-colour cue (underline), so state does
   not rely on colour perception alone. */
.nav .current-menu-item > a,
.nav .current_page_item > a,
.nav .current-menu-ancestor > a,
.nav .current_page_ancestor > a {
	color: var(--ink);
	font-weight: 800;
	text-decoration: underline;
	text-underline-offset: 4px;
}

/* Mobile nav toggle button: hidden on desktop, shown at/under the 1209px
   breakpoint where .nav becomes the collapsible panel. Shares its box with
   .search-toggle (same masthead, same 44px floor, same surface); only the
   padding and the reveal condition differ. */
.menu-toggle,
.search-toggle,
.theme-toggle {
	display: none;
	align-items: center;
	gap: 8px;
	min-height: 44px;
	min-width: 44px;
	padding: 10px 14px;
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: 8px;
	color: var(--ink);
	font-size: 1rem;
	font-weight: 600;
	cursor: pointer;
}
.menu-toggle:hover,
.search-toggle:hover,
.theme-toggle:hover { border-color: var(--line-2); }
/* Submenu chevrons (built by nav.js) exist only inside the mobile panel;
   on the desktop row the dropdowns are hover/focus and need no button. */
.submenu-toggle { display: none; }
/* Log out exists twice for a member (functions.php,
   theotrade_archive_nav_account_items): last child of My Account for the
   desktop dropdown, and a top-level item that ends the phone panel, hidden
   here and shown in the collapsed band. Exactly one is visible per width. */
.nav .menu-item-logout { display: none; }
.menu-toggle:focus-visible {
	outline: 3px solid var(--link);
	outline-offset: 2px;
	border-radius: 4px;
}

/* Square icon variant of the button above: 20px icon + 2 x 10px padding = 40px,
   lifted to the 44px click-target floor by the shared min-width/min-height.
   Square rather than the shared 10px/14px because the row was running out of
   width (arithmetic in the Responsive section), and this buys 4px back. The
   slack is no longer the ~10px this used to cite: the collapse breakpoint moved
   to 1209px in 2026-09, which is where the row's real 1195px requirement sits.
   Ink on card is 14.4:1.
   Stays display:none from the shared rule: only html.search-js reveals it, so a
   page without JS shows the working form itself rather than a dead button.
   No :focus-visible rule on purpose — the global button:focus-visible already
   rings it in accent at 8.8:1. (.menu-toggle:focus-visible above re-declares
   that global rule byte for byte; that is an existing wart, not a pattern.) */
.search-toggle { padding: 10px; }

/* Colour-scheme toggle: same square icon box as .search-toggle, and shown at
   EVERY width, unlike the other two. It carries no text and it is the only way
   back from a theme the visitor dislikes, so hiding it on mobile would strand
   them. It also stays outside #primary-nav, so collapsing the mobile menu never
   takes it away.

   Unhidden by nav.js rather than by a gate class on <html>: the button is the
   one element here whose behaviour is entirely its own, so `hidden` on the
   element itself is both simpler and self-healing (no class can outlive the
   markup it describes). display must beat the `hidden` attribute's UA
   display:none, hence the [hidden] guard rather than a bare display value. */
.theme-toggle { padding: 10px; }
.theme-toggle:not([hidden]) { display: inline-flex; }

/* One icon per theme. The dark page offers the sun (switch to light) and the
   light page offers the moon, i.e. the icon shows the DESTINATION, matching the
   accessible label nav.js keeps in sync. Both ship in the markup so switching
   costs no DOM work.

   The default (no data-theme yet, e.g. the split second before nav.js runs, or
   if it never does) shows the sun alone, so the button is never blank and never
   double-iconed. */
.theme-toggle .icon-moon { display: none; }
html[data-theme="light"] .theme-toggle .icon-sun { display: none; }
html[data-theme="light"] .theme-toggle .icon-moon { display: block; }

/* Header search: the base .search-form rule (further down) carries an 18px/6px
   vertical margin and 52px control heights. Unscoped, that makes the form 76px
   tall and grows the sticky masthead from 70px to ~92px, which both shifts the
   form off-centre and silently undercuts #content's scroll-margin-top above.
   Scope it to the header so the masthead keeps its height while controls stay
   >= 44px. The <=860px block gives the form a full-width row of its own and
   resets the base 560px cap there.

   RESOLVED (this is where the old KNOWN/UNRESOLVED note lived). The header no
   longer tries to fit the form into the masthead row at all: above 860px the
   form lives inside .search-panel, which is absolutely positioned and therefore
   not a flex item of .hd, so the row carries logo + nav + a 44px button and is
   70px at every width above 860px. The arithmetic that used to fail: .hd's
   CONTENT box is 1136px (1180 max-width minus 2 x 22px padding, box-sizing:
   border-box), while logo 215 + nav 648 + form 307 + two 20px gaps = 1211, so
   the form was 75px too wide to fit, wrapped, and made the masthead 131px —
   20px of which was .hd's `gap` acting as row-gap.

   The note here also used to blame flex-wrap: nowrap for horizontal overflow
   "because the nav is 648px with no min-width: 0". That was measurably false
   and is not preserved: .nav ul carries flex-wrap: wrap too, so the nav's
   min-content width is 127px (its widest single link). Under nowrap the nav
   would have shrunk towards that and wrapped its own list, producing a TALLER
   header, never a horizontal scrollbar. nowrap still was not the fix — for that
   reason, not the one previously written down.

   max-width: 320px is gone from the rule below. It capped nothing: the form's
   natural width in the header row is 307px, so the declaration was inert from
   the day it was written. Inside the panel the panel's own width governs, and
   re-adding a cap here could only shrink the form inside the box drawn around
   it. The <=860px reset of it therefore also disappears; what stays there is a
   reset of the BASE .search-form 560px cap, which is a different rule. */
.hd .search-form { margin: 0; }
.hd .search-form input[type="search"],
.hd .search-form input[type="submit"] {
	min-height: 44px;
	font-size: 1rem;
}

/* Search disclosure. Above 860px only: below that the form keeps its visible
   full-width row and every rule in here is inert, which is deliberate (a second
   disclosure on phones would mean two panels, mutual closing and shared Escape
   order for no layout gain, and this audience is better served by a visible
   field).

   No-JS contract, with a gate class of its own and NOT .nav-js. nav.js removes
   .nav-js again when the nav markup is missing; sharing one class would let a
   missing search button re-open the mobile menu, or a missing nav strand the
   search. Each gate heals itself independently. With no JS at all neither class
   is present: .search-toggle stays display:none from its shared rule and
   .search-panel stays an ordinary in-flow flex item holding a working form,
   i.e. exactly the pre-feature header. */
@media (min-width: 861px) {
	html.search-js .search-toggle {
		display: inline-flex;
	}
	html.search-js .hd .search-panel {
		/* Out of .hd's flex layout entirely: an absolutely positioned descendant
		   of a flex container is not a flex item, which is what keeps the logo,
		   nav and button rectangles identical open and closed.
		   right: 22px, not 0, because offsets on an absolutely positioned box
		   resolve against the containing block's PADDING box, and .hd is the
		   .wrap that carries `padding: 0 22px`; right: 0 would sit 22px outside
		   the content column. 22px lines the panel's right edge up with the
		   button above it and with the content column below it, at every width
		   including above 1224px.
		   z-index: 70 clears .nav .sub-menu's 60 inside .site-header's own
		   stacking context (z-index: 50 + backdrop-filter), so if the two ever
		   share the band under the masthead the panel is unambiguously on top;
		   the min-width: 1210px rule below keeps a hover submenu from being
		   there at all. */
		display: none;
		position: absolute;
		top: 100%;
		right: 22px;
		z-index: 70;
		width: 340px; /* 320px of form plus this box's 2 x 10px padding */
		padding: 10px;
		/* Its own opaque surface, in the language of .nav .sub-menu: the header's
		   translucent background stops at its bottom border, so anything hanging
		   below it would otherwise be read over raw page content. */
		background: var(--bg-2);
		border: 1px solid var(--line);
		border-radius: 12px;
		box-shadow: var(--shadow-lg);
	}
	html.search-js.search-open .hd .search-panel {
		display: block;
	}
}
@media (min-width: 1210px) {
	/* One header overlay at a time. Above 1209px the submenus are absolutely
	   positioned overlays that share the band under the masthead with an open
	   search panel, and the rightmost items overlap it outright, so a hover
	   submenu loses while the panel is open. Focus-opened submenus need no rule
	   here: nav.js closes the panel as soon as focus leaves it, so .search-open
	   and :focus-within on a nav item cannot coexist — which also means this
	   rule never takes a submenu away from a keyboard user.
	   Scoped to >=1210px because between 861 and 1209px the submenus are static,
	   always-expanded parts of the mobile nav panel, and hiding those would make
	   menu items unreachable while the search happened to be open. */
	html.search-open .nav li:hover > .sub-menu {
		display: none;
	}

	/* Row buttons get a visible edge and a hover state (Andrii 2026-09-12:
	   "бордери в меню шоб видно було межі кнопки + ховер ефект"). The edge is
	   an INSET box-shadow, not a border: a border is 2px of row width per item,
	   18px for nine, out of a slack measured at 34px on 2026-09-12 (logged-in
	   row 1132 of 1166 after Roman's nav: Daily 56, Traders 74, Masterminds
	   115, Strategy Updates 149, Memberships 119, Classes 75, TOS Links 92,
	   Start Here 94, My Account 106, plus logo 110 and two 44px toggles).
	   box-shadow costs nothing, so the slack stays for Windows font metrics. */
	/* Resting edge REMOVED 2026-09-13 (Andrii: "забрати бордери в десктопверсії"):
	   nine outlined boxes read as a toolbar, not a menu. The edge appears on
	   hover/focus only, still as an inset shadow so the row width never moves. */
	.nav > ul > li > a:hover,
	.nav > ul > li > a:focus-visible { box-shadow: inset 0 0 0 1px var(--line-2); }

	/* The account dropdown is the row's last item: opened from `left: 0` its
	   260px would run 12px past a 1210px viewport, so it hangs from the right. */
	.nav .menu-item-account > .sub-menu { left: auto; right: 0; }
}

/* Page/archive heads ----------------------------------------------------- */
.page-head { padding: 44px 0 10px; }
.page-head h1 {
	font-size: clamp(1.75rem, 4vw, 2.625rem);
	line-height: 1.12;
	letter-spacing: -.02em;
	margin: 0 0 10px;
}
.archive-desc { color: var(--muted); max-width: 680px; }

/* Card grid -------------------------------------------------------------- */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; padding: 24px 0 10px; }
.card {
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	transition: transform .15s ease, border-color .15s ease;
}
.card:hover { transform: translateY(-3px); border-color: var(--line-2); }
.card .thumb { display: block; aspect-ratio: 16 / 9; background: var(--bg-2); position: relative; overflow: hidden; }
.card .thumb img { width: 100%; height: 100%; object-fit: cover; }
.card .thumb .ph {
	position: absolute;
	inset: 0;
	display: grid;
	place-content: center;
	background: radial-gradient(120% 120% at 50% 0, rgba(22, 199, 132, .10), transparent), linear-gradient(135deg, var(--card-2), var(--bg-2));
}
.card .thumb .ph-mark {
	width: 44px;
	height: 44px;
	border-radius: 10px;
	background: linear-gradient(135deg, var(--accent), #0e9c67);
	color: #04140d;
	font-weight: 900;
	font-size: 1.375rem;
	display: grid;
	place-items: center;
}
.card .body { padding: 16px 17px 18px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.card-title { font-size: 1.125rem; line-height: 1.35; margin: 0; letter-spacing: -.01em; }
.card-title a { display: block; padding: 2px 0; }
.card:hover .card-title { color: var(--link); }
.card .ex { color: var(--muted); font-size: 1rem; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.chip {
	align-self: flex-start;
	display: inline-block;
	font-size: .8125rem;
	font-weight: 800;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--link);
	background: rgba(22, 199, 132, .12);
	padding: 5px 10px;
	border-radius: 6px;
}
.meta { color: var(--faint); font-size: .9375rem; margin: 0; margin-top: auto; display: flex; gap: 8px; align-items: center; }
/* .dot is a shared separator with .byline .dot in single.php; card.php currently renders only
   <time> so .dot is unused here — reserved for a future secondary meta item (e.g. author), not dead CSS. */
.dot { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); }

/* Single post / page ----------------------------------------------------- */
.article { max-width: var(--maxw); margin: 0 auto; padding: 40px 22px 60px; }
.article .chip { margin-bottom: 16px; }
.article h1 { font-size: clamp(1.75rem, 4vw, 2.625rem); line-height: 1.12; letter-spacing: -.02em; margin: 0 0 14px; }
/* Breathing room between the page title and the content (a video facade often comes right after it,
   now that the repeated scraped heading is gone). Andrii, 2026-10-02: 20-30px. */
.article-head { margin-bottom: 12px; } /* + the h1 14px = 26px total */
.byline {
	color: var(--faint);
	font-size: 1rem;
	display: flex;
	gap: 9px;
	align-items: center;
	margin: 0 0 26px;
	padding-bottom: 22px;
	border-bottom: 1px solid var(--line);
}
.byline .au { color: var(--muted); font-weight: 600; }
.feat { border-radius: 12px; overflow: hidden; margin: 0 0 28px; border: 1px solid var(--line); }
.feat img { width: 100%; }

/* Breadcrumbs (deep page hierarchy, 4-5 levels) -------------------------- */
.breadcrumbs { color: var(--faint); font-size: 1rem; margin: 0 0 20px; line-height: 2; }
.breadcrumbs a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; padding: 4px 2px; }
.breadcrumbs a:hover { color: var(--link); }
.breadcrumbs .sep { margin: 0 6px; }

/* Content — everything inside the_content() ------------------------------ */
.entry-content { font-size: 1.125rem; line-height: 1.75; }
.entry-content p { margin: 0 0 20px; }
.entry-content a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; }
/* Text links inside content match the content h2 (browser default 1.5em of the
   18px copy = 27px). Andrii, 2026-09-22, Tony's Playbook page: the Beehiiv line and
   "Read Latest Reports"; first asked for +2px, then "as big as the white title".
   Scoped to links that sit directly in a paragraph, list item or plain div so links
   inside headings and the tile grid (.tile-title sets its own size) keep theirs. */
.entry-content :is(p, li, div) > a { font-size: 1.5em; }
.entry-content img { border-radius: 10px; margin: 22px 0; border: 1px solid var(--line); }
/* Two of the seven scraped download icons (alt="icon") are black-on-transparent
   glyphs — the flat PDF on 2,822 pages and DOC on 1 — invisible on the dark
   theme. Invert exactly those two there; the five colourful badge icons
   (PDF/TXT/XLS/PNG/HTML) are legible on both themes and must not be touched.
   :not([data-theme="light"]) rather than ="dark" so the no-JS fallback (which
   is dark) is covered too. The border is dropped: a 1px outline box around a
   floating glyph reads as a broken image. */
html:not([data-theme="light"]) .entry-content img[src*="c2db977f0cb49a1c"],
html:not([data-theme="light"]) .entry-content img[src*="1f1ea3e5442e8d62"] {
	filter: invert(1);
}
.entry-content img[src*="c2db977f0cb49a1c"],
.entry-content img[src*="1f1ea3e5442e8d62"] {
	border: 0;
}
.entry-content h2, .entry-content h3 { letter-spacing: -.01em; margin: 34px 0 12px; line-height: 1.25; }
.entry-content ul, .entry-content ol { margin: 0 0 20px; padding-left: 24px; }
.entry-content blockquote {
	border-left: 3px solid var(--accent);
	margin: 24px 0;
	padding: 6px 0 6px 20px;
	color: var(--muted);
	font-style: italic;
}

/* In-content section trail — the scraped OP2 breadcrumb <ul>, rebuilt as a <nav>
   by theotrade_archive_crumb_strip(). Chip links echo the old archive's arrow
   trail; the current page is plain muted text. Tokens only, so both themes work. */
.entry-content .crumb-strip {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px 10px;
	margin: 0 0 24px;
	font-size: .95rem;
	line-height: 1.4;
}
.entry-content .crumb-strip a {
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 5px 14px;
	color: var(--muted);
	text-decoration: none;
	transition: border-color .15s ease, color .15s ease;
}
.entry-content .crumb-strip a:hover { color: var(--link); border-color: var(--line-2); }
.entry-content .crumb-strip .here { color: var(--faint); padding: 5px 2px; }
.entry-content .crumb-strip .sep { color: var(--faint); }
.entry-content table { width: 100%; border-collapse: collapse; font-size: 1rem; margin: 24px 0; }
.entry-content th {
	text-align: left;
	font-size: .8125rem;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--muted);
	border-bottom: 2px solid var(--line);
	padding: 10px 12px;
}
.entry-content td { padding: 10px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
.entry-content .wp-caption { max-width: 100%; }
.entry-content .wp-caption-text, .entry-content figcaption { color: var(--faint); font-size: .9375rem; margin-top: 8px; }
.alignwide { margin-left: auto; margin-right: auto; }
.aligncenter { margin-left: auto; margin-right: auto; }

/* Child pages navigation (hub pages: member-home, monthly archives) ------ */
.page-children {
	margin: 40px 0 0;
	padding-top: 26px;
	border-top: 1px solid var(--line);
}
.page-children h2 {
	font-size: 1.125rem;
	letter-spacing: -.01em;
	margin: 0 0 16px;
}
.page-children ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: 10px;
}
.page-children a {
	display: block;
	min-height: 44px;
	padding: 12px 16px;
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: 10px;
	color: var(--muted);
	font-weight: 600;
	line-height: 1.3;
}
.page-children a:hover {
	color: var(--ink);
	border-color: var(--line-2);
}
.page-children a:focus-visible {
	outline: 3px solid var(--link);
	outline-offset: 2px;
	border-radius: 4px;
}

/* Vimeo / embeds: responsive 16:9; facade markup gets iframe on click ----- */
.entry-content iframe { max-width: 100%; border: 0; }
.entry-content iframe[src*="player.vimeo"] { width: 100%; aspect-ratio: 16 / 9; height: auto; background: #000; }
/* YouTube had NO rule at all until 2026-09-12, on 2,635 pages. Vimeo is turned into a
   facade before it reaches the browser, so it never needed one; YouTube arrives as a raw
   <iframe width="560" height="315">, and max-width:100% above shrinks the width while
   the height ATTRIBUTE stays 315px. On a 360px phone that is a 340x315 box for 16:9
   footage. Same defect, same fix, as the .op2-col livestream rule further down documents.
   Matches youtube.com and youtube-nocookie.com, which is how the archive splits 691/1,944. */
.entry-content iframe[src*="youtube"] { width: 100%; aspect-ratio: 16 / 9; height: auto; background: #000; }

/* The published Google Sheet on /member-home/shared-charts-scans-studies/, the
   thinkorswim shared-links table. Jeff 2026-09-12: "the embedded spreadsheet
   looks small and cuts off some information on the right".
   Shrinking it is not the fix. The sheet is ~1335px of columns (Date, Individual,
   Category, Name, Shared Link, Description) and will not fit a 1166px content box
   at any width we can offer, which is exactly why the page also carries a
   full-size link out. What this rule fixes is that the right-hand Description
   column was CLIPPED with no way to reach it: the wrapper scrolls horizontally
   and min-width keeps the grid at its natural size instead of letting
   .entry-content iframe's max-width:100% squeeze it.
   Specificity is (0,2,1) by construction so it never ties with
   .entry-content iframe (0,1,1) and depend on file order, the same reasoning as
   the .op2-col iframe rule further down. */
.entry-content .sheet-embed {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	border: 1px solid var(--line);
	border-radius: 12px;
}
/* No width or height here on purpose. The 21 sheets are published at 700px to 1335px
   and each one's own width/height attributes are the only thing that knows which.
   max-width:none is the whole job: it takes back the max-width:100% above, which is
   what was squeezing the grid. */
.entry-content .sheet-embed iframe {
	display: block;
	max-width: none;
	border: 0;
}
/* Matches the search submit and .pagination .current: brand green carries #04140d
   ink, not --ink, because --ink is near-white in the dark theme. */
.entry-content .sheet-actions { margin: 0 0 14px; }
.entry-content .sheet-actions a {
	display: inline-block;
	padding: 12px 18px;
	font-weight: 700;
	color: #04140d;
	background: var(--accent);
	border-radius: 10px;
	text-decoration: none;
}
.entry-content .sheet-actions a:hover { filter: brightness(1.08); }
.wp-embed-responsive .wp-block-embed.wp-embed-aspect-16-9 .wp-block-embed__wrapper { position: relative; aspect-ratio: 16 / 9; }
.embed {
	display: block;
	position: relative;
	aspect-ratio: 16 / 9;
	border-radius: 12px;
	overflow: hidden;
	margin: 0 0 28px;
	border: 1px solid var(--line);
	background: var(--bg-2);
}
/* Note: for a player.vimeo src the rule above (.entry-content
   iframe[src*="player.vimeo"], specificity 0-2-1) OUTRANKS this one (0-1-1) and
   applies height:auto here. That is safe, not luck: with inset:0 giving a
   non-auto top and bottom, CSS2.1 10.6.4 solves the height from the equation
   and the iframe still fills .embed; aspect-ratio does nothing once both axes
   are determined. Proven by measurement, not by reading: the facade click test
   asserts a 0px change in the box rect. A showcase src matches neither
   selector's attribute test, so this rule governs it alone. */
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Player swap (inc/player-swap.php): our own CloudFront <video> sits in the same box as the iframe. */
.embed video { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: #000; }
.embed .tt-player { position: absolute; inset: 0; width: 100%; height: 100%; display: block; background: #000; }
/* Media Chrome theme for the AWS player only. <mux-player> keeps its own theme (Mux derives its menu
   background from --media-primary-color, so overriding the colours there turned the captions/rate menus
   white-on-white); it only gets accent-color, which is the same green, so both players still match. */
.embed media-controller.tt-player {
	--media-primary-color: #fff; --media-secondary-color: rgba(0, 0, 0, .75); --media-accent-color: #16c784;
	--media-range-bar-color: #16c784; --media-text-color: #fff; --media-icon-color: #fff;
	--media-font-family: inherit;
	--media-control-background: transparent; --media-control-hover-background: rgba(255, 255, 255, .12);
	--media-menu-background: rgba(20, 24, 30, .95); }
.embed media-controller.tt-player video { width: 100%; height: 100%; object-fit: contain; }
/* Controls must stay readable on white video frames (slides, logos): white icons need a dark band behind
   them, not a gradient that fades to transparent (Brian, Telegram 2026-10-03, Options 101). Same band on both
   players: the AWS media-control-bar and Mux Player's bottom control bar part. */
.embed media-controller.tt-player media-control-bar,
.embed mux-player.tt-player::part(control-bar bottom) {
	background: linear-gradient(to top, rgba(0, 0, 0, .78), rgba(0, 0, 0, .55));
}
.embed.tt-pl { overflow: hidden; background: #000; }
.tt-pl .tt-pl-stage { position: absolute; inset: 0; }
.tt-pl .tt-pl-stage > .embed { position: absolute; inset: 0; margin: 0; border: 0; border-radius: 0; aspect-ratio: auto; }
.tt-pl .tt-pl-head { position: absolute; top: 0; left: 0; z-index: 3; max-width: min(560px, 92%); padding: 12px 14px;
	color: #fff; background: rgba(0, 0, 0, .62); border-bottom-right-radius: 10px; transition: opacity .25s;
	pointer-events: auto; }
.tt-pl .tt-pl-title { font-weight: 700; font-size: 1.05rem; line-height: 1.25; }
.tt-pl .tt-pl-from { font-size: .85rem; opacity: .8; margin: 2px 0 8px; }
.tt-pl .tt-pl-row { display: flex; align-items: center; gap: 10px; font-size: .9rem; }
.tt-pl .tt-pl-cur { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tt-pl button { display: inline-flex; align-items: center; justify-content: center; padding: 4px; border: 0;
	border-radius: 6px; background: transparent; color: #fff; cursor: pointer; font: inherit; }
.tt-pl button:hover:not(:disabled) { background: rgba(255, 255, 255, .15); }
.tt-pl button:disabled { opacity: .35; cursor: default; }
.tt-pl .tt-pl-toggle svg { transition: transform .2s; }
.tt-pl.is-open .tt-pl-toggle svg { transform: rotate(180deg); }
.tt-pl .tt-pl-list { position: absolute; z-index: 3; left: 0; top: 112px; bottom: 56px; width: min(560px, 92%); margin: 0;
	padding: 6px 0; overflow-y: auto; list-style: none; counter-reset: ttpl; background: rgba(12, 15, 20, .94); }
.tt-pl .tt-pl-list li { counter-increment: ttpl; }
.tt-pl .tt-pl-list button { display: flex; width: 100%; justify-content: flex-start; gap: 10px; padding: 8px 14px;
	border-radius: 0; text-align: left; font-size: .92rem; }
.tt-pl .tt-pl-list button::before { content: counter(ttpl); min-width: 2ch; opacity: .6; }
.tt-pl .tt-pl-list li.is-current button { color: #16c784; font-weight: 600; }
@media (hover: hover) {
	.tt-pl:not(:hover):not(.is-open) .tt-pl-head { opacity: 0; }
}
.embed .tt-start-over { position: absolute; top: 12px; right: 12px; z-index: 2; padding: 6px 12px; border: 0;
	border-radius: 999px; background: rgba(10, 14, 20, .75); color: #fff; font: inherit; font-size: .9rem; cursor: pointer; }
.embed .tt-start-over:hover { background: #16c784; color: #0b0f14; }
.embed .tt-player-note { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; text-align: center; color: #fff; background: rgba(0, 0, 0, .75); }
.embed .poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; margin: 0; border: 0; border-radius: 0; }
.embed.video-facade { cursor: pointer; }
.embed .play { position: absolute; inset: 0; display: grid; place-items: center; background: rgba(4, 10, 7, .38); transition: background .15s; }
.embed.video-facade:hover .play { background: rgba(4, 10, 7, .18); }
.embed .play span {
	width: 76px;
	height: 76px;
	border-radius: 50%;
	background: var(--accent);
	color: #04140d;
	display: grid;
	place-items: center;
	font-size: 1.875rem;
	padding-left: 5px;
	box-shadow: 0 12px 34px rgba(22, 199, 132, .45);
}
.embed.no-poster { background: radial-gradient(120% 120% at 50% 0, rgba(22, 199, 132, .10), transparent), linear-gradient(135deg, var(--card-2), var(--bg-2)); }

/* OP2 columns (op2-cols / op2-col) ---------------------------------------
   704 scraped pages carry this markup with no CSS at all, so every one of them
   renders as a stack today. Measured before this change on
   analyze-tab-overview at 1280px: .op2-cols computes to display:block,
   grid-template-columns:none, and the two .op2-col boxes sit at y=401.69 and
   y=1074.69. Every selector below is scoped under .op2-cols / .op2-col, so the
   667 pages without the wrapper get no new declaration at all. */
.op2-cols {
	display: grid;
	/* Mobile first: one column. The 2/3-column tracks arrive above the theme's
	   860/861px stop, which since the header search disclosure is the
	   PHONE/TABLET stop only (card grid, header search row). The nav's collapse
	   moved up to 1209/1210px and is a header concern; these column tracks
	   deliberately did not follow it, because a content-width decision has no
	   business tracking how wide the primary menu happens to be. */
	grid-template-columns: 1fr;
	gap: 22px; /* the same gap .grid already uses in this file */
	margin: 24px 0;
	/* start, not the grid default stretch: shape D's near-empty column (a lone
	   <h2>Menu</h2>) must not be stretched to a tall neighbour's height.
	   Columns get no background and no border, so a short column simply ends
	   instead of drawing an empty block. */
	align-items: start;
}
@media (min-width: 861px) {
	/* Counts re-measured 2026-08-31 over the full 20,796-page import (the numbers
	   here previously described the 704-page partial scrape): 14,316 pages carry
	   this markup across 14,702 blocks, of which 14,587 have exactly two columns,
	   103 have three, 8 have four and 4 have five. None has one.

	   Real geometry, measured rather than assumed: .article is max-width:1180px
	   with 40px 22px 60px padding, so the content box is exactly 1136px. A
	   percentage track resolves against the container's CONTENT BOX, NOT against
	   the width left after the gap: 58% of 1136 = 658.891px, the 22px gap comes
	   out of what 1fr receives, and 1fr gets 455.109px (confirmed live:
	   "658.875px 455.125px"). The delivered ratio is therefore 59.1/40.9, not
	   58/42. Change --op2-split in theme.json if that reads wrong on a screen.

	   13,933 of the 14,702 blocks put a VIDEO in one column and prose in the
	   other (13,926 video-first, 3 video-second, 4 in both), which is two thirds
	   of the whole archive. Those blocks are handled by the float rule below
	   instead of by these tracks; see the reasoning there. */
	.op2-cols {
		grid-template-columns: var(--op2-split) 1fr;
	}
	.op2-cols:has(> .op2-col:nth-child(3)) {
		/* Mostly "Downloads" lists and scraped OP2 footers. Equal thirds, no
		   per-page special cases. Blocks with four and five columns land here too
		   and wrap into rows of three; 12 blocks in 20,796 pages does not justify
		   a fourth track rule. */
		grid-template-columns: repeat(3, 1fr);
	}

}
/* Description moved out of the column beside the player by
   theotrade_archive_video_desc() (functions.php). It is a sibling of .op2-cols,
   so it simply takes the full content width; the block above keeps its two
   columns, with the metadata still next to the video where it fits. */
.video-desc {
	margin: 22px 0 24px;
}
.video-desc > :first-child {
	margin-top: 0;
}

.op2-col {
	/* Grid items default to min-width:auto, which lets one long unbreakable
	   token (a bare URL, a long download label) push its track past its share
	   and give the whole page a horizontal scrollbar. min-width:0 lets the
	   track shrink; overflow-wrap lets the token actually break. Both are
	   needed, neither is sufficient alone. */
	min-width: 0;
	overflow-wrap: anywhere;
}

/* Paired multi-video blocks — theotrade_archive_pair_video_columns() rebuilds
   the "N videos in one column, N captions in the other" pages (3,711, mostly
   daily chat-room archives) into caption-over-video cells. Two tracks above the
   same 860/861 stop the other content grids use, one below it. */
.video-pairs {
	display: grid;
	grid-template-columns: 1fr;
	gap: 28px 22px;
	margin: 24px 0;
	align-items: start;
}
@media (min-width: 861px) {
	.video-pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.video-pair { min-width: 0; overflow-wrap: anywhere; }
/* The caption block repeats a big page-level heading in every cell
   ("TheoTrade Class Archive" / "...Chat Room Archive"); inside a cell it is a
   label, not a section heading — scale it down and tighten the metadata line. */
.video-pair h2, .video-pair h3 { font-size: 1.05rem; margin: 0 0 6px; }
.video-pair p { margin: 0 0 10px; }
/* Zero the first element's own top margin so the two column tops line up.
   This has to reach 0, 1 or 2 div levels deep because OP2 wrapped content
   differently per page: a bare <h2> straight under .op2-col (shape D,
   measured on whitelist-instructions), an <h2> or <img> inside one div, or an
   <img>/<iframe> inside two divs (shape A and the livestream embeds). The
   7487 wrapper divs inside columns carry no attributes and need no rule of
   their own: this chain passes THROUGH them and declares on the child.
   Without this, .entry-content h2's 34px or .entry-content img's 22px top
   margin drops one column and the tops miss the 4px budget. */
.op2-col > :first-child,
.op2-col > div:first-child > :first-child,
.op2-col > div:first-child > div:first-child > :first-child {
	margin-top: 0;
}
/* Non-faced iframes inside a column: the 35 pages carrying a livestream.com
   embed as <iframe width="640" height="360">.
   THIS RULE IS NOT ABOUT OVERFLOW. .entry-content iframe { max-width: 100% }
   (line 381) and .op2-col { min-width: 0 } above already prevent horizontal
   scroll — verified before the change: scrollWidth === clientWidth === 1280 on
   the livestream page. It is about the height="360" ATTRIBUTE, which nothing
   overrides: clamped into a 455px column (or a 364px third) the box becomes
   455x360 or 364x360 and the 16:9 ratio breaks. Delete this and the aspect
   ratio breaks again, silently, on 35 pages.
   The border, 12px radius and 28px bottom margin match .embed so that a
   livestream box and a Vimeo box sitting in the same column are framed
   identically. A replaced element clips its own corners, so no overflow is
   needed, and no focus ring is involved because a raw iframe is not the
   focusable facade.
   SPECIFICITY, deliberately by construction and not by order: a plain
   .op2-col iframe would be (0,1,1) — an exact tie with BOTH .embed iframe
   (line 394) and .entry-content iframe (line 381) — and, sitting later in the
   file, it would win both ties and force height:auto onto a faced iframe that
   needs height:100%. The :not(.embed iframe) guard makes the two match sets
   DISJOINT, so that tie can never occur, and it lifts this selector to (0,2,2)
   so it also outranks .entry-content iframe's border:0 for the border below.
   Both effects are required; neither is cosmetic. */
.op2-col iframe:not(.embed iframe) {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	margin: 0 0 28px;
	border: 1px solid var(--line);
	border-radius: 12px;
	background: #000; /* literal, exactly as line 382 already does */
}

/* Hub tile grid (tile-grid / tile) ---------------------------------------
   7,412 scraped OP2 navigation tiles on 172 pages, rebuilt by
   theotrade_archive_tile_grid() at the_content priority 9. Every selector here is
   scoped under .tile-grid, the same discipline as the OP2 block above (:574-577), so
   the 1,202 pages without tiles receive no new declaration at all.

   Deliberately NOT reusing .grid: it is redeclared at :806 (1fr 1fr) and :820 (1fr),
   which would import fixed 3/2/1 breakpoints and defeat auto-fill. Measured at a
   666px viewport, auto-fill gives two 300px columns where .grid would give one 622px
   column. No media query in this section; auto-fill is the whole point.

   The 300px floor is the source material, not a taste call: 5,768 of the 7,412 tiles
   have a 300x169 image and all 72 unique files are 300x169 on disk, so a wider card
   upscales the majority with nothing better to fall back on. Measured card widths:
   364 at 1180+ (content box 1136), 397 at 860 (816), 339 at 375 (339 -- .article
   drops to 18px padding at :821, so it is 339 and not 331), and 300 at 666 where the
   grid still fits two columns. */
.tile-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
	gap: 22px; /* the same gap .grid (:383) and .op2-cols (:587) already use */
	margin: 24px 0;
}
.tile-grid .tile {
	/* Containing block for the stretched-link ::after below. */
	position: relative;
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	/* Grid items default to min-width:auto, so one unbreakable caption token could
	   push its track past its share and give the page a horizontal scrollbar. Same
	   reasoning as .op2-col (:620). */
	min-width: 0;
	transition: transform .15s ease, border-color .15s ease;
}
.tile-grid .tile:hover { transform: translateY(-3px); border-color: var(--line-2); }
/* 16:9 box so rows align across the six source sizes in the archive (300x169 for
   5,768 tiles, 960x540 for 1,295, 1920x1080 for 262, and 12 rarer sizes). Cropping
   is under one pixel: 300/169 = 1.7751 against 16/9 = 1.7778. */
.tile-grid .tile-media { display: block; aspect-ratio: 16 / 9; background: var(--bg-2); overflow: hidden; }
.tile-grid .tile-media a { display: block; height: 100%; }
/* Stretched link: the whole card navigates, no JS and no third anchor. inset:0
   resolves against .tile's padding box. The audience skews 60-90, so the hit area
   must not be the caption text alone. */
.tile-grid .tile-media a::after { content: ""; position: absolute; inset: 0; z-index: 1; }
/* Neutralise .entry-content img (:464 -- 10px radius, 1px border, 22px block margin).
   (0,2,1) against (0,1,1), so this wins on specificity and not on source order. Also
   outranks img{height:auto} (:103), which is (0,0,1). */
.tile-grid .tile-media img { width: 100%; height: 100%; object-fit: cover; margin: 0; border: 0; border-radius: 0; }
/* Neutralise .entry-content h2, .entry-content h3 (:465 -- 34px/12px margins).
   (0,2,0) against (0,1,1): two classes beat one class plus one element, so this wins
   outright rather than tying and depending on staying below line 465. */
.tile-grid .tile-title { margin: 0; font-size: 1.125rem; line-height: 1.35; letter-spacing: -.01em; }
/* Neutralise .entry-content a (:463 -- accent colour plus green underline). (0,3,1)
   against (0,1,1). display:block plus padding makes the whole caption band the click
   and focus target: measured 362x52 at a 1280px viewport, so comfortably past 44px.
   z-index:2 paints it above the stretched ::after, which keeps the caption text
   selectable; mirrors .card-title a (:416). */
.tile-grid .tile-title a {
	display: block;
	position: relative;
	z-index: 2;
	padding: 13px 17px 15px;
	min-height: 44px;
	color: var(--ink);
	text-decoration: none;
}
.tile-grid .tile:hover .tile-title { color: var(--link); }
/* The base focus ring (:104-108) uses outline-offset:2px, and .tile's overflow:hidden
   clips descendant painting at the padding box. Measured at 1280px: the caption anchor
   spans x=73..435 and the padding box ends at x=435, so a +2px ring would be drawn to
   x=440 and clipped on three sides. A negative offset draws it inside the box, where
   nothing clips it. (0,3,2) against a:focus-visible (0,1,1). */
.tile-grid .tile-title a:focus-visible { outline-offset: -3px; }
/* The 63 tiles that carry a description. Neutralises .entry-content p (:462, 20px
   bottom margin), (0,2,0) against (0,1,1). Left OUTSIDE the stretched link's stacking
   context on purpose: the description area stays clickable, at the cost of not being
   selectable. Same two-line clamp as .card .ex (:418). */
.tile-grid .ex {
	margin: 0;
	padding: 0 17px 15px;
	color: var(--muted);
	font-size: 1rem;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* Post navigation (prev/next, global chronological) ----------------------- */
.post-nav {
	display: flex;
	flex-wrap: wrap;
	gap: 14px;
	margin: 40px 0 0;
	padding-top: 26px;
	border-top: 1px solid var(--line);
}
.post-nav .nav-previous,
.post-nav .nav-next {
	flex: 1 1 240px;
}
.post-nav .nav-next { text-align: right; }
.post-nav a {
	display: block;
	min-height: 44px;
	padding: 10px 16px;
	border: 1px solid var(--line);
	border-radius: 10px;
	color: var(--muted);
	font-weight: 600;
	line-height: 1.3;
}
.post-nav a:hover { color: var(--ink); border-color: var(--line-2); background: var(--card); }

/* Pagination ------------------------------------------------------------- */
.pagination { padding: 26px 0 50px; }
.pagination .nav-links { display: flex; gap: 8px; flex-wrap: wrap; }
.pagination .page-numbers {
	display: inline-grid;
	place-items: center;
	min-width: 48px;
	min-height: 48px;
	padding: 0 14px;
	border: 1px solid var(--line);
	border-radius: 10px;
	color: var(--muted);
	font-weight: 700;
}
.pagination .page-numbers:hover { color: var(--ink); background: var(--card); }
.pagination .page-numbers.current { background: var(--accent); border-color: var(--link); color: #04140d; }

/* Search / 404 ----------------------------------------------------------- */
.search-form { display: flex; gap: 10px; max-width: 560px; margin: 18px 0 6px; }
.search-form input[type="search"] {
	flex: 1;
	min-height: 52px;
	padding: 0 16px;
	font-size: 1.125rem;
	color: var(--ink);
	background: var(--card);
	border: 1px solid var(--line-2);
	border-radius: 10px;
}
.search-form input[type="submit"] {
	min-height: 52px;
	padding: 0 22px;
	font-size: 1.125rem;
	font-weight: 800;
	color: #04140d;
	background: var(--accent);
	border: 0;
	border-radius: 10px;
	cursor: pointer;
}
.not-found { padding: 60px 0 80px; max-width: 640px; }
.not-found p { color: var(--muted); }

/* Footer ------------------------------------------------------------------ */
.site-footer { border-top: 1px solid var(--line); background: var(--bg-2); margin-top: 50px; }
/* Compliance disclaimer — same text as the production archive footer, on every page.

   padding-TOP, not the `padding` shorthand. Both boxes in this footer are also
   `.wrap`, which carries `padding: 0 22px`, and a shorthand here reset that side
   padding to zero: the footer text then ran edge to edge with no gutter at all
   below 1180px (measured: paragraphs at left 0, right 390 in a 390px viewport),
   and above 1180px it sat 22px wider on each side than every other column on the
   page (measured at 1366px: header logo and article h1 both start at x=115, the
   footer text started at x=93). Setting only the block side keeps .wrap's gutter
   and keeps the footer aligned with the content column at every width. */
.site-footer .disclaimer { padding-top: 30px; color: var(--faint); font-size: .875rem; line-height: 1.6; }
.site-footer .disclaimer p { margin: 0 0 12px; }
.site-footer .disclaimer a { color: var(--muted); }
.site-footer .disclaimer a:hover { color: var(--link); }
/* padding-block, for the same reason as .disclaimer above. */
.ft { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; padding-block: 34px; color: var(--faint); font-size: 1rem; }
.ft a { color: var(--muted); padding: 6px 0; }
.ft a:hover { color: var(--link); }

/* Login page (page-member-login.php) --------------------------------------- */
/* One narrow centred column. The two cards hold iM360's [i4w_login_form] and the
   Keap forgot-password form, both coming from the page content, so the selectors
   below target the plugin's own class names (.i4w_login_*) and the form markup
   stored on page 2224, not anything this theme generates. */
.login-page { max-width: 560px; }
.login-page .article-head { text-align: center; }
.login-page .article-head h1 { font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 500; color: var(--muted); }
.login-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 28px 28px 20px; margin: 0 0 24px; }
.login-card h2 { margin: 0 0 14px; font-size: 1.375rem; }
.login-card p { margin: 0 0 16px; }
.login-card label { display: block; font-size: .9rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.login-card input[type="text"],
.login-card input[type="password"],
.login-card input[type="email"] { width: 100%; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg); color: var(--ink); font: inherit; }
.login-card input:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
.login-card .i4w_login_remember label { display: flex; align-items: center; gap: 8px; font-weight: 400; color: var(--ink); }
.login-card .i4w_login_remember input { width: auto; }
.login-card input[type="submit"],
.login-card button[type="submit"] { width: 100%; padding: 13px 16px; border: 0; border-radius: 10px; background: var(--accent); color: #04140d; font: inherit; font-weight: 700; cursor: pointer; }
.login-card input[type="submit"]:hover,
.login-card button[type="submit"]:hover { filter: brightness(1.08); }
.login-card .i4w_login_err { color: var(--danger); font-weight: 600; margin: 0 0 14px; }
.login-card .fine { font-size: .85rem; color: var(--faint); margin: 8px 0 0; }

/* Responsive -------------------------------------------------------------- */
/* Two stops, and they are not interchangeable:

   1209px — the nav collapses behind .menu-toggle. Raised from 860px by the
   search disclosure, and that raise is what makes "70px at every width above
   860px" true rather than aspirational.

   Raised again from 1000px on 2026-09-01, exactly as the previous note here said
   to: "if the primary menu ever grows past six items, THIS number goes up". The
   menu is now the 8-item primary-2026, and the row was measured on the live site
   at logo 88 + nav 775 + search 44 + theme 44 + Log out 84 = 1035, plus four
   10px gaps = 1075. Add .wrap's 2 x 22px padding and the row needs a 1119px
   viewport. At 1000px the query stopped applying while the row still could not
   fit, so between 1001 and 1118px the nav wrapped to a second line AND the
   burger was hidden: a 126px double-height masthead with no way to collapse it,
   measured at both 1001 and 1100px, and iPad landscape (1024px) sat inside it.
   1139/1140 is 1119 plus ~21px of slack for font-rendering differences.
   Between 861 and 1209px the row carries only a logo and three buttons, so one
   row is never in doubt. Do not squeeze the nav, and do not put the form back in
   the row.

   Raised again to 1209/1210 on 2026-09-12 for the 9th item, "TOS Links" (Jeff:
   members ask for the thinkorswim shared links about once a day, so it has to be
   in the header rather than one level down). Measured in the browser on the live
   masthead, modelling the logged-in row (iM360 hides Futures and Classes from
   anonymous visitors, and Log out only exists for members, so the logged-out
   header is 191px narrower and must not be used for this sum): logo 110 + nav +
   search 44 + theme 44 + Log out, with 10px .hd gaps, needs 1151px of content
   box, so a 1195px viewport. 1209/1210 is that plus ~14px.
   Three things bought the room: .nav a side padding 10 -> 8px (36px), .nav ul gap
   4 -> 3px (8px), and --maxw 1180 -> 1210 (30px, which is what Andrii allowed).
   The logo going 40 -> 50px spent 22px of it back.
   At/above the breakpoint --maxw pins the content box at 1166px, 15px more than
   the row needs. That 15px is the whole safety margin for a Windows client, where
   the system stack resolves to Segoe UI rather than SF: do not spend it. A 10th
   item does not fit. Put the next one in a dropdown or take one out.

   THREE numbers move together, not two. NAV_BREAKPOINT in assets/js/nav.js is
   where the JS closes the menu, and it must equal this query's max-width or the
   panel stays open in a layout with no button to close it. The `min-width` query
   further up ("one header overlay at a time") must be this plus one, because it
   describes the width band where submenus are hover overlays rather than static
   parts of the panel, and that band starts exactly where the panel ends.

   860px — the phone/tablet layout: two-column card grid, and the header search
   drops the disclosure for a visible full-width row. Paired with min-width:861px
   above (the disclosure) and with SEARCH_BREAKPOINT in nav.js. */
@media (max-width: 1209px) {
	.menu-toggle { display: inline-flex; }

	/* Masthead controls hug the RIGHT edge with the logo alone on the left
	   (Andrii 2026-09-12: space-between scattered Menu, search and theme across
	   the whole width). Menu goes last in the row, next to the panel it opens:
	   order 5 sits above the zero-order controls and below the search row (8)
	   and the panel (9) that the phone layout assigns further down. */
	.hd { justify-content: flex-end; }
	.hd .logo { margin-right: auto; }
	.hd .menu-toggle { order: 5; }

	/* Log out: the top-level copy ends the panel; the copy inside My Account
	   is for the desktop dropdown only (both from theotrade_archive_nav_account_items). */
	.nav .menu-item-logout { display: block; margin-top: 8px; }
	.nav .menu-item-logout a { color: var(--ink); }
	.nav .menu-item-logout-desktop { display: none; }

	/* #primary-nav becomes a full-width panel. No-JS contract: with no
	   .nav-js class on <html> (JS never ran), the rules below make the
	   panel and every submenu inside it visible/reachable by default —
	   nothing here depends on JS to be usable. */
	.nav {
		display: flex;
		flex-direction: column;
		width: 100%;
		gap: 2px;
		padding: 10px 0;
	}
	.nav ul { flex-direction: column; align-items: stretch; gap: 4px; width: 100%; }
	.nav li { width: 100%; }

	/* Every panel item is a full-width bordered block, 44px tall (the usual
	   touch floor), text centred. Before 2026-09-12 the link was inline-block
	   with `padding: 12px 8px`, so the hit area was only as wide as the text and
	   a thumb scrolling past the panel could land on the item to its side. The
	   border is what makes each target a visible, deliberate thing rather than
	   a word floating in the panel (Andrii's screenshots at 561px). */
	.nav a {
		display: block;
		width: 100%;
		min-height: 44px;
		padding: 11px 14px;
		text-align: center;
		white-space: normal;
		background: var(--card);
		border: 1px solid var(--line);
	}
	.nav a:hover,
	.nav a:focus-visible { color: var(--ink); border-color: var(--line-2); }

	/* Parent with children: the chevron sits INSIDE the full-width link's row,
	   not beside a narrower link. Andrii 2026-09-12: with the link 48px
	   narrower its caption centred 24px left of every plain item's caption.
	   Grid, not absolute positioning: link and button share cell 1/1, so the
	   button stretches to whatever height the link has (a two-line caption
	   included) and the submenu falls into row 2 on its own; an absolutely
	   positioned button would need a hard-coded height or would stretch over
	   the open submenu. The link's side padding is symmetrical (58px, room for
	   the 44px button plus 14px) so its caption centres on the ROW. */
	.nav li.has-submenu-toggle { display: grid; grid-template-columns: 1fr; gap: 4px; }
	.nav li.has-submenu-toggle > a { grid-area: 1 / 1; padding-left: 58px; padding-right: 58px; }
	.submenu-toggle {
		grid-area: 1 / 1;
		justify-self: end;
		align-self: stretch;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		padding: 0;
		background: transparent;
		border: 0;
		border-left: 1px solid var(--line);
		margin: 1px; /* stays inside the link's 1px border instead of painting over it */
		border-radius: 0 7px 7px 0;
		color: var(--ink);
		cursor: pointer;
	}
	.nav li.has-submenu-toggle > .sub-menu { grid-row: 2; }
	.submenu-toggle:hover { background: var(--card-2); }
	.submenu-toggle:focus-visible { outline: 3px solid var(--link); outline-offset: 2px; }
	/* Chevron drawn with two borders, so it takes currentColor and needs no
	   glyph: the theme's font stack has no reliable monochrome chevron. */
	.submenu-toggle-glyph {
		width: 10px;
		height: 10px;
		margin-top: -5px;
		border-right: 2px solid currentColor;
		border-bottom: 2px solid currentColor;
		transform: rotate(45deg);
		transition: transform .15s ease;
	}
	.submenu-open > .submenu-toggle .submenu-toggle-glyph { margin-top: 5px; transform: rotate(225deg); }

	/* Submenus: static parts of the panel, not hover/focus overlays
	   (style.css:413-415 once hid them entirely on mobile, making them
	   unreachable by touch/keyboard). Children sit 16px in and without the
	   card fill, so the hierarchy reads without an indent per level of text. */
	.nav .sub-menu {
		display: flex;
		position: static;
		flex: 1 1 100%;
		width: 100%;
		min-width: 0;
		gap: 4px;
		/* 16px on BOTH sides, so a child caption centres on the same axis as a
		   parent's; a left-only indent shifted it 8px right. */
		padding: 0 16px 4px;
		background: transparent;
		border: 0;
		box-shadow: none;
	}
	.nav .sub-menu a { font-weight: 500; background: transparent; }
	.nav .sub-menu a:hover,
	.nav .sub-menu a:focus-visible { background: var(--card); }

	/* Collapsed states exist ONLY once nav.js has run: .nav-js on <html> for
	   the panel, .has-submenu-toggle on a parent for its submenu (nav.js adds
	   both, and the chevron button, together). With no JS the CSS default
	   above is "everything open and reachable". The submenu rule outranks
	   `.nav li:hover > .sub-menu`, so hovering a parent in the panel does not
	   reopen what the visitor closed. */
	html.nav-js #primary-nav { display: none; }
	html.nav-js.nav-open #primary-nav { display: flex; }

	/* Open panel scrolls itself, the page behind it does not. nav.js sets
	   max-height to the space left under the masthead (measured on open and on
	   resize); without that inline value there is no cap and the no-JS panel
	   stays a plain part of the page flow. overscroll-behavior stops the page
	   from taking over when the panel hits its end. */
	html.nav-js.nav-open #primary-nav { overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
	html.nav-js.nav-open { overflow: hidden; }
	html.nav-js .nav li.has-submenu-toggle:not(.submenu-open) > .sub-menu { display: none; }

	/* The panel goes LAST in the masthead, and this is the only thing standing
	   between a member and the Log out link on a phone. With every submenu
	   expanded (the no-JS state, and the only state before 2026-09-12) the open
	   panel is 1,987px tall; sitting third in
	   DOM order it pushed the search field, the theme toggle and Log out that far
	   down the document (measured at 390px: header 2,180px tall, .logout-link at
	   y=2,171 in an 844px viewport). Ordering it after them means opening the
	   menu never moves them. */
	.hd .nav { order: 9; }
}
@media (max-width: 860px) {
	/* Intentional tablet stop at 2 columns; 1-column applies only below 560px (phone). */
	.grid { grid-template-columns: 1fr 1fr; }

	/* Header search keeps its own full-width row here, with no disclosure. The
	   flex sizing now belongs to .search-panel, because the PANEL (not the form)
	   is the flex item of .hd since the disclosure was added; the panel carries
	   no decoration at all at this width, all of that lives in the min-width:861
	   block. The form's max-width reset is still needed, for a different reason
	   than before: with the header's own inert 320px cap gone, the form would
	   inherit the base .search-form 560px cap and stop short of the row it was
	   just given. */
	.hd .search-panel { flex: 1 1 100%; }
	.hd .search-form { max-width: none; margin: 10px 0 0; }

	/* Search onto the SECOND row instead of the middle one, which takes the
	   masthead from three rows to two. Before: logo + Menu / search / theme +
	   Log out, measured 184px at 390px, i.e. 22% of an 844px viewport and 28% of
	   an iPhone SE. After: logo + theme + Menu / search (Log out lives at the
	   bottom of the panel since 2026-09-12). Row one is 44 + 44 + 94 = 182px of
	   controls plus 2 x 10px gap = 202px, inside the 316px content box at 360px,
	   the narrowest width this theme targets.

	   order: 8, deliberately not 3. The DOM order is logo, Menu, nav, search,
	   theme, Log out; giving the panel a number ABOVE theme and Log out (and
	   below the nav's 9) leaves the visual order identical to the DOM order, so
	   tab order still follows what the eye sees. Pulling the panel to the front
	   with order: 3 would have looked the same and made tabbing zigzag. */
	.hd .search-panel { order: 8; }

	/* The only horizontal overflow anywhere in this theme: measured scrollWidth
	   330 against a 320px viewport. It takes BOTH declarations below, which is
	   why they are commented together (measured: panel alone leaves scrollWidth
	   at 330, input alone leaves it at 330, together 320).

	   The panel is a flex item of .hd with min-width: auto, so it cannot shrink
	   below its min-content, and its min-content comes from the form inside it,
	   measured at 336px (the text input contributes its default size-attribute
	   intrinsic width, ~180px, plus the 97px submit and the 10px gap). That
	   floor beat the 276px content box and the panel rendered 308px wide.

	   Zeroing the panel lets it take its `flex: 1 1 100%` basis of 276px, and
	   zeroing the input is then what lets the form re-fit inside it: the input
	   has `flex: 1` from the base rule, so its own min-width: auto would
	   otherwise hold it at 201px and push the submit back out. */
	.hd .search-panel { min-width: 0; }
	.hd .search-form input[type="search"] { min-width: 0; }

	/* Anchor targets clear the taller masthead. The base value (70px + 16px) is
	   correct only above 860px; here the header is ~127px, so the skip link used
	   to land about 98px underneath it. */
	#content { scroll-margin-top: calc(127px + 16px); }
}
@media (max-width: 560px) {
	.grid { grid-template-columns: 1fr; }
	.article { padding: 28px 18px 44px; }

	/* Breadcrumbs collapse to "parent / current page" on a phone. The full trail
	   costs more screen than the content it introduces: measured 128px (4 lines)
	   on a daily newsletter page and 256px (10 lines) on
	   /member-home/member-classes-archive/, whose inherited title is a keyword
	   dump. On top of the 128px masthead that left the h1 at y=303 and y=534
	   respectively, in an 844px viewport. After: 64px, h1 at y=239.

	   Two separate measures, and both are needed. Clamping the current page alone
	   fixed only the keyword-dump page, because on a typical page the four lines
	   come from the ANCESTOR titles ("December 2025 Member Newsletter"), not from
	   the current one. Hiding ancestors alone leaves the long current title
	   wrapping. Verified: clamp alone 128px, both together 64px.

	   The last ancestor link survives because "up one level" is the action people
	   actually take from a breadcrumb; the rest of the path stays on desktop. */
	.breadcrumbs > a:not(:last-of-type) { display: none; }

	/* Every separator goes except the one before the current page. It has to be
	   selected by :has() rather than by position: the separators and the current
	   page are all <span>, so :last-of-type among spans is the CURRENT PAGE, and
	   `.sep:not(:last-of-type)` therefore hides every separator including the one
	   meant to survive (measured: the trail rendered as "December 2025 Member
	   NewsletterMonday, December 1", with no separator at all).
	   :nth-last-child(2) would also work today, but silently picks the wrong node
	   if theotrade_archive_breadcrumbs() ever stops appending the current page
	   last; :has() simply stops matching. */
	.breadcrumbs > .sep { display: none; }
	.breadcrumbs > .sep:has( + [aria-current="page"] ) { display: inline; }

	/* And the current page itself to one line. Ellipsised rather than hidden:
	   someone arriving from search still uses it to tell where they are, and
	   clipping keeps the text in the accessibility tree where display: none
	   would remove it. */
	.breadcrumbs [aria-current="page"] {
		display: inline-block;
		max-width: 100%;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		vertical-align: bottom;
	}
}

/* Member feedback button + dialog (functions.php, members only) --------- */
.tt-feedback-open {
	position: fixed;
	right: 18px;
	bottom: 18px;
	z-index: 80;
	padding: 12px 18px;
	font-weight: 700;
	color: #04140d;
	background: var(--accent);
	border: 0;
	border-radius: 999px;
	box-shadow: var(--shadow-lg);
	cursor: pointer;
}
.tt-feedback-open:hover { filter: brightness(1.08); }
.tt-feedback-open:focus-visible { outline: 3px solid var(--link); outline-offset: 2px; }
.tt-feedback-dialog {
	position: fixed;
	right: 18px;
	bottom: 72px;
	z-index: 81;
	width: min(380px, calc(100vw - 36px));
	padding: 18px;
	background: var(--bg-2);
	border: 1px solid var(--line);
	border-radius: 14px;
	box-shadow: var(--shadow-lg);
}
.tt-feedback-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.tt-feedback-head h2 { margin: 0 0 10px; font-size: 1.05rem; line-height: 1.3; }
.tt-feedback-close { background: transparent; border: 0; color: var(--muted); font-size: 1.4rem; line-height: 1; cursor: pointer; }
.tt-feedback-cancel { font-size: 0.95rem; padding: 10px 12px; }
.tt-feedback-dialog label { display: block; margin: 8px 0 4px; font-size: 0.9rem; color: var(--muted); }
.tt-feedback-dialog select,
.tt-feedback-dialog textarea {
	width: 100%;
	padding: 10px 12px;
	color: var(--ink);
	background: var(--card);
	border: 1px solid var(--line);
	border-radius: 8px;
	font: inherit;
}
.tt-feedback-dialog textarea { resize: vertical; }
.tt-feedback-hp { position: absolute; left: -9999px; width: 1px; height: 1px; }
.tt-feedback-note { margin: 8px 0; font-size: 0.85rem; color: var(--faint, var(--muted)); }
.tt-feedback-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tt-feedback-actions button[type="submit"] {
	padding: 10px 18px;
	font-weight: 700;
	color: #04140d;
	background: var(--accent);
	border: 0;
	border-radius: 8px;
	cursor: pointer;
}
.tt-feedback-actions button[disabled] { opacity: .6; cursor: default; }
.tt-feedback-status { font-size: 0.9rem; color: var(--muted); }

/* My Classes (inc/my-classes.php) */
.tt-mc-head { color: var(--muted); }
.tt-mc-filter { width: 100%; max-width: 420px; margin: 4px 0 18px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); color: var(--ink); font: inherit; }
.tt-mc-cat h2 { font-size: 1.15rem; margin: 22px 0 8px; }
.tt-mc-cat ul { margin: 0; padding-left: 1.1em; columns: 2 320px; column-gap: 32px; }
.tt-mc-cat li { margin: 0 0 6px; break-inside: avoid; }
.tt-mc-hublink { padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); }

/* 403 "not included in your subscription" (404.php, theotrade_archive_no_access_status) */
.no-access-links { margin: 14px 0 0; padding-left: 1.1em; }
.no-access-links li { margin: 0 0 8px; }
