pandorga 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/_includes/content-runtime/41-listing-helpers.html +7 -0
- data/_includes/content-runtime/43-blocks.html +4 -3
- data/_includes/content-runtime/60-fragments.html +77 -11
- data/_includes/theme/critical-preload.html +35 -0
- data/_includes/theme/json-ld.html +35 -0
- data/_layouts/articles.html +27 -5
- data/_layouts/cv.html +20 -14
- data/_layouts/home.html +23 -3
- data/_layouts/shell.html +48 -11
- data/assets/css/ledger/cv.css +13 -0
- data/assets/css/ledger/home.css +27 -0
- data/assets/css/ledger/writing.css +30 -3
- data/assets/css/tailwind.css +3 -0
- data/assets/tailwind-play.js +2 -1
- data/lib/pandorga/discovery.rb +78 -0
- data/lib/pandorga/jekyll/plugins/discovery.rb +32 -0
- data/lib/pandorga/version.rb +1 -1
- data/scripts/cdn-allowlist.json +2 -5
- data/scripts/tailwind/build.sh +19 -0
- data/scripts/tailwind/input.css +12 -0
- data/scripts/tailwind/tailwind.config.js +102 -0
- data/scripts/test/render-parity/blocks/project-inventory-row.json +3 -2
- data/scripts/test/test-css-delivery.rb +32 -0
- data/scripts/test/test-discovery.rb +46 -0
- data/scripts/test/test-examples-build.rb +15 -0
- data/scripts/test/test-hero-portraits.mjs +1 -1
- data/scripts/test/test-hydrate-reserve.rb +68 -0
- data/scripts/validate.sh +3 -0
- metadata +12 -2
- data/assets/css/cv.css +0 -13
data/assets/css/ledger/home.css
CHANGED
|
@@ -239,6 +239,10 @@
|
|
|
239
239
|
margin: 0;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
.home-hero-tagline:empty {
|
|
243
|
+
min-height: calc(var(--type-body) * 1.35);
|
|
244
|
+
}
|
|
245
|
+
|
|
242
246
|
/* Name spans the copy column (container query on .home-hero-copy). The floor
|
|
243
247
|
is the band-title step — Articles, Rascunhos, Projects — so a phone does
|
|
244
248
|
not drop the name to the card size to keep it on one line. From 640px the
|
|
@@ -308,6 +312,12 @@
|
|
|
308
312
|
line-height: var(--leading-body);
|
|
309
313
|
}
|
|
310
314
|
|
|
315
|
+
/* Hold the summary's box until the fragment paints, so the CTA under it
|
|
316
|
+
does not jump when the paragraph arrives. */
|
|
317
|
+
.home-hero-summary:empty {
|
|
318
|
+
min-height: calc(var(--type-body) * var(--leading-body) * 6);
|
|
319
|
+
}
|
|
320
|
+
|
|
311
321
|
.home-hero-summary p {
|
|
312
322
|
margin: 0 0 var(--space-md);
|
|
313
323
|
}
|
|
@@ -781,6 +791,16 @@ html[data-theme="light"] .home-hero-plate {
|
|
|
781
791
|
}
|
|
782
792
|
|
|
783
793
|
/* ── Writing cards ── */
|
|
794
|
+
.home-card-grid:empty,
|
|
795
|
+
.home-draft-list:empty,
|
|
796
|
+
[data-home-featured-slot]:empty {
|
|
797
|
+
min-height: 16rem;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.home-project-inventory:empty {
|
|
801
|
+
min-height: 8rem;
|
|
802
|
+
}
|
|
803
|
+
|
|
784
804
|
.home-card-grid {
|
|
785
805
|
display: grid;
|
|
786
806
|
grid-template-columns: minmax(0, 1fr);
|
|
@@ -1232,6 +1252,13 @@ html[data-theme="light"] .home-hero-plate {
|
|
|
1232
1252
|
padding: 0;
|
|
1233
1253
|
}
|
|
1234
1254
|
|
|
1255
|
+
button.home-project-inventory-row {
|
|
1256
|
+
font: inherit;
|
|
1257
|
+
color: inherit;
|
|
1258
|
+
text-align: start;
|
|
1259
|
+
width: 100%;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1235
1262
|
.home-project-inventory-row {
|
|
1236
1263
|
position: relative;
|
|
1237
1264
|
display: flex;
|
|
@@ -272,10 +272,37 @@ html[data-theme="light"] .writing-index .writing-lead-media .ledger-poster {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
/* The revision date is the quieter half of the ledger line
|
|
276
|
-
|
|
275
|
+
/* The revision date is the quieter half of the ledger line. It uses the
|
|
276
|
+
variant ink solid: a 78% mix with transparent measured 3.69 and 3.90
|
|
277
|
+
against the light paper, under the 4.5:1 text floor. */
|
|
277
278
|
.writing-index .writing-revised {
|
|
278
|
-
color:
|
|
279
|
+
color: var(--color-on-surface-variant);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.writing-index .writing-archive-reserve {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
gap: var(--space-md);
|
|
286
|
+
max-width: none;
|
|
287
|
+
padding: 0;
|
|
288
|
+
background: transparent;
|
|
289
|
+
border: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.writing-index .writing-reserve-row {
|
|
293
|
+
display: block;
|
|
294
|
+
min-height: 7.25rem;
|
|
295
|
+
border: 1.5px solid var(--border-ink);
|
|
296
|
+
border-radius: var(--radius-card);
|
|
297
|
+
background: var(--color-surface-card);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Hold the featured panel only until the listing decides. A standing
|
|
301
|
+
:empty rule kept this 16rem gap on page 2 and later, where no specimen
|
|
302
|
+
is drawn, between the header rule and the filter. */
|
|
303
|
+
#articles-lead.is-pending:empty {
|
|
304
|
+
min-height: 16rem;
|
|
305
|
+
margin-bottom: var(--space-xl);
|
|
279
306
|
}
|
|
280
307
|
|
|
281
308
|
/* ══ Archive and rail ══
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/* Compiled Tailwind utilities (3.4.17, forms plugin). Replaces the Play CDN.
|
|
2
|
+
Regenerate with scripts/tailwind/build.sh after adding a utility class. */
|
|
3
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-body),sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-full{top:100%}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.col-span-full{grid-column:1/-1}.m-0{margin:0}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1\.5{margin-top:.375rem;margin-bottom:.375rem}.my-8{margin-top:2rem;margin-bottom:2rem}.my-\[1\.55em\]{margin-top:1.55em;margin-bottom:1.55em}.mb-10{margin-bottom:2.5rem}.mb-16{margin-bottom:4rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mb-\[0\.35rem\]{margin-bottom:.35rem}.ml-2{margin-left:.5rem}.mt-1{margin-top:.25rem}.mt-16{margin-top:4rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.\!grid{display:grid!important}.grid{display:grid}.contents{display:contents}.\!hidden{display:none!important}.hidden{display:none}.h-12{height:3rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-\[12vw\]{height:12vw}.h-\[1px\]{height:1px}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[14rem\]{min-width:14rem}.min-w-\[3\.5rem\]{min-width:3.5rem}.min-w-\[9\.5rem\]{min-width:9.5rem}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-xs{max-width:20rem}.flex-shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.resize{resize:both}.scroll-mt-28{scroll-margin-top:7rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.rounded{border-radius:.125rem}.rounded-\[9999px\]{border-radius:9999px}.border{border-width:1px}.border-0{border-width:0}.border-t{border-top-width:1px}.border-gentle{border-color:var(--border-gentle)}.border-gentle-divider{border-color:var(--border-gentle-divider)}.border-gentle-subtle{border-color:var(--border-gentle-subtle)}.bg-background{background-color:var(--color-background)}.bg-surface{background-color:var(--color-surface)}.bg-surface-container-highest{background-color:var(--color-surface-container-highest)}.bg-surface-container-low{background-color:var(--color-surface-container-low)}.bg-transparent{background-color:transparent}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-1{padding:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pb-16{padding-bottom:4rem}.pb-3\.5{padding-bottom:.875rem}.pt-10{padding-top:2.5rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.align-top{vertical-align:top}.font-body{font-family:var(--font-body),sans-serif}.font-label{font-family:var(--font-label),serif}.font-mono{font-family:var(--font-mono)}.font-title{font-family:var(--font-title)}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.8em\]{font-size:.8em}.text-\[0\.9em\]{font-size:.9em}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-light{font-weight:300}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.ordinal{--tw-ordinal:ordinal}.ordinal,.tabular-nums{font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.tabular-nums{--tw-numeric-spacing:tabular-nums}.leading-\[1\.55\]{line-height:1.55}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-widest{letter-spacing:.1em}.text-on-surface{color:var(--color-on-surface)}.text-on-surface-variant{color:var(--color-on-surface-variant)}.text-outline{color:var(--color-outline)}.text-primary{color:var(--color-primary)}.text-secondary{color:var(--color-secondary)}.text-tertiary{color:var(--color-tertiary)}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline{outline-style:solid}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.bg-outline-variant\/30{background-color:color-mix(in srgb,var(--color-outline-variant) 30%,transparent)}.border-tertiary\/40{border-color:color-mix(in srgb,var(--color-tertiary) 40%,transparent)}.hover\:border-primary:hover{border-color:var(--color-primary)}.hover\:border-tertiary:hover{border-color:var(--color-tertiary)}.hover\:bg-surface-container-low:hover{background-color:var(--color-surface-container-low)}.hover\:text-primary:hover{color:var(--color-primary)}.hover\:underline:hover{text-decoration-line:underline}.group:hover .group-hover\:bg-primary-container{background-color:var(--color-primary-container)}.group:hover .group-hover\:text-on-primary-container{color:var(--color-on-primary-container)}@media (min-width:640px){.sm\:gap-4{gap:1rem}.sm\:gap-5{gap:1.25rem}.sm\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:768px){.md\:sticky{position:sticky}.md\:top-32{top:8rem}.md\:mb-12{margin-bottom:3rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:px-16{padding-left:4rem;padding-right:4rem}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (min-width:1024px){.lg\:sticky{position:sticky}.lg\:top-32{top:8rem}.lg\:gap-12{gap:3rem}}
|
data/assets/tailwind-play.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
#
|
|
2
|
+
# Kept as the readable theme binding. The shell does not load this file.
|
|
3
|
+
# Utilities are compiled to assets/css/tailwind.css (scripts/tailwind/build.sh).
|
|
3
4
|
# Named *-play.js so the Tailwind IntelliSense extension does not treat it as
|
|
4
5
|
# tailwind.config.js and fail with "tailwind is not defined" / _site duplicates.
|
|
5
6
|
---
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pandorga
|
|
4
|
+
# Sitemap and robots text. No Jekyll dependency, so the gate can call it
|
|
5
|
+
# without building a site.
|
|
6
|
+
module Discovery
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def origin(config)
|
|
10
|
+
config = {} unless config.is_a?(Hash)
|
|
11
|
+
pandorga = config["pandorga"]
|
|
12
|
+
pandorga = {} unless pandorga.is_a?(Hash)
|
|
13
|
+
identity = pandorga["identity"]
|
|
14
|
+
identity = {} unless identity.is_a?(Hash)
|
|
15
|
+
url = identity["url"].to_s.strip
|
|
16
|
+
url = config["url"].to_s.strip if url.empty?
|
|
17
|
+
url.sub(%r{/\z}, "")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def absolute(origin, path)
|
|
21
|
+
path = path.to_s
|
|
22
|
+
return path if path.match?(%r{\Ahttps?://}i)
|
|
23
|
+
|
|
24
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
25
|
+
path = "/" if path.empty?
|
|
26
|
+
base = origin.to_s.sub(%r{/\z}, "")
|
|
27
|
+
return path if base.empty?
|
|
28
|
+
|
|
29
|
+
"#{base}#{path}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def public_paths(pages)
|
|
33
|
+
Array(pages).filter_map do |page|
|
|
34
|
+
data = page_data(page)
|
|
35
|
+
next if data["sitemap"] == false
|
|
36
|
+
|
|
37
|
+
url = page.respond_to?(:url) ? page.url.to_s : page["url"].to_s
|
|
38
|
+
next if url.empty?
|
|
39
|
+
next if File.extname(url).match?(/\A\.(xml|txt|json)\z/i)
|
|
40
|
+
|
|
41
|
+
url
|
|
42
|
+
end.uniq
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sitemap_xml(origin, paths)
|
|
46
|
+
locs = Array(paths).map { |path| absolute(origin, path) }.uniq
|
|
47
|
+
body = locs.map { |loc| " <url><loc>#{xml_escape(loc)}</loc></url>" }.join("\n")
|
|
48
|
+
<<~XML
|
|
49
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
50
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
51
|
+
#{body}
|
|
52
|
+
</urlset>
|
|
53
|
+
XML
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def robots_txt(sitemap_url)
|
|
57
|
+
<<~TXT
|
|
58
|
+
User-agent: *
|
|
59
|
+
Allow: /
|
|
60
|
+
Disallow: /studio/
|
|
61
|
+
|
|
62
|
+
Sitemap: #{sitemap_url}
|
|
63
|
+
TXT
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def xml_escape(value)
|
|
67
|
+
value.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">").gsub('"', """)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def page_data(page)
|
|
71
|
+
return page.data if page.respond_to?(:data) && page.data.is_a?(Hash)
|
|
72
|
+
|
|
73
|
+
data = page["data"]
|
|
74
|
+
data.is_a?(Hash) ? data : {}
|
|
75
|
+
end
|
|
76
|
+
private_class_method :page_data
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jekyll"
|
|
4
|
+
require_relative "../../discovery"
|
|
5
|
+
|
|
6
|
+
module Pandorga
|
|
7
|
+
module Jekyll
|
|
8
|
+
# Writes /sitemap.xml and /robots.txt from the pages already generated.
|
|
9
|
+
class DiscoveryGenerator < ::Jekyll::Generator
|
|
10
|
+
safe true
|
|
11
|
+
priority :lowest
|
|
12
|
+
|
|
13
|
+
def generate(site)
|
|
14
|
+
origin = Pandorga::Discovery.origin(site.config)
|
|
15
|
+
paths = Pandorga::Discovery.public_paths(site.pages)
|
|
16
|
+
sitemap = Pandorga::Discovery.absolute(origin, "/sitemap.xml")
|
|
17
|
+
emit(site, "sitemap.xml", Pandorga::Discovery.sitemap_xml(origin, paths))
|
|
18
|
+
emit(site, "robots.txt", Pandorga::Discovery.robots_txt(sitemap))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def emit(site, name, content)
|
|
24
|
+
page = ::Jekyll::PageWithoutAFile.new(site, site.source, "", name)
|
|
25
|
+
page.data["layout"] = nil
|
|
26
|
+
page.data["sitemap"] = false
|
|
27
|
+
page.content = content
|
|
28
|
+
site.pages << page
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/pandorga/version.rb
CHANGED
data/scripts/cdn-allowlist.json
CHANGED
|
@@ -5,15 +5,12 @@
|
|
|
5
5
|
"https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.js",
|
|
6
6
|
"https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
|
|
7
7
|
"https://cdn.jsdelivr.net/npm/plotly.js-dist-min@3.1.0/plotly.min.js",
|
|
8
|
-
"https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css"
|
|
9
|
-
"https://cdn.tailwindcss.com?plugins=forms,container-queries"
|
|
8
|
+
"https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css"
|
|
10
9
|
],
|
|
11
10
|
"scan_globs": [
|
|
12
11
|
"_layouts",
|
|
13
12
|
"_includes",
|
|
14
13
|
"content/media/plots"
|
|
15
14
|
],
|
|
16
|
-
"exceptions": {
|
|
17
|
-
"tailwind_play_cdn": "Tailwind play CDN cannot be SRI-pinned; URL must match exactly."
|
|
18
|
-
}
|
|
15
|
+
"exceptions": {}
|
|
19
16
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Rebuild assets/css/tailwind.css. Not part of the site build or validate.sh.
|
|
3
|
+
# Requires tailwindcss 3.4 and @tailwindcss/forms on NODE_PATH or via npx.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
7
|
+
cd "$ROOT"
|
|
8
|
+
|
|
9
|
+
npx --yes tailwindcss@3.4.17 -c scripts/tailwind/tailwind.config.js \
|
|
10
|
+
-i scripts/tailwind/input.css \
|
|
11
|
+
-o assets/css/tailwind.css \
|
|
12
|
+
--minify
|
|
13
|
+
|
|
14
|
+
HEADER='/* Compiled Tailwind utilities (3.4.17, forms plugin). Replaces the Play CDN.
|
|
15
|
+
Regenerate with scripts/tailwind/build.sh after adding a utility class. */
|
|
16
|
+
'
|
|
17
|
+
TMP="$(mktemp)"
|
|
18
|
+
printf '%s' "$HEADER" | cat - assets/css/tailwind.css > "$TMP"
|
|
19
|
+
mv "$TMP" assets/css/tailwind.css
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/* Opacity modifiers do not apply to colors stored as var(--token). */
|
|
6
|
+
.bg-outline-variant\/30 {
|
|
7
|
+
background-color: color-mix(in srgb, var(--color-outline-variant) 30%, transparent);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.border-tertiary\/40 {
|
|
11
|
+
border-color: color-mix(in srgb, var(--color-tertiary) 40%, transparent);
|
|
12
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const forms = require("@tailwindcss/forms");
|
|
3
|
+
|
|
4
|
+
const root = path.join(__dirname, "..", "..");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
content: [
|
|
8
|
+
path.join(root, "_layouts/**/*.{html,liquid}"),
|
|
9
|
+
path.join(root, "_includes/**/*.{html,liquid,js}"),
|
|
10
|
+
path.join(root, "assets/**/*.{js,html,liquid}"),
|
|
11
|
+
],
|
|
12
|
+
darkMode: ["selector", '[data-theme="dark"]'],
|
|
13
|
+
theme: {
|
|
14
|
+
extend: {
|
|
15
|
+
colors: {
|
|
16
|
+
primary: "var(--color-primary)",
|
|
17
|
+
secondary: "var(--color-secondary)",
|
|
18
|
+
tertiary: "var(--color-tertiary)",
|
|
19
|
+
"primary-container": "var(--color-primary-container)",
|
|
20
|
+
"secondary-container": "var(--color-secondary-container)",
|
|
21
|
+
"tertiary-container": "var(--color-tertiary-container)",
|
|
22
|
+
"on-primary": "var(--color-on-primary)",
|
|
23
|
+
"on-secondary": "var(--color-on-secondary)",
|
|
24
|
+
"on-tertiary": "var(--color-on-tertiary)",
|
|
25
|
+
"on-primary-container": "var(--color-on-primary-container)",
|
|
26
|
+
"on-secondary-container": "var(--color-on-secondary-container)",
|
|
27
|
+
"on-tertiary-container": "var(--color-on-tertiary-container)",
|
|
28
|
+
"on-surface": "var(--color-on-surface)",
|
|
29
|
+
"on-surface-variant": "var(--color-on-surface-variant)",
|
|
30
|
+
"on-background": "var(--color-on-background)",
|
|
31
|
+
outline: "var(--color-outline)",
|
|
32
|
+
"outline-variant": "var(--color-outline-variant)",
|
|
33
|
+
"gentle-faint": "var(--border-gentle-faint)",
|
|
34
|
+
"gentle-subtle": "var(--border-gentle-subtle)",
|
|
35
|
+
"gentle-divider": "var(--border-gentle-divider)",
|
|
36
|
+
gentle: "var(--border-gentle)",
|
|
37
|
+
"gentle-hover": "var(--border-gentle-hover)",
|
|
38
|
+
background: "var(--color-background)",
|
|
39
|
+
"background-secondary": "var(--color-background-secondary)",
|
|
40
|
+
surface: "var(--color-surface)",
|
|
41
|
+
"surface-bright": "var(--color-surface-bright)",
|
|
42
|
+
"surface-container-lowest": "var(--color-surface-container-lowest)",
|
|
43
|
+
"surface-container-low": "var(--color-surface-container-low)",
|
|
44
|
+
"surface-container": "var(--color-surface-container)",
|
|
45
|
+
"surface-container-high": "var(--color-surface-container-high)",
|
|
46
|
+
"surface-container-highest": "var(--color-surface-container-highest)",
|
|
47
|
+
"surface-variant": "var(--color-surface-variant)",
|
|
48
|
+
"surface-dim": "var(--color-surface-dim)",
|
|
49
|
+
"panel-contrast": "var(--color-panel-contrast)",
|
|
50
|
+
"surface-card": "var(--color-surface-card)",
|
|
51
|
+
"surface-card-contrast": "var(--color-surface-card-contrast)",
|
|
52
|
+
"surface-high": "var(--color-surface-high)",
|
|
53
|
+
"surface-high-contrast": "var(--color-surface-high-contrast)",
|
|
54
|
+
"surface-tint": "var(--color-surface-tint)",
|
|
55
|
+
"inverse-primary": "var(--color-inverse-primary)",
|
|
56
|
+
"inverse-surface": "var(--color-inverse-surface)",
|
|
57
|
+
"inverse-on-surface": "var(--color-inverse-on-surface)",
|
|
58
|
+
"primary-fixed": "var(--color-primary-fixed)",
|
|
59
|
+
"primary-fixed-dim": "var(--color-primary-fixed-dim)",
|
|
60
|
+
"secondary-fixed": "var(--color-secondary-fixed)",
|
|
61
|
+
"secondary-fixed-dim": "var(--color-secondary-fixed-dim)",
|
|
62
|
+
"tertiary-fixed": "var(--color-tertiary-fixed)",
|
|
63
|
+
"tertiary-fixed-dim": "var(--color-tertiary-fixed-dim)",
|
|
64
|
+
"on-primary-fixed": "var(--color-on-primary-fixed)",
|
|
65
|
+
"on-primary-fixed-variant": "var(--color-on-primary-fixed-variant)",
|
|
66
|
+
"on-secondary-fixed": "var(--color-on-secondary-fixed)",
|
|
67
|
+
"on-secondary-fixed-variant": "var(--color-on-secondary-fixed-variant)",
|
|
68
|
+
"on-tertiary-fixed": "var(--color-on-tertiary-fixed)",
|
|
69
|
+
"on-tertiary-fixed-variant": "var(--color-on-tertiary-fixed-variant)",
|
|
70
|
+
error: "var(--color-error)",
|
|
71
|
+
"on-error": "var(--color-on-error)",
|
|
72
|
+
"error-container": "var(--color-error-container)",
|
|
73
|
+
"on-error-container": "var(--color-on-error-container)",
|
|
74
|
+
"res-youtube": "var(--color-res-youtube)",
|
|
75
|
+
"res-podcast": "var(--color-res-podcast)",
|
|
76
|
+
"res-doc": "var(--color-res-doc)",
|
|
77
|
+
link: "var(--color-link)",
|
|
78
|
+
"link-hover": "var(--color-link-hover)",
|
|
79
|
+
},
|
|
80
|
+
fontFamily: {
|
|
81
|
+
title: ["var(--font-title)"],
|
|
82
|
+
subtitle: ["var(--font-subtitle)", "serif"],
|
|
83
|
+
label: ["var(--font-label)", "serif"],
|
|
84
|
+
body: ["var(--font-body)", "sans-serif"],
|
|
85
|
+
headline: ["var(--font-title)"],
|
|
86
|
+
editorial: ["var(--font-title)"],
|
|
87
|
+
serif: ["var(--font-title)"],
|
|
88
|
+
sans: ["var(--font-body)", "sans-serif"],
|
|
89
|
+
mono: ["var(--font-mono)"],
|
|
90
|
+
code: ["var(--font-code)"],
|
|
91
|
+
},
|
|
92
|
+
borderRadius: {
|
|
93
|
+
DEFAULT: "0.125rem",
|
|
94
|
+
lg: "0.25rem",
|
|
95
|
+
xl: "0.5rem",
|
|
96
|
+
full: "0.75rem",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
safelist: ["text-primary", "text-secondary", "text-tertiary", "text-outline"],
|
|
101
|
+
plugins: [forms],
|
|
102
|
+
};
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
],
|
|
25
25
|
"selectors": [
|
|
26
|
-
"
|
|
26
|
+
"button.home-project-inventory-row",
|
|
27
27
|
"div.home-project-inventory-media",
|
|
28
28
|
"div.ledger-poster",
|
|
29
29
|
"div.home-project-inventory-code",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"span.home-project-inventory-resource"
|
|
36
36
|
],
|
|
37
37
|
"forbidden": [
|
|
38
|
-
"ledger-card-stretch"
|
|
38
|
+
"ledger-card-stretch",
|
|
39
|
+
"role=\"button\""
|
|
39
40
|
],
|
|
40
41
|
"contains": [
|
|
41
42
|
">02<",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Stylesheet delivery (PLT-AC-19).
|
|
5
|
+
# The Play CDN blocked first paint, and every page linked every ledger sheet.
|
|
6
|
+
|
|
7
|
+
require "pathname"
|
|
8
|
+
|
|
9
|
+
ROOT = Pathname.new(__dir__).join("..", "..").expand_path
|
|
10
|
+
|
|
11
|
+
def fail!(message)
|
|
12
|
+
warn "FAIL test-css-delivery: #{message}"
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
shell = ROOT.join("_layouts/shell.html").read
|
|
17
|
+
css = ROOT.join("assets/css/tailwind.css").read
|
|
18
|
+
|
|
19
|
+
fail!("shell still loads the Tailwind Play CDN") if shell.include?("cdn.tailwindcss.com")
|
|
20
|
+
fail!("shell still loads tailwind-play.js") if shell.include?("tailwind-play.js")
|
|
21
|
+
fail!("shell does not link the compiled utilities") unless shell.include?('href="/assets/css/tailwind.css"')
|
|
22
|
+
fail!("shell still links every ledger sheet") if shell.include?('sheet.path contains "/assets/css/ledger/"')
|
|
23
|
+
fail!("shell still links the empty cv.css") if shell.include?('href="/assets/css/cv.css"')
|
|
24
|
+
fail!("empty cv.css is still in the tree") if ROOT.join("assets/css/cv.css").exist?
|
|
25
|
+
fail!("home layout has no ledger sheet") unless shell.include?('when "home"') && shell.include?('assign _ledger = "home"')
|
|
26
|
+
fail!("articles layout misses the writing sheet") unless shell.include?("writing|detail")
|
|
27
|
+
fail!("shared chrome is not always linked") unless shell.include?("ledger/chrome.css") && shell.include?("ledger/footer.css")
|
|
28
|
+
fail!("compiled utilities are missing flex") unless css.include?(".flex{")
|
|
29
|
+
fail!("compiled utilities dropped the separator tint") unless css.include?("bg-outline-variant\\/30")
|
|
30
|
+
fail!("compiled utilities dropped the dev-reexport border") unless css.include?("border-tertiary\\/40")
|
|
31
|
+
|
|
32
|
+
puts "PASS test-css-delivery"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Portrait sources and discovery documents (PLT-AC-20, PLT-AC-21).
|
|
5
|
+
|
|
6
|
+
require "pathname"
|
|
7
|
+
|
|
8
|
+
require_relative "../../lib/pandorga/discovery"
|
|
9
|
+
|
|
10
|
+
ROOT = Pathname.new(__dir__).join("..", "..").expand_path
|
|
11
|
+
|
|
12
|
+
def fail!(message)
|
|
13
|
+
warn "FAIL test-discovery: #{message}"
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
origin = Pandorga::Discovery.origin(
|
|
18
|
+
"url" => "https://fallback.example",
|
|
19
|
+
"pandorga" => { "identity" => { "url" => "https://example.com/" } }
|
|
20
|
+
)
|
|
21
|
+
fail!("identity.url must win over site.url") unless origin == "https://example.com"
|
|
22
|
+
|
|
23
|
+
paths = Pandorga::Discovery.public_paths([
|
|
24
|
+
{ "url" => "/", "data" => {} },
|
|
25
|
+
{ "url" => "/pages/articles/", "data" => {} },
|
|
26
|
+
{ "url" => "/sitemap.xml", "data" => {} },
|
|
27
|
+
{ "url" => "/secret/", "data" => { "sitemap" => false } }
|
|
28
|
+
])
|
|
29
|
+
fail!("sitemap filtering is wrong: #{paths.inspect}") unless paths == ["/", "/pages/articles/"]
|
|
30
|
+
|
|
31
|
+
xml = Pandorga::Discovery.sitemap_xml("https://example.com", paths)
|
|
32
|
+
fail!("sitemap missing a loc") unless xml.include?("<loc>https://example.com/pages/articles/</loc>")
|
|
33
|
+
fail!("sitemap included a skipped path") if xml.include?("secret")
|
|
34
|
+
|
|
35
|
+
robots = Pandorga::Discovery.robots_txt("https://example.com/sitemap.xml")
|
|
36
|
+
fail!("robots missing the sitemap line") unless robots.include?("Sitemap: https://example.com/sitemap.xml")
|
|
37
|
+
fail!("robots missing the studio skip") unless robots.include?("Disallow: /studio/")
|
|
38
|
+
|
|
39
|
+
runtime = ROOT.join("_includes/content-runtime/60-fragments.html").read
|
|
40
|
+
fail!("portrait srcset is not read") unless runtime.include?("record.srcset = srcset")
|
|
41
|
+
fail!("portrait sources are not applied") unless runtime.include?("applyHeroPortraitSources")
|
|
42
|
+
shell = ROOT.join("_layouts/shell.html").read
|
|
43
|
+
fail!("shell missing JSON-LD") unless shell.include?("theme/json-ld.html")
|
|
44
|
+
fail!("discovery generator is not loaded from the plugin dir") unless ROOT.join("lib/pandorga/jekyll/plugins/discovery.rb").file?
|
|
45
|
+
|
|
46
|
+
puts "PASS test-discovery"
|
|
@@ -55,6 +55,21 @@ end
|
|
|
55
55
|
warn "FAIL test-examples-build: #{name} home layout missing"
|
|
56
56
|
exit 1
|
|
57
57
|
end
|
|
58
|
+
origin = name == "full" ? "https://example.org" : "https://example.com"
|
|
59
|
+
sitemap = example.join("_site/sitemap.xml").read
|
|
60
|
+
robots = example.join("_site/robots.txt").read
|
|
61
|
+
unless sitemap.include?("<loc>#{origin}/</loc>") || sitemap.include?("<loc>#{origin}/index.html</loc>")
|
|
62
|
+
warn "FAIL test-examples-build: #{name} sitemap missing the home URL\n#{sitemap}"
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
|
65
|
+
unless robots.include?("Sitemap: #{origin}/sitemap.xml")
|
|
66
|
+
warn "FAIL test-examples-build: #{name} robots missing the sitemap line\n#{robots}"
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
unless html.include?('application/ld+json') && html.include?('"@type": "Person"')
|
|
70
|
+
warn "FAIL test-examples-build: #{name} home is missing JSON-LD"
|
|
71
|
+
exit 1
|
|
72
|
+
end
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
puts "PASS test-examples-build"
|
|
@@ -13,7 +13,7 @@ const source = fs.readFileSync(
|
|
|
13
13
|
);
|
|
14
14
|
|
|
15
15
|
const start = source.indexOf('function heroPortraitEntries');
|
|
16
|
-
const end = source.indexOf('function applyHeroPortrait');
|
|
16
|
+
const end = source.indexOf('function applyHeroPortrait(');
|
|
17
17
|
if (start < 0 || end < start) {
|
|
18
18
|
console.error('hero portrait functions missing from 60-fragments.html');
|
|
19
19
|
process.exit(1);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Hero LCP, listing/CV shift, revision contrast, inventory button.
|
|
5
|
+
# PLT-AC-16, PLT-AC-17, PLT-AC-18. The subtitle size is PLT-AC-13.
|
|
6
|
+
|
|
7
|
+
require "pathname"
|
|
8
|
+
|
|
9
|
+
ROOT = Pathname.new(__dir__).join("..", "..").expand_path
|
|
10
|
+
|
|
11
|
+
def fail!(message)
|
|
12
|
+
warn "FAIL test-hydrate-reserve: #{message}"
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
shell = ROOT.join("_layouts/shell.html").read
|
|
17
|
+
preload = ROOT.join("_includes/theme/critical-preload.html").read
|
|
18
|
+
home = ROOT.join("_layouts/home.html").read
|
|
19
|
+
articles = ROOT.join("_layouts/articles.html").read
|
|
20
|
+
cv = ROOT.join("_layouts/cv.html").read
|
|
21
|
+
fragments = ROOT.join("_includes/content-runtime/60-fragments.html").read
|
|
22
|
+
listing = ROOT.join("_includes/content-runtime/41-listing-helpers.html").read
|
|
23
|
+
blocks = ROOT.join("_includes/content-runtime/43-blocks.html").read
|
|
24
|
+
home_css = ROOT.join("assets/css/ledger/home.css").read
|
|
25
|
+
writing_css = ROOT.join("assets/css/ledger/writing.css").read
|
|
26
|
+
cv_css = ROOT.join("assets/css/ledger/cv.css").read
|
|
27
|
+
|
|
28
|
+
fail!("shell does not preload critical JSON") unless shell.include?("theme/critical-preload.html")
|
|
29
|
+
fail!("home preload misses the hero summary") unless preload.include?("pages/cv/summary.json")
|
|
30
|
+
fail!("home preload misses the subtitle") unless preload.include?("pages/home/subtitle.json")
|
|
31
|
+
fail!("articles preload misses the writing index") unless preload.include?("data/writing_index.json")
|
|
32
|
+
fail!("preload must run before stylesheets") unless shell.index("critical-preload.html") < shell.index('href="/assets/css/base.css"')
|
|
33
|
+
|
|
34
|
+
fail!("loadJson ignores the preload cache") unless fragments.include?("window.__pandorgaPreload")
|
|
35
|
+
fail!("quiet fragments still write Loading...") unless fragments.include?("data-content-quiet")
|
|
36
|
+
fail!("a painted fragment is written twice") unless fragments.include?("data-content-painted")
|
|
37
|
+
|
|
38
|
+
fail!("hero summary is not quiet") unless home.include?('data-content-fragment="cv/summary" data-content-quiet')
|
|
39
|
+
fail!("hero subtitle is not quiet") unless home.include?('data-content-fragment="home/subtitle" data-content-quiet')
|
|
40
|
+
fail!("hero does not paint the summary early") unless home.include?("paint('home-cv-summary'")
|
|
41
|
+
fail!("hero subtitle size left --type-body") unless home_css.include?("font-size: var(--type-body);")
|
|
42
|
+
tagline = home_css[/\.home-hero-tagline \{[^}]*\}/m]
|
|
43
|
+
fail!("tagline rule missing") unless tagline
|
|
44
|
+
fail!("tagline is not body size") unless tagline.include?("font-size: var(--type-body);")
|
|
45
|
+
fail!("hero summary has no reserved box") unless home_css.include?(".home-hero-summary:empty")
|
|
46
|
+
|
|
47
|
+
fail!("articles listing has no row reserve") unless articles.include?("writing-reserve-row")
|
|
48
|
+
fail!("articles lead has no reserved box") unless writing_css.include?("#articles-lead.is-pending:empty")
|
|
49
|
+
fail!("articles lead reserve stays after the listing decides") if writing_css.match?(/^#articles-lead:empty/m)
|
|
50
|
+
fail!("articles lead is not pending until the listing decides") unless articles.include?('id="articles-lead" class="is-pending"')
|
|
51
|
+
fail!("page 2 does not drop the lead reserve before paint") unless articles.include?("!(page > 1)")
|
|
52
|
+
fail!("listing never clears the lead reserve") unless articles.include?("leadRoot.classList.remove('is-pending')")
|
|
53
|
+
fail!("CV experience has no reserve") unless cv.include?('id="cv-experience-list"') && cv.include?("data-content-reserve")
|
|
54
|
+
fail!("CV summary still ships Loading...") if cv.include?("Loading...")
|
|
55
|
+
fail!("showLoadingState collapses a reserve") unless listing.include?("data-content-reserve")
|
|
56
|
+
fail!("CV reserve card has no height") unless cv_css.include?(".cv-reserve-card")
|
|
57
|
+
|
|
58
|
+
revised = writing_css[/\.writing-index \.writing-revised \{[^}]+\}/m]
|
|
59
|
+
fail!("writing-revised rule missing") unless revised
|
|
60
|
+
fail!("writing-revised still fades the ink") if revised.include?("color-mix") || revised.include?("transparent")
|
|
61
|
+
fail!("writing-revised is not the variant ink") unless revised.include?("color: var(--color-on-surface-variant);")
|
|
62
|
+
|
|
63
|
+
selectable = blocks[/function renderProjectInventoryRow.*?function renderSystemCard/m]
|
|
64
|
+
fail!("inventory renderer missing") unless selectable
|
|
65
|
+
fail!("selectable inventory row is still an article with role=button") if selectable.include?("role', 'button'") || selectable.include?('role", "button"') || selectable.include?("setAttribute('role'")
|
|
66
|
+
fail!("selectable inventory row is not a button") unless selectable.include?("createElement(selectable ? 'button' : 'article')")
|
|
67
|
+
|
|
68
|
+
puts "PASS test-hydrate-reserve"
|
data/scripts/validate.sh
CHANGED