`; return name === 'views' ? `${inner}` : ``; }).join('')}
`;
}
const MODES_TEXT = m => m.text;
/** One of X's icons: an svg that fills with the current color (`r-yyyyoo`); `extra` adds X's color class. */
function icon(extra = '') { return ``; }
/**
* X's composer (read live, Lights out, 2026-09-30): the editor, the audience row in X's blue, and
* the toolbar, whose icons are `svg.r-1otekoa` (one of X's border grays, 1.7:1 on X's own black)
* inside a button whose content color is X's primary text. The Post button is X's filled button.
*/
function composer(m, { inline = true } = {}) {
const tools = ['Add photos or video', 'Add a GIF', 'Enhance your post with Grok', 'Add poll', 'Add emoji', 'Schedule post', 'Tag location'];
return `
What is happening?!
${icon()}${T('rgb(29, 155, 240)', 'Everyone can reply', ' r-b88u0q')}
`;
}
function nav(m) {
// X's rail as read live (2026-09-30): header > width wrapper > sticky column > [logo + nav + Post, account].
// Nav items: a[data-testid=AppTabBar_*] > div (the pill) > [div > svg, div > span].
const items = [['AppTabBar_Home_Link', '/home', 'Home'], ['AppTabBar_Explore_Link', '/explore', 'Explore'], ['AppTabBar_Notifications_Link', '/notifications', 'Notifications'],
['AppTabBar_DirectMessage_Link', '/i/chat', 'Chat'], ['', '/i/grok', 'Grok'], ['premium-hub-tab', '/i/premium_sign_up', 'Premium'], ['', '/i/bookmarks', 'Bookmarks'],
// Read live 2026-10-02: X shows up to 12 rail items (History, Creator Studio, Articles join the usual ones).
['', '/i/history', 'History'], ['', '/i/jf/creators/studio', 'Creator Studio'], ['', '/compose/articles', 'Articles'],
['AppTabBar_Profile_Link', '/placeholder', 'Profile'], ['AppTabBar_More_Menu', '', 'More']];
const glyph = ``;
const item = ([testid, href, label]) => {
const tag = href ? 'a' : 'button';
return `<${tag} ${href ? `href="${href}" role="link"` : 'type="button" role="button"'}${testid ? ` data-testid="${testid}"` : ''} aria-label="${label}" class="css-175oi2r r-16y2uox r-13qz1uu" style="text-decoration: none; border: 0; background: transparent; padding: 4px 0;">
`;
}
function timeline(m, count = 5) {
const posts = [
['Lena Ortiz', 'lena', 'Shipping the new build tonight. Themes should never fight the layout.', true],
['Theo Park', 'theo', 'The best interfaces feel inevitable once you see them.'],
['Mira Chen', 'mira', 'Hot take: hover cards should be opaque. Always.'],
['Jonas Weber', 'jonas', 'Wrote 2k words on typographic rhythm and nobody asked.', true],
['Sana Iqbal', 'sana', 'Split panes at 1275px are the real stress test.'],
];
return `
`,
// XChat as it is on x.com (chat-fixtures.mjs): a group with a pinned message, a 1:1, and the message menu.
'chat-group': m => chatRoute(m, 'group'),
'chat-direct': m => chatRoute(m, 'direct'),
'chat-direct-menu': m => chatRoute(m, 'direct', true),
// X's chat embed: the same app inside an open shadow root (x.com since 2026-10-01).
'chat-embed': m => chatRoute(m, 'direct', false, true),
// Articles: X's row with content that has no intrinsic width (absolutely positioned editor).
articles: m => `
${T(m.text, 'Write a title', ' r-b88u0q r-adyw6z')}${T(m.muted, 'Start writing your article…')}
`,
// Theme v3 fixture cells (live failures, 2026-10-02). Creator Studio /i/monetization: jetfuel's "New" pill on its
// indigo fill rgb(30,27,75), the label coloured through --jf-text-color (1.19:1 live under Windows 98).
monetization: m => standardShell(m, `${columnHeader(m, 'Monetization')}
`),
// The empty sidebar module (trends hidden: X keeps the framed section with nothing in it) and the nested
// wrapper of 1.0.2 (div > div > section, which a :has() framed as a whole).
'sidebar-empty': m => standardShell(m, `${columnHeader(m, 'Home', ['For you', 'Following'])}${timeline(m, 2)}`, { sidebarExtra: `
` }),
// Settings: two panes inside X's row, no primaryColumn testid on x.com.
settings: m => `
${columnHeader(m, 'Settings')}${['Your account', 'Security and account access', 'Privacy and safety', 'Notifications', 'Accessibility, display, and languages'].map((t, i) => `${T(m.text, t)}${T(m.muted, '›')}`).join('')}${columnHeader(m, 'Your account')}
${T(m.muted, 'See information about your account, download an archive of your data, or learn about your account deactivation options.')}${['Account information', 'Change your password', 'Download an archive of your data'].map(t => `
`, 'Save'),
compose: m => modal(m, '', composer(m, { inline: false })),
// X's tooltip under a post's caret ("More"): X's grey tooltip fill with the label in X's text colour.
'hover-label': m => `
More
`,
// The account menu as X places it: from the account button's rect (above it, left-aligned), wherever a theme puts
// the button (the right end of a top or bottom bar pushed it 52px past the edge live).
'account-menu': m => `
`,
// A hover card anchored near the bottom edge (X opens it below the anchor: cut off live).
'hovercard-bottom': m => `
${T(m.text, 'Placeholder User', ' r-b88u0q')}${T(m.muted, '@placeholder')}${T(m.text, 'Building small tools for the timeline. Theme engines, mostly.')}
`,
};
function modal(m, title, body, action = '') {
// x.com structure (read live): an outer zero-height dialog, a group, the mask, and the sheet
// (`role=dialog aria-modal aria-labelledby=modal-header`) whose child carries the layer color.
return `
✕
${title}
${action ? `` : ''}
${body}
`;
}
/** Cells of the matrix: a route shell, optionally with a layer open over it. */
export const CELLS = {
home: ['home'], 'home-composer': ['home-composer'], profile: ['profile'], status: ['status'], notifications: ['notifications'], chat: ['chat'],
'creator-studio': ['creator-studio'], articles: ['articles'], settings: ['settings'], explore: ['explore'], bookmarks: ['bookmarks'],
'compose-modal': ['home', 'compose'], 'edit-profile-modal': ['profile', 'edit-profile'], 'more-menu': ['profile', 'more-menu'], 'hover-card': ['notifications', 'hover-card'],
'signed-out-landing': ['signed-out-landing'], 'signed-out-login': ['signed-out-login'],
'chat-group': ['chat-group'], 'chat-direct': ['chat-direct'], 'chat-menu': ['chat-direct-menu'], 'chat-embed': ['chat-embed'],
// Theme v3 cells: each one a live failure on 2026-10-02.
monetization: ['monetization'], 'sidebar-empty': ['sidebar-empty'], 'sidebar-nested': ['sidebar-nested'],
'hover-label': ['status', 'hover-label'], 'account-menu': ['home', 'account-menu'], 'hovercard-bottom': ['notifications', 'hovercard-bottom'],
};
/** Cells with widths of their own: the chat is checked at split-pane widths (one panel, one panel with the nav, both panels). */
export const CELL_WIDTHS = { 'chat-group': [360, 600, 1000, 1400], 'chat-direct': [360, 600, 1000, 1400], 'chat-menu': [600, 1400], 'chat-embed': [360, 1000, 1400] };
/** Routes with no `[data-testid="primaryColumn"]`: X's signed-out experience never shows X's own
* app shell, so these get their own top-level document in `fixtureHtml` instead of `nav()` + a row. */
export const SIGNED_OUT_ROUTES = new Set(['signed-out-landing', 'signed-out-login']);
/**
* X's logged-out flow (read live, 2026-09-28, no sign-in): a separate Tailwind app
* (`.jetfuel-style-root`, `.jf-element`) that paints literal black/white/gray, not X's palette
* variables — the shapes `signedOutCss` in `theme.ts` has to make legible. Colors below are the
* exact computed values captured from `x.com/` and `x.com/i/jf/onboarding/web?mode=login` logged
* out; independent of X's Default/Dim/Lights out mode (X renders this flow by the OS/browser color
* scheme, not by a signed-in preference), so both fixtures use the same literals in every X mode.
*/
function signedOutLanding() {
return `