/* Shared styles used across all themes (basket, dropdown/toggle, etc.).
   Theme-specific look and feel belongs in /Theme/{ThemeName}/css instead. */

/* TUB-165 — the accent colour the platform's own pages (user area, admin, the wizards) use through Tailwind's
   rust utilities (bg-rust, hover:bg-rust, text-rust, focus:border-rust …). Tenant themes define --color-rust in
   their own theme.css / ThemeStyle, which load AFTER this file and win; the platform layouts never did, so every
   rust utility there computed to an invalid colour — measured 2026-09-30 on tubetotable.com/userarea: the wizard's
   "Choose a picture" button (bg-rust) was invisible until hovered, and "Next" (hover:bg-rust) vanished on hover. */
:root {
    --color-rust: 193 68 14;
}

/* Backstop for AI custom CSS that sets top/left/transform on the header without position.
   On a static element top/left are ignored while transform still applies, so
   `left:50%; transform:translateX(-50%)` renders the nav at x ≈ −600. `relative` makes
   those offsets meaningful without pulling the header out of flow the way `fixed` would
   for every existing site. AI sheets that set their own position (sticky/fixed) still win
   when they load later at equal specificity. */
.site-header {
    position: relative;
}

/* Backstop for AI custom CSS that sets `display` on the mobile menu panel's own class.
   The panel ships closed via Tailwind's `.hidden{display:none}`, but a generated sheet that
   writes e.g. `.bbq-panel{display:flex}` beats it — same specificity (0,1,0) and site.css
   loads after tailwind.build.css. Measured live on website-S1: the panel stood permanently
   open over the page on both generated sites. Attribute+class is (0,2,0), so a closed panel
   stays closed regardless of load order.
   ⚠️ `#mobile-menu` is the load-bearing half: conversion STRIPS `data-slot` from the stored
   headerHtml (verified in the browser — the served panel has the id but no data-slot), so the
   attribute selector alone matched nothing. The id is the same hook the toggle script uses via
   `data-toggle-target="mobile-menu"`. The attribute selector is kept for any markup that does
   retain it. The toggle still works: it removes `.hidden`, and
   with the class gone this rule no longer matches. */
#mobile-menu.hidden,
[data-slot="menu-panel"].hidden {
    display: none;
}

/* The site header is a TOP bar, never a side column. Owner's rule, 2026-08-29: "Menu nav should be
   always standart menu (top page). We need block other types of menu in the future."

   Measured on a generated site (cmp-pro-4): the model wrote
   `.site-header { width: var(--sidebar-width); height: 100vh; position: sticky }`, expecting a flex
   ROW wrapper around the header and the content. No such wrapper survives conversion — page assembly
   puts header/main/footer directly on body — so the header became a full-height block on the left and
   `main` flowed BELOW it. The result was a dark sidebar beside an empty white page.

   Only the side-column geometry is neutralised. `position: sticky` is left alone: a sticky top bar is
   a good design and a common one. `body > header.site-header` is (0,1,2) — it outranks the
   `.site-header` (0,1,0) a generated sheet can write, so load order does not matter. */
body > header.site-header {
    width: auto;
    height: auto;
    float: none;
}

/* ...and the menu inside it runs ACROSS, never down. Owner's rule, 2026-08-29: "Menu should be
   always in header (apart from mobile menu)."

   Fixing the header box alone was not enough — measured on cmp-pro-4 at 1440px, the header was
   full-width but 671px TALL, because the nav itself was `display:flex; flex-direction:column` and
   its four links sat at y=292, 367, 442 and 518. A vertical nav makes the header a wall whatever
   width it is.

   The second rule is the owner's stated exception and it carries an id, so it outranks the first
   whatever the order: the mobile panel's nav stays stacked. The panel is a DIV (`#mobile-menu`) and
   the desktop nav is nested inside the header's own wrapper (`.header-inner` on this site), which is
   why this is a descendant rule rather than a child one — models nest the nav differently every
   time, but never inside the panel. */
body > header.site-header nav {
    flex-direction: row;
    grid-auto-flow: column;
}

body > header.site-header #mobile-menu nav {
    flex-direction: column;
    grid-auto-flow: row;
}

/* TUB-14: a vertical menu the platform had to build from the DESKTOP link class — a header stored
   before the converter kept the panel's own link class, or a panel the design left without links.
   Desktop links are coloured for the header bar; measured live 2026-09-04 that put white links on
   the design's white panel and the menu vanished. The fallback nav paints itself in the site's own
   body pair, which is readable by construction, and stacks the links. Selector carries the panel id
   so it outranks a generated `#mobile-menu a { color }` (1,0,1) and `.nav-link` (0,1,0): this one is
   (1,2,4). Generated CSS cannot target `ttt-` classes at all. Sites regenerated after TUB-14 never
   carry the marker, so their own panel design is untouched. */
body > header.site-header #mobile-menu nav.ttt-menu-fallback {
    background-color: var(--site-bg, #ffffff);
    color: var(--site-ink, #1c1917);
    padding: 0.5rem 1rem;
    gap: 0;
}
body > header.site-header #mobile-menu nav.ttt-menu-fallback a {
    color: var(--site-ink, #1c1917);
    display: block;
    padding: 0.75rem 0;
}

[data-toggle-target].is-open .icon-open {
    display: none;
}

/* Toggled by Shared.Session when hydrating per-user header state onto the anonymous shell. */
.auth-hidden {
    display: none !important;
}

.basket-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.25rem;
    border-radius: 9999px;
    font-size: 0.65rem;
    line-height: 1;
}

.basket-badge[data-basket-count="0"] {
    display: none;
}

/* Hero slider: horizontal scroll-snap carousel with the scrollbar hidden (Shared.Slider adds the
   visible prev/next/dot controls; native swipe/trackpad scroll works regardless). */
.hero-slider-track {
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.hero-slider-track::-webkit-scrollbar {
    display: none;
}

.slider-dot.is-active {
    background-color: rgba(255, 255, 255, 0.95);
}

/* TUB-24 — the page shell's own structural layout (_PageShell.cshtml, main-sidebar skeleton).
   Measured live: a generated site's stylesheet can define `.flex { display:flex; align-items:center }`
   (or `.container`/`.grid`/`.inline`/`.center`), and /site.css loads after tailwind.build.css at equal
   specificity, so the design's rule beats Tailwind's bare `.flex`/`.flex-1`/`.w-full` on the shell's own
   row and the recipes column rendered vertically centred against a taller sidebar. `ttt-` is a reserved
   token CustomCssValidator already strips from every site rule's selector, so a class built from it is
   a name no design can ever reach. These are a straight rename of the Tailwind utilities that used to
   sit on this markup — the values are transcribed exactly, not redesigned. Shared between books and
   websites (both render through _PageShell); a book page's own stylesheet (book-print.css) never
   targets any of these tokens.

   `align-items: stretch` on the row is written explicitly even though it is flex's initial value: it
   is the declaration this ticket exists to protect, and an explicit one says so to the next reader. */
.ttt-shell-container {
    max-width: 80rem;             /* max-w-7xl */
    margin-inline: auto;          /* mx-auto */
    padding-inline: 1.5rem;       /* px-6 */
    padding-block: 3.5rem;        /* py-14 */
}

@media (min-width: 1024px) {
    .ttt-shell-container {
        padding-inline: 2rem;     /* lg:px-8 */
    }
}

/* Full-bleed band variant (a leading full-bleed component was hoisted above this container): no top
   padding, the band above already provides the visual breathing room. Higher specificity than
   .ttt-shell-container alone, so load order does not matter. */
.ttt-shell-container.ttt-shell-container-band {
    padding-top: 0;               /* pb-14 (no py-14) */
}

.ttt-shell-row {
    display: flex;
    flex-direction: column;       /* flex-col */
    align-items: stretch;         /* the invariant this ticket protects — see comment above */
    gap: 2.5rem;                  /* gap-10 */
}

@media (min-width: 1024px) {
    .ttt-shell-row {
        flex-direction: row;      /* lg:flex-row */
    }
}

.ttt-shell-main {
    flex: 1 1 0%;                 /* flex-1 */
    min-width: 0;                 /* min-w-0 */
}

.ttt-shell-aside {
    width: 100%;                  /* w-full */
    flex-shrink: 0;               /* flex-shrink-0 */
}

@media (min-width: 1024px) {
    .ttt-shell-aside {
        width: 18rem;              /* lg:w-72 */
    }
}

@media (min-width: 1280px) {
    .ttt-shell-aside {
        width: 20rem;              /* xl:w-80 */
    }
}

/* space-y-8 rhythm (Tailwind's `> * + * { margin-top }` shape, not `> * + *`, so a `[hidden]` widget
   never contributes a gap) — the sidebar's own children in the non-edit path, and the edit-mode
   sortable-root wrapper's children below, which is itself the sidebar's only child so needs its own
   copy of the rhythm. */
.ttt-shell-aside > :not([hidden]) ~ :not([hidden]),
.ttt-shell-stack > :not([hidden]) ~ :not([hidden]) {
    margin-top: 2rem;             /* space-y-8 */
}

/* Edit-mode component wrapper (PageComponentHtmlHelperExtensions.RenderComponentsAsync) and its
   "+" add-component placeholder. Plain CSS rather than Tailwind utility classes: this markup is
   built in a .cs file outside every Tailwind content glob in tailwind.config.js, so utility
   classes there would be purged from the compiled build (see the Provisioning/SeedApi globs,
   which exist for exactly this reason but only cover those two folders). */
.ttt-component-slot {
    position: relative !important;
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
    pointer-events: auto !important;
}

.ttt-add-component {
    position: absolute !important;
    bottom: 0.75rem;
    left: 0.75rem;
    z-index: 30;
    display: flex !important;
    visibility: visible !important;
    opacity: 1 !important;
    pointer-events: auto !important;
    align-items: center;
    gap: 0.375rem;
}

/* Syncfusion's RichTextEditor gives its contenteditable area a default 16px padding on every side.
   Text component content is full-bleed HTML (sections already carry their own padding), so that
   default insets it ~16px narrower than the published page on both sides — the component visibly
   shrinks only while editing. Zero it out so the edit view matches the public render exactly. */
.e-richtexteditor .e-content {
    padding: 0 !important;
}

/* Syncfusion's inline RichTextEditor renders a static "preview" wrapper (.e-rte-content, and the
   control root itself while not focused) whose fluent theme paints it opaque white. That wrapper
   sits OUTSIDE whatever width/background the component's own content sets (e.g. a book recipe
   article narrower than its page canvas), so Syncfusion's white shows through as a band around the
   content while editing — a mismatch the published page (which has no such wrapper) never has.
   Same principle as the padding rule above: make the edit view match the public render. */
.e-richtexteditor.e-rte-inline,
.e-richtexteditor .e-rte-content {
    background-color: transparent !important;
}

/* "+" add-component button (Shared.AddComponent) rendered bottom-left of every component slot in
   edit mode. Hidden until the component is hovered (or the dropdown is open), so it never reads
   as part of the page's own content; the dropdown caret is hidden since "+" alone is the affordance.
   !important is needed on the Syncfusion-owned properties (background/border/color/caret) because
   the Syncfusion CDN theme stylesheet (_SyncfusionStyles) is linked after shared.css and wins
   equal-specificity ties on its own .e-btn/.e-caret rules.
   Border/icon use the same rust as the AI design panel header so the controls stay visible on
   white page backgrounds. */
.ttt-add-component-btn {
    position: static !important;
    display: inline-flex !important;
    visibility: visible !important;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 9999px !important;
    background-color: transparent !important;
    color: rgb(var(--color-rust)) !important;
    font-size: 1.1rem;
    line-height: 1;
    border: 1px solid rgb(var(--color-rust)) !important;
    opacity: 0 !important;
    pointer-events: auto !important;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.ttt-component-slot:hover .ttt-add-component-btn,
.ttt-add-component-btn.e-active {
    opacity: 1 !important;
}

.ttt-add-component-btn:hover {
    background-color: rgb(var(--color-rust) / 0.12) !important;
}

.ttt-add-component-btn .e-caret {
    display: none !important;
}

/* Kebab "⋮" component-options button (Shared.RemoveComponent), rendered to the right of the "+"
   inside the same .ttt-add-component host. Mirrors .ttt-add-component-btn's visual language
   (round, hover-revealed, rust border) so the pair reads as one control group. */
.ttt-component-menu-btn {
    position: static !important;
    display: inline-flex !important;
    visibility: visible !important;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 9999px !important;
    background-color: transparent !important;
    color: rgb(var(--color-rust)) !important;
    font-size: 1rem;
    line-height: 1;
    border: 1px solid rgb(var(--color-rust)) !important;
    opacity: 0 !important;
    pointer-events: auto !important;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.ttt-component-slot:hover .ttt-component-menu-btn,
.ttt-component-menu-btn.e-active {
    opacity: 1 !important;
}

.ttt-component-menu-btn:hover {
    background-color: rgb(var(--color-rust) / 0.12) !important;
}

.ttt-component-menu-btn .e-caret {
    display: none !important;
}

/* Drag handle (Shared.ComponentReorder), pinned to top-center of the component slot —
   deliberately separate from the "+"/"⋮" group at bottom-left so it reads as the slot's own
   control rather than part of the insert/remove menu. Centered (not a corner) so it never
   collides with the fixed bottom-left "Design with AI" panel, nor with the top-left component
   type badges some *Edit views render (see e.g. HeroEdit/Default.cshtml) — both corners are
   already spoken for. Mirrors the other buttons' visual language; a plain button (not a
   Syncfusion dropdown), so no .e-caret override is needed. */
.ttt-component-drag-btn {
    position: absolute !important;
    top: 0.75rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 30;
    display: inline-flex !important;
    visibility: visible !important;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 9999px !important;
    background-color: transparent !important;
    color: rgb(var(--color-rust)) !important;
    font-size: 1rem;
    line-height: 1;
    border: 1px solid rgb(var(--color-rust)) !important;
    /* Intentionally opacity: 0 at rest, revealed on hover below — !important on BOTH so custom CSS
       can't pin this to a fixed value (plan: custom-css-opus5-vivid-vireo, chrome self-defence),
       without permanently disabling the hover reveal (which an !important on only one side would). */
    opacity: 0 !important;
    pointer-events: auto !important;
    cursor: grab;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.ttt-component-slot:hover .ttt-component-drag-btn {
    opacity: 1 !important;
}

.ttt-component-drag-btn:hover {
    background-color: rgb(var(--color-rust) / 0.12) !important;
}

.ttt-component-drag-btn:active {
    cursor: grabbing;
}

/* A Layout's own handle is an exception to the centering above: centered over the whole
   multi-column block, it lands inside whichever column the midpoint happens to fall in (e.g. the
   boundary column of a two-equal split), reading as if it belongs to that column's content instead
   of the layout. Pin it to the outer top-right edge of the block instead, and mute it to grey (the
   same tone as the empty-column "+" affordance) so it reads as a distinct, lower-emphasis control
   from a child component's own rust handle sitting inside its column. */
.ttt-component-slot[data-component-type="Layout"] > .ttt-component-drag-btn {
    left: auto;
    right: 0.75rem;
    transform: none;
    color: rgba(28, 25, 23, 0.65) !important;
    border-color: rgba(28, 25, 23, 0.35) !important;
}

.ttt-component-slot[data-component-type="Layout"] > .ttt-component-drag-btn:hover {
    background-color: rgba(28, 25, 23, 0.12) !important;
}

/* Sortable's drag ghost/chosen classes (configured via ghostClass/chosenClass in
   Shared.ComponentReorder) so the dragged slot reads clearly while in flight. !important on the
   ghost opacity because the base .ttt-component-slot rule now pins opacity: 1 !important (see the
   chrome self-defence comment above it) — this state override needs the same weight to still win. */
.ttt-component-slot.ttt-sortable-ghost {
    opacity: 0.4 !important;
}

.ttt-component-slot.ttt-sortable-chosen {
    outline: 2px dashed rgb(var(--color-rust) / 0.5);
    outline-offset: 2px;
}

/* Layout columns (Layout/LayoutEdit Default.cshtml): a positioning context for the normal
   absolutely-positioned .ttt-add-component overlay when a column already has children. */
.ttt-layout-column {
    position: relative !important;
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
    pointer-events: auto !important;
}

/* Empty layout column placeholder (LayoutEdit/Default.cshtml). Unlike the normal
   .ttt-add-component overlay (absolutely positioned over existing content), an empty column has
   nothing to overlay, so this variant lays out in flow instead: a dashed drop target with its own
   centered "+", always visible (not hover-gated like .ttt-add-component-btn normally is) since
   there's no surrounding content whose hover would reveal it. */
.ttt-add-component.ttt-add-empty-column {
    position: static !important;
    display: flex !important;
    visibility: visible !important;
    opacity: 1 !important;
    align-items: center;
    justify-content: center;
    min-height: 6rem;
    border: 2px dashed rgba(28, 25, 23, 0.25);
    border-radius: 0.75rem;
}

.ttt-add-empty-column .ttt-add-component-btn {
    opacity: 1 !important;
    background-color: rgba(28, 25, 23, 0.06) !important;
    border-color: rgba(28, 25, 23, 0.35) !important;
    color: rgba(28, 25, 23, 0.65) !important;
    mix-blend-mode: normal;
}

.ttt-add-empty-column .ttt-add-component-btn:hover {
    background-color: rgba(28, 25, 23, 0.12) !important;
}

/* Chrome self-defence (plan: custom-css-opus5-vivid-vireo / custom-css-reachable). Per-site custom
   CSS is validated to reject !important, so as long as the editor chrome's own visibility/position
   declarations here carry !important, no custom-CSS rule — however broad its selector (a bare `div`,
   `*`, or anything else that isn't itself blocked for naming the chrome directly) — can hide, detach
   or reposition these controls. pointer-events is also guarded because custom CSS can disable
   interaction without using !important at all (e.g. `div { pointer-events: none }`). Values below
   match each element's actual current layout exactly; this only makes them immune to override, it
   changes nothing visible today.

   ⚠️ This guard alone does NOT stop the editor-lockout hole (plan: editor-lockout) — z-index only
   orders siblings within the same stacking context, so custom CSS can paint an ordinary (no
   !important, no chrome selector) fixed full-screen layer in a stacking context that composites
   above this element regardless of these declarations. #ai-design-panel is additionally promoted
   into the browser's TOP LAYER via the Popover API (see the <script> at the end of
   _AiDesignPanel.cshtml) — top-layer content paints above all page content unconditionally, which is
   the actual fix for that hole. This guard and the top-layer promotion are complementary: top-layer
   stops the "paint an opaque layer above it" attack, this guard stops the (already pre-existing,
   unrelated) "target it directly with a broad selector" attack — neither alone is sufficient. */
#ai-design-panel {
    position: fixed !important;
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
}

/* Scoped to the whole subtree, not just the panel element — see the pointer-events comment below for
   why an element-only guard leaves descendants exposed to a bare `div { opacity: 0 }`-style rule.
   display is deliberately NOT included here (unlike the element-only guard above): the panel's own
   hide/show toggles (the unsaved-changes banner, spinners, image previews, …) all use Tailwind's
   `.hidden` (display: none), and an !important subtree override on display would win over `.hidden`
   too, permanently un-hiding all of them. opacity/visibility carry no such conflict — nothing inside
   the panel legitimately sets either to hide itself. */
#ai-design-panel * {
    visibility: visible !important;
    opacity: 1 !important;
}

/* pointer-events must cover the whole panel SUBTREE, not just the panel element. It is an inherited
   property, but `div { pointer-events: none }` matches the panel's inner wrapper divs DIRECTLY, and a
   direct match beats an inherited value — so guarding only #ai-design-panel leaves the Save/Discard
   buttons inside those wrappers dead while the panel itself looks fine. Verified in a browser: with
   the element-only guard, the Save button computes `pointer-events: none`; with this one it computes
   `auto`. Scoped to chrome subtrees only — never .ttt-component-slot's children, which are the
   owner's own content and must keep whatever pointer behaviour their design asks for. */
#ai-design-panel,
#ai-design-panel * {
    pointer-events: auto !important;
}

/* Top-layer positioning (plan: editor-lockout). A `[popover]` element's UA stylesheet default is
   `inset: 0; margin: auto` (centers it, sized to content) — Tailwind's `bottom-4`/`left-2` utility
   classes on #ai-design-panel only override the `bottom`/`left` longhands individually, leaving the
   UA's `top: 0; right: 0` in place and over-constraining the box. Pin all four explicitly so the
   fixed bottom-left position is unambiguous once the element is shown in the top layer. Values
   match the Tailwind classes already on the element (bottom-4 = 1rem, left-2 = 0.5rem); this changes
   nothing visually, it only removes the ambiguity for browsers that support the Popover API. */
#ai-design-panel {
    top: auto !important;
    left: 0.5rem !important;
    bottom: 1rem !important;
    right: auto !important;
    margin: 0 !important;
}

/* #ttt-account-menu (TUB-30a — one avatar instead of four buttons) — same top-layer promotion, same
   reasoning, same self-defence shape as #ai-design-panel above. Replaces the old
   #ttt-owner-tools-menu (one popover behind the "Manage" pill alone): Manage/Platform/Sign out now
   collapse into ONE popover behind the avatar, with the menu partials nested inside it as sections
   rather than each opening their own dropdown (F1, TUB-301: the per-item owner-tools section has since
   left it for the /admin left menu — .ttt-adm-* below). Positioned fixed near the top of the
   viewport rather than anchored under the avatar button: once promoted, the popover is no longer a
   descendant of the header's normal-flow box, so an `absolute right-0 mt-2` position has nothing to
   be relative to. A fixed corner is deliberately different from full CSS anchor positioning
   (`position-anchor`/`anchor()`), whose cross-browser support is newer and less certain — see the
   plan's GOTCHA 3 note to confirm rather than assume browser support. */
/* Positioning applies unconditionally (harmless while the popover is closed and not rendered at
   all). display/visibility/opacity are scoped to :popover-open ONLY — unlike #ai-design-panel, this
   element has a genuine closed state (native popover behaviour, equivalent to the <details> it
   replaced), and an unconditional `display: block !important` here would force it permanently open,
   breaking the toggle entirely.
   TUB-132: JS (Shared.AccountMenuPosition) sets the two custom properties on open so the menu sits
   under the avatar; the literals are the no-JS fallback. */
#ttt-account-menu {
    position: fixed !important;
    top: var(--ttt-account-menu-top, 5rem) !important;
    right: var(--ttt-account-menu-right, 1rem) !important;
    bottom: auto !important;
    left: auto !important;
    margin: 0 !important;
    z-index: 50;
    /* TUB-148 — the popover is fixed to the viewport, so on a short window (or on a phone, on the
       generated-header path where the avatar stays — see .ttt-bar-with-drawer below) its lower items
       were simply off-screen: "Log out" and the PLATFORM group could not be reached. Cap it to the
       space under its own top edge and let it scroll. --ttt-account-menu-top is the property TUB-132's
       Shared.AccountMenuPosition sets in px on open; the 5rem literal is the same no-JS fallback the
       top: line uses. 0.5rem = bottom gutter. The vh line is the fallback for browsers without dvh. */
    max-height: calc(100vh - var(--ttt-account-menu-top, 5rem) - 0.5rem);
    max-height: calc(100dvh - var(--ttt-account-menu-top, 5rem) - 0.5rem);
    overflow-y: auto;
}

#ttt-account-menu:popover-open {
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
}

#ttt-account-menu:popover-open * {
    visibility: visible !important;
    opacity: 1 !important;
}

#ttt-account-menu,
#ttt-account-menu * {
    pointer-events: auto !important;
}

/* TUB-31 — reaches the TUB-30a header layout on every design that was already generated. An
   AI-authored header replaces the theme partials entirely and renders only {{slot:menu}}{{slot:account}}
   — the platform fills those in, but WHERE they sit is that design's own header markup (flex, or
   grid with pinned columns), made at generation time and never touched again by this ticket
   (GOTCHA 1: no stored HeaderHtml is edited, no migration, no Firestore write). SlottedHtmlRenderer
   wraps each slot's RENDERED OUTPUT in these reserved containers (HeaderViewComponent only — see its
   own comment); placing them here reaches every existing site/template with no data change.
   "ttt-" is a name CustomCssValidator forbids a design's own selectors from using at all, and
   TUB-23's @scope donut excludes .ttt-chrome subtrees from a site's rules matching (not from custom-
   property INHERITANCE — the avatar below relies on exactly that distinction) — so nothing a design
   generates can override the rules below, on the header's flex row or (GOTCHA 2) a grid one. */
.ttt-header-nav {
    /* TUB-31, reviewer: the card asks for the nav "aligned to the left, like logo with NICE MARGIN to
       logo". Without this the two touch on a design whose header container has no gap of its own —
       measured on the owner's "Red Pizza" site, which rendered "Red PizzaHOME". A margin here rather
       than a gap on the container, because the container belongs to the design and we do not style it. */
    margin-left: 2rem;
    margin-right: auto;
    grid-column: 1;
}

.ttt-header-account {
    margin-left: auto;
    justify-self: end;
    grid-column: -1;
}

/* TUB-31 §3 — the avatar's own colours, read from the design's palette at RENDER TIME (never
   stored — AvatarPaletteColors.Choose runs once per Account-slot render, not per element) with a
   MANDATORY 4.5:1 fallback to plain black/white baked in server-side. The four custom properties are
   set once, inline, on the [data-auth-widget] chrome root by Default.cshtml; ttt-chrome's donut does
   not block custom-property INHERITANCE (only rule matching), so a value set there reaches these
   classes normally. Literal fallback values here are the OLD stone-100/-400/-200/-600 fixed colours,
   used only if the inline style is ever absent (defensive; Default.cshtml always sets it). */
.ttt-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 9999px;
    transition: opacity 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.ttt-avatar:hover {
    opacity: 0.85;
}

.ttt-avatar--out {
    background-color: var(--ttt-avatar-circle-out, #f5f5f4);
    color: var(--ttt-avatar-fg-out, #a8a29e);
}

.ttt-avatar--in {
    background-color: var(--ttt-avatar-circle-in, #e7e5e4);
    color: var(--ttt-avatar-fg-in, #57534e);
}

/* TUB-30a §3.2 (card point 7) — the menu must be dark-on-white with visible borders and must never
   inherit a generated site's design. ttt-chrome (TUB-23's @scope donut) is one independent
   guarantee — a site's own custom CSS cannot cascade into a .ttt-chrome subtree at all; this is the
   SECOND, independent one: every colour/border below is a literal value under a "ttt-" name, never a
   bare Tailwind utility class, so even a rule that somehow reused a common utility's name (TUB-23/24's
   `.block { padding: 5rem 0 }`) cannot restyle it. CustomCssValidator already rejects "ttt-" appearing
   anywhere in a site's own selectors (ReservedSelectorTokens), so this is enforced twice over. */
.ttt-account-menu-panel {
    width: 16rem;
    padding-block: 0.5rem;
    background-color: #ffffff;
    color: #1c1917;
    border: 1px solid #e7e5e4;
    border-radius: 0.75rem;
    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}

.ttt-account-menu-username {
    display: block;
    padding: 0 1rem 0.5rem;
    margin-bottom: 0.25rem;
    font-size: 0.8125rem;
    color: #57534e;
    border-bottom: 1px solid #f5f5f4;
}

.ttt-account-menu-heading {
    padding: 0.5rem 1rem 0.25rem;
    font-size: 0.625rem;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #a8a29e;
}

.ttt-account-menu-item {
    display: block;
    width: 100%;
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
    text-align: left;
    background: none;
    border: 0;
    color: #44403c;
    text-decoration: none;
    cursor: pointer;
}

.ttt-account-menu-item:hover {
    background-color: #fffbeb;
    color: #c1440e;
}

.ttt-account-menu-divider {
    margin-top: 0.25rem;
    padding-top: 0.25rem;
    border-top: 1px solid #f5f5f4;
}

/* TUB-30a — the editing switch that replaces the "Editing: On/Off" text pill. Visual only; the
   click binding (Shared.EditModeSwitch.bindSwitchButtons) still reads/writes aria-pressed, unchanged. */
.ttt-edit-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0;
    background: none;
    border: 0;
    color: #44403c;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

.ttt-edit-switch-track {
    position: relative;
    display: inline-block;
    flex-shrink: 0;
    width: 2.25rem;
    height: 1.25rem;
    border-radius: 9999px;
    background-color: #d6d3d1;
    transition: background-color 0.15s ease;
}

.ttt-edit-switch-thumb {
    position: absolute;
    top: 0.125rem;
    left: 0.125rem;
    width: 1rem;
    height: 1rem;
    border-radius: 9999px;
    background-color: #ffffff;
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
    transition: transform 0.15s ease;
}

.ttt-edit-switch[aria-pressed="true"] .ttt-edit-switch-track {
    background-color: #c1440e;
}

.ttt-edit-switch[aria-pressed="true"] .ttt-edit-switch-thumb {
    transform: translateX(1rem);
}

.ttt-edit-switch-label {
    white-space: nowrap;
}

/* The label hides under the switch's own control at very narrow widths so the toggle itself never
   contributes to header crowding; the mobile drawer's block variant always keeps its label (drawer
   width is never the constraint there). */
@media (max-width: 639px) {
    .ttt-edit-switch:not(.ttt-edit-switch--block) .ttt-edit-switch-label {
        display: none;
    }
}

/* TUB-148 — "on mobile we need only the mobile menu" (owner, on a phone, 2026-09-26). The three
   STRUCTURED headers (Components/Header/Classic, Components/Header/Centered, Theme/CoNaObiad/_Header)
   duplicate every account control into their hamburger drawer (#mobileMenu: Sign in / Sign out /
   the Admin-area links / _EditModeSwitchMobile / _PlatformAdminMenuMobile), so below the
   drawer's breakpoint the bar's own copies hide: the editing switch and the avatar wrapper (signed-out
   link, signed-in button AND the #ttt-account-menu popover element inside it — display:none on the
   wrapper removes the popover's box, so it cannot open at all).
   767.98px is the complement of the hamburger's Tailwind `md:hidden` (min-width: 768px);
   MobileSingleMenuTests pins the two numbers together.
   The hook is the container class `ttt-bar-with-drawer`, set ONLY by those three headers.
   Components/Account/Default.cshtml — every AI-generated header — does NOT set it: a generated
   design's #mobile-menu panel carries nav links only ({{slot:menu:vertical}} renders Menu/Vertical.cshtml
   and nothing else), so hiding its avatar would leave the owner of a generated site with no route to
   sign in or to /admin on a phone. Measured on tubetotable-org-dev.web.app 2026-09-26.
   !important for the same reason .auth-hidden carries it: the inner .ttt-edit-switch declares its own
   display at equal specificity and must lose regardless of source order. */
@media (max-width: 767.98px) {
    .ttt-bar-with-drawer > .ttt-edit-switch,
    .ttt-bar-with-drawer > .ttt-bar-account {
        display: none !important;
    }
}

/* TUB-148 — the structured drawer (#mobileMenu) is a normal-flow child of a `sticky top-0` header. When
   its content is taller than the viewport the HEADER is taller than the viewport, sticks at top:0, and
   its lower part can never be scrolled into view — measured on tubetotable.com: "Log out" and the
   PLATFORM group were unreachable. The drawer scrolls itself instead, within the space below the bar
   row. 4rem = the h-16 bar of Classic/CoNaObiad; Centered's bar is h-20 and sets the modifier below.
   The vh line is the fallback for browsers without dvh; dvh wins where supported because the mobile
   URL bar eats part of 100vh. No body scroll lock on purpose: the header stays put, the drawer scrolls,
   and a lock would need JS on Shared.Toggle, which every generated design's hamburger also uses. */
.ttt-drawer {
    max-height: calc(100vh - var(--ttt-drawer-offset, 4rem));
    max-height: calc(100dvh - var(--ttt-drawer-offset, 4rem));
    overflow-y: auto;
}

.ttt-drawer--tall-bar {
    --ttt-drawer-offset: 5rem;
}

/* TUB-151 — AI-generated headers on a phone, the way tubetotable.com works since TUB-148. A generated
   design decides for itself when its hamburger ([data-toggle-target]) shows (Red Pizza: 820px, not
   768), so no fixed breakpoint can be right for every site and CSS cannot see another element's
   computed display. mobile-nav-mode.js (loaded synchronously right after the header) sets
   html.ttt-mobile-nav when — and only when — the header's hamburger is rendered AND its panel carries
   the platform's account block (.ttt-menu-account, appended by SlottedHtmlRenderer). Under that class
   the bar's avatar wrapper and editing switch hide; without it (no hamburger, no panel, no block) they
   stay, so a signed-in owner is never left with neither. !important for the same reason .auth-hidden
   carries it: hydrate toggles auth-hidden on these very elements. */
html.ttt-mobile-nav .ttt-header-account .ttt-slot-avatar,
html.ttt-mobile-nav .ttt-header-account .ttt-edit-switch:not(.ttt-edit-switch--block) {
    display: none !important;
}

/* The design's own panel scrolls while the account block is in it. The panel is the DESIGN's element
   (`.mobile-panel {…}` in its site.css loads after this sheet at equal specificity and may set
   overflow/max-height), so these two are !important — CustomCssValidator strips !important from a
   site's CSS, so the platform's always wins. --ttt-panel-offset is the OPEN panel's own top edge in px,
   measured by mobile-nav-mode.js on the frame after Shared.Toggle removes `hidden` (and on resize),
   which is right for an absolute-under-the-header, an in-flow and a fixed panel alike. vh line = the
   fallback for browsers without dvh. */
.ttt-panel-scroll {
    max-height: calc(100vh - var(--ttt-panel-offset, 4rem)) !important;
    max-height: calc(100dvh - var(--ttt-panel-offset, 4rem)) !important;
    overflow-y: auto !important;
}

/* The account block inside a generated drawer: its own opaque, design-independent surface (the same
   literals .ttt-account-menu-* use), so it reads on a dark or a light panel. The reused mobile
   partials carry Tailwind utilities (text-stone-700, hover:text-rust, border-amber-100 …) tuned for
   the cream structured drawer; the descendant rules below outrank them ((0,1,1) vs (0,1,0); the
   :hover rule (0,2,1) vs .hover\:text-rust:hover (0,2,0)) regardless of source order, and ttt-chrome on
   the root keeps the design's own rules out entirely. */
.ttt-menu-account {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 1rem;
    padding: 0.75rem 1rem 1rem;
    background-color: #ffffff;
    color: #1c1917;
    border: 1px solid #e7e5e4;
    border-radius: 0.75rem;
    text-align: left;
}

.ttt-menu-account a,
.ttt-menu-account button,
.ttt-menu-account .ttt-menu-account-item {
    display: block;
    width: 100%;
    padding: 0.375rem 0;
    background: none;
    border: 0;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.25rem;
    color: #44403c;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.ttt-menu-account a:hover,
.ttt-menu-account button:hover {
    color: #c1440e;
}

.ttt-menu-account p {
    margin: 0;
    padding-top: 0.25rem;
    font-size: 0.625rem;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #a8a29e;
}

.ttt-menu-account > [data-auth-owner],
.ttt-menu-account > [data-auth-platform-admin],
.ttt-menu-account-divider {
    border-top: 1px solid #f5f5f4;
}

.ttt-menu-account-divider {
    padding-top: 0.5rem;
}

/* The editing switch keeps its own literal styling (.ttt-edit-switch*), only its colour is pinned. */
.ttt-menu-account .ttt-edit-switch {
    color: #44403c;
}

/* F1 (TUB-301) — the website administration menu (Pages/Admin/_AdminLayout.cshtml + _AdminNav/_BusinessAdminNav). The owner: "add a
   left menu to the website administration page and move all website-related menu (recipes, categories, pages etc.) there". It renders
   inside the site's own theme layout, so — like the avatar menu above — every colour, border and font is a literal value under a
   ttt- name (never a site variable such as --color-rust, never a bare Tailwind utility a site's CSS could redefine), and ttt-chrome on
   both roots keeps the design's own rules out. Neutral stone, the system font: it reads the same on every site.
   1024px = Tailwind lg, the user-area menu's own breakpoint: the sidebar from 1024px up, the collapsible panel above the page below. */
.ttt-adm-shell {
    display: flex;
    flex-direction: column;
    max-width: 84rem;
    margin-inline: auto;
}

.ttt-adm-content {
    flex: 1 1 auto;
    min-width: 0;
}

.ttt-adm-nav,
.ttt-adm-mobile {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    color: #1c1917;
    background-color: #ffffff;
    border: 1px solid #e7e5e4;
    border-radius: 0.75rem;
    text-align: left;
}

.ttt-adm-nav {
    display: none;
}

.ttt-adm-mobile {
    display: block;
    margin: 1rem 1rem 0;
    padding: 0.25rem 0.75rem;
}

.ttt-adm-mobile[open] {
    padding-bottom: 0.75rem;
}

.ttt-adm-summary {
    cursor: pointer;
    padding: 0.5rem 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: #1c1917;
}

@media (min-width: 1024px) {
    .ttt-adm-shell {
        flex-direction: row;
        align-items: flex-start;
        gap: 2rem;
        padding-inline: 1.5rem;
    }

    .ttt-adm-nav {
        display: block;
        flex: none;
        width: 15rem;
        position: sticky;
        top: 5rem;
        margin-top: 3rem;
        max-height: calc(100vh - 6rem);
        overflow-y: auto;
        padding: 1rem 0.75rem;
    }

    .ttt-adm-mobile {
        display: none;
    }
}

.ttt-adm-site {
    margin: 0;
    padding: 0 0.5rem;
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.25rem;
    color: #1c1917;
    overflow-wrap: anywhere;
}

.ttt-adm-back {
    display: block;
    margin: 0.25rem 0 0.5rem;
    padding: 0.25rem 0.5rem;
    font-size: 0.8125rem;
    color: #57534e;
    text-decoration: none;
}

.ttt-adm-back:hover {
    color: #c1440e;
}

.ttt-adm-heading {
    margin: 0;
    padding: 0.75rem 0.5rem 0.25rem;
    border-top: 1px solid #f5f5f4;
    font-size: 0.625rem;
    line-height: 1;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #a8a29e;
}

.ttt-adm-link {
    display: block;
    padding: 0.375rem 0.5rem;
    border-radius: 0.5rem;
    font-size: 0.875rem;
    line-height: 1.25rem;
    color: #44403c;
    text-decoration: none;
}

.ttt-adm-link:hover {
    background-color: #fffbeb;
    color: #c1440e;
}

.ttt-adm-link-active,
.ttt-adm-link-active:hover {
    background-color: #f5f5f4;
    color: #1c1917;
    font-weight: 600;
}

/* The layout-picker and remove-component modals toggle via JS swapping the `hidden`/`flex` classes
   (Shared.LayoutPicker / Shared.RemoveComponent) — position/visibility/opacity never change between
   those states, so only `display` is guarded per-state; guarding it unconditionally would break the
   open/close toggle itself. */
[data-layout-picker-modal],
[data-remove-component-modal] {
    position: fixed !important;
    visibility: visible !important;
    opacity: 1 !important;
}

/* Subtree guard, same reasoning as #ai-design-panel above — the modals' confirm/cancel buttons sit
   inside wrapper divs, so an element-only pointer-events guard leaves them unclickable. */
[data-layout-picker-modal],
[data-layout-picker-modal] *,
[data-remove-component-modal],
[data-remove-component-modal] * {
    pointer-events: auto !important;
}

[data-layout-picker-modal].hidden,
[data-remove-component-modal].hidden {
    display: none !important;
}

[data-layout-picker-modal].flex,
[data-remove-component-modal].flex {
    display: flex !important;
}

/* Category MultiSelect (Shared.CategoryMultiSelect) inside create-recipe / recipes-admin modals.
   Popup is body-appended with zIndex 10000; keep the input chrome aligned with other form fields. */
.ttt-category-multiselect.e-multiselect,
.ttt-category-multiselect.e-input-group {
    border-radius: 0.5rem;
    border-color: #d6d3d1;
    font-size: 0.875rem;
}

.ttt-category-multiselect.e-input-focus,
.ttt-category-multiselect.e-input-group.e-input-focus:hover:not(.e-success):not(.e-warning):not(.e-error):not(.e-disabled) {
    border-color: rgb(var(--color-rust)) !important;
    box-shadow: none;
}

.e-popup.ttt-category-multiselect {
    z-index: 10000 !important;
}

/* Blog page components (Recipes/RecentPosts/WatchVideos/PopularTags/Search/Newsletter/Categories):
   bespoke classes from the CoNaObiad blog mockup, ported here since they live outside every
   Tailwind content glob (freeform markup pasted into static component views). Rust color tracks
   the site theme via the same CSS variable Tailwind's `rust` utility resolves to. */
.section-label {
    font-family: 'Inter', sans-serif;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: rgb(var(--color-rust));
}

.card-lift {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card-lift:hover {
    transform: translateY(-4px);
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.10);
}

.tag-pill {
    transition: background 0.2s, color 0.2s;
}

.tag-pill:hover {
    background: rgb(var(--color-rust));
    color: white;
}

.sidebar-heading {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 1.1rem;
    font-weight: 700;
    color: #1c1917;
    padding-bottom: 0.75rem;
    border-bottom: 2px solid #fbbf24;
    margin-bottom: 1.25rem;
    display: inline-block;
}

.cat-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.55rem 0;
    border-bottom: 1px solid #f5f0e8;
}

.cat-row:last-child {
    border-bottom: none;
}

/* Anchor-scoped on purpose: rows render as <span> while CategoryLink.Url is still "#", and a hover
   colour change on a non-navigable row is the same false affordance as the dead link itself. */
.cat-row:hover a.cat-name {
    color: rgb(var(--color-rust));
}

.pagination-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    font-size: 0.875rem;
    font-weight: 600;
    border: 1.5px solid #e7e0d6;
    color: #57534e;
    transition: all 0.2s;
}

.pagination-btn:hover,
.pagination-btn.active {
    background: rgb(var(--color-rust));
    color: white;
    border-color: rgb(var(--color-rust));
}

/* Touch targets: the desktop sizes above are below the 44px iOS/Android guideline, which matters on
   the public site. Bumped only on coarse pointers, so desktop density is unchanged. */
@media (pointer: coarse) {
    .pagination-btn {
        width: 2.75rem;
        height: 2.75rem;
    }

    .cat-row {
        padding: 0.85rem 0;
    }
}

/* Platform-admin site-template gallery — colour/type swatches (no thumbnail images). */
.tmpl-card {
    display: flex;
    flex-direction: column;
    border: 1px solid #e7e5e4;
    border-radius: 1rem;
    background: #fff;
    overflow: hidden;
}

.tmpl-swatch {
    --tmpl-primary: #C1440E;
    --tmpl-bg: #FDF8F0;
    --tmpl-serif: 'Playfair Display', serif;
    --tmpl-sans: Inter, sans-serif;
}

/* TUB-32 — 16:9, and the header/nav must survive the crop.
   The captures are 1200x675 (TemplateThumbnailComposer.Width/Height, and tools/shot-template.mjs
   uses the same size), i.e. 16:9 already. `height: 5.5rem` squashed the card to roughly 3.5:1 and
   `object-fit: cover` then took the MIDDLE of the picture — which is exactly where the site's header
   is not. The owner's report was "I need see menu nav on thumbnails": the nav was being cropped off
   the top of every card.
   aspect-ratio matches the source, so a real screenshot is now shown whole. object-position: top is
   for the legacy thumbnails that are not 16:9 — a template with no blob storage falls back to the
   raw Theme.HeroImageUrl, which can be any shape — and keeps the top of those visible too. */
.tmpl-thumb {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    object-position: top;
    border-radius: 1rem 1rem 0 0;
    background: #f5f5f4;
}

/* The no-thumbnail fallback tile. Same aspect ratio as .tmpl-thumb above, or a row mixing the two
   would have cards of two different heights. */
.tmpl-swatch-bg {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
    aspect-ratio: 16 / 9;
    padding: 1rem 1.25rem;
    background: var(--tmpl-bg);
    border-bottom: 1px solid #e7e5e4;
}

.tmpl-swatch-serif {
    font-family: var(--tmpl-serif);
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1;
    color: var(--tmpl-primary);
}

.tmpl-swatch-sans {
    font-family: var(--tmpl-sans);
    font-size: 0.95rem;
    font-weight: 500;
    color: #44403c;
    opacity: 0.85;
}

.tmpl-swatch-bar {
    margin-left: auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 0.5rem;
    background: var(--tmpl-primary);
}

.tmpl-card-body {
    padding: 1rem 1.25rem 1.25rem;
}

.tmpl-status {
    flex-shrink: 0;
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.2rem 0.5rem;
    border-radius: 9999px;
    background: #f5f5f4;
    color: #57534e;
}

.tmpl-status-draft { background: #f5f5f4; color: #78716c; }
.tmpl-status-available { background: #dcfce7; color: #166534; }
.tmpl-status-claimed { background: #ffedd5; color: #9a3412; }
.tmpl-status-retired { background: #e7e5e4; color: #44403c; }

.tmpl-meta {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem;
    font-size: 0.75rem;
}

.tmpl-meta dt {
    color: #a8a29e;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
    font-size: 0.65rem;
}

.tmpl-meta dd {
    color: #44403c;
    margin: 0.1rem 0 0;
    word-break: break-all;
}

.tmpl-meta code {
    font-size: 0.7rem;
}

.tmpl-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.tmpl-btn {
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.35rem 0.75rem;
    border-radius: 9999px;
    border: 1px solid #d6d3d1;
    background: #fff;
    color: #44403c;
    cursor: pointer;
}

.tmpl-btn:hover {
    border-color: #a8a29e;
}

/* "See me" is an <a>, not a <button> — keep it the same pill as its neighbours. */
a.tmpl-btn {
    display: inline-block;
    text-decoration: none;
    line-height: 1.4;
}

.tmpl-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.tmpl-btn-primary {
    background: rgb(var(--color-rust, 193 68 14));
    border-color: rgb(var(--color-rust, 193 68 14));
    color: #fff;
}

.tmpl-btn-primary:hover {
    filter: brightness(0.95);
}

.tmpl-btn-muted {
    color: #78716c;
}

.tmpl-btn-danger {
    color: #b91c1c;
    border-color: #fecaca;
}

.tmpl-btn-danger:hover {
    border-color: #f87171;
    background: #fef2f2;
}

.tmpl-upload-label {
    position: relative;
    display: inline-flex;
    align-items: center;
    cursor: pointer;
}

.tmpl-upload-input {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

.tmpl-busy-label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.tmpl-spinner {
    display: inline-block;
    width: 0.85em;
    height: 0.85em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 9999px;
    animation: tmpl-spin 0.7s linear infinite;
    vertical-align: -0.1em;
}

@keyframes tmpl-spin {
    to { transform: rotate(360deg); }
}

.tmpl-thumb-wrap {
    display: block;
}

/* The hero overlay is invisible in edit mode without this, and the cause is two rules deep.
   Syncfusion ships `.e-rte-content .e-content img:not(.e-resize) { z-index: 1000 }`, and its
   `.e-rte-image` class — added by the editor's JS to content images, not by any stylesheet —
   supplies `position: relative`. z-index is inert on a static element, so it is the COMBINATION
   that promotes the hero photo above `.hero-overlay` (which sits at z-index auto) and hides the
   owner's colour/opacity entirely. Measured 2026-08-07: visitor `static/auto`, editor
   `relative/1000`.
   Syncfusion is loaded in layer(ttt-vendor) (_SyncfusionStylesLayered), and shared.css is
   unlayered, so this wins without a specificity fight — but the layer alone did NOT save us,
   because a layered rule still applies when nothing opposes it, and the AI's generated CSS
   declares no z-index on the hero image.
   Neutralising the promotion is preferred over raising the overlay: Syncfusion's image resize
   handles also sit at z-index 1000, and out-stacking them covers the handles.
   Anchored on .hero-overlay because the AI rewrites .hero-section / .hero-bg-image /
   .hero-image-wrapper on every generation — .hero-overlay is the name the platform itself
   treats as a contract (findHeroRoots, shared.js). */
.e-rte-content .e-content :has(> .hero-overlay) img {
    z-index: auto;
}

/* ── #ttt-ai-assistview: beat Tailwind preflight (unlayered) vs fluent.css in layer(ttt-vendor).
   Preflight zeros input/textarea padding/border/radius; an unlayered rule at any specificity beats
   any layered vendor rule. Scope ONLY to this root — do not un-layer _SyncfusionStylesLayered.
   AssistView appendTo(#ttt-ai-assistview) adds .e-aiassistview ON the host itself, so selectors
   must match the host as well as any nested .e-aiassistview. */
#ttt-ai-assistview,
#ttt-ai-assistview.e-aiassistview {
    font-size: 13px;
    overflow: hidden;
}

#ttt-ai-assistview.e-aiassistview,
#ttt-ai-assistview .e-aiassistview {
    height: 100%;
    width: 100%;
    max-width: 100%;
    border: 1px solid #e7e5e4;
    border-radius: 0.75rem;
    background: #fafaf9;
    overflow: hidden;
    box-sizing: border-box;
    box-shadow: 0 10px 25px rgba(28, 25, 23, 0.12);
}

#ttt-ai-assistview.e-aiassistview,
#ttt-ai-assistview.e-aiassistview *,
#ttt-ai-assistview .e-aiassistview,
#ttt-ai-assistview .e-aiassistview * {
    max-width: 100%;
    box-sizing: border-box;
}

#ttt-ai-assistview .e-footer,
#ttt-ai-assistview .e-footer-wrapper,
#ttt-ai-assistview .e-assist-textarea,
#ttt-ai-assistview .e-input-group {
    max-width: 100%;
    width: 100%;
    box-sizing: border-box;
}

/* Restore control chrome that preflight strips. */
#ttt-ai-assistview button {
    font: inherit;
    background-color: transparent;
    background-image: none;
    color: inherit;
    cursor: pointer;
}

#ttt-ai-assistview textarea,
#ttt-ai-assistview input,
#ttt-ai-assistview .e-assist-textarea,
#ttt-ai-assistview [contenteditable="true"] {
    font: inherit;
    color: inherit;
    background-color: #fff;
    border: 1px solid #d6d3d1;
    border-radius: 0.5rem;
    padding: 0.45rem 0.65rem;
    margin: 0;
}

#ttt-ai-assistview svg {
    display: inline-block;
    vertical-align: middle;
    width: 1rem;
    height: 1rem;
}

#ttt-ai-assistview .e-assist-send-icon,
#ttt-ai-assistview .e-assistview-icon,
#ttt-ai-assistview .e-assist-attachment-icon,
#ttt-ai-assistview .e-icons.e-assist-send,
#ttt-ai-assistview .e-btn,
#ttt-ai-assistview .e-tbar-btn {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    min-width: 2rem;
    min-height: 2rem;
    padding: 0.25rem;
}

#ttt-ai-assistview .e-assist-banner {
    padding: 1.25rem 1rem;
    text-align: center;
    color: #57534e;
}

#ttt-ai-assistview .e-assist-banner-title {
    margin: 0 0 0.35rem;
    font-size: 1rem;
    font-weight: 600;
    color: #1c1917;
}

#ttt-ai-assistview .e-assist-banner-body {
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.4;
}

#ttt-ai-assistview .ttt-ai-chat-options {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.5rem;
}

#ttt-ai-assistview .ttt-ai-chat-options button,
#ttt-ai-assistview [data-ai-chat-option] {
    font-size: 11px;
    padding: 0.25rem 0.6rem;
    border-radius: 9999px;
    border: 1px solid #d6d3d1 !important;
    background: #f5f5f4 !important;
    color: #44403c !important;
    cursor: pointer;
    display: inline-block;
    margin: 0.15rem 0.15rem 0 0;
    white-space: normal;
    max-width: 100%;
    word-break: break-word;
}

#ttt-ai-assistview .ttt-ai-chat-options button:hover,
#ttt-ai-assistview [data-ai-chat-option]:hover {
    border-color: #c2410c !important;
    color: #c2410c !important;
    background: #fff !important;
}

#ttt-ai-assistview.ttt-ai-chat-busy [data-ai-chat-option],
#ttt-ai-assistview [data-ai-chat-option]:disabled {
    pointer-events: none !important;
    opacity: 0.55;
    cursor: default;
}

#ttt-ai-assistview .ttt-ai-chat-undo {
    margin-top: 0.4rem;
    font-size: 11px;
    text-decoration: underline;
    color: #78716c;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    display: inline-block;
}

#ttt-ai-assistview .ttt-ai-chat-undo:hover {
    color: #c2410c;
}

/* Part 5 — spacing only inside the AssistView thread (padding / gaps). No colour or layout redesign. */
#ttt-ai-assistview .e-view-container,
#ttt-ai-assistview .e-content,
#ttt-ai-assistview .e-output-container,
#ttt-ai-assistview .e-output {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}

#ttt-ai-assistview .e-prompt-item,
#ttt-ai-assistview .e-output-item,
#ttt-ai-assistview .e-output-container > div {
    margin-bottom: 0.75rem;
}

#ttt-ai-assistview .e-prompt-content,
#ttt-ai-assistview .e-output-content,
#ttt-ai-assistview .e-content-wrapper {
    padding: 0.35rem 0;
}

#ttt-ai-assistview .ttt-ai-chat-options {
    margin-top: 0.65rem;
    margin-bottom: 0.35rem;
    gap: 0.45rem;
}

#ttt-ai-assistview .e-response-toolbar,
#ttt-ai-assistview .e-assist-toolbar,
#ttt-ai-assistview .e-output-toolbar,
#ttt-ai-assistview [class*="response-toolbar"],
#ttt-ai-assistview [class*="ResponseToolbar"] {
    margin-top: 0.5rem;
    margin-bottom: 0.35rem;
    padding-top: 0.25rem;
}

#ttt-ai-assistview .ttt-ai-chat-undo {
    margin-top: 0.55rem;
    margin-bottom: 0.25rem;
}

/* NEWS-1 (TUB-1) — inline-editable newsletter heading/text in edit mode.

   The affordance has to be visible without moving anything: a contenteditable that reflows on
   focus would shift the whole sidebar widget under the cursor. Hence an outline (which does not
   take part in layout) and a padding-free tint, never a border or margin. The "ttt-" prefix is a
   reserved token CustomCssValidator already rejects in selectors, so AI- or owner-authored custom
   CSS cannot hide this chrome. */
.ttt-newsletter-editable {
    outline: 1px dashed rgb(168 162 158 / 0.7);
    outline-offset: 2px;
    border-radius: 2px;
    cursor: text;
}

.ttt-newsletter-editable:hover {
    outline-color: rgb(120 113 108 / 0.9);
}

.ttt-newsletter-editable:focus {
    outline: 2px solid rgb(var(--color-rust));
    outline-offset: 2px;
}

/* The gear sits inside the component box (top-right), never fixed to a viewport corner — PANEL-1
   owns bottom-left (unsaved-changes toast) and bottom-right (AI Assist launcher). */
.ttt-newsletter-gear {
    opacity: 0;
    transition: opacity 120ms ease-in-out;
}

[data-ttt-newsletter-edit]:hover .ttt-newsletter-gear,
.ttt-newsletter-gear:focus-visible,
.ttt-newsletter-gear[aria-expanded="true"] {
    opacity: 1;
}

/* TUB-24 — Featured Categories editor mark (FeaturedCategoriesEdit/Default.cshtml). Was a PERMANENT
   `ring-2 ring-dashed ring-rust/60` — visible on every site for as long as edit mode is on, and
   `ring-dashed` is not a Tailwind class at all, so it rendered as a solid ring that read as part of
   the design. Every other editor affordance in this file only appears on hover
   (.ttt-newsletter-gear above, .ttt-add-component-btn below) — this one now matches. `outline` rather
   than `ring`/`border`: an outline never takes part in layout, so a hovered module cannot shift the
   page by a pixel. */
.ttt-featured-categories-edit {
    outline: 2px dashed transparent;
    outline-offset: 2px;
    transition: outline-color 120ms ease-in-out;
}

.ttt-featured-categories-edit:hover {
    outline-color: rgb(var(--color-rust) / 0.6);
}

.ttt-featured-categories-label {
    opacity: 0;
    transition: opacity 120ms ease-in-out;
}

.ttt-featured-categories-edit:hover .ttt-featured-categories-label {
    opacity: 1;
}

/* TUB-91 — the generic editor mark for every module wrapper (*Edit/Default.cshtml). Same rule as TUB-24's
   .ttt-featured-categories-edit: invisible until hovered, an outline (never a border or ring) so it cannot move
   the page by a pixel. The ttt- prefix is reserved, so no generated stylesheet can restyle it. */
.ttt-edit-module {
    outline: 2px dashed transparent;
    outline-offset: 2px;
    transition: outline-color 120ms ease-in-out;
}

.ttt-edit-module:hover {
    outline-color: rgb(var(--color-rust) / 0.6);
}

/* TUB-12 — "back to top" (Pages/Shared/_ScrollToTop.cshtml). Semi-transparent, bottom right,
   appears once the viewer has scrolled past 400px. Every selector carries the ttt- prefix, which
   CustomCssValidator reserves, so no AI-authored sheet can restyle or hide it. Neutral dark
   translucent rather than a theme colour: it sits over arbitrary generated designs and must stay
   readable on any of them. */
.ttt-scroll-top {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 45;                    /* above page content, below the assist launcher (50) and modals */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    border-radius: 9999px;
    background-color: rgba(15, 23, 42, 0.55);
    color: #fff;
    cursor: pointer;
    opacity: 0;
    visibility: hidden;             /* also takes it out of the tab order and the a11y tree */
    transform: translateY(0.5rem);
    transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}

.ttt-scroll-top svg { width: 1.25rem; height: 1.25rem; fill: currentColor; }

.ttt-scroll-top:hover,
.ttt-scroll-top:focus-visible { background-color: rgba(15, 23, 42, 0.85); }

.ttt-scroll-top--visible { opacity: 1; visibility: visible; transform: translateY(0); }

/* The AI assist launcher pill owns bottom-4 right-2 (≈16–58px up from the bottom edge) wherever
   edit mode renders it — website edit mode and the book editors. Lift above it rather than tracking
   a flag that can drift out of sync with the markup. */
body:has(#ttt-ai-assist-launcher) .ttt-scroll-top { bottom: 5.25rem; }

@media (prefers-reduced-motion: reduce) {
    .ttt-scroll-top { transition: none; transform: none; }
    .ttt-scroll-top--visible { transform: none; }
}

/* TUB-287 — cookie consent (Pages/Shared/_CookieConsent.cshtml). App-owned chrome like the back-to-top button: every selector
   carries the reserved ttt- prefix, both roots carry ttt-chrome, colours are neutral and the font is the system's so the bar
   reads on any generated design. Accept and Reject are the same size on the same row — the law wants refusing as easy as accepting. */
.ttt-cookie {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 46;                    /* above .ttt-scroll-top (45), below the assist launcher (50) and modals */
    padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom));
    background: rgba(15, 23, 42, 0.96); color: #fff;
    font: 400 0.9375rem/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.25);
}
.ttt-cookie[hidden] { display: none; }
.ttt-cookie-inner { max-width: 72rem; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem 1.5rem; align-items: center; }
.ttt-cookie-title { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
.ttt-cookie-text { margin: 0; opacity: 0.92; }
.ttt-cookie-more { margin-left: 0.25rem; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-decoration: underline; cursor: pointer; }
.ttt-cookie-details { margin: 0.5rem 0 0; padding: 0 0 0 1.1rem; font-size: 0.875rem; opacity: 0.85; }
.ttt-cookie-details[hidden] { display: none; }
.ttt-cookie-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.ttt-cookie-btn { min-width: 11rem; padding: 0.625rem 1rem; border: 1px solid rgba(255, 255, 255, 0.75); border-radius: 0.5rem; font: inherit; font-weight: 600; cursor: pointer; }
.ttt-cookie-accept { background: #fff; color: #0f172a; border-color: #fff; }
.ttt-cookie-reject { background: transparent; color: #fff; }
.ttt-cookie-btn:hover, .ttt-cookie-btn:focus-visible, .ttt-cookie-more:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.ttt-cookie-reopen {
    position: fixed; left: 1rem; bottom: 1rem; z-index: 45;
    display: inline-flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; padding: 0;
    border: 0; border-radius: 9999px; background-color: rgba(15, 23, 42, 0.55); color: #fff; cursor: pointer;
}
.ttt-cookie-reopen[hidden] { display: none; }
.ttt-cookie-reopen svg { width: 1.25rem; height: 1.25rem; fill: currentColor; }
.ttt-cookie-reopen:hover, .ttt-cookie-reopen:focus-visible { background-color: rgba(15, 23, 42, 0.85); }
@media (max-width: 40rem) {
    .ttt-cookie-inner { grid-template-columns: 1fr; }
    .ttt-cookie-btn { flex: 1 1 100%; }
}

/* Chrome is never part of a printed page — same rule book-print.css applies to its own overlays. */
@media print {
    .ttt-cookie, .ttt-cookie-reopen, .ttt-scroll-top { display: none !important; }
}

/* TUB-25 GOTCHA 5 — the AdSense module's own reserved-space sizing lives HERE, under ttt- names,
   never as inline styles or bare Tailwind utility classes on the component markup. Two reasons:
   (1) the "ttt-" prefix is a reserved token CustomCssValidator already rejects in selectors (see the
   newsletter comment above), so no AI- or owner-authored custom CSS can zero out the reserved space
   and cause layout shift the moment a real ad loads; (2) it is the one place both the real ad
   wrapper (AdSense/Default.cshtml) and the placeholder (_AdSensePlaceholder.cshtml) share their
   sizing from, so a real ad and its own placeholder always occupy the identical box. Heights match
   Google's own leaderboard/skyscraper units (plan §3 rule 4). */
.ttt-adsense-landscape {
    min-height: 90px;
}

@media (min-width: 1024px) {
    .ttt-adsense-landscape {
        min-height: 100px;
    }
}

.ttt-adsense-portrait {
    min-height: 600px;
    max-width: 300px;
    margin-left: auto;
    margin-right: auto;
}

.ttt-adsense {
    display: block;
    width: 100%;
}

.ttt-adsense-placeholder {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1px dashed rgb(168 162 158 / 0.6);
    border-radius: 0.5rem;
    background-color: rgb(250 250 249 / 0.6);
    color: rgb(120 113 108);
    font-size: 0.75rem;
    line-height: 1.4;
    text-align: center;
}

.ttt-adsense-setup-link {
    color: rgb(var(--color-rust));
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Same "hidden until the owner needs it" convention as .ttt-newsletter-gear above. */
.ttt-adsense-gear {
    opacity: 0;
    transition: opacity 120ms ease-in-out;
}

[data-ttt-adsense-edit]:hover .ttt-adsense-gear,
.ttt-adsense-gear:focus-visible,
.ttt-adsense-gear[aria-expanded="true"] {
    opacity: 1;
}

/* TUB-34 — public templates gallery. New block, ttt-namespaced per plan §3 A3: this component can
   render on a page whose own generated CSS loads after this file at equal specificity, and "ttt-" is
   the one prefix CustomCssValidator.ReservedSelectorTokens keeps out of a design's reach. Leaves the
   admin gallery's .tmpl-thumb / .tmpl-swatch-bg (TUB-32) completely untouched. */
.ttt-tmpl-gallery-heading {
    margin: 0 0 1.5rem;
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.2;
}

.ttt-tmpl-gallery-empty {
    margin: 0;
    color: #78716c;
    font-size: 0.9rem;
}

.ttt-tmpl-gallery-grid {
    display: grid;
    grid-template-columns: repeat(1, minmax(0, 1fr));
    gap: 1.5rem;
}

@media (min-width: 640px) {
    .ttt-tmpl-gallery-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .ttt-tmpl-gallery-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.ttt-tmpl-gallery-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid #e7e5e4;
    border-radius: 1rem;
    background: #fff;
}

.ttt-tmpl-card-thumb {
    /* Plan §3 A3 — 16:9 by aspect-ratio, not a fixed height: the captures are already 1200x675
       (TemplateThumbnailComposer.Width/Height), so the source is 16:9 already. */
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: #f5f5f4;
}

.ttt-tmpl-card-thumb-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: #a8a29e;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.ttt-tmpl-card-thumb-placeholder::before {
    content: "No preview";
}

.ttt-tmpl-gallery-card-body {
    /* TUB-48 — now holds up to three stacked children (name, optional category, CTA): row +
       space-between (TUB-34's original two-child shape) squeezed the title into a sliver next to the
       category label. Column stacking, left-aligned, is what a three-line card footer wants. */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
    padding: 1.1rem 1.25rem;
}

.ttt-tmpl-card-name {
    /* TUB-48 — width: 100% so the ellipsis truncation below still has a bounded box to truncate
       against: a flex-column's items shrink to content width by default (align-items: flex-start
       on the parent), which would let a long title just overflow the card instead of truncating. */
    width: 100%;
    margin: 0;
    overflow: hidden;
    color: #1c1917;
    font-size: 1rem;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ttt-tmpl-card-cta {
    /* TUB-48 — align-self: flex-start added now that the body stacks in a column (see
       .ttt-tmpl-gallery-card-body above): without it a flex column stretches every child to the
       container's full width, and this pill should stay pill-sized, not span the card. */
    align-self: flex-start;
    flex-shrink: 0;
    padding: 0.45rem 1rem;
    border: 1px solid #d6d3d1;
    border-radius: 9999px;
    color: #44403c;
    font-size: 0.8rem;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
}

.ttt-tmpl-card-cta:hover {
    border-color: #a8a29e;
    background: #fafaf9;
}

/* TUB-81 §1.1 — "Create website" is the PRIMARY action once a visitor already knows the template:
   a filled pill next to the outlined "Open template" default, so the two read as primary/secondary
   rather than two equal buttons. Same box model as .ttt-tmpl-card-cta above (a modifier, not a
   second class) — align-self/padding/radius etc. all still apply. */
.ttt-tmpl-card-cta-primary {
    border-color: transparent;
    background: #1c1917;
    color: #fff;
}

.ttt-tmpl-card-cta-primary:hover {
    border-color: transparent;
    background: #44403c;
}

.ttt-tmpl-gallery-sentinel {
    height: 1px;
}

/* TUB-48 — /templates filters, search, count and pager. Appended to the same ttt-tmpl- namespaced
   block above; nothing outside this block changes and nothing here uses a Tailwind class (the
   built stylesheet, tailwind.build.css, is untouched). */
.ttt-tmpl-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}

.ttt-tmpl-search {
    min-width: 16rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid #e7e5e4;
    border-radius: 0.5rem;
}

.ttt-tmpl-cats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.ttt-tmpl-chip {
    padding: 0.4rem 0.9rem;
    border: 1px solid #d6d3d1;
    border-radius: 9999px;
    background: #fff;
    color: #44403c;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
}

.ttt-tmpl-chip[aria-pressed="true"] {
    border-color: #1c1917;
    background: #1c1917;
    color: #fff;
}

.ttt-tmpl-gallery-count {
    margin: 0 0 1rem;
    color: #78716c;
    font-size: 0.9rem;
}

.ttt-tmpl-card-link {
    display: block;
}

.ttt-tmpl-card-category {
    margin: 0;
    color: #78716c;
    font-size: 0.8rem;
}

.ttt-tmpl-pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.25rem;
    margin-top: 2rem;
}

.ttt-tmpl-page {
    padding: 0.4rem 0.75rem;
    border: 1px solid #d6d3d1;
    border-radius: 0.4rem;
    background: #fff;
    color: #44403c;
    font-size: 0.85rem;
    cursor: pointer;
}

.ttt-tmpl-page[aria-current="page"] {
    border-color: #1c1917;
    background: #1c1917;
    color: #fff;
}

.ttt-tmpl-page[disabled] {
    opacity: 0.4;
    cursor: default;
}

/* TUB-183 — edit-mode loading cover (both theme layouts, EditMode only). Fixed, above everything
   (Syncfusion dialogs use z-index up to ~1000+; the account-menu popover is top-layer and is closed
   on load), removed from the DOM by Shared.EditLoading, so nothing here needs a "hidden" state. */
.ttt-edit-loading {
    position: fixed;
    inset: 0;
    z-index: 2147483000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #ffffff;
}
.ttt-edit-loading-card {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    border: 1px solid #e7e5e4;
    border-radius: 9999px;
    background: #fafaf9;
    color: #1c1917;
    font: 600 0.875rem/1.25rem ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.ttt-edit-loading-spinner {
    width: 1rem;
    height: 1rem;
    flex: none;
    border-radius: 9999px;
    border: 2px solid #d6d3d1;
    border-top-color: #b45309;
    animation: ttt-edit-loading-spin 0.8s linear infinite;
}
@keyframes ttt-edit-loading-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .ttt-edit-loading-spinner { animation: none; }
}

/* TUB-142 — the chef 404 block. Under ttt- names with literal values (the account-menu rule, TUB-30a):
   dark ink on a white card, so it reads on any design's background; only the accent comes from the
   site's palette (--color-rust from ThemeStyle, with the CoNaObiad default as fallback). The root also
   carries ttt-chrome, so a site's custom sheet cannot match inside it at all (TUB-23's @scope donut). */
.ttt-nf { padding: 3rem 1rem 4rem; }
.ttt-nf-card {
    max-width: 36rem; margin: 0 auto; padding: 2.5rem 1.5rem 2.75rem; text-align: center;
    background-color: #ffffff; color: #1c1917; border: 1px solid #e7e5e4; border-radius: 1.5rem;
    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.08);
    font-family: var(--font-sans, system-ui, sans-serif);
}
.ttt-nf-art { display: block; width: 220px; max-width: 70vw; height: auto; margin: 0 auto 1.25rem; overflow: visible; }
.ttt-nf-title {
    margin: 0 0 0.75rem; font-family: var(--font-serif, Georgia, serif);
    font-size: 2rem; line-height: 1.15; font-weight: 700; color: #1c1917;
}
.ttt-nf-text { margin: 0 0 1rem; font-size: 1.05rem; line-height: 1.6; color: #57534e; }
.ttt-nf-code { margin: 0 0 1.75rem; font-size: 0.8rem; color: #78716c; word-break: break-all; }
.ttt-nf-code code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.ttt-nf-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; }
.ttt-nf-btn {
    display: inline-flex; align-items: center; padding: 0.7rem 1.4rem; border-radius: 9999px;
    font-weight: 600; font-size: 0.95rem; line-height: 1.2; text-decoration: none;
    border: 2px solid rgb(var(--color-rust, 193 68 14));
    transition: background-color 0.15s ease, color 0.15s ease;
}
.ttt-nf-btn--primary { background-color: rgb(var(--color-rust, 193 68 14)); color: #ffffff; }
.ttt-nf-btn--primary:hover, .ttt-nf-btn--primary:focus-visible { background-color: rgb(var(--color-rust, 193 68 14) / 0.85); color: #ffffff; }
.ttt-nf-btn--secondary { background-color: transparent; color: rgb(var(--color-rust, 193 68 14)); }
.ttt-nf-btn--secondary:hover, .ttt-nf-btn--secondary:focus-visible { background-color: rgb(var(--color-rust, 193 68 14) / 0.08); }

/* The drawing: strokes are the ink, fills are literal, the jacket takes the accent. */
.ttt-nf-art path, .ttt-nf-art circle, .ttt-nf-art ellipse, .ttt-nf-art rect {
    stroke: #292524; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none;
}
.ttt-nf-shadow { fill: #e7e5e4; stroke: none; }
.ttt-nf-accent { fill: rgb(var(--color-rust, 193 68 14)); }
.ttt-nf-apron { fill: #f5f5f4; }
.ttt-nf-skin { fill: #f6d9bd; }
.ttt-nf-hat { fill: #ffffff; }
.ttt-nf-ink { fill: #292524; stroke: none; }
.ttt-nf-mustache { fill: #57534e; }
.ttt-nf-pot-body { fill: #44403c; }
.ttt-nf-pot-rim { fill: #57534e; }
.ttt-nf-pot-inside { fill: #1c1917; }
.ttt-nf-steam { stroke: #a8a29e; opacity: 0.5; }

/* Subtle: the chef peers (a 5° tilt about the neck) and the last wisps drift up. Off under
   prefers-reduced-motion, where the wisps stay as a still drawing. */
@keyframes ttt-nf-peek { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-5deg); } }
@keyframes ttt-nf-steam { 0% { opacity: 0; transform: translateY(6px); } 40% { opacity: 0.5; } 100% { opacity: 0; transform: translateY(-14px); } }
.ttt-nf-head { transform-origin: 120px 104px; transform-box: view-box; animation: ttt-nf-peek 3.2s ease-in-out infinite; }
.ttt-nf-steam { animation: ttt-nf-steam 2.8s ease-out infinite; }
.ttt-nf-steam--2 { animation-delay: 0.9s; }
.ttt-nf-steam--3 { animation-delay: 1.7s; }
@media (prefers-reduced-motion: reduce) {
    .ttt-nf-head, .ttt-nf-steam { animation: none; }
    .ttt-nf-steam { opacity: 0.4; }
}
@media print { .ttt-nf-art { display: none; } }

/* TUB-144 §3.3 — the e-book store component. Every class is "ttt-" namespaced
   (CustomCssValidator.ReservedSelectorTokens), appended at the end of the file so TUB-140's own
   append to this file merges trivially (plan §8). */
.ttt-ebook-store { display: flex; flex-direction: column; gap: 1rem; }
.ttt-ebook-store__heading { font-size: 1.5rem; font-weight: 700; }
.ttt-ebook-store__intro { color: #57534e; font-size: 0.95rem; }
.ttt-ebook-store__notice { color: #78716c; font-size: 0.9rem; font-style: italic; }
.ttt-ebook-store__grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1.25rem;
}
.ttt-ebook-store__item {
    display: flex; flex-direction: column; gap: 0.4rem; padding: 1rem;
    border: 1px solid #e7e5e4; border-radius: 0.75rem; background: #ffffff;
}
.ttt-ebook-store__cover { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border-radius: 0.5rem; }
.ttt-ebook-store__item-title { font-weight: 600; font-size: 1rem; }
.ttt-ebook-store__item-author { color: #78716c; font-size: 0.85rem; }
.ttt-ebook-store__formats { display: flex; gap: 0.35rem; }
.ttt-ebook-store__format-badge {
    font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
    padding: 0.15rem 0.5rem; border-radius: 9999px; background: #f5f5f4; color: #57534e;
}
.ttt-ebook-store__price { font-weight: 700; font-size: 1.05rem; }
.ttt-ebook-store__add, .ttt-ebook-store__checkout {
    padding: 0.6rem 1rem; border-radius: 9999px; border: none; font-weight: 600; cursor: pointer;
    background-color: rgb(var(--color-rust, 193 68 14)); color: #ffffff;
}
.ttt-ebook-store__add:hover, .ttt-ebook-store__checkout:hover:not(:disabled) {
    background-color: rgb(var(--color-rust, 193 68 14) / 0.85);
}
.ttt-ebook-store__checkout:disabled { opacity: 0.6; cursor: not-allowed; }
.ttt-ebook-store__basket {
    display: flex; flex-direction: column; gap: 0.6rem; padding: 1rem;
    border: 1px solid #e7e5e4; border-radius: 0.75rem; background: #fafaf9;
}
.ttt-ebook-store__basket-heading { font-weight: 700; }
.ttt-ebook-store__basket-items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.ttt-ebook-store__basket-total { font-weight: 600; }
.ttt-ebook-store__consent { display: flex; align-items: flex-start; gap: 0.5rem; font-size: 0.85rem; }
.ttt-ebook-store__error { color: #b91c1c; font-size: 0.85rem; }
.ttt-ebook-store__seller-line { color: #57534e; font-size: 0.85rem; }
/* TUB-179 phase 2 — a book included in the website membership: the badge + Join link every visitor sees, and the member block ebook-store-public.js reveals for a subscriber. */
.ttt-ebook-store__included {
    align-self: flex-start; font-size: 0.75rem; font-weight: 600; padding: 0.2rem 0.6rem; border-radius: 9999px;
    background: #fef3c7; color: #78350f;
}
.ttt-ebook-store__join { align-self: flex-start; font-weight: 600; font-size: 0.9rem; color: rgb(var(--color-rust, 193 68 14)); text-decoration: underline; }
.ttt-ebook-store__member { display: flex; flex-direction: column; gap: 0.35rem; }
.ttt-ebook-store__member[hidden], .ttt-ebook-store__included[hidden], .ttt-ebook-store__join[hidden], .ttt-ebook-store__price[hidden], .ttt-ebook-store__add[hidden] { display: none; }
.ttt-ebook-store__member-note { font-size: 0.85rem; font-weight: 600; color: #166534; }
.ttt-ebook-store__member-download {
    align-self: flex-start; padding: 0.5rem 1rem; border-radius: 9999px; font-weight: 600; font-size: 0.9rem; text-decoration: none;
    background-color: rgb(var(--color-rust, 193 68 14)); color: #ffffff;
}
.ttt-ebook-store__identity { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #e7e5e4; color: #78716c; font-size: 0.8rem; }

/* TUB-159 F1 — a recipe photo made from the video in portrait (4:5, the 4K "print" option) sits in a
   landscape hero box. Owner rule 2026-09-28: always the whole dish, never cropped — contain, not cover.
   Emitted only by RecipeVideoHeroPhoto on the website. !important because the site's generated CSS sets
   object-fit on its own hero class and loads after this file; generated CSS cannot target ttt- classes
   (CustomCssValidator.ReservedSelectorTokens), so nothing can override this back.
   TUB-160: also emitted by ResponsiveCardImage on recipe cards. */
img.ttt-img-whole {
    object-fit: contain !important;
}

/* TUB-172 — a portrait (4K) video photo sits beside the description instead of letterboxed in the hero.
   Emitted only by RecipeVideoHeroPhoto on the website (the transform is class-marked, never data-marked:
   the sanitiser strips every data-* except six). Phone: photo on top (it is first in the DOM), centred,
   at most 28rem wide. From 768 px: the photo is 4/12 on the right, the text 8/12 on the left.
   !important on the image because the site's generated CSS can target `section img` generically; it
   cannot target ttt- classes (CustomCssValidator.ReservedSelectorTokens), so nothing overrides this back. */
.ttt-recipe-aside-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.ttt-recipe-aside-photo { margin: 0 auto; max-width: 28rem; width: 100%; }
.ttt-recipe-aside-photo img { display: block; width: 100% !important; height: auto !important; max-height: none !important; object-fit: contain !important; border-radius: 0.75rem; }
.ttt-recipe-aside-photo figcaption { margin-top: 0.5rem; font-size: 0.875rem; line-height: 1.4; opacity: 0.75; text-align: center; }
@media (min-width: 768px) {
  .ttt-recipe-aside-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 2rem; }
  .ttt-recipe-aside-text { grid-column: span 8 / span 8; min-width: 0; }
  .ttt-recipe-aside-photo { grid-column: span 4 / span 4; order: 2; max-width: none; margin: 0; }
}

/* TUB-178 — video courses. Every selector is "ttt-course-" namespaced: CustomCssValidator.ReservedSelectorTokens
   ("ttt-") stops a tenant's generated CSS from restyling it, and nothing here needs a Tailwind class (the build
   CSS is committed by hand). Colours inherit from the site (currentColor / opacity) so the block reads on any
   palette, light or dark; only the accent uses the theme's rust token, as .ttt-ebook-store does.
   Phone: single column, the player first (it is first in the DOM), the lesson list below. From 768 px: a 12-column
   grid, player 8/12 on the left, curriculum 4/12 on the right (same shape as .ttt-recipe-aside-grid above). */
.ttt-course { max-width: 72rem; margin: 0 auto; padding: 2rem 1.5rem 3rem; box-sizing: border-box; }
.ttt-course *, .ttt-course *::before, .ttt-course *::after { box-sizing: border-box; }
.ttt-course-header { margin-bottom: 1.5rem; }
.ttt-course-title { font-size: clamp(1.75rem, 4.5vw, 2.5rem); font-weight: 700; line-height: 1.15; margin: 0 0 0.75rem; }
.ttt-course-description { max-width: 48rem; margin: 0 0 0.75rem; line-height: 1.65; opacity: 0.85; }
.ttt-course-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.ttt-course-player-col { min-width: 0; }
.ttt-course-curriculum { min-width: 0; border: 1px solid rgb(120 113 108 / 0.35); border-radius: 0.75rem; overflow: hidden; }
@media (min-width: 768px) {
  .ttt-course-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 2rem; }
  .ttt-course-player-col { grid-column: span 8 / span 8; }
  .ttt-course-curriculum { grid-column: span 4 / span 4; }
}

.ttt-course-poster {
  position: relative; width: 100%; aspect-ratio: 16 / 9; overflow: hidden; cursor: pointer;
  border-radius: 0.75rem; background: #1c1917; box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
}
.ttt-course-poster-img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s; }
.ttt-course-poster:hover .ttt-course-poster-img { transform: scale(1.03); }
.ttt-course-poster iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.ttt-course-play {
  position: absolute; inset: 0; margin: auto; width: 4.5rem; height: 4.5rem; border-radius: 9999px;
  display: flex; align-items: center; justify-content: center; color: #fff;
  background: rgb(var(--color-rust, 193 68 14) / 0.9); box-shadow: 0 10px 25px rgb(0 0 0 / 0.35);
  transition: transform 0.2s; pointer-events: none;
}
.ttt-course-poster:hover .ttt-course-play { transform: scale(1.08); }
.ttt-course-play svg { margin-left: 0.2rem; }
.ttt-course-now-playing { font-size: 1.25rem; font-weight: 700; line-height: 1.3; margin: 1rem 0 0; }

.ttt-course-locked, .ttt-course-empty {
  width: 100%; aspect-ratio: 16 / 9; border-radius: 0.75rem; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0.5rem; padding: 1.5rem; text-align: center;
}
.ttt-course-locked { background: #292524; color: #fafaf9; }
.ttt-course-empty { border: 1px dashed rgb(120 113 108 / 0.5); opacity: 0.8; }
.ttt-course-locked-title { font-size: 1.25rem; font-weight: 700; margin: 0.25rem 0 0; }
.ttt-course-locked-note { margin: 0; font-size: 0.9rem; opacity: 0.75; }
/* TUB-179 — the membership call to action on a locked course (only when the site sells a membership). */
.ttt-course-locked-cta {
  display: inline-block; margin-top: 0.5rem; padding: 0.55rem 1.25rem; border-radius: 9999px;
  background: #fafaf9; color: #1c1917; font-size: 0.9rem; font-weight: 700; text-decoration: none;
}
.ttt-course-locked-cta:hover { background: rgb(var(--color-rust, 193 68 14)); color: #fff; }

/* TUB-179 — the legal documents of a membership (the buyer terms page and the seller-terms box on /admin/subscription). Tailwind's preflight strips every heading, list and
   paragraph of its default look and `prose` was never built, so a long legal text needs its own small typography. */
.ttt-membership-terms { line-height: 1.6; color: #44403c; }
.ttt-membership-terms h1 { font-size: 1.75rem; font-weight: 700; line-height: 1.25; margin: 0 0 1rem; color: #1c1917; }
.ttt-membership-terms h2 { font-size: 1.2rem; font-weight: 700; margin: 1.75rem 0 0.5rem; color: #1c1917; }
.ttt-membership-terms p { margin: 0.6rem 0; }
.ttt-membership-terms ul, .ttt-membership-terms ol { margin: 0.6rem 0 0.6rem 1.4rem; }
.ttt-membership-terms ul { list-style: disc; }
.ttt-membership-terms ol { list-style: decimal; }
.ttt-membership-terms li { margin: 0.25rem 0; }
.ttt-membership-terms blockquote { margin: 0.8rem 0; padding: 0.4rem 1rem; border-left: 3px solid #d6d3d1; background: rgb(120 113 108 / 0.08); }
.ttt-membership-terms a { color: rgb(var(--color-rust, 193 68 14)); text-decoration: underline; }

.ttt-course-curriculum-heading { font-size: 1.05rem; font-weight: 700; margin: 0; padding: 1rem 1rem 0.15rem; }
.ttt-course-curriculum-count { margin: 0; padding: 0 1rem 0.75rem; font-size: 0.8rem; opacity: 0.65; }
.ttt-course-lessons { list-style: none; margin: 0; padding: 0; border-top: 1px solid rgb(120 113 108 / 0.25); }
@media (min-width: 768px) { .ttt-course-lessons { max-height: 34rem; overflow-y: auto; } }
.ttt-course-lessons > li { border-bottom: 1px solid rgb(120 113 108 / 0.2); }
.ttt-course-lessons > li:last-child { border-bottom: 0; }
.ttt-course-lesson {
  display: flex; align-items: center; gap: 0.75rem; width: 100%; padding: 0.65rem 1rem;
  background: transparent; color: inherit; font: inherit; text-align: left; border: 0;
  border-left: 3px solid transparent;
}
button.ttt-course-lesson { cursor: pointer; }
button.ttt-course-lesson:hover { background: rgb(120 113 108 / 0.12); }
.ttt-course-lesson-active { border-left-color: rgb(var(--color-rust, 193 68 14)); background: rgb(120 113 108 / 0.14); }
.ttt-course-lesson-num { flex: none; min-width: 1.25rem; font-size: 0.8rem; font-weight: 600; opacity: 0.6; text-align: right; }
.ttt-course-lesson-thumb { flex: none; width: 6rem; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 0.375rem; background: #1c1917; }
.ttt-course-lesson-name { min-width: 0; font-size: 0.9rem; line-height: 1.35; overflow-wrap: anywhere; }
.ttt-course-lesson-active .ttt-course-lesson-name { font-weight: 700; }

.ttt-course-list-title { margin: 0; }
.ttt-course-list-empty { margin-top: 1.5rem; opacity: 0.75; }
.ttt-course-card-media { position: relative; }
.ttt-course-card-locked {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.35rem;
  background: #292524; color: #fafaf9;
}
.ttt-course-card-locked-label { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.8; }

.ttt-course-editbar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.5rem 1rem;
  background: #1c1917; color: #fafaf9; font-size: 0.8rem; font-weight: 600;
}
.ttt-course-editbar-link { color: #fff; background: rgb(var(--color-rust, 193 68 14)); padding: 0.25rem 0.75rem; border-radius: 9999px; text-decoration: none; }

/* TUB-180 — in-recipe Google ads. ttt-namespaced like the AdSense block above: the "ttt-" prefix is a reserved
   token CustomCssValidator strips from every site rule's selector, so no AI- or owner-authored CSS can zero out
   the reserved space. .ttt-recipe-ad wraps a REAL ad between two recipe sections (RecipeText/Default.cshtml);
   .ttt-recipe-ad-preview is the FAKE ad on the recipe template page (_RecipeAdPreview.cshtml) — it also carries
   .ttt-adsense-placeholder + .ttt-adsense-landscape for its box styling and height. The [hidden] rule is needed
   because .ttt-adsense-placeholder sets display:flex, which would otherwise beat the UA's hidden.
   Review F1: both boxes are inserted as a DIRECT CHILD of the sections' common ancestor, which an AI template may
   itself lay out as a grid or a flex row — grid-column: 1 / -1 makes the ad a full-width ROW of that grid (instead
   of a stray cell that shoves the sidebar down), and flex-basis/width: 100% does the same in a wrapping flex row.
   In normal block flow all three are no-ops. */
.ttt-recipe-ad {
    margin: 1.5rem 0;
    grid-column: 1 / -1;
    flex-basis: 100%;
    width: 100%;
}

.ttt-recipe-ad-preview {
    margin: 1.5rem 0;
    grid-column: 1 / -1;
    flex-basis: 100%;
    width: 100%;
}

.ttt-recipe-ad-preview[hidden] {
    display: none !important;
}

/* An ad is never part of a printed recipe. (A book never holds an AdSense element, so this cannot reach one.) */
@media print {
    .ttt-adsense,
    .ttt-adsense-placeholder,
    .ttt-recipe-ad,
    .ttt-recipe-ad-preview {
        display: none !important;
    }
}

/* TUB-209 — dark content blocks. A content template inserted as "Dark" carries content-block--dark on its root
   (ContentTemplates.HtmlFor(dark: true)); nothing else in its HTML differs. The block paints itself as a dark
   rounded panel and swaps the five site colour tokens for everything inside it, DERIVED from the site's own light
   colours (so the Colours page drives dark blocks too). Two steps on purpose: the root computes --cb-* from the
   light --site-* it inherits (a one-step redefinition of --site-* in terms of itself would be a cycle), then the
   children get --site-* := --cb-*. The root also sets color to --cb-ink, so text with no colour class of its own
   (a paragraph the owner adds with Enter) reads light on the panel; elements that carry text-site-* or text-white
   keep their own colour. Websites only — no book page carries this class. */
.content-block--dark {
    --cb-bg: var(--site-ink);
    --cb-surface: color-mix(in srgb, var(--site-ink) 82%, white);
    --cb-ink: var(--site-bg);
    --cb-muted: color-mix(in srgb, var(--site-bg) 70%, var(--site-ink));
    --cb-accent: color-mix(in srgb, var(--site-accent) 75%, white);
    background-color: var(--cb-bg);
    color: var(--cb-ink);
    border-radius: 1.5rem;
}
.content-block--dark > * {
    --site-bg: var(--cb-bg);
    --site-surface: var(--cb-surface);
    --site-ink: var(--cb-ink);
    --site-muted: var(--cb-muted);
    --site-accent: var(--cb-accent);
}
/* The picker's schematics in Dark mode (chrome only). */
.ttt-content-picker-dark .ttt-cp-schematic { background-color: #1C1917; }
.ttt-content-picker-dark .ttt-cp-schematic .bg-stone-300 { background-color: #78716C; }
.ttt-content-picker-dark .ttt-cp-schematic .bg-stone-400 { background-color: #A8A29E; }

/* TUB-225 — the platform's own logo in the dashboard, administration and platform-admin chrome
   (PlatformBrand.Link). The wordmark's face, Oswald, is loaded by those three layouts. */
.ttt-brand { display: inline-flex; align-items: center; gap: 0.625rem; text-decoration: none; }
.ttt-brand-mark { display: block; height: 2.25rem; width: auto; }
.ttt-brand-word { font-family: 'Oswald', 'Arial Narrow', Arial, sans-serif; font-size: 1.125rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1; text-transform: uppercase; white-space: nowrap; }
.ttt-brand-red { color: #ff0000; }
.ttt-brand-dark { color: #282828; }
