From f4225976e13ea2535697a1cf30b493123ccd5feb Mon Sep 17 00:00:00 2001 From: anderdc Date: Mon, 13 Jul 2026 11:24:10 -0500 Subject: [PATCH 1/4] docs(agents): treat mainnet as the live network MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reframe the agent quickstart around the live network (mainnet, netuid 7) instead of "internal bring-up". It now reads as what a user agent actually does to interact with allways for real: - Wallet callout leads with mainnet as the live network (real SOL/BTC/TAO); testnet is presented as a free mirror for dry-runs. - CLI-config section leads with the mainnet block; testnet is the dry-run block. - Testnet section framed as "rehearse before spending real assets". - BTC swap example uses a mainnet `bc1q…` source by default. public/llms.txt kept in sync with AgentMarkdown.ts. --- public/llms.txt | 24 ++++++++++++------------ src/components/agents/AgentMarkdown.ts | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index 4cfe539..2303e27 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -14,11 +14,11 @@ collateral and quote exchange rates. Validators verify both legs of each swap. The contract slashes collateral (in SOL) on failure and pays the taker. No custodian, no wrapped asset, no bridge token. -> **Where to point your wallets right now:** **testnet** (Bittensor netuid 19 + -> Solana devnet) is the live, usable environment — dry-run here. **Mainnet** -> (netuid 7) is in internal bring-up. Both use the same Solana program -> `6JVBEj5w27J2SVjERmv2c7wXgFee9nSSBKUJevHehyBD` — the program id is the same -> across clusters; only the network differs. Setup blocks for both are below. +> **Where to point your wallets:** **Mainnet** (Bittensor netuid 7 + Solana +> mainnet) is the live network — real SOL, BTC, and TAO. **Testnet** (netuid 19 + +> Solana devnet) mirrors it with free funds for dry-runs. Both use the same Solana +> program `6JVBEj5w27J2SVjERmv2c7wXgFee9nSSBKUJevHehyBD` — the program id is the +> same across clusters; only the network differs. Setup blocks for both are below. ## Resources (skim these first) @@ -140,15 +140,15 @@ Optionally set a WIF via env (shell env, a project `.env` walked up from CWD, or ### 5. Configure the CLI -**Testnet (live — netuid 19):** +**Mainnet (live — netuid 7):** - alw config set env testnet # bittensor test + solana devnet + btc testnet4 + netuid 19 + alw config set env mainnet # bittensor finney + solana mainnet + btc mainnet + netuid 7 alw config set wallet alw config set hotkey -**Mainnet (in bring-up — netuid 7):** +**Testnet (free dry-runs — netuid 19):** - alw config set env mainnet # bittensor finney + solana mainnet + btc mainnet + netuid 7 + alw config set env testnet # bittensor test + solana devnet + btc testnet4 + netuid 19 `env` resolves the Bittensor network, Solana cluster, BTC network, and netuid together — no program id or RPC to copy (the program id is baked in, same across clusters). Config persists at `~/.allways/config.json`. @@ -179,10 +179,10 @@ The "You receive" line in a quote is the post-fee amount that lands in your dest --from btc --to sol \ --amount 0.001 \ --receive-address \ - --from-address tb1q... \ + --from-address bc1q... \ --yes -(`--from-address` is your source address on the source chain: a `tb1q…` on testnet4, `bc1q…` on mainnet.) +(`--from-address` is your source address on the source chain: a `bc1q…` on mainnet, `tb1q…` on testnet4.) **SOL → TAO** (send SOL, receive TAO). `--receive-address` here is a TAO ss58: @@ -274,7 +274,7 @@ Base URL: `https://api.all-ways.io` (testnet: `https://test-api.all-ways.io`). L ## Testnet -Testnet (Bittensor netuid 19 + Solana devnet) is the recommended place to dry-run. Configure: +Testnet (Bittensor netuid 19 + Solana devnet) mirrors mainnet with free funds — rehearse a flow here before spending real assets. Configure: alw config set env testnet alw config set wallet diff --git a/src/components/agents/AgentMarkdown.ts b/src/components/agents/AgentMarkdown.ts index 4287d4c..752c027 100644 --- a/src/components/agents/AgentMarkdown.ts +++ b/src/components/agents/AgentMarkdown.ts @@ -17,11 +17,11 @@ collateral and quote exchange rates. Validators verify both legs of each swap. The contract slashes collateral (in SOL) on failure and pays the taker. No custodian, no wrapped asset, no bridge token. -> **Where to point your wallets right now:** **testnet** (Bittensor netuid 19 + -> Solana devnet) is the live, usable environment — dry-run here. **Mainnet** -> (netuid 7) is in internal bring-up. Both use the same Solana program -> \`6JVBEj5w27J2SVjERmv2c7wXgFee9nSSBKUJevHehyBD\` — the program id is the same -> across clusters; only the network differs. Setup blocks for both are below. +> **Where to point your wallets:** **Mainnet** (Bittensor netuid 7 + Solana +> mainnet) is the live network — real SOL, BTC, and TAO. **Testnet** (netuid 19 + +> Solana devnet) mirrors it with free funds for dry-runs. Both use the same Solana +> program \`6JVBEj5w27J2SVjERmv2c7wXgFee9nSSBKUJevHehyBD\` — the program id is the +> same across clusters; only the network differs. Setup blocks for both are below. ## Resources (skim these first) @@ -143,15 +143,15 @@ Optionally set a WIF via env (shell env, a project \`.env\` walked up from CWD, ### 5. Configure the CLI -**Testnet (live — netuid 19):** +**Mainnet (live — netuid 7):** - alw config set env testnet # bittensor test + solana devnet + btc testnet4 + netuid 19 + alw config set env mainnet # bittensor finney + solana mainnet + btc mainnet + netuid 7 alw config set wallet alw config set hotkey -**Mainnet (in bring-up — netuid 7):** +**Testnet (free dry-runs — netuid 19):** - alw config set env mainnet # bittensor finney + solana mainnet + btc mainnet + netuid 7 + alw config set env testnet # bittensor test + solana devnet + btc testnet4 + netuid 19 \`env\` resolves the Bittensor network, Solana cluster, BTC network, and netuid together — no program id or RPC to copy (the program id is baked in, same across clusters). Config persists at \`~/.allways/config.json\`. @@ -182,10 +182,10 @@ The "You receive" line in a quote is the post-fee amount that lands in your dest --from btc --to sol \\ --amount 0.001 \\ --receive-address \\ - --from-address tb1q... \\ + --from-address bc1q... \\ --yes -(\`--from-address\` is your source address on the source chain: a \`tb1q…\` on testnet4, \`bc1q…\` on mainnet.) +(\`--from-address\` is your source address on the source chain: a \`bc1q…\` on mainnet, \`tb1q…\` on testnet4.) **SOL → TAO** (send SOL, receive TAO). \`--receive-address\` here is a TAO ss58: @@ -277,7 +277,7 @@ Base URL: \`https://api.all-ways.io\` (testnet: \`https://test-api.all-ways.io\` ## Testnet -Testnet (Bittensor netuid 19 + Solana devnet) is the recommended place to dry-run. Configure: +Testnet (Bittensor netuid 19 + Solana devnet) mirrors mainnet with free funds — rehearse a flow here before spending real assets. Configure: alw config set env testnet alw config set wallet From 2ba49cc80ea28b41b12e91329e5fe9ffad1924bb Mon Sep 17 00:00:00 2001 From: anderdc Date: Mon, 13 Jul 2026 11:30:20 -0500 Subject: [PATCH 2/4] =?UTF-8?q?docs(agents):=20accuracy=20pass=20=E2=80=94?= =?UTF-8?q?=20stop=20documenting=20not-yet-wired=20CLI=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final scan against the actual CLI (test branch, what pipx installs) turned up two commands the doc presented as working that print "not yet available from the CLI": - `alw claim` — the TimedOut 1.1× SOL payout is automatic; reframed as such, with the manual claim noted as coming to the CLI (removed the "run alw claim" steps). - `alw swap resume-reservation` — reframed interrupted-flow recovery to the real path: check `alw view reservation`, finish it if live, else re-run `alw swap now`. - Cheat-sheet rows for both annotated as not-yet-wired. Everything else verified accurate: all `alw view` subcommands + `--watch` exist, SwapStatus enum (Active/Fulfilled/Completed/TimedOut) matches, 1.1× collateral = COLLATERAL_REQUIREMENT_BPS 11_000, and both mainnet and testnet APIs return 200. llms.txt kept in sync. --- public/llms.txt | 18 ++++++++++-------- src/components/agents/AgentMarkdown.ts | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index 2303e27..82efcf3 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -70,7 +70,7 @@ custodian, no wrapped asset, no bridge token. 5. **Relay.** `alw swap post-tx ` immediately. Validators verify the deposit (sender, recipient, amount, freshness), submit the claim, and vote to initiate. Status: `PendingAttestation → Active`. 6. **Miner fulfils.** The miner sends 99% of the destination amount to your receive address and marks fulfilled. Status: `Active → Fulfilled`. 7. **Validators confirm.** Both legs verified → vote confirm. Contract skims the 1% fee from miner collateral. Status: `Fulfilled → Completed`. -8. **Timeout / refund.** If the miner doesn't deliver in time, validators vote `TimedOut`; the contract slashes 1.1× collateral to the taker in SOL. If the auto-payout fails, `alw claim ` releases it. +8. **Timeout / refund.** If the miner doesn't deliver in time, validators vote `TimedOut` and the contract pays 1.1× collateral to the taker's pinned SOL identity — **automatically, no action needed**. (A manual `alw claim` fallback for a failed auto-payout is coming to the CLI.) Throughout, poll live state — pre-send via `alw view reservation`, post-initiate via `alw view swap --watch` or the API. @@ -208,12 +208,14 @@ In every case `alw swap now` only **reserves** — once it prints the miner's ad After it returns a swap key: alw view swap --watch # live timeline until Completed or TimedOut - alw claim # if TimedOut, claim the SOL slash -If interrupted before the send: +On `TimedOut`, the 1.1× SOL slash pays your pinned identity automatically — no action needed. - alw view reservation - alw swap resume-reservation [--from-tx-hash ] [--yes] +If interrupted after the reserve but before the send, check for a still-live reservation and finish it: + + alw view reservation # still live? send the source funds to the miner, then alw swap post-tx + +If the reservation has lapsed, just re-run `alw swap now` (you only forfeit the small reservation fee). ### Interactive @@ -235,8 +237,8 @@ If interrupted before the send: | `alw swap now [...flags]` | Run a swap | | `alw swap quote --from --to --amount ` | Preview rate + receive amount | | `alw swap post-tx ` | Relay your source tx to validators | -| `alw swap resume-reservation [...]` | Resume an interrupted flow | -| `alw claim [-y]` | Claim a SOL slash from a TimedOut swap | +| `alw swap resume-reservation [...]` | Resume an interrupted flow — *not yet wired in the CLI; re-run `alw swap now` instead* | +| `alw claim [-y]` | Claim a SOL slash — *not yet wired in the CLI; the TimedOut payout is automatic* | Miner-only commands (`alw miner …`, `alw collateral …`) and admin commands are documented at https://docs.all-ways.io/cli. @@ -270,7 +272,7 @@ Base URL: `https://api.all-ways.io` (testnet: `https://test-api.all-ways.io`). L - **`BTC_MODE=lightweight requires BTC_PRIVATE_KEY`** — set the WIF for auto-send, or accept the manual flow (`alw swap post-tx ` after sending). - **Reservation expired before send** — you only forfeit the small reservation fee. Start a new swap. Live TTL via `alw view config`. - **BTC tx stuck unconfirmed → reservation timed out → BTC sent but no swap** — fee too low; the tx never confirmed inside the extension budget. Prevention: let the CLI auto-estimate `--btc-fee-rate`, or check next-block tiers. If stuck pre-confirmation, RBF up immediately — recovery is only possible while the tx is in mempool. -- **Miner timed out — where's my refund?** — slashed collateral pays your pinned identity in SOL automatically; if the transfer fails, `alw claim ` releases it. +- **Miner timed out — where's my refund?** — the 1.1× slash pays your pinned SOL identity automatically; no action needed. (A manual `alw claim` fallback for the rare failed auto-payout is coming to the CLI.) ## Testnet diff --git a/src/components/agents/AgentMarkdown.ts b/src/components/agents/AgentMarkdown.ts index 752c027..8489bbd 100644 --- a/src/components/agents/AgentMarkdown.ts +++ b/src/components/agents/AgentMarkdown.ts @@ -73,7 +73,7 @@ custodian, no wrapped asset, no bridge token. 5. **Relay.** \`alw swap post-tx \` immediately. Validators verify the deposit (sender, recipient, amount, freshness), submit the claim, and vote to initiate. Status: \`PendingAttestation → Active\`. 6. **Miner fulfils.** The miner sends 99% of the destination amount to your receive address and marks fulfilled. Status: \`Active → Fulfilled\`. 7. **Validators confirm.** Both legs verified → vote confirm. Contract skims the 1% fee from miner collateral. Status: \`Fulfilled → Completed\`. -8. **Timeout / refund.** If the miner doesn't deliver in time, validators vote \`TimedOut\`; the contract slashes 1.1× collateral to the taker in SOL. If the auto-payout fails, \`alw claim \` releases it. +8. **Timeout / refund.** If the miner doesn't deliver in time, validators vote \`TimedOut\` and the contract pays 1.1× collateral to the taker's pinned SOL identity — **automatically, no action needed**. (A manual \`alw claim\` fallback for a failed auto-payout is coming to the CLI.) Throughout, poll live state — pre-send via \`alw view reservation\`, post-initiate via \`alw view swap --watch\` or the API. @@ -211,12 +211,14 @@ In every case \`alw swap now\` only **reserves** — once it prints the miner's After it returns a swap key: alw view swap --watch # live timeline until Completed or TimedOut - alw claim # if TimedOut, claim the SOL slash -If interrupted before the send: +On \`TimedOut\`, the 1.1× SOL slash pays your pinned identity automatically — no action needed. - alw view reservation - alw swap resume-reservation [--from-tx-hash ] [--yes] +If interrupted after the reserve but before the send, check for a still-live reservation and finish it: + + alw view reservation # still live? send the source funds to the miner, then alw swap post-tx + +If the reservation has lapsed, just re-run \`alw swap now\` (you only forfeit the small reservation fee). ### Interactive @@ -238,8 +240,8 @@ If interrupted before the send: | \`alw swap now [...flags]\` | Run a swap | | \`alw swap quote --from --to --amount \` | Preview rate + receive amount | | \`alw swap post-tx \` | Relay your source tx to validators | -| \`alw swap resume-reservation [...]\` | Resume an interrupted flow | -| \`alw claim [-y]\` | Claim a SOL slash from a TimedOut swap | +| \`alw swap resume-reservation [...]\` | Resume an interrupted flow — *not yet wired in the CLI; re-run \`alw swap now\` instead* | +| \`alw claim [-y]\` | Claim a SOL slash — *not yet wired in the CLI; the TimedOut payout is automatic* | Miner-only commands (\`alw miner …\`, \`alw collateral …\`) and admin commands are documented at https://docs.all-ways.io/cli. @@ -273,7 +275,7 @@ Base URL: \`https://api.all-ways.io\` (testnet: \`https://test-api.all-ways.io\` - **\`BTC_MODE=lightweight requires BTC_PRIVATE_KEY\`** — set the WIF for auto-send, or accept the manual flow (\`alw swap post-tx \` after sending). - **Reservation expired before send** — you only forfeit the small reservation fee. Start a new swap. Live TTL via \`alw view config\`. - **BTC tx stuck unconfirmed → reservation timed out → BTC sent but no swap** — fee too low; the tx never confirmed inside the extension budget. Prevention: let the CLI auto-estimate \`--btc-fee-rate\`, or check next-block tiers. If stuck pre-confirmation, RBF up immediately — recovery is only possible while the tx is in mempool. -- **Miner timed out — where's my refund?** — slashed collateral pays your pinned identity in SOL automatically; if the transfer fails, \`alw claim \` releases it. +- **Miner timed out — where's my refund?** — the 1.1× slash pays your pinned SOL identity automatically; no action needed. (A manual \`alw claim\` fallback for the rare failed auto-payout is coming to the CLI.) ## Testnet From b964c6423626c78ae500e4c2e22295e3c70bb529 Mon Sep 17 00:00:00 2001 From: anderdc Date: Mon, 13 Jul 2026 11:32:41 -0500 Subject: [PATCH 3/4] feat(rates): auto-scrolling rate ticker on mobile; drop duplicate refresh note On mobile the rate strip stacked into a tall column. Wrap the direction segments in a shared Ticker: an auto-scrolling marquee on xs (tap to pause, prefers-reduced-motion -> static swipeable row), unchanged wrapping row on sm+. Used by both RatesTicker (dashboard) and StickyNetworkHeader (miners). Also drop the BlockIndicator 'last event' line from the miner-page header so it carries only the validator 'last refresh' (scoring flush) age -- the two similar 'X ago' notes read as duplicate refreshes. --- src/components/Ticker.tsx | 83 +++++++++++++++++++ src/components/dashboard/RatesTicker.tsx | 10 +-- src/components/miners/StickyNetworkHeader.tsx | 18 ++-- 3 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 src/components/Ticker.tsx diff --git a/src/components/Ticker.tsx b/src/components/Ticker.tsx new file mode 100644 index 0000000..db728a2 --- /dev/null +++ b/src/components/Ticker.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Box, Stack, useMediaQuery } from '@mui/material'; + +// Rate-strip layout. Desktop (sm+) is the original wrapping row, untouched. +// Mobile (xs) becomes an auto-scrolling marquee — the four direction segments +// loop across one line so the strip never stacks into a tall column. Tap +// toggles pause so a moving rate can be read, and prefers-reduced-motion +// degrades to a static, hand-scrollable row (no animation at all). +// +// Shared by the dashboard (RatesTicker) and the miners page (StickyNetworkHeader) +// so the two strips scroll identically. + +const GAP = 2; // theme spacing between segments + +// One pass of the segments. Two identical copies sit in the animated track and +// the transform slides exactly one copy-width (-50%), so the loop is seamless +// regardless of how wide the content is. +const COPY_SX = { + display: 'inline-flex', + alignItems: 'center', + gap: GAP, + pr: GAP, + '& > *': { flexShrink: 0 }, +} as const; + +const Ticker: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const items = React.Children.toArray(children); + const reduced = useMediaQuery('(prefers-reduced-motion: reduce)'); + const [paused, setPaused] = React.useState(false); + + return ( + <> + {/* Desktop / tablet: the original wrapping row. */} + + {items} + + + {/* Mobile: marquee, or a static swipeable row when motion is reduced. */} + setPaused((p) => !p)} + sx={{ + display: { xs: 'block', sm: 'none' }, + width: '100%', + overflowX: reduced ? 'auto' : 'hidden', + WebkitOverflowScrolling: 'touch', + // Fade both edges so a clipped segment reads as "more to scroll", + // not cut off. Skipped in the static case (nothing is clipped mid-item). + maskImage: reduced + ? undefined + : 'linear-gradient(to right, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%)', + }} + > + {reduced ? ( + {items} + ) : ( + + {items} + + {items} + + + )} + + + ); +}; + +export default Ticker; diff --git a/src/components/dashboard/RatesTicker.tsx b/src/components/dashboard/RatesTicker.tsx index 057efaa..fc529cc 100644 --- a/src/components/dashboard/RatesTicker.tsx +++ b/src/components/dashboard/RatesTicker.tsx @@ -4,6 +4,7 @@ import { useAllSwaps, useCurrentCrown } from '../../api'; import { ALL_DIRECTIONS } from '../../api/models/MinersDashboard'; import { FONTS } from '../../theme'; import { BlockIndicator } from '../index'; +import Ticker from '../Ticker'; import CrownDirectionSegment from '../miners/CrownDirectionSegment'; import { latestEmaRate } from './marketRate'; import { formatRate } from '../../utils/format'; @@ -38,12 +39,7 @@ const RatesTicker: React.FC = () => { }} > - + {DIRECTIONS.map((dir) => { const emaRate = latestEmaRate(swaps, dir); return ( @@ -98,7 +94,7 @@ const RatesTicker: React.FC = () => { ); })} - + ); }; diff --git a/src/components/miners/StickyNetworkHeader.tsx b/src/components/miners/StickyNetworkHeader.tsx index aaf24b1..289124a 100644 --- a/src/components/miners/StickyNetworkHeader.tsx +++ b/src/components/miners/StickyNetworkHeader.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Box, Stack, Typography } from '@mui/material'; -import { BlockIndicator } from '../index'; import { useCurrentCrown, useHaltState, useScoringState } from '../../api'; import { ALL_DIRECTIONS } from '../../api/models/MinersDashboard'; import CrownDirectionSegment from './CrownDirectionSegment'; +import Ticker from '../Ticker'; import { FONTS } from '../../theme'; // Live "time since the validator last flushed crown/rate data". The validator @@ -92,15 +92,15 @@ const StickyNetworkHeader: React.FC = () => { rowGap: { xs: 0.5, sm: 1 }, }} > - - - {segments} - + {segments} + Date: Mon, 13 Jul 2026 11:43:31 -0500 Subject: [PATCH 4/4] fix(rates): match marquee speed across strips + kill jitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Duration was a fixed 20s, but the animation travels one content-width (-50%) per cycle — so the wider dashboard strip (EMA suffix) scrolled faster than the narrower miners strip. Derive duration from measured copy width at a constant ~22 px/s so both match. Also composite on the GPU (will-change + translate3d) so the miner header's per-second 'last refresh' re-render can't stutter it. --- src/components/Ticker.tsx | 41 +++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/components/Ticker.tsx b/src/components/Ticker.tsx index db728a2..4cb5d01 100644 --- a/src/components/Ticker.tsx +++ b/src/components/Ticker.tsx @@ -12,6 +12,13 @@ import { Box, Stack, useMediaQuery } from '@mui/material'; const GAP = 2; // theme spacing between segments +// Marquee speed. The duration is derived from the measured content width so both +// strips scroll at the same pixels/second — a fixed duration made the wider +// dashboard strip (it carries the EMA suffix) look faster than the narrower +// miners strip. ~22 px/s is a calm pace; MIN keeps very narrow content sane. +const PX_PER_SEC = 22; +const MIN_DURATION_SEC = 8; + // One pass of the segments. Two identical copies sit in the animated track and // the transform slides exactly one copy-width (-50%), so the loop is seamless // regardless of how wide the content is. @@ -28,6 +35,24 @@ const Ticker: React.FC<{ children: React.ReactNode }> = ({ children }) => { const reduced = useMediaQuery('(prefers-reduced-motion: reduce)'); const [paused, setPaused] = React.useState(false); + // Measure one copy's width and set the duration to width / PX_PER_SEC, so the + // marquee holds a constant visual speed no matter how wide the segments are. + const copyRef = React.useRef(null); + const [durationSec, setDurationSec] = React.useState(20); + React.useEffect(() => { + if (reduced) return; + const el = copyRef.current; + if (!el) return; + const measure = () => { + const w = el.getBoundingClientRect().width; + if (w > 0) setDurationSec(Math.max(MIN_DURATION_SEC, w / PX_PER_SEC)); + }; + measure(); + const ro = new ResizeObserver(measure); + ro.observe(el); + return () => ro.disconnect(); + }, [reduced]); + return ( <> {/* Desktop / tablet: the original wrapping row. */} @@ -61,15 +86,23 @@ const Ticker: React.FC<{ children: React.ReactNode }> = ({ children }) => { sx={{ display: 'inline-flex', width: 'max-content', - animation: 'aw-ticker 20s linear infinite', + // GPU-composited so the miner header's per-second "last refresh" + // re-render can't stutter the scroll (main-thread reflow jitter). + willChange: 'transform', + animationName: 'aw-ticker', + animationDuration: `${durationSec}s`, + animationTimingFunction: 'linear', + animationIterationCount: 'infinite', animationPlayState: paused ? 'paused' : 'running', '@keyframes aw-ticker': { - from: { transform: 'translateX(0)' }, - to: { transform: 'translateX(-50%)' }, + from: { transform: 'translate3d(0, 0, 0)' }, + to: { transform: 'translate3d(-50%, 0, 0)' }, }, }} > - {items} + + {items} + {items}