// Static x.com fixtures for the theme regression matrix (scripts/theme-matrix.mjs). // // They reproduce the parts of X's markup the theme engine reads and the shapes that broke it, // with values read from live x.com (Lights out, logged in, 2026-09-27) and X's Default palette: // - react-native-web atomic classes (`.css-175oi2r` base with `background-color:rgba(0,0,0,0)`, // `.r-kemksi` page black, `.r-cl2sl0` = rgb(20,20,20), the surface of every Lights out layer), // inline `style="color: rgb(...)"` text, X's 1050px row / 600px column cap / 350px sidebar; // - the Tailwind shells (chat, settings) that color through `--color-gray-*` / `--x-fg-*` // variables declared on `:root[data-theme="dark"]`; // - Creator Studio's jetfuel neutrals with variable alpha (`rgba(0, 0, 0, var(--jbgo))`); // - Articles, whose row content has no intrinsic width (it collapsed to 0 under the old rules); // - `#layers`: More menu, hover card, modal sheets, mask. // X's own breakpoints (nav 275/88px, sidebar hidden below 1000px) are emulated by swapping the // same atomic classes on resize, as react-native-web does. Names and text are placeholders. // X's chat (XChat) has its own token layer and markup: see chat-fixtures.mjs. import { CHAT_CSS, CHAT_SCRIPT, EMBED_SCRIPT, chatRoute } from './chat-fixtures.mjs'; const MODES = { light: { page: 'rgb(255, 255, 255)', layer: 'rgb(255, 255, 255)', border: 'rgb(239, 243, 244)', text: 'rgb(15, 20, 25)', muted: 'rgb(83, 100, 113)', hover: 'rgba(15, 20, 25, 0.1)', search: 'rgb(239, 243, 244)', module: 'rgb(247, 249, 249)', fill: 'rgb(15, 20, 25)', onFill: 'rgb(255, 255, 255)', mask: 'rgba(0, 0, 0, 0.4)', shadow: 'rgba(101, 119, 134, 0.2) 0px 0px 15px, rgba(101, 119, 134, 0.15) 0px 0px 3px 1px', // Mirrored from Lights out (X's border gray as the toolbar icon color): unverified on x.com Default. dimIcon: 'rgb(207, 217, 222)', iconButton: 'rgb(15, 20, 25)', theme: '#ffffff', dataTheme: 'light', }, dark: { page: 'rgb(0, 0, 0)', layer: 'rgb(20, 20, 20)', border: 'rgb(47, 51, 54)', text: 'rgb(231, 233, 234)', muted: 'rgb(113, 118, 123)', hover: 'rgba(231, 233, 234, 0.1)', search: 'rgb(32, 35, 39)', module: 'rgb(22, 24, 28)', fill: 'rgb(239, 243, 244)', onFill: 'rgb(15, 20, 25)', mask: 'rgba(0, 0, 0, 0.5)', shadow: 'rgba(0, 0, 0, 0.5) 0px 4px 12px, rgba(0, 0, 0, 0.35) 0px 0px 2px', // Read live (2026-09-30): the composer toolbar icons are `svg.r-1otekoa` inside a div with this inline color. dimIcon: 'rgb(51, 54, 57)', iconButton: 'rgb(239, 243, 244)', theme: '#000000', dataTheme: 'dark', }, }; // X's Tailwind variable blocks, as declared on x.com (HSL triples). const TAILWIND = ` :root, :root[data-theme="light"], [data-theme="light"] { --background: 0 0% 100%; --foreground: 210 25% 8%; --color-text: 210 25% 8%; --border: 197 16% 91%; --color-modal-background: 0 0% 100%; --color-nested-border: 200 19% 84%; --x-fg-primary: #0f1419; --x-fg-secondary: #536471; --x-bg-primary: #ffffff; --color-gray-0: 180 14% 97%; --color-gray-50: 192 19% 95%; --color-gray-100: 197 16% 91%; --color-gray-200: 200 19% 84%; --color-gray-300: 201 23% 78%; --color-gray-400: 203 23% 69%; --color-gray-500: 205 20% 59%; --color-gray-600: 206 14% 49%; --color-gray-700: 206 15% 38%; --color-gray-800: 206 19% 31%; --color-gray-900: 207 17% 26%; --color-gray-1000: 207 20% 18%; --color-gray-1100: 210 25% 8%; } :root[data-theme="dark"], [data-theme="dark"], .dark { --background: 0 0% 0%; --foreground: 200 7% 91%; --color-text: 200 7% 91%; --border: 210 7% 18%; --color-modal-background: 0 0% 8%; --color-nested-border: 210 4% 46%; --x-fg-primary: #e7e9ea; --x-fg-secondary: #71767b; --x-bg-primary: #000000; --color-gray-0: 220 12% 10%; --color-gray-50: 214 10% 14%; --color-gray-100: 210 7% 18%; --color-gray-200: 210 6% 21%; --color-gray-300: 210 5% 25%; --color-gray-400: 214 4% 31%; --color-gray-500: 213 5% 37%; --color-gray-600: 207 4% 41%; --color-gray-700: 210 4% 46%; --color-gray-800: 207 4% 59%; --color-gray-900: 210 4% 73%; --color-gray-1000: 204 6% 85%; --color-gray-1100: 200 7% 91%; } .tw-name { color: hsl(var(--color-gray-1000)); font-weight: 700; } .tw-preview { color: hsl(var(--color-gray-800)); } .tw-time { color: hsl(var(--color-gray-700)); } .tw-title { color: hsl(var(--foreground)); } .tw-pane { background-color: hsl(var(--background)); } .tw-list { background-color: hsl(var(--color-gray-0)); } .tw-row:hover, .tw-row.is-active { background-color: hsl(var(--color-gray-50)); } .tw-divider { border-color: hsl(var(--color-gray-100)); } .tw-search { background-color: hsl(var(--color-gray-50)); color: hsl(var(--color-gray-700)); } :root { --x-white: #ffffff; --x-black: #000000; --x-blue-500: #1d9bf0; } :root { --chat-accent: 204 88% 53%; --x-white-60: rgba(255, 255, 255, 0.6); } /* XChat (live, 2026-09-27): the sent bubble is an HSL variable fill, its label X's white variable. */ .bg-chat-accent { background-color: hsl(var(--chat-accent)); } .text-white { color: var(--x-white); } .text-white-60 { color: var(--x-white-60); } .text-inherit { color: inherit; } .tw-fg { color: var(--x-fg-primary); } .tw-fg2 { color: var(--x-fg-secondary); } `; // Creator Studio (jetfuel): neutral colors with variable alpha, dark rules scoped under data-theme. const JETFUEL = ` .jetfuel-style-root { --jbgo: 1; --jtxo: 1; } .jetfuel-style-root .jf-surface { background-color: rgba(255, 255, 255, var(--jbgo)); --jf-bg-color: rgba(255, 255, 255, var(--jbgo)); } .jetfuel-style-root .jf-text { color: rgba(0, 0, 0, var(--jtxo)); --jf-text-color: rgba(0, 0, 0, var(--jtxo)); } .jetfuel-style-root .jf-muted { color: rgba(115, 115, 115, var(--jtxo)); } .jetfuel-style-root .jf-pill { background-color: rgba(229, 229, 229, var(--jbgo)); color: rgba(82, 82, 82, var(--jtxo)); } [data-theme="dark"] .jetfuel-style-root .jf-surface { background-color: rgba(0, 0, 0, var(--jbgo)); --jf-bg-color: rgba(0, 0, 0, var(--jbgo)); } [data-theme="dark"] .jetfuel-style-root .jf-text { color: rgba(255, 255, 255, var(--jtxo)); --jf-text-color: rgba(255, 255, 255, var(--jtxo)); } [data-theme="dark"] .jetfuel-style-root .jf-muted { color: rgba(115, 115, 115, var(--jtxo)); } [data-theme="dark"] .jetfuel-style-root .jf-pill { background-color: rgba(38, 38, 38, var(--jbgo)); color: rgba(115, 115, 115, var(--jtxo)); } `; function xCss(m) { return ` html, body { margin: 0; height: 100%; } body { font-family: TwitterChirp, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .css-175oi2r { align-items: stretch; background-color: rgba(0, 0, 0, 0); border: 0 solid black; box-sizing: border-box; display: flex; flex-basis: auto; flex-direction: column; flex-shrink: 0; list-style: none; margin: 0; min-height: 0; min-width: 0; padding: 0; position: relative; z-index: 0; } .css-1jxf684 { background-color: rgba(0, 0, 0, 0); border: 0 solid black; box-sizing: border-box; color: inherit; display: inline; font: inherit; margin: 0; padding: 0; white-space: inherit; overflow-wrap: break-word; } .r-chirp { font-family: "TwitterChirp", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .r-a023e6 { font-size: 15px; } .r-rjixqe { line-height: 20px; } .r-adyw6z { font-size: 20px; } .r-135wba7 { line-height: 24px; } .r-b88u0q { font-weight: 700; } .r-16dba41 { font-weight: 400; } .r-kemksi { background-color: ${m.page}; } .r-cl2sl0 { background-color: ${m.layer}; } .r-1jyoszn { box-shadow: ${m.shadow}; } .r-1kqtdi0 { border-color: ${m.border}; } .r-1cvl2hr { color: rgb(29, 155, 240); } .r-l5o3uw { background-color: rgb(29, 155, 240); } .r-jwli3a { color: rgb(255, 255, 255); } .r-hover { background-color: ${m.hover}; } .r-yyyyoo { fill: currentcolor; } .r-1otekoa { color: ${m.dimIcon}; } /* Read live 2026-10-01: X brightens the dim tool icons with a filter on their wrapper. */ .r-1jodnrb { filter: brightness(2.5); } .r-module { background-color: ${m.module}; } .r-search { background-color: ${m.search}; } .r-sdzlij { border-top-left-radius: 9999px; border-top-right-radius: 9999px; border-bottom-right-radius: 9999px; border-bottom-left-radius: 9999px; } .r-1q9bdsx { border-top-left-radius: 16px; } .r-card16 { border-top-left-radius: 16px; border-top-right-radius: 16px; border-bottom-right-radius: 16px; border-bottom-left-radius: 16px; } .r-ttdzmv { padding-top: 12px; } .r-kzbkwu { padding-bottom: 12px; } .r-qklmqi { border-bottom-width: 1px; } .r-lrvibr { border-left-width: 1px; } .r-thb0q2 { border-right-width: 1px; } /* Layout (X's own widths) */ .r-1ye8kvj { max-width: 600px; } .r-13qz1uu { width: 100%; } .r-16y2uox { flex-grow: 1; } .r-1wbh5a2 { flex-shrink: 1; } .r-1habvwh { align-items: flex-start; } .r-18u37iz { flex-direction: row; } .r-113js5t { width: 1050px; } .r-row990 { width: 990px; } .r-row920 { width: 920px; } .r-row600 { width: 600px; } .r-1hycxz { width: 350px; } .r-side290 { width: 290px; } .r-10f7w94 { margin-right: 70px; } .r-nav275 { width: 275px; } .r-nav88 { width: 88px; } .r-chatrow { width: 1257px; } .r-maxw100 { max-width: 100%; } .r-sticky { position: sticky; top: 0; } ${TAILWIND} ${JETFUEL} ${CHAT_CSS} /* Fixture-only helpers (not X classes): pure geometry, no colors. */ .fx-avatar { width: 40px; height: 40px; border-radius: 9999px; background: linear-gradient(135deg, #8aa4b8, #4f6b80); flex: 0 0 40px; } .fx-media { height: 180px; border-radius: 16px; background: linear-gradient(135deg, #3b5b76, #9fb6c8); margin-top: 10px; } .fx-icon { width: 26px; height: 26px; border-radius: 6px; border: 2px solid currentColor; box-sizing: border-box; } `; } const T = (color, text, extra = '') => `${text}`; function tweet(m, name, handle, body, media = false) { return `
${T(MODES_TEXT(m), name, ' r-b88u0q')}${T(m.muted, `@${handle} · 2h`)}
${T(m.text, body)}
${media ? '
' : ''}
${['reply', 'retweet', 'like', 'views'].map((name, i) => { const inner = `
${icon()}
${T(m.muted, ['12', '48', '1.2K', '9K'][i])}
`; 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;">
${glyph}
${label}
`; }; return ``; } function sidebar(m, extra = '') { return `
${extra}
`; } function columnHeader(m, title, tabs = []) { return `

${title}

${tabs.length ? `
${tabs.map((t, i) => `${T(i ? m.muted : m.text, t, i ? '' : ' r-b88u0q')}`).join('')}
` : ''}
`; } 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 `
${posts.slice(0, count).map(p => tweet(m, ...p)).join('')}
`; } function newPostsPill() { return `
`; } /** X's usual shell: nav + main row (primary column + sidebar). */ function standardShell(m, primary, { sidebarShown = true, sidebarExtra = '' } = {}) { return `
${primary}
${sidebarShown ? sidebar(m, sidebarExtra) : ''}
`; } const ROUTES = { home: m => standardShell(m, `${columnHeader(m, 'Home', ['For you', 'Following'])}${newPostsPill()}${timeline(m)}`), // Home with X's inline composer on top of the timeline. 'home-composer': m => standardShell(m, `${columnHeader(m, 'Home', ['For you', 'Following'])}${composer(m)}${timeline(m, 3)}`), explore: m => standardShell(m, `${columnHeader(m, 'Explore', ['For You', 'Trending', 'News'])}
${['Design', 'Tauri 3.0', 'Typography', 'Split panes'].map((t, i) => `
${T(m.muted, `${i + 1} · Trending in Technology`)}${T(m.text, t, ' r-b88u0q')}${T(m.muted, `${(i + 2) * 3}K posts`)}
`).join('')}
${timeline(m, 2)}`), notifications: m => standardShell(m, `${columnHeader(m, 'Notifications', ['All', 'Verified', 'Mentions'])}
${[1, 2, 3].map(i => `
${[1, 2, 3, 4].map(() => '
').join('')}
${T(m.text, `Person ${i} and ${i * 7} others liked your reply · 1m`)}${T(m.muted, 'Procedural olarak generate etmek için yöntemler var ...')}
`).join('')}
${timeline(m, 2)}`), bookmarks: m => standardShell(m, `${columnHeader(m, 'Bookmarks')}${timeline(m, 4)}`), profile: m => standardShell(m, `${columnHeader(m, 'Placeholder User')}
${T(m.text, 'Edit profile', ' r-b88u0q')}
${T(m.text, 'Placeholder User', ' r-b88u0q r-adyw6z')}${T(m.muted, '@placeholder')}${T(m.text, 'Computer scientist. Writes about interfaces.')}
${T(m.text, '544')}${T(m.muted, 'Following')}${T(m.text, '240')}${T(m.muted, 'Followers')}
${['Posts', 'Replies', 'Media'].map((t, i) => `${T(i ? m.muted : m.text, t)}`).join('')}
${timeline(m, 3)}`), status: m => standardShell(m, `${columnHeader(m, 'Post')}
${T(m.text, 'Lena Ortiz', ' r-b88u0q')}${T(m.muted, '@lena')}
A focused status page: long text, the reply box and replies below. example.com
${T(m.muted, '7:14 PM · Sep 27, 2026 · 12K Views')}
${T(m.muted, 'Post your reply')}
${timeline(m, 2)}`), // Creator Studio: X's primary column hosts a jetfuel root with its own neutrals. 'creator-studio': m => standardShell(m, `
Creator Studio
${[['Programs', [['Original Content Rewards', 'Earn from your posts', 'Ineligible'], ['Subscriptions', '', 'Ineligible']]], ['Tools', [['Live Studio', 'Go live professionally', 'New'], ['Analytics', '', ''], ['Inspiration', 'Top posts by engagement', '']]]].map(([h, rows]) => `
${h}
${rows.map(([t, s, pill]) => `
${t}
${s ? `
${s}
` : ''}
${pill ? `${pill}` : ''}
`).join('')}`).join('')}
`), // Chat: X's primary column holds a full-width Tailwind app (list + conversation), no 600px cap, no sidebar. chat: m => `
ChatAll
${['AI Game Lobby', 'Servet Carey', 'Casie Nakamura', 'mrdoob', 'robot 2.0', 'Jorrit Boumann'].map((n, i) => `
${n}${i + 1}h
You: see you there, the build is almost ready
`).join('')}
hey, did the theme fix land?
yes, layers are opaque now23:26
`, // 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, 'Articles', ' r-b88u0q r-adyw6z')}
${T(m.muted, 'Drafts')}
${T(m.text, 'Why opaque layers matter')}
${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')}
${['Subscriptions', 'Creator Revenue Sharing'].map((t, i) => `

${t}

${i === 0 ? '

New

' : ''}
`).join('')}
`), // 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: `

` }), 'sidebar-nested': m => standardShell(m, `${columnHeader(m, 'Home', ['For you', 'Following'])}${timeline(m, 2)}`, { sidebarExtra: `
${T(m.text, 'What’s happening', ' r-b88u0q')}
${T(m.muted, 'Placeholder trend')}
` }), // 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 => `
${T(m.text, t)}${T(m.muted, 'Placeholder description')}
`).join('')}
`, }; // `#layers`: portaled surfaces. Real X gives them the layer color (rgb(20,20,20) in Lights out). const LAYERS = { 'more-menu': m => `
`, 'hover-card': m => `
${T(m.text, 'Fishies', ' r-b88u0q')}${T(m.muted, '@playfishies')}${T(m.text, 'The social aquarium that lives while you’re away.')}
${T(m.text, '30')}${T(m.muted, 'Following')}${T(m.text, '21')}${T(m.muted, 'Followers')}
`, 'edit-profile': m => modal(m, 'Edit profile', `
${['Name', 'Bio', 'Location', 'Website'].map(label => ``).join('')}
`, '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.')}
${T(m.text, '120', ' r-b88u0q')}${T(m.muted, 'Following')}${T(m.text, '4,500', ' r-b88u0q')}${T(m.muted, 'Followers')}
`, }; 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 `
`; } /** 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 `

Happening now.

Join today.


or

By signing up, you agree to the Terms of Service and Privacy Policy, including Cookie Use.

Scan to get the app
`; } function signedOutLogin() { // The live Jetfuel login uses a child pill for phone/Apple and a separate visible Google face. // A flat `; return `
`; } const SIGNED_OUT = { 'signed-out-landing': signedOutLanding, 'signed-out-login': signedOutLogin }; /** A complete x.com-like document for one cell in one X mode. */ export function fixtureHtml(cell, modeName) { const m = MODES[modeName]; const [route, layer] = CELLS[cell]; if (SIGNED_OUT_ROUTES.has(route)) { // No nav, no primaryColumn, no #layers portal: X never shows its own app shell signed out. return `${cell} / X fixture
${SIGNED_OUT[route]()}
`; } return `${cell} / X fixture
${nav(m)}${ROUTES[route](m)}
${layer ? LAYERS[layer](m) : ''}
`; } export const MODE_NAMES = Object.keys(MODES);