feat: strengthen design skill and refresh all showcases
Some checks failed
ci / build (push) Has been cancelled
Some checks failed
ci / build (push) Has been cancelled
This commit is contained in:
parent
69e5cc1efd
commit
e09d4efb1c
257 changed files with 8196 additions and 2374 deletions
|
|
@ -54,6 +54,7 @@ const catalogUrl = `http://127.0.0.1:${address.port}/work/twentyoz-corp/products
|
|||
let pass = 0;
|
||||
let fail = 0;
|
||||
const failures = [];
|
||||
const observations = [];
|
||||
const suites = new Map();
|
||||
const assert = (condition, suite, name, detail = "") => {
|
||||
const current = suites.get(suite) || { pass: 0, fail: 0 };
|
||||
|
|
@ -124,12 +125,46 @@ try {
|
|||
const h1 = document.querySelector("h1");
|
||||
const lineHeight = Number.parseFloat(getComputedStyle(h1).lineHeight);
|
||||
const navTops = [...document.querySelectorAll(".nav a")].map((link) => Math.round(link.getBoundingClientRect().top));
|
||||
const navLinks = [...document.querySelectorAll(".nav a")].map((link) => {
|
||||
const rect = link.getBoundingClientRect();
|
||||
const style = getComputedStyle(link);
|
||||
return {
|
||||
text: link.textContent.trim(),
|
||||
visible: style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0,
|
||||
height: Math.round(rect.height),
|
||||
insideDocument: rect.left >= 0 && rect.right <= document.documentElement.clientWidth
|
||||
&& rect.top >= 0 && rect.bottom <= document.documentElement.scrollHeight,
|
||||
};
|
||||
});
|
||||
const headSiblings = [...document.querySelectorAll(".head-inner > *")].map((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
name: element.className || element.tagName.toLowerCase(),
|
||||
visible: visible(element),
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
};
|
||||
});
|
||||
const headOverlaps = [];
|
||||
for (let index = 0; index < headSiblings.length; index += 1) {
|
||||
for (let other = index + 1; other < headSiblings.length; other += 1) {
|
||||
const first = headSiblings[index];
|
||||
const second = headSiblings[other];
|
||||
if (first.visible && second.visible && first.left < second.right && first.right > second.left && first.top < second.bottom && first.bottom > second.top) {
|
||||
headOverlaps.push(`${first.name}/${second.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
width: innerWidth,
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
h1Count: document.querySelectorAll("h1").length,
|
||||
h1Lines: Math.round(h1.getBoundingClientRect().height / lineHeight),
|
||||
navTopCount: new Set(navTops).size,
|
||||
navLinks,
|
||||
headOverlaps,
|
||||
checked: document.querySelector('input[name="field"]:checked')?.value,
|
||||
visiblePanels: [...document.querySelectorAll(".field-panel")].filter(visible).map((panel) => panel.dataset.panel),
|
||||
fieldTargets: [...document.querySelectorAll(".field-tabs label")].map((label) => {
|
||||
|
|
@ -147,7 +182,11 @@ try {
|
|||
assert(layout.scrollWidth <= layout.width, suite, "no horizontal document overflow", `${layout.scrollWidth}/${layout.width}`);
|
||||
assert(layout.h1Count === 1, suite, "one h1", String(layout.h1Count));
|
||||
assert(layout.h1Lines <= 2, suite, "hero title at most two lines", String(layout.h1Lines));
|
||||
assert(layout.navTopCount === 1, suite, "navigation remains one row", String(layout.navTopCount));
|
||||
observations.push(`${suite} · navigation rows observed: ${layout.navTopCount}`);
|
||||
assertEvery(layout.navLinks, (link) => link.visible, suite, "navigation link visible", (link) => link.text);
|
||||
assertEvery(layout.navLinks, (link) => link.height >= 24, suite, "navigation link height >=24", (link) => `${link.text} ${link.height}px`);
|
||||
assertEvery(layout.navLinks, (link) => link.insideDocument, suite, "navigation link stays inside document", (link) => link.text);
|
||||
assert(layout.headOverlaps.length === 0, suite, "header siblings do not overlap", layout.headOverlaps.join(","));
|
||||
assert(layout.checked === "edu", suite, "default field is education", String(layout.checked));
|
||||
assert(layout.visiblePanels.length === 1 && layout.visiblePanels[0] === "edu", suite, "one matching panel visible", layout.visiblePanels.join(","));
|
||||
assertEvery(layout.fieldTargets, (target) => target.height >= 44, suite, "field target height >=44", (target) => `${target.width}x${target.height}`);
|
||||
|
|
@ -280,9 +319,9 @@ try {
|
|||
|
||||
const coreLayoutCases = [
|
||||
{ width: 390, height: 844, mobile: true, tier: "mobile", offset: 0, minRatio: 0.495, maxRatio: 0.505 },
|
||||
{ width: 768, height: 900, mobile: false, tier: "tablet", offset: 0.9, minRatio: 0.72, maxRatio: 0.75 },
|
||||
{ width: 1024, height: 768, mobile: false, tier: "compact", offset: 0.75, minRatio: 0.61, maxRatio: 0.64 },
|
||||
{ width: 1440, height: 900, mobile: false, tier: "wide", offset: 0.72, minRatio: 0.58, maxRatio: 0.62 },
|
||||
{ width: 768, height: 900, mobile: false, tier: "tablet", offset: 0.9 },
|
||||
{ width: 1024, height: 768, mobile: false, tier: "compact", offset: 0.75 },
|
||||
{ width: 1440, height: 900, mobile: false, tier: "wide", offset: 0.72 },
|
||||
];
|
||||
for (const expected of coreLayoutCases) {
|
||||
await glPage.setViewport({ width: expected.width, height: expected.height, isMobile: expected.mobile, hasTouch: expected.mobile });
|
||||
|
|
@ -291,22 +330,37 @@ try {
|
|||
{ timeout: 10000 },
|
||||
expected.tier,
|
||||
);
|
||||
const placement = await glPage.$eval("#coreCanvas", (canvas) => ({
|
||||
tier: canvas.dataset.layoutTier,
|
||||
offset: Number(canvas.dataset.coreOffsetX),
|
||||
ratio: Number(canvas.dataset.coreScreenRatio),
|
||||
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||
actionRows: new Set(
|
||||
[...document.querySelectorAll(".hero-actions > *")]
|
||||
.filter((element) => getComputedStyle(element).display !== "none")
|
||||
.map((element) => Math.round(element.getBoundingClientRect().top)),
|
||||
).size,
|
||||
motionHeight: document.querySelector("#motionToggle").getBoundingClientRect().height,
|
||||
}));
|
||||
const placement = await glPage.$eval("#coreCanvas", (canvas) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const heading = document.querySelector(".hero h1").getBoundingClientRect();
|
||||
const ratio = Number(canvas.dataset.coreScreenRatio);
|
||||
return {
|
||||
tier: canvas.dataset.layoutTier,
|
||||
offset: Number(canvas.dataset.coreOffsetX),
|
||||
ratio,
|
||||
centerX: rect.left + ratio * rect.width,
|
||||
canvasLeft: rect.left,
|
||||
canvasRight: rect.right,
|
||||
heroHeadingRight: heading.right,
|
||||
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||
actionRows: new Set(
|
||||
[...document.querySelectorAll(".hero-actions > *")]
|
||||
.filter((element) => getComputedStyle(element).display !== "none")
|
||||
.map((element) => Math.round(element.getBoundingClientRect().top)),
|
||||
).size,
|
||||
motionHeight: document.querySelector("#motionToggle").getBoundingClientRect().height,
|
||||
};
|
||||
});
|
||||
const suite = `WebGL layout ${expected.width}`;
|
||||
assert(placement.tier === expected.tier, suite, "responsive layout tier is selected", placement.tier);
|
||||
assert(Math.abs(placement.offset - expected.offset) <= 0.01, suite, "core offset matches composition", String(placement.offset));
|
||||
assert(placement.ratio >= expected.minRatio && placement.ratio <= expected.maxRatio, suite, "core screen position fills the hero center", String(placement.ratio));
|
||||
assert(Number.isFinite(placement.ratio) && placement.ratio >= 0 && placement.ratio <= 1, suite, "core screen ratio stays inside canvas", String(placement.ratio));
|
||||
assert(placement.centerX >= placement.canvasLeft && placement.centerX <= placement.canvasRight, suite, "core projection center stays inside canvas", `${placement.centerX}/${placement.canvasLeft}-${placement.canvasRight}`);
|
||||
if (expected.mobile) {
|
||||
assert(placement.ratio >= expected.minRatio && placement.ratio <= expected.maxRatio, suite, "mobile core screen position stays centered", String(placement.ratio));
|
||||
} else {
|
||||
assert(placement.centerX > placement.heroHeadingRight, suite, "desktop core projection clears hero heading", `${placement.centerX}/${placement.heroHeadingRight}`);
|
||||
}
|
||||
assert(placement.overflow <= 0, suite, "visible 3D controls create no horizontal overflow", String(placement.overflow));
|
||||
assert(placement.actionRows === 1, suite, "hero actions stay on one row", String(placement.actionRows));
|
||||
assert(placement.motionHeight >= 44, suite, "motion control keeps a 44px target", String(placement.motionHeight));
|
||||
|
|
@ -403,7 +457,7 @@ const lines = [...suites.entries()].map(([name, result]) => `${result.fail ? "FA
|
|||
const stamp = new Date().toISOString().replace("T", " ").slice(0, 16);
|
||||
fs.writeFileSync(
|
||||
path.join(here, "twentyoz-e2e-report.md"),
|
||||
[`# TWENTYOZ E2E — ${stamp}`, "", fail ? `**${fail} failed · deploy blocked**` : `**${pass} assertions passed · GREEN**`, "", ...lines.map((line) => `- ${line}`), ...(failures.length ? ["", "## Failures", "", ...failures.map((item) => `- ${item}`)] : [])].join("\n") + "\n",
|
||||
[`# TWENTYOZ E2E — ${stamp}`, "", fail ? `**${fail} failed · deploy blocked**` : `**${pass} assertions passed · GREEN**`, "", ...lines.map((line) => `- ${line}`), ...(observations.length ? ["", "## Observations", "", ...observations.map((item) => `- ${item}`)] : []), ...(failures.length ? ["", "## Failures", "", ...failures.map((item) => `- ${item}`)] : [])].join("\n") + "\n",
|
||||
);
|
||||
|
||||
console.log(lines.join("\n"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue