fix(auto): stop footer-flooding flat content pages#3
Open
mixflavor wants to merge 76 commits into
Open
Conversation
Four-day surgical rewrite. v1's manual `trackScrollColors(stops, options)`
is replaced by `bleed/auto` (zero-config) and `createBleedAuto()` (with
options). The library now auto-detects body::before gradients, opaque
section colors, and the page-end section, and染色 chrome to match the
content at each viewport edge.
BREAKING CHANGES:
- `trackScrollColors`, `setupBleedMeta`, `setThemeColor`, `getSafeAreaInsets`
removed. Replaced by `createBleedAuto()` and `import 'bleed/auto'`.
- React, Vue, Svelte, UnoCSS framework wrappers removed. Zero-config import
works from any framework now.
NEW:
- `bleed/auto` — side-effect import that wires everything up.
- `bleed/utils` (rewritten) — exports `createBleedAuto()` + helpers
(parseColor, parseGradient, sampleColorAt, findLastOpaqueSection, etc.).
- Smart state machine per edge: STICKY_OWNED / SAFE_NATURAL / BLEED_OVERRIDE.
- Detects `boundary.source` ('gradient' vs 'section') for smart decisions.
- Page-end overscroll handling: overwrites <html>, <body>, AND body::before
bg via injected <style> so iOS rubber-band染色 the right color.
UNCHANGED:
- `bleed/style` CSS (`.bleed-top`, `.bleed-bottom`, `.bleed-inner-blur`).
- Tailwind plugin.
HANDOFF.md added — mental model + iOS 26 quirks documented for future
maintainers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Page-end染色 overwrite (html/body bg to last section color, body::before to solid section color) now fades 400ms instead of snapping. Injected as a one-time <style id="bleed-transition-style"> on first update, removed on controller.destroy(). The transition only fully interpolates the color portion (body::before gradient→solid still can't transition smoothly), but in practice when the gradient terminal color matches the last section color (as in cver.net's gradient ending in the footer color), the visible transition is fully smooth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The old demo was a v1 interactive simulator for the backdrop-filter clipping fix. v2 is a different problem class (zero-config chrome染色), so the demo is a focused showcase: gradient hero, the four-day rabbit hole writeup, opaque belt section, footer at page-end. The page itself IS the demo — open it on iOS Safari to see bleed染色 status bar + URL bar in action. A floating toggle lets visitors destroy / re-create the controller for side-by-side comparison. Removed v1 demo assets (cver-oss-showcase.css, simulator.css). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two phone frames scroll in sync — left has bleed disabled so chrome染色 stays on the html-bg fallback (the seam you'd see on real iOS Safari without bleed); right runs a mini-bleed sampling loop against the inner scrollable container and染色 chrome dynamically as content moves past the viewport edges. Same Hero → belt → footer content as cver.net so visitors can map the demo behavior to the production result. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings back the iPhone-frame + control-panel layout that worked so well in v1, but rewires every interaction for v2's smart染色 model: - Top/Bottom "buggy / bleed" mode buttons → single "bleed染色 mode" toggle (Off shows the broken iOS fallback, On runs the v2 logic). - Color presets → 5 gradient themes (teal / rose / violet / sun / forest). Each theme sets the hero gradient, belt color, footer color on the in-phone demo page. - Phone screen content (dummy cards) → cver.net-style demo page: gradient hero + opaque belt + opaque footer. - New "Live diagnostics" panel exposes top.state, bot.state, chrome.top, chrome.bot so visitors can watch the state machine in action while scrolling. JS runs a mini-bleed sampling loop (using parseColor / parseColorWithAlpha / colorsClose from bleed/utils) against the inner phone-screen,染色 the status-bar-info + safari-bottom-liquid-bar + home-indicator-area DOM elements to mirror what bleed does to real iOS chrome. Tabs Layout (Compact / Bottom / Top / PWA), Safe Area sliders, and the overall page chrome (header, footer) are preserved from v1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two issues with the deployed demo at oss.cver.net/bleed/: 1. Buttons did nothing because the script imported from ../src/utils.mjs, but gh-pages only serves /demo — the src/ folder is not deployed. The import failed silently and the script never registered event listeners. Fix: inline the few helpers we actually use (parseColor, parseColorWithAlpha, colorToRgb, colorToHex, colorsClose) directly in the demo script. Source of truth still lives in src/utils.mjs. 2. UI text was mixed Chinese / English. Translated everything visible to English (state machine labels, scroll hints, theme picker copy). Code comments inside the simulator script are also English now. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The simulator's inner page used to be a simplified hero / belt / footer that just hinted at the production layout. Replace it with the actual cver.net homepage shape: hero + intro paragraph + three product cards (MixFairy, REEF, Open Source) in the belt, plus a faithful footer with links / logo / copyright. The result is a much more honest demo — visitors see exactly what bleed v2 produces on the real cver.net page that drove its design. Includes an inline SVG cver-mark so no external assets / links are needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HANDOFF.md was accidentally pushed publicly. Untrack from main and add
to .gitignore so future commits don't re-include it. NOTE: the file
still exists in earlier git history — see notes in PR / issue if a
full history purge is needed.
- demo: drop the self-drawn SVG cver mark, point at the real
cver.net/cver-logo{,-small}.png so the simulator looks identical to
production. External-asset trade-off accepted for visual fidelity.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per chodaict's request: keep the cver.net replica page scroll (v2 story) but bring back v1's three-state Top/Bottom Banner Mode pickers (the .bleed-top / .bleed-bottom CSS class showcase) and drop the Safe Area Simulator sliders (Tabs Layout already sets sensible insets per shell mode). The control panel is now: - Bleed v2 auto-tint Off / On - Top Banner Mode None / Buggy Blur / Bleed Fix - Bottom Action Mode None / Buggy Blur / Bleed Fix - Tabs Layout Compact / Bottom / Top / PWA - Page Theme 5 gradient palettes - Live diagnostics state machine output When a Bleed Fix banner is active, the sim element carries `.bleed-top` / `.bleed-bottom` and the v2 controller switches to STICKY_OWNED on that edge — chrome染色 then comes from the banner directly (matching real iOS Safari edge sampling). Banner colors are crimson (top) / emerald (bottom), independent of the Page Theme, so the showcase reads at a glance. Banner CSS hard-coded to crimson/emerald (replacing the v1 CSS-var indirection that depended on color presets we no longer ship). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The v2 release notes / mental model section still had Chinese mixed in from the original prototype docs. Replace 染色 → tinting / tints, 進場 → engage, 退場 → step back. Fix sed concat artifacts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After the README and demo UI translation, a few code comments in src/utils.mjs and demo/index.html still mentioned 染色 / 進場 / 退場. package.json description also had 染色 in the npm metadata. Translated all to English consistent with the rest of the repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…wnload was failing on GitHub Pages)
…outs User flagged that Compact / Bottom / Top chrome positions in the iPhone simulator didn't match real iOS Safari. Root cause: the bottom pill was placed *above* the safe-area-bottom region (`bottom: calc(safe + 8px)`), but iOS actually puts it *inside* the chrome region, hugging the viewport edge. Same issue for the top capsule in Top mode (hardcoded 67px instead of anchoring to the safe-area-top boundary). Fixes: - `.sim-safari-bottom-liquid-bar` now anchors `bottom: 14px` (inside the chrome region) regardless of safe-area-bottom — matches Compact / Bottom / Top pill positions in real iOS Safari screenshots. - `.sim-safari-top-capsule` anchors `top: calc(--sim-safe-top - 50px)` so it sits at the bottom of the safe-area-top region (just below the status bar), instead of a hardcoded 67px. - Tightened safe-area values per Tabs Layout mode to match the actual pill heights + anchor + small breathing room. Compact 90→78, Bottom 140→128, Top 90→78 (bottom inset) / 103→104 (top inset). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…look)
Previously the demo painted chrome with a solid bg-color in every state,
which made it look like bleed v2 was always actively painting. In real
iOS, SAFE_NATURAL state means bleed's tint is display:none and Safari
falls back to its native Liquid Glass sampling: a translucent overlay
over the page content with backdrop-filter blur.
Two paint modes now:
- 'solid' : bg-color flat fill (BLEED_OVERRIDE, banner Bleed Fix,
or Bleed v2 Off fallback)
- 'translucent' : low-alpha bg + backdrop-filter blur (SAFE_NATURAL —
page content shows through with iOS-style blur)
Top edge always uses translucent (matches v2's "top always SAFE_NATURAL"
rule). Bottom uses solid only when BLEED_OVERRIDE engages (gradient
terminal ≠ html bg, or page-end section).
Reported by chodaict.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… remove computer icon Three small simulator polish fixes reported by chodaict: 1. Status-bar icons (time / signal / wifi / battery) shifted down in Top mode because --sim-safe-top is taller (104 vs 59) and flex centered them vertically. Pin them to the TOP of the safe-area region instead (align-items: flex-start + padding-top: 14px). Position now stable across Compact / Bottom / Top / PWA. 2. Removed the small computer/bed icon next to the clock — it was the carrier branding placeholder from the v1 mockup, not real iOS chrome. 3. Auto-contrasting status bar text. Compute Rec. 601 luminance of the chrome bg color; flip to black text on light bg (>0.62 luminance), white otherwise. Status time + signal / wifi / battery SVGs all use currentColor so they pick up the inverted color for free. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pointing at https://cver.net/logo.gif (the same animated mark cver.net uses) instead of the static cver-logo.png. The footer mark stays on cver-logo-small.png for the smaller scale. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…2 tabs The option-group flex was hardcoded to 3 columns, so: - Bleed mode (2 buttons) left a dead column on the right. - Tabs Layout (4 buttons) wrapped to 3+1 with PWA orphan on row 2. - 'On (smart bleed)' wrapped *inside* its button because longer text pushed it past 1fr. Switch each option-group to a custom column count via `--cols` inline style, and tighten the buttons: - Bleed mode → 2 cols - Top / Bottom → 3 cols (None / Buggy / Bleed — drop 'Blur' / 'Fix') - Tabs Layout → 2 cols (renders as a 2×2 grid) - .option-btn → white-space: nowrap + ellipsis so labels never break Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
no longer reads as a solid band User flagged that the top status bar and the bottom home-indicator area still looked like solid color blocks even in translucent mode. The 55% alpha overlay was too opaque — iOS Liquid Glass chrome actually shows page content with just a very light tint and heavy blur. Drop the translucent rgba alpha to 0.12 — blur does the visual work now, the tint just hints at edge color. Bands no longer stand out as "solid" sitting on top of the page. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…erived themes - Replace SAFE_NATURAL translucent (low-alpha + blur) with solid sampled fill — matches iOS 26 Safari's actual chrome tinting (opaque sample of edge content, not Liquid Glass over the page). - Move gradient from .demo-page::before (absolute, stretched over full page length) to .phone-screen (the scroll container) so it stays viewport-anchored — same shape as cver.net's fixed body::before, the deep end of the gradient now reads at the bottom of the viewport instead of being scrolled past. - Re-derive theme palettes from the CVER mint→teal shape via hue rotation (60/80 → 87/30 in HSL, +28° hue shift). Drops the mismatched sun/rose/violet/forest hardcoded swatches in favor of teal / ocean / violet / rose / forest that all share the same visual gravity. Adds two solid presets (white, black) for testing chrome on max- / no- contrast edge cases. - Fix chrome.bot diagnostics reading .style.background instead of .style.backgroundColor (was always showing dash). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reverts the demo footer to the brand-neutral version from before 80f6611 ("replicate full cver.net homepage inside the phone"), which was intended as the reusable template for future demos. The CVER- specific footer (cver-logo-small.png img + "CVER.NET © 2011–2026 CVER Inc. | Language") was a one-off for the homepage replication and shouldn't be the canonical demo template. Drops the now-unused .demo-footer-sep and .demo-footer-logo CSS rules and restores the original padding / link styling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Internal state-machine output (top.state, bot.state, chrome.top, chrome.bot) is jargon-heavy for visitors evaluating bleed and adds noise to the panel without explaining what the values mean. Developers who want the same view at runtime can read window.__bleed_* globals in devtools on their own page. Removes the HTML section, the JS writeback in runUpdate, the now- unused colorToHex helper, and the .log-card / .log-line styles in cver-oss-showcase.css. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS 26 Safari no longer renders the home indicator pill inside the browser viewport, so showing one in the simulator is misleading. The .home-indicator-area band stays — it's still the bottom safe-area tint strip that visualizes the rubber-band-overscroll region. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hero section had min-height: 100% but its parent .demo-page only sets min-height: 100% (not height), so the percentage chain collapses and the hero ended up just tall enough for its own content. The belt section was visible above the fold, which doesn't match how the real cver.net page reads on iPhone — hero owns the first viewport. Switch to container-query units: make .phone-screen a size container and anchor the hero to 100cqh. The hero now reliably fills the phone viewport regardless of where the percentage chain breaks down. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Time text and signal/wifi/battery icons now sit vertically centered on the dynamic island's center line (island is top:11 height:30, center y=26), matching how real iOS positions the status bar around the notch. The status-bar-info element still spans the full safe-area-top so chrome tinting paints all the way to the top edge — only the inner flex contents are pinned to the island row. Also bumps font-size 0.75rem → 0.9rem and grows each SVG icon ~25% for readability at the simulator's scale. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Apple's chassis corners since iPhone 15 are a G2-curvature-continuous squircle, not a circular radius. A symmetric border-radius reads slightly sharper than real iPhones at this scale. Switch .phone-frame and .phone-screen to the asymmetric `X / Y` shorthand to stretch the arc transition zone and approximate the squircle look in pure CSS: - frame: 48 48 48 48 / 50 50 50 50 - screen: 38 38 38 38 / 40 40 40 40 Tuned for the standard (non-Max) class around 6.1–6.3" (logical width ~393px; demo uses 375 — values translate cleanly). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous commit centered the status bar items on the dynamic island's vertical center via align-items, but the time text still read as floating above the SVG icons. Cause: default line-height of 1.2 leaves vertical leading inside the text's line box, so flex center-alignment lined up the box but the glyph itself sat above center. Set line-height: 1 on each inner flex row so text and SVGs share an optical center. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Status bar, Safari capsules, sticky banners, and the bottom safe-area tint band were direct children of .phone-frame, sitting outside the .phone-screen rounded clip. With the iPhone 15+ squircle radii now on the screen, those overlays kept hard 90° corners — most visible in Top/Bottom Banner Bleed combinations where the System Warning red banner and the Proceed-to-checkout green banner painted square edges into the bezel. Move them inside .phone-screen (only the camera notch stays on the bezel, as it should). The screen's overflow + border-radius now clips all overlays to the squircle. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…to custom inputs only
…n into the dynamic section title
…tanium finishes selector
…x), slimmer titanium band (1.5px), larger Dynamic Island (102×28px), updated inner radii (46.5px)
… compact bar matching real iOS Safari
… for compact pill tabs
…scrolls on top of it
…ch real proportions
…ching real project
…publish) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tor.css Dogfood the extracted liquidframe OSS: vendor liquidframe.css (@f36c2b0) as the phone-frame base, switch shell-mode switching to its .chrome-* classes, and split demo-only styles (fake page, banners, inner-blur) into bleedblend-demo.css. Drops the duplicated simulator.css. The bespoke auto-tint engine is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cver-oss-showcase.css Dogfood the extracted demodeck OSS: vendor demodeck.css (@d926da2), rename the control-panel markup to demodeck's .dd-* classes, and reskin it to bleedblend's teal via demodeck's :root retheme (bleedblend-panel.css). Drops the duplicated cver-oss-showcase.css. Keeps the auto-tinting toggle's signature green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hem from --dd-accent Re-sync vendored demodeck.css (@fbe18a2): its accent glows now use color-mix(in srgb, var(--dd-accent) …%, transparent), so bleedblend's teal :root retheme carries the badge/active-btn glows automatically. Removes the now-redundant glow patches from bleedblend-panel.css (the green auto-tinting toggle stays). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oss site shapes Drives the locally installed Chrome over CDP (no npm install) plus pure-function unit tests. Exercises bleedblend's entire JS decision path — edge state machine, chosen color, tint application, html/body/::before overscroll overwrite, desktop no-op, double-init idempotency and destroy cleanup — against a battery of synthetic "any site" shapes (solid, gradient vs html-bg, sections+footer, dark mode, sticky owned edges, translucent compositing, image bg, non-semantic markup + custom sectionSelector escape hatch). Surfaces one real robustness gap: oklch()/color(display-p3) backgrounds are serialized verbatim by the browser and parseColor returns null for them, so such sites silently fall back to SAFE_NATURAL. The native Safari chrome paint itself still needs a real iOS 26 device to confirm. npm run test:pure # node-only npm run test:auto # headless Chrome via CDP npm run test:all Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…adback getComputedStyle serializes modern color functions verbatim instead of down-converting to rgb, so the regex-only parseColor returned null for oklch()/lab()/lch()/color(display-p3 …) backgrounds and gradient stops — those sites silently fell back to SAFE_NATURAL (no tint). parseColor now adds a last-resort normalizer that paints the value into a 1px canvas and reads it back as sRGB, covering ANY color the browser can render (alpha preserved, invalid → null). parseGradient's stop tokenizer now captures balanced-paren function colors and named keywords so modern-syntax gradient stops resolve too. DOM-guarded: off-DOM (Node) the canvas path is skipped and these return null by design, so the pure exports stay pure. Mirrored across utils.mjs and utils.js. Verified by new integration cases L (oklch gradient terminal) and M (display-p3 footer): both now engage BLEED_OVERRIDE with the correct color (previously SAFE_NATURAL / mis-sampled). Full suite: pure 31/31, integration 59/59. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns package.json with the src/index.css header and releases the universal CSS-color resolution (oklch/lab/lch/hwb/color()) feature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Universal CSS-color auto-tint + zero-dependency verification harness
The dd-logo linked to the GitHub repo; it should lead back to the project's home on cver.net so the demo ↔ site loop is closed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…26 family) Rework "Browser support" to prevent a recurring misread: bleedblend does not *manufacture* chrome tinting, it *tames* the tinting Safari 26 already does. Safari 26's Liquid Glass tints chrome from page content across the whole Safari family (iPhone, iPad, AND Mac) via body bg / top fixed element sampling; theme-color meta is now ignored. Per-surface table now states what bleedblend actually contributes: actively tames iOS/iPadOS quirks; deliberately steps back on Mac (native model is well-behaved, so display:none deferral is by-design, not a gap); no-op on non-tinting browsers. Adds a sourced "Safari 26 tinting model" background section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…int (#2) The 'Sticky banners' section opened with 'if you have your own sticky header that already tints chrome correctly … bleedblend will step out of the way' — which reads as a defer flag for bars that ALREADY work. A reader whose nav does NOT tint (the common case) skips the section, missing that .bleedblend-top + bleedblend/style is precisely the recipe to MAKE it tint. - Retitle + reframe as 'Make your own sticky header / footer tint the chrome'. - Lead with the value; call out stripping backdrop-filter as the #1 reason a sticky nav silently refuses to tint (WebKit safe-area clipping), with the tell-tale symptom (looks right on screen, chrome stays white). - Show the .bleedblend-inner-blur pattern to keep the frosted look. - Note the viewport-fit=cover-in-SSR-head prerequisite (JS-injected after load is unreliable on iOS). - Add an opt-in pointer from the 'top stays light' bullet so readers know top branding is achievable, not out of scope. Found the hard way while wiring bleedblend onto a real cream sticky nav with backdrop-filter — only auto was loaded, the top never tinted, and the docs framing was why the .bleedblend-top fix got skipped. Co-authored-by: mixflavor <mr.crazyx2@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Page-end overwrite dyed html+body+body::before with the last opaque section's color, flooding the visible background of flat light pages with a short high-contrast footer. Classify the end section as a designed end-zone vs an incidental footer; for the incidental case tint <html> only (correct rubber-band color, body left alone). Add overscrollFill: 'auto'|'always'|'never' escape hatch. Backward compatible: tall/gradient/continuous endings still fully overwrite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
26a0bd0 to
662131d
Compare
d5d95e2 to
df5d352
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Page-end overwrite dyed
html+body+body::beforewith the last opaque section's color, flooding the visible background of flat light pages with a short high-contrast footer. This classifies the end section as a designed end-zone vs an incidental footer:html+body+body::beforeoverwrite (unchanged)<html>only (correct rubber-band color, visible body left alone)Adds an
overscrollFill: 'auto' | 'always' | 'never'escape hatch (default'auto'). Backward compatible — every pre-existing overscroll case (footer ≥ 50vh) still classifies as end-zone and floods exactly as before.Tests
node test/pure.mjs→ 31/0node test/integration.mjs→ 79/0 (incl. new N–N5: incidental-no-flood / always / never / tall-still-floods / continuous-color-floods)Both engines kept in lockstep (
src/utils.mjsESM +src/utils.jsCJS),.d.ts+ README updated.The chrome tint + overscroll overwrite only render on real iOS Safari 26+ (
if (!isIOS) return); headless/CI proves the decision logic, not the native paint. Before publishing 2.2.0, the real-iPhone regression matrix must pass: gradient page stays seamless / flat page no longer floods brown / rubber-band tints correctly on both.🤖 Generated with Claude Code