diff --git a/docs/userscript-validation.md b/docs/userscript-validation.md index 6576532..ab982c9 100644 --- a/docs/userscript-validation.md +++ b/docs/userscript-validation.md @@ -94,10 +94,10 @@ primary button already released is canceled without saving. For every resize, project the saved preference into the current viewport while keeping the header reachable. Enlarging the viewport restores the saved -coordinates. The trading panel is 480 px wide and the CMC panel is 500 px wide, -each limited by the viewport. Without a saved preference, one panel uses the +coordinates. Both panels are 384 px wide, limited by the viewport. Without a +saved preference, one panel uses the right edge with a 16 px margin and a 60 px top offset. When both are present, -viewports at least 1028 px wide place them side by side, with CMC to the left of +viewports at least 816 px wide place them side by side, with CMC to the left of trading and a 16 px gap. Smaller viewports split the available height into two scrollable panels with 8 px outer margins and an 8 px vertical gap. These defaults are never persisted. Saved positions remain authoritative, even when a user's @@ -150,6 +150,14 @@ Taker activity uses separate buy and sell volume bars; its numeric field is clea labelled as the buy/sell ratio. Click or focus a chart to inspect its values and dated observations; arrow keys select points and Escape closes the detail. +Compact rows use 4 px vertical padding and 26 px charts. The footer states the +shared five-minute sampling period and distinguishes settled funding history; +individual observation counts appear in the opened history detail. The funding +row also retains its settled-history caption, last settled value, current rate, +confirmed interval, and countdown. The composite heading opens the voting rules +with native `details`/`summary`; its current open state survives display refreshes. +Body text remains 13 px on desktop and annotations remain at least 11 px. + All Binance requests use the existing `https://www.binance.com` origin: | Display | Endpoint | Source fields | @@ -237,6 +245,17 @@ denominators do not produce a ratio. Null, empty, or invalid provider numbers st unavailable, while genuine zero stays zero. Treasury holdings retain their token unit and cannot become a holder-address count. +Compact rows keep their brief interpretation visible. Secondary notes are in the +interpretation button's tooltip and expanded explanation, and token units share +the value line when space allows. Automatic table layout reserves the complete +width of a small token price or 24-hour change before wrapping prose; browser +checks compare text ranges against their own cells as well as overall overflow. +Body text remains 12 px on desktop and +annotations remain at least 11 px. The generated-panel browser scenarios require +both default panels, all metric rows, and both footers to fit a 1366-by-768 desktop +without scrolling in Chinese and English. Narrow screens retain separate scroll +areas and no horizontal overflow. + Interpretations describe what the value supports. The volume ratio's 50% threshold is explicitly a descriptive convention, not a statistical anomaly or trading signal. Valuation comparisons need peers, volume changes need history, DEX @@ -251,6 +270,23 @@ embeds unchanged generated artifacts and labelled example data. It supports both languages, light/dark themes, and each panel separately, without external network requests. It is not live Binance or Tampermonkey evidence. +### Compact layout validation (2026-10-09) + +Trading-data `1.2.1` and CMC-data `0.2.1` passed the full 2528-test Node suite, +all 39 panel browser scenarios, both builds, generated syntax checks, test lint, +metadata/install URL checks, and independent source review. Six new browser +scenarios verify complete default panels on a 1366-by-768 screen and precise +small prices staying inside their value cells at 1366/320 px in both languages. +Resize scenarios wait for browser rendering frames before reading geometry. + +At the same 1600-by-1056 preview viewport, the Chinese trading panel decreased +from approximately 897 to 500 px high and CMC from 779 to 567 px. English heights +decreased from 913 to 534 px and 826 to 618 px. Both widths are now 384 px, with +unchanged 13/12 px desktop body text and a minimum 11 px annotation size. The +four preview combinations again have no panel overlap, horizontal overflow, +page errors, or external HTTP requests. These are offline rendering results; +the installed Tampermonkey scripts and live Binance pages were not operated. + ### Data-panel implementation validation (2026-10-09) The trading-data `1.2.0` and CMC-data `0.2.0` implementations passed independent diff --git a/e2e/binance-orderbook/helpers/data-panels-host.js b/e2e/binance-orderbook/helpers/data-panels-host.js index 6b13977..e0c0f86 100644 --- a/e2e/binance-orderbook/helpers/data-panels-host.js +++ b/e2e/binance-orderbook/helpers/data-panels-host.js @@ -8,11 +8,11 @@ export const DATA_PANEL_NOW = Date.UTC(2026, 9, 9, 0, 0, 0); export const POSITION_AUDIT_KEY = '__data_panel_fixture_position_writes__'; export const DATA_PANELS = Object.freeze([ { - name: 'trading', id: 'jh-binance-trading-data-panel', width: 480, + name: 'trading', id: 'jh-binance-trading-data-panel', width: 384, key: 'jh_binance_trading_data_pos', artifact: 'binance-trading-data', }, { - name: 'cmc', id: 'jh-binance-cmc-data-panel', width: 500, + name: 'cmc', id: 'jh-binance-cmc-data-panel', width: 384, key: 'jh_binance_cmc_data_pos', artifact: 'binance-coinmarketcap-data', }, ]); diff --git a/e2e/binance-orderbook/specs/data-panel-position.pw.js b/e2e/binance-orderbook/specs/data-panel-position.pw.js index 9db5eaa..b1c3a80 100644 --- a/e2e/binance-orderbook/specs/data-panel-position.pw.js +++ b/e2e/binance-orderbook/specs/data-panel-position.pw.js @@ -119,7 +119,7 @@ test('user restores both separate panel preferences after a smaller viewport', a await resize(page, SMALL); // Then only the displayed positions clamp, leaving each original preference unchanged. - await assertPositions(page, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } }); + await assertPositions(page, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } }); expect(await readPositions(page)).toEqual(INITIAL); expect(await readWrites(page)).toEqual([]); await page.screenshot({ path: testInfo.outputPath('small-clamped.png') }); @@ -145,7 +145,7 @@ test('user keeps large-screen preferences when a small tab opens reloads and clo await fixture.open(small, { viewport: SMALL }); // Then the small tab displays reachable headers while both tabs retain the original preferences. - await assertPositions(small, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } }); + await assertPositions(small, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } }); expect(await readPositions(page)).toEqual(INITIAL); expect(await readWrites(page)).toEqual([]); @@ -154,7 +154,7 @@ test('user keeps large-screen preferences when a small tab opens reloads and clo await installPanels(small); // Then reload and initialization leave the shared preferences untouched. - await assertPositions(small, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } }); + await assertPositions(small, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } }); expect(await readPositions(page)).toEqual(INITIAL); expect(await readWrites(page)).toEqual([]); @@ -304,7 +304,7 @@ for (const panel of PANELS) { await nextFrames(page); // Then the viewport adjustment is temporary and cannot become a completed drag preference. - await assertPositions(page, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } }); + await assertPositions(page, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } }); expect(await readPositions(page)).toEqual(INITIAL); expect(await readWrites(page)).toEqual([]); @@ -359,14 +359,14 @@ test('user keeps responsive default panel positions without creating a saved pre await resize(page, SMALL); const trading = await page.locator(`#${PANELS[0].id}`).boundingBox(); const cmc = await page.locator(`#${PANELS[1].id}`).boundingBox(); - expect(trading).toMatchObject({ x: 144, y: 8, width: 480 }); - expect(cmc).toMatchObject({ x: 124, width: 500 }); + expect(trading).toMatchObject({ x: 240, y: 8, width: 384 }); + expect(cmc).toMatchObject({ x: 240, width: 384 }); expect(cmc.y - trading.y - trading.height).toBeCloseTo(8, 1); expect(cmc.y + cmc.height).toBeLessThanOrEqual(SMALL.height); await resize(page, LARGE); // Then the original right inset and separate desktop columns return without new position keys. - await assertPositions(page, { trading: { left: 1104, top: 60 }, cmc: { left: 588, top: 60 } }); + await assertPositions(page, { trading: { left: 1200, top: 60 }, cmc: { left: 800, top: 60 } }); expect(await readPositions(page)).toEqual({ trading: null, cmc: null }); expect(await readWrites(page)).toEqual([]); diff --git a/e2e/binance-orderbook/specs/data-panels-presentation.pw.js b/e2e/binance-orderbook/specs/data-panels-presentation.pw.js index 2ac2f8d..116f277 100644 --- a/e2e/binance-orderbook/specs/data-panels-presentation.pw.js +++ b/e2e/binance-orderbook/specs/data-panels-presentation.pw.js @@ -1,5 +1,5 @@ import { test, expect } from '../test.js'; -import { tradingDataset } from '../../../test/helpers/data-media-migration-host.js'; +import { cmcDetail, tradingDataset } from '../../../test/helpers/data-media-migration-host.js'; import { createDataPanelsFixture, installDataPanels, DATA_PANEL_NOW, DATA_PANELS, POSITION_AUDIT_KEY, } from '../helpers/data-panels-host.js'; @@ -146,6 +146,68 @@ test('user sees the calibrated countdown advance while historical nodes and upda }); for (const locale of ['zh-CN', 'en']) { + for (const width of [1366, 320]) { + test(`user reads a tiny CMC price and ${locale} changes inside their own column at ${width}px`, async ({ page, context }) => { + // Given a token price needs all fourteen fractional digits to remain distinguishable from zero + const detail = cmcDetail(); + detail.statistics.price = 0.00000000001234; + const fixture = await createDataPanelsFixture(context, { detail }); + + // When the complete CMC panel renders at the requested width + await fixture.open(page, { locale, panels: ['cmc'], viewport: { width, height: 768 } }); + const cmc = page.locator('#jh-binance-cmc-data-panel'); + + // Then the precise price and changes remain readable without crossing into another column + await expect(cmc.locator('[data-metric="price"] .cmc-number')).toHaveText('$0.00000000001234'); + const crossing = await cmc.evaluate(panel => [...panel.querySelectorAll('.cmc-number, .cmc-change, .cmc-unit')].flatMap(node => { + const range = document.createRange(); + range.selectNodeContents(node); + const text = range.getBoundingClientRect(); + const cell = node.closest('td').getBoundingClientRect(); + return text.left < cell.left || text.right > cell.right + ? [{ text: node.textContent, left: text.left - cell.left, right: text.right - cell.right }] + : []; + })); + expect(crossing).toEqual([]); + const body = await cmc.locator('[id$="-body"]').evaluate(element => ({ width: element.clientWidth, scrollWidth: element.scrollWidth })); + expect(body.scrollWidth).toBeLessThanOrEqual(body.width); + expect(fixture.errors).toEqual([]); + }); + } + + test(`user reads every ${locale} data row on a laptop without scrolling and can open the signal rules`, async ({ page, context }) => { + // Given both panels have complete data and no saved position on a laptop-sized screen + const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() }); + + // When the generated scripts render their default expanded panels + await fixture.open(page, { locale, viewport: { width: 1366, height: 768 } }); + const trading = page.locator('#jh-binance-trading-data-panel'); + await expect(trading.locator('[data-role="current-funding"]')).toHaveText('0.00378%'); + + // Then all rows and both footers fit without vertical or horizontal scrolling + await expect(trading.locator('tr[data-metric]')).toHaveCount(8); + await expect(page.locator('#jh-binance-cmc-data-panel tr[data-metric]')).toHaveCount(12); + for (const { id } of DATA_PANELS) { + const body = page.locator(`#${id}-body`); + const dimensions = await body.evaluate(element => ({ + height: element.clientHeight, scrollHeight: element.scrollHeight, + width: element.clientWidth, scrollWidth: element.scrollWidth, + })); + expect(dimensions.scrollHeight).toBeLessThanOrEqual(dimensions.height); + expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.width); + await expect(page.locator(`#${id}-footer`)).toBeInViewport({ ratio: 1 }); + } + await expect(trading.locator('.td-method-note')).toBeHidden(); + + // When the user asks for the composite signal methodology + await trading.locator('.td-composite-line').click(); + + // Then the settled-rate voting rule is available on demand + await expect(trading.locator('.td-method-note')).toBeVisible(); + await expect(trading.locator('.td-method-note')).toContainText(locale === 'zh-CN' ? '资金费率投票使用最新已结算值' : 'the funding vote uses the latest settled rate'); + expect(fixture.errors).toEqual([]); + }); + test(`user reads ${locale} panels in both themes on a narrow screen without tiny text or horizontal overflow`, async ({ page, context }) => { // Given both generated panels are rendered on a narrow viewport in the light theme const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() }); @@ -155,6 +217,7 @@ for (const locale of ['zh-CN', 'en']) { // When the user changes to the dark theme and narrows the viewport further await page.emulateMedia({ colorScheme: 'dark' }); await page.setViewportSize({ width: 320, height: 680 }); + await completedLayoutFrames(page); // Then theme colors change while every visible annotation stays readable and within its panel width const readings = await page.evaluate(ids => ids.map(id => { @@ -206,11 +269,12 @@ for (const installation of [['trading', 'cmc'], ['cmc', 'trading']]) { await expect(cmc.locator('#jh-binance-cmc-data-panel-footer a')).toHaveText('CMC data-api'); // Then each desktop panel has its own column regardless of installation order - expect(await trading.boundingBox()).toMatchObject({ x: 1104, y: 60, width: 480 }); - expect(await cmc.boundingBox()).toMatchObject({ x: 588, y: 60, width: 500 }); + expect(await trading.boundingBox()).toMatchObject({ x: 1200, y: 60, width: 384 }); + expect(await cmc.boundingBox()).toMatchObject({ x: 800, y: 60, width: 384 }); // When the viewport becomes too narrow for adjacent columns await page.setViewportSize({ width: 320, height: 680 }); + await completedLayoutFrames(page); // Then both headers stay reachable above separate scrolling bodies with an eight-pixel gap const readings = await page.evaluate(ids => ids.map(id => { @@ -262,6 +326,7 @@ for (const panel of DATA_PANELS) { // When the user closes one panel and resizes the surviving panel's viewport await page.locator(`#${panel.id}-close`).click(); await page.setViewportSize({ width: 1360, height: 900 }); + await completedLayoutFrames(page); // Then the remaining panel projects its saved preference and closing writes no coordinates await expect(page.locator(`#${panel.id}`)).toBeHidden(); @@ -298,7 +363,7 @@ test('user releases a stationary CMC header after the trading panel mounts witho // When trading mounts and automatically moves the CMC panel into the adjacent column await installDataPanels(page, { panels: ['trading'] }); - await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(588); + await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(800); await page.mouse.up(); await completedLayoutFrames(page); @@ -318,7 +383,7 @@ test('user releases a stationary CMC header after its peer closes without saving // When keyboard activation closes trading while the mouse remains pressed on CMC await page.locator('#jh-binance-trading-data-panel-close').focus(); await page.keyboard.press('Enter'); - await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(1084); + await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(1200); await page.mouse.up(); await completedLayoutFrames(page); @@ -335,7 +400,7 @@ test('user keeps the one saved panel coordinate when the other panel uses an aut // When the desktop shrinks and then returns to its original size await page.setViewportSize({ width: 1000, height: 760 }); - await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(500); + await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(616); await page.setViewportSize({ width: 1600, height: 1200 }); await completedLayoutFrames(page); @@ -353,7 +418,7 @@ test('user saves a collapsed panel lower once and expands a body that fits the r await expect(panel.locator('[data-role="current-funding"]')).toHaveText('0.00378%'); await expect(panel.locator('[data-role="funding-period"]')).toHaveText('当前 · 4小时'); const initial = await panel.boundingBox(); - const target = { left: 80, top: 600 }; + const target = { left: 80, top: 900 }; await page.locator('#jh-binance-trading-data-panel-collapse').click(); await expect(page.locator('#jh-binance-trading-data-panel-body')).toBeHidden(); await page.mouse.move(initial.x + 14, initial.y + 14); diff --git a/scripts/binance-coinmarketcap-data.user.js b/scripts/binance-coinmarketcap-data.user.js index 4cd33a5..3e7db63 100644 --- a/scripts/binance-coinmarketcap-data.user.js +++ b/scripts/binance-coinmarketcap-data.user.js @@ -3,7 +3,7 @@ // @namespace binance.coinmarketcap.data // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 0.2.0 +// @version 0.2.1 // @author jackhai9 // @description Show localized CoinMarketCap valuation, supply, and metric interpretations on Binance futures pages // @match https://www.binance.com/*/futures/* @@ -578,7 +578,7 @@ box-shadow: 0 6px 24px #00000020; font-family: BinancePlex, system-ui, -apple-system, sans-serif; font-size: 12px; - line-height: 1.4; + line-height: 1.35; text-align: left; overflow: hidden; user-select: none; @@ -590,7 +590,7 @@ align-items: center; justify-content: space-between; gap: 6px; - padding: 8px 10px; + padding: 5px 8px; flex: 0 0 auto; cursor: move; background: var(--cmc-head); @@ -601,47 +601,46 @@ ${scope} #${panelId}-symbol { color: var(--cmc-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ${scope} .cmc-controls { display: flex; gap: 2px; flex: 0 0 auto; } ${scope} button { font: inherit; cursor: pointer; color: inherit; background: transparent; border: 0; } - ${scope} .cmc-controls button { min-width: 25px; min-height: 26px; font-size: 16px; padding: 2px 4px; color: var(--cmc-muted); } + ${scope} .cmc-controls button { min-width: 24px; min-height: 24px; font-size: 16px; padding: 2px 4px; color: var(--cmc-muted); } ${scope} button:focus-visible, ${scope} a:focus-visible { outline: 2px solid var(--cmc-accent); outline-offset: 2px; border-radius: 3px; } ${scope} #${panelId}-body { min-height: 0; overflow: auto; overscroll-behavior: contain; } - ${scope} table { width: 100%; table-layout: fixed; border-collapse: collapse; } - ${scope} .cmc-name-col { width: 31%; } + /* Reserve space for complete prices and changes before wrapping the surrounding prose. */ + ${scope} table { width: 100%; table-layout: auto; border-collapse: collapse; } + ${scope} .cmc-name-col { width: 33%; } ${scope} .cmc-value-col { width: 26%; } - ${scope} .cmc-reading-col { width: 43%; } + ${scope} .cmc-reading-col { width: 41%; } ${scope} thead { position: sticky; top: 0; z-index: 1; background: var(--cmc-head); } - ${scope} thead th { color: var(--cmc-muted); font-size: 11px; font-weight: 500; padding: 7px 10px; text-align: left; } + ${scope} thead th { color: var(--cmc-muted); font-size: 11px; font-weight: 500; padding: 4px 6px; text-align: left; } ${scope} thead th:nth-child(2) { text-align: right; } - ${scope} tbody th, ${scope} tbody td { padding: 9px 10px; border-top: 1px solid var(--cmc-border); vertical-align: middle; overflow-wrap: anywhere; } + ${scope} tbody th, ${scope} tbody td { padding: 4px 6px; border-top: 1px solid var(--cmc-border); vertical-align: middle; overflow-wrap: anywhere; } ${scope} .cmc-name { text-align: left; font-weight: 500; } - ${scope} small { display: block; margin-top: 3px; font-size: 11px; line-height: 1.4; color: var(--cmc-muted); } + ${scope} small { display: block; margin-top: 1px; font-size: 11px; line-height: 1.35; color: var(--cmc-muted); } ${scope} .cmc-value { text-align: right; font-variant-numeric: tabular-nums; } - ${scope} .cmc-number { display: block; font-weight: 650; white-space: nowrap; } + ${scope} .cmc-number { display: inline-block; font-weight: 650; white-space: nowrap; } + ${scope} .cmc-unit { display: inline-block; margin: 0 0 0 4px; } ${scope} .cmc-change { font-size: 11px; white-space: nowrap; } ${scope} .cmc-change[data-tone="positive"], ${scope} [data-tone="positive"] .cmc-reading-text { color: var(--cmc-buy); } ${scope} .cmc-change[data-tone="negative"], ${scope} [data-tone="negative"] .cmc-reading-text { color: var(--cmc-sell); } ${scope} .cmc-change[data-tone="neutral"] { color: var(--cmc-muted); } ${scope} .cmc-key { background: color-mix(in srgb, var(--color-PrimaryYellow, #f0b90b) 7%, var(--cmc-bg)); } ${scope} .cmc-group th, ${scope} .cmc-group td { border-top-width: 2px; } - ${scope} .cmc-reading-button { display: flex; align-items: flex-start; justify-content: space-between; gap: 5px; width: 100%; padding: 0; line-height: inherit; text-align: left; } + ${scope} .cmc-reading-button { display: flex; align-items: center; justify-content: space-between; gap: 3px; width: 100%; min-height: 24px; padding: 0; line-height: inherit; text-align: left; } ${scope} .cmc-reading-text { font-weight: 500; } ${scope} [data-tone="missing"] .cmc-reading-text { color: var(--cmc-muted); } ${scope} [data-tone="active"] .cmc-reading-text { color: var(--cmc-accent); } ${scope} .cmc-info { color: var(--cmc-muted); flex: 0 0 auto; font-size: 11px; } - ${scope} .cmc-explanation td { padding: 10px 12px; color: var(--cmc-muted); background: var(--cmc-head); font-size: 11px; line-height: 1.6; user-select: text; } + ${scope} .cmc-explanation td { padding: 6px 8px; color: var(--cmc-muted); background: var(--cmc-head); font-size: 11px; line-height: 1.5; user-select: text; } ${scope} .cmc-explanation strong { color: var(--cmc-text); margin-right: 8px; font-weight: 600; } ${scope} .cmc-status { padding: 10px 0; color: var(--cmc-muted); } ${scope} .cmc-error-title { font-weight: 600; color: var(--cmc-sell); margin-bottom: 4px; } - ${scope} #${panelId}-footer { padding: 8px 10px; color: var(--cmc-muted); font-size: 11px; border-top: 1px solid var(--cmc-border); } - ${scope} .cmc-source-line { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 5px 10px; } + ${scope} #${panelId}-footer { padding: 5px 8px; color: var(--cmc-muted); font-size: 11px; border-top: 1px solid var(--cmc-border); } + ${scope} .cmc-source-line { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 3px 8px; } ${scope} #${panelId}-footer a { color: var(--cmc-accent); text-decoration: none; } ${scope} .cmc-times { font-variant-numeric: tabular-nums; } @media (max-width: 540px) { ${scope} { font-size: 11px; } - ${scope} tbody th, ${scope} tbody td { padding: 8px 6px; } - ${scope} thead th { padding: 7px 6px; } - ${scope} .cmc-name-col { width: 31%; } - ${scope} .cmc-value-col { width: 27%; } - ${scope} .cmc-reading-col { width: 42%; } + ${scope} tbody th, ${scope} tbody td { padding: 4px; } + ${scope} thead th { padding: 4px; } ${scope} .cmc-info { display: none; } } `; @@ -649,11 +648,12 @@ // src/shared/data-panel-layout.js var DATA_PANEL_LAYOUT_EVENT = "jh-data-panels-layout-change"; + var DATA_PANEL_WIDTH = 384; var PANEL_IDS = Object.freeze({ trading: "jh-binance-trading-data-panel", cmc: "jh-binance-cmc-data-panel" }); - var TWO_COLUMN_WIDTH = 480 + 500 + 48; + var TWO_COLUMN_WIDTH = DATA_PANEL_WIDTH * 2 + 48; function hasVisibleDataPanelPeer(document2, kind) { const peer = document2.getElementById(PANEL_IDS[kind === "trading" ? "cmc" : "trading"]); return peer !== null && peer.style.display !== "none"; @@ -667,7 +667,7 @@ const sectionHeight = Math.max(48, Math.floor((viewportHeight - 24) / 2)); const targetTop = stacked ? kind === "trading" ? 8 : 16 + sectionHeight : 60; const top = Math.max(0, Math.min(targetTop, viewportHeight - 48)); - const peerWidth = hasPeer && !stacked && kind === "cmc" ? 480 + 16 : 0; + const peerWidth = hasPeer && !stacked && kind === "cmc" ? DATA_PANEL_WIDTH + 16 : 0; const targetLeft = viewportWidth - panelWidth - 16 - peerWidth; const left = Math.max(0, Math.min(Math.max(stacked ? 8 : 16, targetLeft), viewportWidth - panelWidth)); const availableHeight = Math.max(48, viewportHeight - top - 8); @@ -683,7 +683,6 @@ const PANEL_ID = "jh-binance-cmc-data-panel"; const STORAGE_POS_KEY = "jh_binance_cmc_data_pos"; const STORAGE_COLLAPSED_KEY = "jh_binance_cmc_data_collapsed"; - const PANEL_WIDTH = 500; const REFRESH_MS = 30 * 1e3; const ROUTE_WATCHDOG_MS = 5e3; const CMC_MAP_API = "https://api.coinmarketcap.com/data-api/v1/cryptocurrency/map"; @@ -931,7 +930,7 @@ } panel = document.createElement("div"); panel.id = PANEL_ID; - panel.style.width = PANEL_WIDTH + "px"; + panel.style.width = DATA_PANEL_WIDTH + "px"; const collapsed = loadCollapsed(); panel.innerHTML = [ "