diff --git a/documentation/reference-capability-exemplars.md b/documentation/reference-capability-exemplars.md index e97769fc6..44eb379ed 100644 --- a/documentation/reference-capability-exemplars.md +++ b/documentation/reference-capability-exemplars.md @@ -63,7 +63,6 @@ Generated from 22 bundle(s). | `doxa-simple-map` | `doxa-simple-map` | `parameterized` | | `doxa-vision-card` | `doxa-vision-card` | `parameterized` | | `people-groups-grid` | `people-groups-grid` | `parameterized` | -| `upg-neighbor` | `upg-neighbor` | `parameterized` | ## Capability 3 — multi-view-element (monolithic) @@ -71,6 +70,7 @@ Generated from 22 bundle(s). |---|---|---| | `doxa-research-map` | `research-map` | `monolithic` | | `research-dashboard` | `research-dashboard` | `monolithic` | +| `upg-neighbor` | `upg-neighbor` | `monolithic` | ## Capability 4 — composite-element (nested-element SPA) diff --git a/library/components/poster/PosterWall.vue b/library/components/poster/PosterWall.vue index b3492b889..25f6ddb2b 100644 --- a/library/components/poster/PosterWall.vue +++ b/library/components/poster/PosterWall.vue @@ -47,6 +47,7 @@ import { useI18n } from 'vue-i18n' // (contain:layout on .vtl-content). Measured 1024x793 @ (256,107) at 1280x900 — rail clickable // behind the "modal". See library/utils/viewportScrim.js for the measurements and rejected fixes. import { useViewportScrim } from '@map/utils/viewportScrim.js' +import { bundleTreeRoot } from '@map/utils/embedBase.js' const props = defineProps({ items: { type: Array, required: true }, @@ -149,7 +150,29 @@ const props = defineProps({ const plural = computed(() => props.itemNounPlural || `${props.itemNoun}s`) const base = computed(() => String(props.assetsBase || './assets').replace(/\/+$/, '')) -const previewUrl = (id) => `${base.value}/previews/${id}.webp` + +// ★ A SECOND PLACE TO LOOK, because "poster not generated yet" was being shown for posters that +// ARE generated. The DRIVER reported it on Ethiopia, whose country-eth.webp exists (174 of the 175 +// records have their .webp; India is the one that legitimately does not and carries its own +// reason). MEASURED here — static build AND dev server, every request 200, zero cards in that +// branch — so the file is not missing; a HOST LAYOUT is. resolveAssetsBase points at +// /../assets, the shared pool, which is correct for the layout generate-tree documents +// and wrong on a host that publishes only the tree folder. generate-tree ALSO stages a +// self-contained copy INSIDE the tree ("staged 421 runtime asset(s) → doxa-maps/assets"), and +// that copy is reachable on both layouts. So: try the pool, and if the bytes do not arrive, try +// the copy before telling the user anything. +const fallbackBase = computed(() => { + const root = String(bundleTreeRoot() || '').replace(/\/+$/, '') + return root ? `${root}/assets` : '' +}) +// 0/absent = first attempt, 1 = the fallback is being tried. +const attempt = ref({}) +const previewUrl = (id) => `${attempt.value[id] ? fallbackBase.value : base.value}/previews/${id}.webp` +function onPreviewError(id) { + const fb = fallbackBase.value + if (!attempt.value[id] && fb && fb !== base.value) { attempt.value = { ...attempt.value, [id]: 1 }; return } + missing.value = { ...missing.value, [id]: true } +} // Sections ordered by TOTAL count (largest first), items within a section by their // own count — the densest prayer need leads at BOTH levels. @@ -630,11 +653,18 @@ useShadowStyles( @click="previewIsControl && exportPoster(item)" @keydown.enter.prevent="previewIsControl && exportPoster(item)" @keydown.space.prevent="previewIsControl && exportPoster(item)" - @error="missing[item.id] = true" /> + @error="onPreviewError(item.id)" />
{{ item.label }}
{{ item.count.toLocaleString() }} people groups
-
poster not generated yet
+ +
preview unavailable
diff --git a/plugin-designs/regions-dashboard/dxlife-tokens.js b/plugin-designs/regions-dashboard/dxlife-tokens.js index 88b49bbcf..97be4b5fd 100644 --- a/plugin-designs/regions-dashboard/dxlife-tokens.js +++ b/plugin-designs/regions-dashboard/dxlife-tokens.js @@ -20,10 +20,19 @@ // rem values would resolve against the host's root (16px by default) and every space would be // 1.6x too large. That is exactly why these were resolved to px in the first place. // -// THE VALUES ARE OURS AS THEY ALREADY WERE, not re-derived from live. Live carries more precision -// (2.352637rem = 23.52637px vs our 23.5264px). Naming the numbers and changing the numbers are two -// different changes; this one only names them, which is what lets a full computed-style -// fingerprint prove it altered nothing. Re-deriving would be a separate, separately measured step. +// THE VALUES ARE NOW LIVE'S FULL PRECISION. They started as the rounded numbers this file was +// created to name (23.5264px for --spacing-lg, where live is 2.352637rem = 23.52637px), and the +// header said re-deriving them would be "a separate, separately measured step". THIS IS THAT STEP, +// and a measurement forced it: their country-list bullet is sized in em, 1.5em of --font-size-md, +// and against live it came out 29.2812px where theirs is 29.2969px. 0.0157px, which is nothing to +// look at — but it is the ROUNDING of --font-size-md (19.5312 for their 19.53125) multiplied by +// 1.5, and every other em- or percentage-derived length on the page inherits the same error. The +// scales are exact powers of their two ratios (type 1.25, spacing 1.33) on a 10px root, so full +// precision is available for free and there is no reason to ship a rounded clone. +// type: 10px * 1.25^n -> 15.625 / 19.53125 / 24.4140625 / 30.517578125 / ... +// spacing: 10px * 1.33^n -> 13.3 / 17.689 / 23.52637 / 31.2900721 / ... +// The vs-live diffs compare with a +/-0.5px tolerance, so this cannot be what turns one green; the +// bullet's exact-equality compare is what it is for. // // Mapping verified against live's own custom properties, not arithmetic: // spacing 3xs .5653230821rem · 2xs .7518796992 · md 1.7689 · lg 2.352637 · xl 3.12900721 @@ -36,10 +45,10 @@ // a magic number because it reads as deliberate. export const DXLIFE_TOKENS = ':host{' + - ' --spacing-3xs:5.6532px; --spacing-2xs:7.5188px; --spacing-md:17.689px;' + - ' --spacing-lg:23.5264px; --spacing-xl:31.2901px; --spacing-2xl:41.6158px;' + - ' --spacing-3xl:55.349px; --spacing-4xl:73.6142px;' + - ' --font-size-sm:15.625px; --font-size-md:19.5312px; --font-size-lg:24.4141px;' + - ' --font-size-xl:30.5176px; --font-size-2xl:38.147px; --font-size-3xl:47.6837px;' + - ' --font-size-4xl:59.6046px; --font-size-5xl:74.5058px;' + + ' --spacing-3xs:5.653230821px; --spacing-2xs:7.518796992px; --spacing-md:17.689px;' + + ' --spacing-lg:23.52637px; --spacing-xl:31.2900721px; --spacing-2xl:41.615795893px;' + + ' --spacing-3xl:55.349008538px; --spacing-4xl:73.614181355px;' + + ' --font-size-sm:15.625px; --font-size-md:19.53125px; --font-size-lg:24.4140625px;' + + ' --font-size-xl:30.517578125px; --font-size-2xl:38.146972656px; --font-size-3xl:47.68371582px;' + + ' --font-size-4xl:59.604644775px; --font-size-5xl:74.505805969px;' + '}' diff --git a/plugin-designs/regions-dashboard/region-dashboards.vue b/plugin-designs/regions-dashboard/region-dashboards.vue index d44de56d2..b2c2e42fd 100644 --- a/plugin-designs/regions-dashboard/region-dashboards.vue +++ b/plugin-designs/regions-dashboard/region-dashboards.vue @@ -651,7 +651,13 @@ useShadowStyles( ' --dx-surface:#f3f3f1; --dx-elevated:#fff; --dx-text:#2a332a; --dx-header-bg:#3b463d;' + ' --dx-header-text:#fff; --dx-muted:#6d766e; --dx-line:#d1d4cd; --dx-accent:#92b195;' + ' --dx-dark:#2a332a; --dx-on-dark:#f3f3f1; --dx-track:#4e594f; --dx-partial:#f0c64a;' + - ' --dx-radius:35px;' + + ' --dx-radius:35px; --dx-radius-lg:25px;' + + // base/_globals.scss:99-105 + :205-207 style EVERY table on their site: --table-border-color + // is $color-gray-500 (#666) and --border-radius-lg is 2.5rem at their 10px root = 25px. + ' --dx-table-border:#666;' + + // base/_globals.scss:132-141 styles EVERY anchor: --color-brand-light on hover to + // --color-brand-lighter. $color-green-400 / $color-green-300. + ' --dx-link:#4e594f; --dx-link-hover:#6d766e;' + ' background:var(--dx-surface); color:var(--dx-text);' + ' font:var(--font-size-md)/1.5 Poppins,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; }' + // Same responsive context + same 1200px cap as tab 1 (regions-list.vue). DX-life applies its @@ -669,15 +675,24 @@ useShadowStyles( // We cannot set this on the host document from inside a shadow root, and should not; // this element's own scroller is the box every child measures against. '.rd-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; scrollbar-gutter:stable; -webkit-overflow-scrolling:touch;' + - ' padding:var(--spacing-3xl) 0 var(--spacing-4xl); display:flex; flex-direction:column; gap:var(--spacing-3xl);' + + ' padding:var(--spacing-3xl) 0 var(--spacing-4xl); display:flex; flex-direction:column;' + ' container-type:inline-size; }' + + '.rd-scroll > * + *{ margin-top:var(--spacing-3xl); }' + // .stack .stack--3xl '.rd-scroll > *{ box-sizing:border-box; width:calc(90% + 16px); max-width:1200px;' + ' margin-left:auto; margin-right:auto; }' + '.rd-status{ padding:28px; text-align:center; color:var(--dx-muted); font-size:var(--font-size-md); }' + '.rd-status.rd-failed{ font-weight:600; }' + // ── head: RegionDetail.vue:47-51, a .stack--xs (7.5px) block, centred ── - '.rd-head{ display:flex; flex-direction:column; gap:10px; text-align:center; }' + + // ★ EVERY .stack HERE IS NOW THEIR MECHANISM, not `gap`. The goal card proved the two are not + // interchangeable (a child's margin-top:auto can override a margin and cannot override a gap), + // and the extended live diff then caught the residue: their .stack rowGap computes to `normal` + // because the spacing is margin, while ours read 17.689px. Same pixels, different code — and the + // DRIVER asked for the code to match, not just the screenshot. Converted, so the next + // margin-overriding child anywhere on this page behaves as it does on their page. + // .stack .stack--xs — --spacing-xs is 1rem at their 10px root. + '.rd-head{ display:flex; flex-direction:column; text-align:center; }' + + '.rd-head > * + *{ margin-top:10px; }' + '.rd-backlink{ margin:0; }' + // their .light-link on a dark-on-light page: brand-light, underlined on hover // THEIR back-link is an , so it inherits the UA underline plus their @@ -734,7 +749,9 @@ useShadowStyles( // clone below is untouched by it. '.rd-chooser{ display:flex; flex-wrap:wrap; gap:13.3px; align-items:flex-end;' + ' justify-content:center; }' + - '.rd-mapblock{ display:flex; flex-direction:column; gap:13.3px; }' + + // .stack .stack--sm — --spacing-sm, 1.33rem. + '.rd-mapblock{ display:flex; flex-direction:column; }' + + '.rd-mapblock > * + *{ margin-top:13.3px; }' + '.rd-mapnote{ margin:0; text-align:center; color:#92b195; font-weight:700; font-size:var(--font-size-md); }' + // .rounded-xlg on their ; the child takes border-radius:inherit from here. '.rd-mapframe{ border-radius:var(--dx-radius); overflow:hidden; }' + @@ -776,7 +793,16 @@ useShadowStyles( // heading keeps the full clamp -- it has 1200px and no such constraint. // .stack--lg (var(--spacing-lg)) inside each card, centred; .region-goal pins to the bottom // (margin-top:auto) so three cards of different heights still align their goal lines. - '.rd-goal-card{ display:flex; flex-direction:column; gap:var(--spacing-lg); text-align:center; }' + + // ★ THEIR .stack IS margin-top, AND THAT IS LOAD-BEARING — not a stylistic detail. + // composition/_stack.scss: `.stack > * + * { margin-top: var(--stack-spacing) }`, with + // `.stack--lg` setting that to --spacing-lg. RegionDetail.vue's scoped + // `.region-goal { margin-top: auto }` then OVERRIDES the same property on the last child, + // so the goal line is pushed to the bottom and the 23.526px stack gap above it is GONE. + // This was ported as `gap`, which margin cannot override: gap AND the auto both applied, + // so every goal card carried ~23.5px of space their cards do not have. The DRIVER saw it + // ('spcing is nto ths ame', naming these three cards). Ported as their mechanism. + '.rd-goal-card{ display:flex; flex-direction:column; text-align:center; }' + + '.rd-goal-card > * + *{ margin-top:var(--spacing-lg); }' + // h2: _globals.scss clamp(--font-size-2xl, 6vw, --font-size-4xl) = clamp(var(--font-size-2xl),_,var(--font-size-4xl)) '.rd-h2{ margin:0; font-size:clamp(var(--font-size-2xl), 6cqw, var(--font-size-4xl)); line-height:1;' + ' font-family:"Bebas Neue","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' + @@ -791,8 +817,16 @@ useShadowStyles( '.rd-big{ margin:0; font-size:var(--font-size-4xl); font-weight:500; line-height:1.1; }' + '.rd-goal-card .rd-big{ white-space:nowrap; }' + '.rd-big-label{ margin:0; font-size:var(--font-size-lg); }' + - '.rd-stack-xs{ display:flex; flex-direction:column; gap:10px; }' + - '.rd-goal{ margin:auto 0 0; font-size:var(--font-size-sm); }' + + // .stack .stack--xs, inside the Prayer Coverage card. + '.rd-stack-xs{ display:flex; flex-direction:column; }' + + '.rd-stack-xs > * + *{ margin-top:10px; }' + + // .region-goal — margin-top:auto, spelled at higher specificity than the stack rule above + // so the override is unambiguous rather than depending on source order. + '.rd-goal{ margin:0; font-size:var(--font-size-sm); }' + + // base/_globals.scss:200-202 — is --font-weight-medium (500) on their site, not the + // UA's bold. The 'Goal:' label in all three cards is a , so ours was rendering 700. + '.rd-root strong{ font-weight:500; }' + + '.rd-goal-card > .rd-goal{ margin-top:auto; }' + // ── .progress-bar: blocks/_progress-bar.scss. The HEIGHT comes from padding-top, not height // -- clamp(15px, 6%, 25px) at [data-size="md"] -- so the track scales with its own width. @@ -817,16 +851,45 @@ useShadowStyles( // ── the breakdown table: RegionDetail.vue's scoped block. min-width 560px inside an // overflow-x:auto wrapper is how they keep five columns readable on a phone instead of // crushing them -- the table scrolls sideways, the page does not. - '.rd-section{ display:flex; flex-direction:column; gap:var(--spacing-md); }' + + // ★ base/_globals.scss:19-23 — their countries block is a
, and EVERY
on + // their site carries padding-top clamp(--spacing-2xl, 6vw, --spacing-4xl) and padding-bottom + // --spacing-4xl. At 1440 that is 73.614px above and below, ON TOP of the --spacing-3xl the + // page stack already puts between blocks: 128.96px of air above the heading, where ours had + // 55.35px and nothing below. Another global rule, another thing reading RegionDetail.vue + // alone could not tell me. Same clamp, in container units for the embedded case. + '.rd-section{ display:flex; flex-direction:column;' + + ' padding-top:clamp(var(--spacing-2xl), 6cqw, var(--spacing-4xl));' + + ' padding-bottom:var(--spacing-4xl); }' + + '.rd-section > * + *{ margin-top:var(--spacing-md); }' + // .stack .stack--md + '.rd-table-scroll{ overflow-x:auto; }' + - '.rd-table{ width:100%; min-width:560px; border-collapse:collapse; font-size:var(--font-size-md); }' + + // ★ base/_globals.scss:99-105 — the part of their table that is NOT in RegionDetail's + // scoped block and was therefore missed: every on their site is a rounded, + // ring-outlined WHITE card, and every
carries a 1px gridline (:205-207). Ours was a + // bare borderless table on the page background. That is what the DRIVER meant by 'the + // tbabel compoent is not the same' — the rule lived in a global stylesheet, not next to + // the markup, so reading RegionDetail.vue alone could not show it. + // overflow:hidden is what makes border-radius clip the header's own background. + '.rd-table{ width:100%; min-width:560px; border-collapse:collapse; font-size:var(--font-size-md);' + + ' border-radius:var(--dx-radius-lg); overflow:hidden; background:#fff;' + + ' box-shadow:0 0 0 1px var(--dx-table-border); }' + + // td ONLY — their th has no border of its own, it has the header fill. + '.rd-table td{ border:1px solid var(--dx-table-border); }' + '.rd-table th, .rd-table td{ padding:var(--spacing-2xs) 13.3px; text-align:start; }' + '.rd-table th:not(:first-child), .rd-table td:not(:first-child){ text-align:end; }' + '.rd-table th{ background:var(--dx-muted); color:var(--dx-on-dark); font-weight:500; }' + '.rd-tr--selected{ background:#e8ece7; font-weight:600; }' + + // ★ Their country cell is a , i.e. an , which picks up base/_globals.scss:132-141: + // --color-brand-light, --font-weight-medium, the UA underline at 2px thickness / 4px offset, + // hovering to --color-brand-lighter. Ours is a