README: a hero that shows the app

This commit is contained in:
CRTkafa 2026-10-03 15:27:40 +07:00
parent 467aea2e28
commit 89c9bfc75f
32 changed files with 933 additions and 71 deletions

View file

@ -12,6 +12,8 @@ const POST = 'article[data-testid="tweet"]';
const COLUMN = '[data-testid="primaryColumn"]';
/** The main column on every route but chat (a drawn bar above X's chat app would push its composer out of the window). */
// Keyed on the engine's page flag (THEME_FLAGS in theme.ts), never `:has()` over the page: see SIGNED_OUT_ROOT.
/** Where an active 98 title bar's gradient ends (98.css `--dialog-blue-light`). */
const TITLE_END = '#1084d0';
const FRAMED = 'html:not([data-wb-chat]) [data-testid="primaryColumn"]';
const RAIL = 'header[role="banner"]';
const NAV = `${RAIL} nav[role="navigation"]`;
@ -44,7 +46,10 @@ export const X_PARTS: PartMap = {
composerTool: { sel: '[data-testid="toolBar"] nav [role="button"]', text: '[data-testid="toolBar"] nav [role="button"] svg' },
// Live 2026-10-01: the painted, bordered card is the grandparent of X's aside/section (a plain div sits
// between), or the parent of a `*_sidebar` module (news, scores).
sidebarCard: { sel: '[data-testid="sidebarColumn"] div:has(> div > :is(aside[role="complementary"],section[role="region"])),[data-testid="sidebarColumn"] div:has(> [data-testid$="_sidebar"])' },
// One module per card: the column holding all the modules has a section's wrapper as a direct child
// too, so `div:has(> div > section)` framed the whole column (live 1.0.2). Asides sit two deep,
// sections (trends) one deep, the news/scores cards beside a `*_sidebar` tracker.
sidebarCard: { sel: '[data-testid="sidebarColumn"] div:not(:has(> :nth-child(3))):has(> div > aside[role="complementary"]),[data-testid="sidebarColumn"] div:has(> section[role="region"]),[data-testid="sidebarColumn"] div:has(> [data-testid$="_sidebar"])' },
menu: { sel: '#layers [role="menu"]' },
hoverCard: { sel: '#layers [data-testid="HoverCard"]' },
// The compose dialog's painted sheet (the child of the modal that has a composer toolbar). Never every modal: the photo
@ -61,10 +66,10 @@ export function xDecorTargets(): DecorTargets<XSkinContext> {
moving: `${POST},${NAV} > :is(a,button) > div,[role="tab"],[data-testid$="-follow"],[data-testid="tweetButtonInline"]`,
pressable: `${NAV} > :is(a,button),[data-testid="tweetButtonInline"],[data-testid="tweetButton"],[data-testid$="-follow"],${POST} [role="group"] [role="button"]`,
chrome: {
// A window title bar on the main column, and on each sidebar card with X's own label for it.
// A window title bar on the main column (98.css: navy fading to #1084d0, not a flat accent), and on each sidebar card with X's own label for it.
titlebar: (title, { colors }) => [
`${FRAMED}::before{content:${cssString(title || 'Home')};display:block;order:-1;box-sizing:border-box;height:24px;padding:4px 8px;font:700 12px/16px "Pixelated MS Sans Serif","MS Sans Serif","Tahoma",sans-serif;letter-spacing:.2px;color:#ffffff;background:linear-gradient(90deg, ${colors.inverse}, ${colors.accent});white-space:nowrap;overflow:hidden}`,
`[data-testid="sidebarColumn"] :is(aside[role="complementary"],section[role="region"])[aria-label]::before{content:attr(aria-label);display:block;margin:-12px -16px 10px;padding:3px 8px;font:700 12px/16px "Pixelated MS Sans Serif","MS Sans Serif","Tahoma",sans-serif;color:#ffffff;background:linear-gradient(90deg, ${colors.inverse}, ${colors.accent})}`,
`${FRAMED}::before{content:${cssString(title || 'Home')};display:block;order:-1;box-sizing:border-box;height:24px;padding:4px 8px;font:700 12px/16px "Pixelated MS Sans Serif","MS Sans Serif","Tahoma",sans-serif;letter-spacing:.2px;color:#ffffff;background:linear-gradient(90deg, ${colors.inverse}, ${TITLE_END});white-space:nowrap;overflow:hidden}`,
`[data-testid="sidebarColumn"] :is(aside[role="complementary"],section[role="region"])[aria-label]::before{content:attr(aria-label);display:block;margin:-12px -16px 10px;padding:3px 8px;font:700 12px/16px "Pixelated MS Sans Serif","MS Sans Serif","Tahoma",sans-serif;color:#ffffff;background:linear-gradient(90deg, ${colors.inverse}, ${TITLE_END})}`,
].join('\n'),
// A newspaper masthead: the theme's title between two rules at the top of the column.
masthead: (title, { colors, fonts }) => `${FRAMED}::before{content:${cssString(title || 'The Timeline')};display:block;order:-1;padding:18px 16px 12px;text-align:center;font:700 34px/1.1 ${fonts.heading};letter-spacing:-.01em;color:${colors.text};border-bottom:4px double ${colors.text};margin:0 16px}`,
@ -152,28 +157,37 @@ ${NOT_CHAT} ${inner} > div > div:first-child{flex:1 1 auto !important}
${NOT_CHAT} ${inner} > div > div:last-child{flex:0 0 auto !important}
/* The same for the groups inside the row (the nav and the Post button sit in full-width wrappers). */
${NOT_CHAT} ${inner} > div > div > div{width:auto !important;flex:0 1 auto !important;min-width:0 !important}
${NOT_CHAT} ${NAV}{flex-direction:row !important;align-items:center !important;flex-wrap:nowrap !important;gap:2px}
${NOT_CHAT} ${NAV}{min-width:0 !important;overflow:hidden}
/* Items share the bar like taskbar buttons: each up to 160px, all shrinking alike, the label ending in an
ellipsis and then only the icon (live 2026-10-02: X shows up to 12 items, more than a row of whole labels). */
${NOT_CHAT} ${NAV} > :is(a,button){width:auto !important;flex:0 1 176px !important;min-width:36px !important;overflow:hidden}
${NOT_CHAT} ${NAV} > :is(a,button) > div{padding:8px 8px !important;gap:0 !important;min-width:0 !important;max-width:100% !important;box-sizing:border-box;justify-content:flex-start !important}
/* The nav's wrapper takes what is left of the row, so the items spread over the whole bar and no dead
space sits between the last item and the account (owner, 1.0.2 at 2000px). */
${NOT_CHAT} ${inner} > div > div:first-child > div:has(> nav[role="navigation"]){flex:1 1 0 !important}
${edge === 'bottom' ? `/* A taskbar: Start (the logo), the quick launch (Post), then the task buttons. */
${NOT_CHAT} ${inner} > div > div:first-child > div:has([data-testid="SideNav_NewTweet_Button"]){order:1}
${NOT_CHAT} ${inner} > div > div:first-child > div:has(> nav[role="navigation"]){order:2}
` : ''}${NOT_CHAT} ${NAV}{flex-direction:row !important;align-items:center !important;flex-wrap:nowrap !important;gap:2px}
${NOT_CHAT} ${NAV}{min-width:0 !important;width:100% !important}
/* Items are taskbar buttons. Whether they carry their labels is measured (themeBar.ts, html[data-wb-bar]):
with labels, equal widths up to 168px and never narrower than the whole label; without, icon buttons with
the label as a tooltip. A label is whole or absent: no ellipsis anywhere in the bar (1.0.2 "H...", "E..."). */
${NOT_CHAT} ${NAV} > :is(a,button){position:relative;width:auto !important;flex:1 1 0 !important;max-width:168px !important;min-width:max-content !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div{padding:8px 8px !important;gap:0 !important;max-width:100% !important;box-sizing:border-box;justify-content:flex-start !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div > div:first-child{flex:none !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div > div:last-child:not(:first-child){display:block !important;flex:0 1 auto !important;margin-left:8px !important;margin-right:0 !important;padding-right:0 !important;min-width:0 !important;overflow:hidden !important;text-overflow:ellipsis !important;white-space:nowrap !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div > div:last-child:not(:first-child){display:block !important;flex:none !important;margin-left:8px !important;margin-right:0 !important;padding-right:0 !important;white-space:nowrap !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div > div:last-child:not(:first-child) span{display:inline !important;white-space:nowrap !important}
/* Icons mode (also before the first measurement, so a label is never seen cut). */
html:not([data-wb-chat]):not([data-wb-bar="labels"]) ${NAV}{justify-content:${edge === 'bottom' ? 'flex-start' : 'center'} !important}
html:not([data-wb-chat]):not([data-wb-bar="labels"]) ${NAV} > :is(a,button){flex:0 0 48px !important;min-width:0 !important}
html:not([data-wb-chat]):not([data-wb-bar="labels"]) ${NAV} > :is(a,button) > div{justify-content:center !important}
html:not([data-wb-chat]):not([data-wb-bar="labels"]) ${NAV} > :is(a,button) > div > div:last-child:not(:first-child){display:none !important}
html:not([data-wb-chat]):not([data-wb-bar="labels"]) ${NAV} > :is(a,button):is(:hover,:focus-visible)::after{content:attr(aria-label);position:absolute;${edge === 'bottom' ? 'bottom' : 'top'}:calc(100% + 6px);left:50%;transform:translateX(-50%);z-index:40;padding:4px 8px;white-space:nowrap;max-width:calc(100vw - 16px);font:12px/16px var(--wb-font-interface, inherit);background-color:${colors.text};color:${colors.bg};pointer-events:none}
${NOT_CHAT} ${RAIL} [data-testid="SideNav_NewTweet_Button"]{margin:0 0 0 8px !important;width:auto !important;min-height:40px !important;padding:0 16px !important}
${NOT_CHAT} ${RAIL} [data-testid="SideNav_NewTweet_Button"]{margin:0 ${edge === 'bottom' ? '0 0 0' : '0 0 8px'} !important;width:auto !important;min-height:40px !important;padding:0 16px !important}
${NOT_CHAT} ${RAIL} [data-testid="SideNav_AccountSwitcher_Button"]{margin:0 !important}
${NOT_CHAT} main[role="main"]{padding-${edge}:56px !important;align-items:center !important}
/* X's floating Grok and Chat buttons (bottom right) ride above a bottom bar instead of over its last items. */
${edge === 'bottom' ? `${NOT_CHAT} :is([data-testid="chat-drawer-root"],[data-testid="GrokDrawer"]){transform:translateY(-56px)}` : ''}
/* Popovers clear the bar: the layer guard (themeGuards.ts) keeps them inside the viewport minus this inset. */
:root{--wb-bar-inset-${edge}:56px}
${NOT_CHAT} ${COLUMN} > div > div:first-child{${edge === 'top' ? 'top:56px !important' : ''}}
}
/* Too narrow for words: the items are icon buttons (a label cut to one letter reads as noise). */
@media (min-width:700px) and (max-width:1199px){
${NOT_CHAT} ${NAV} > :is(a,button){flex:0 1 48px !important}
${NOT_CHAT} ${NAV} > :is(a,button) > div{justify-content:center !important}
/* Only a label is hidden: below 1265px X itself renders the items with their icon alone (live 2026-10-02). */
${NOT_CHAT} ${NAV} > :is(a,button) > div > div:last-child:not(:first-child){display:none !important}
}`;
`;
}