Skip to content

Center play triangle in click-to-play button#289

Open
wilcoxjay wants to merge 1 commit into
simonw:mainfrom
wilcoxjay:fix-play-button-triangle-centering
Open

Center play triangle in click-to-play button#289
wilcoxjay wants to merge 1 commit into
simonw:mainfrom
wilcoxjay:fix-play-button-triangle-centering

Conversation

@wilcoxjay

Copy link
Copy Markdown

The margin-left:7% on the play icon's triangle SVG pushes it right of the disc center.

Before / after with crosshairs

Engine Before After
Blink / Chrome blink-chrome-before-crosshair blink-chrome-after-crosshair
Gecko / Firefox gecko-firefox-before-crosshair gecko-firefox-after-crosshair
WebKit / Safari webkit-safari-before-crosshair webkit-safari-after-crosshair

Before / after no crosshairs

Engine Before After
Blink / Chrome blink-chrome-before-clean blink-chrome-after-clean
Gecko / Firefox gecko-firefox-before-clean gecko-firefox-after-clean
WebKit / Safari webkit-safari-before-clean webkit-safari-after-clean

Reproducing the screenshots

To reproduce these screenshots using playwright, save harness.html and capture.py, and then:

python3 -m venv venv
venv/bin/pip install playwright
venv/bin/playwright install chromium firefox webkit   # one-time engine download (~250 MB)
venv/bin/python capture.py                            # 12 images -> ./out/

harness.html

Renders the button with the disc + icon CSS copied verbatim from click-to-play-component.html. Query params toggle the margin-left under test (?m=7%25 vs ?m=0), the red centre crosshair (?g=1), and an engine badge (?badge=1) written by in-page feature detection so each screenshot self-identifies its engine.

<!doctype html>
<meta charset="utf-8">
<title>ctp harness</title>
<style>
  html,body{margin:0;font-family:-apple-system,system-ui,sans-serif}
  .wrap{position:relative;width:300px;height:300px;display:grid;place-items:center;background:#2b1a4a}

  /* ===== verbatim from click-to-play-component.html (injectStyles) ===== */
  .ctp-btn{width:200px;height:200px;aspect-ratio:1;display:grid;place-items:center;
    border-radius:50%;background:rgba(12,14,22,.34);
    -webkit-backdrop-filter:blur(3px) saturate(1.15);backdrop-filter:blur(3px) saturate(1.15);
    box-shadow:0 6px 22px rgba(0,0,0,.34),inset 0 0 0 1.6px rgba(255,255,255,.9);
    opacity:.92}
  .ctp-btn svg{grid-area:1/1;width:40%;height:40%;fill:#fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))}
  /* margin-left on .ctp-i-play is applied from ?m=... below (the property under test) */
  /* ===================================================================== */

  .guide-v{position:absolute;left:50%;top:0;bottom:0;width:1px;background:red;transform:translateX(-.5px)}
  .guide-h{position:absolute;top:50%;left:0;right:0;height:1px;background:red;transform:translateY(-.5px)}
  .hidden{display:none}
  .badge{position:absolute;left:0;right:0;top:0;padding:6px 8px;font:700 13px/1.3 ui-monospace,Menlo,monospace;
    color:#fff;background:rgba(0,0,0,.55);text-align:center;letter-spacing:.02em}
</style>

<div class="wrap">
  <div class="badge hidden" id="badge"></div>
  <div class="ctp-btn">
    <!-- verbatim PLAY_ICON path from click-to-play-component.html -->
    <svg class="ctp-i-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.53.85l10-6.5a1 1 0 0 0 0-1.7l-10-6.5A1 1 0 0 0 8 5.5z"/></svg>
  </div>
  <div class="guide-v" id="gv"></div>
  <div class="guide-h" id="gh"></div>
</div>

<script>
  function has(o,k){ try { return k in o; } catch(e){ return false; } }
  function supports(p,v){ try { return CSS.supports(p,v); } catch(e){ return false; } }
  var gecko  = navigator.productSub === "20100101" || supports("-moz-appearance","none") ||
               navigator.oscpu !== undefined;
  var webkit = has(window,"GestureEvent") && navigator.vendor === "Apple Computer, Inc.";
  var blink  = has(navigator,"userAgentData") || has(Intl,"v8BreakIterator");
  var verdict = gecko ? "GECKO · Firefox" : webkit ? "WEBKIT · Safari" : blink ? "BLINK · Chrome" : "UNKNOWN";

  var q = new URLSearchParams(location.search);
  document.querySelector(".ctp-i-play").style.marginLeft = q.get("m") || "0";
  if (q.get("g") !== "1"){
    document.getElementById("gv").classList.add("hidden");
    document.getElementById("gh").classList.add("hidden");
  }
  if (q.get("badge") === "1"){
    var disc = document.querySelector('.ctp-btn').getBoundingClientRect();
    var tri  = document.querySelector('.ctp-i-play path').getBoundingClientRect();
    var off  = (tri.x + tri.width/2) - (disc.x + disc.width/2);
    var b = document.getElementById("badge");
    b.classList.remove("hidden");
    b.textContent = verdict + "  ·  offset " + off.toFixed(3) + "px";
  }
</script>

capture.py

Loads the harness in each engine at 2× scale and screenshots the 300×300 button for every
combination of engine × {before, after} × {crosshair, clean} — the 12 images — and prints
the measured offset per engine.

import json
from pathlib import Path

from playwright.sync_api import sync_playwright

HARNESS = (Path(__file__).resolve().parent / "harness.html").as_uri()
OUT = Path(__file__).resolve().parent / "out"
OUT.mkdir(exist_ok=True)

ENGINES = [("blink-chrome", "chromium"), ("gecko-firefox", "firefox"), ("webkit-safari", "webkit")]
STATES = [("before", "7%"), ("after", "0")]          # before = buggy margin-left:7%, after = fixed
GUIDES = [("crosshair", "1"), ("clean", "0")]

MEASURE = """
() => {
  const disc = document.querySelector('.ctp-btn').getBoundingClientRect();
  const tri  = document.querySelector('.ctp-i-play path').getBoundingClientRect();
  return Math.round(((tri.x + tri.width/2) - (disc.x + disc.width/2)) * 1000) / 1000;
}
"""

measured = {}
with sync_playwright() as p:
    launchers = {"chromium": p.chromium, "firefox": p.firefox, "webkit": p.webkit}
    for eng_slug, eng_key in ENGINES:
        browser = launchers[eng_key].launch()
        for state_slug, margin in STATES:
            for guide_slug, g in GUIDES:
                page = browser.new_page(viewport={"width": 300, "height": 300}, device_scale_factor=2)
                page.goto(f"{HARNESS}?m={margin}&g={g}&badge=1", wait_until="networkidle")
                if guide_slug == "crosshair":
                    measured[f"{eng_slug}/{state_slug}"] = page.evaluate(MEASURE)
                page.locator(".wrap").screenshot(path=str(OUT / f"{eng_slug}-{state_slug}-{guide_slug}.png"))
                page.close()
        browser.close()

print("wrote screenshots to", OUT)
print(json.dumps(measured, indent=2))

The play icon's SVG path is already balanced, so the margin-left:7% nudge
over-corrected and pushed the triangle right of the disc center. Remove it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant