@cogenta/starters 0.2.2 → 0.4.0
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.
- package/dist/blueprints/assets/photos/association/coat-bank.jpg +0 -0
- package/dist/blueprints/assets/photos/association/food-bank.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-corner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hall.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/association/homework-club.jpg +0 -0
- package/dist/blueprints/assets/photos/association/orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-college.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-delcourt.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-fondation.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-habitat.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-mairie.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-pharmacie.png +0 -0
- package/dist/blueprints/assets/photos/association/supper.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/library.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebook-and-coffee.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/platform.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/pour-over.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/typing.jpg +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow-en.png +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow-fr.png +0 -0
- package/dist/blueprints/assets/photos/magazine/allotments.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/archive-stacks.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/baguettes.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/dining-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/empty-tram-stop.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/gallery.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/office-block-dusk.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/reading-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/reception-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/theatre.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/town-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/tram-stop.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-boats.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-ridge.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-water.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-beech.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-birch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-branches.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-cave.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-melt.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-tongue.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-egret.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-shore.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-storm.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-dusk.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-marina.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-quay.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-green.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-single.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-valley.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-grapes.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-shed.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-tree.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/beetroot-carpaccio.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/cod-vin-jaune.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/duck-confit.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/grilled-octopus.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/market-stall.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/mushroom-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pike-quenelle.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/praline-cake.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pumpkin-soup.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/street-burdeau.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-castellan-alimentaire.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-cliniques-du-vivier.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-marenne-logistique.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-nordgate-habitat.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-tessaly-energies.png +0 -0
- package/dist/blueprints/assets/photos/saas/office-london.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-montreal.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-paris.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-toronto.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/security-racks.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim-fr.png +0 -0
- package/dist/blueprints/assets/photos/store/aleppo-soap.jpg +0 -0
- package/dist/blueprints/assets/photos/store/copper-pan.jpg +0 -0
- package/dist/blueprints/assets/photos/store/crochet-throw.jpg +0 -0
- package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
- package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/knit-sweater.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-boots.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-duffel.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-wallet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/moka-pot.jpg +0 -0
- package/dist/blueprints/assets/photos/store/skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/stoneware-bottles.jpg +0 -0
- package/dist/blueprints/assets/photos/store/story.jpg +0 -0
- package/dist/blueprints/assets/photos/store/turned-bowl.jpg +0 -0
- package/dist/blueprints/association-copy-en.d.ts +3 -0
- package/dist/blueprints/association-copy-en.d.ts.map +1 -0
- package/dist/blueprints/association-copy-en.js +834 -0
- package/dist/blueprints/association-copy-en.js.map +1 -0
- package/dist/blueprints/association-copy-fr.d.ts +3 -0
- package/dist/blueprints/association-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/association-copy-fr.js +841 -0
- package/dist/blueprints/association-copy-fr.js.map +1 -0
- package/dist/blueprints/association-copy.d.ts +301 -0
- package/dist/blueprints/association-copy.d.ts.map +1 -0
- package/dist/blueprints/association-copy.js +17 -0
- package/dist/blueprints/association-copy.js.map +1 -0
- package/dist/blueprints/association-credits.d.ts +16 -0
- package/dist/blueprints/association-credits.d.ts.map +1 -0
- package/dist/blueprints/association-credits.js +80 -0
- package/dist/blueprints/association-credits.js.map +1 -0
- package/dist/blueprints/association-typography.d.ts +23 -0
- package/dist/blueprints/association-typography.d.ts.map +1 -0
- package/dist/blueprints/association-typography.js +55 -0
- package/dist/blueprints/association-typography.js.map +1 -0
- package/dist/blueprints/association.d.ts +86 -287
- package/dist/blueprints/association.d.ts.map +1 -1
- package/dist/blueprints/association.js +510 -1082
- package/dist/blueprints/association.js.map +1 -1
- package/dist/blueprints/blog-copy-en.d.ts +4 -0
- package/dist/blueprints/blog-copy-en.d.ts.map +1 -0
- package/dist/blueprints/blog-copy-en.js +701 -0
- package/dist/blueprints/blog-copy-en.js.map +1 -0
- package/dist/blueprints/blog-copy-fr.d.ts +12 -0
- package/dist/blueprints/blog-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/blog-copy-fr.js +722 -0
- package/dist/blueprints/blog-copy-fr.js.map +1 -0
- package/dist/blueprints/blog-copy.d.ts +157 -0
- package/dist/blueprints/blog-copy.d.ts.map +1 -0
- package/dist/blueprints/blog-copy.js +2 -0
- package/dist/blueprints/blog-copy.js.map +1 -0
- package/dist/blueprints/blog-credits.d.ts +28 -0
- package/dist/blueprints/blog-credits.d.ts.map +1 -0
- package/dist/blueprints/blog-credits.js +73 -0
- package/dist/blueprints/blog-credits.js.map +1 -0
- package/dist/blueprints/blog-rich.d.ts +19 -0
- package/dist/blueprints/blog-rich.d.ts.map +1 -0
- package/dist/blueprints/blog-rich.js +51 -0
- package/dist/blueprints/blog-rich.js.map +1 -0
- package/dist/blueprints/blog.d.ts +51 -176
- package/dist/blueprints/blog.d.ts.map +1 -1
- package/dist/blueprints/blog.js +358 -947
- package/dist/blueprints/blog.js.map +1 -1
- package/dist/blueprints/content-pack.d.ts.map +1 -1
- package/dist/blueprints/content-pack.js +11 -16
- package/dist/blueprints/content-pack.js.map +1 -1
- package/dist/blueprints/content-packs.d.ts +2 -4
- package/dist/blueprints/content-packs.d.ts.map +1 -1
- package/dist/blueprints/content-packs.js +27 -15
- package/dist/blueprints/content-packs.js.map +1 -1
- package/dist/blueprints/documentation-copy-en.d.ts +3 -0
- package/dist/blueprints/documentation-copy-en.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy-en.js +883 -0
- package/dist/blueprints/documentation-copy-en.js.map +1 -0
- package/dist/blueprints/documentation-copy-fr.d.ts +3 -0
- package/dist/blueprints/documentation-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy-fr.js +939 -0
- package/dist/blueprints/documentation-copy-fr.js.map +1 -0
- package/dist/blueprints/documentation-copy.d.ts +52 -0
- package/dist/blueprints/documentation-copy.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy.js +2 -0
- package/dist/blueprints/documentation-copy.js.map +1 -0
- package/dist/blueprints/documentation-writer.d.ts +67 -0
- package/dist/blueprints/documentation-writer.d.ts.map +1 -0
- package/dist/blueprints/documentation-writer.js +190 -0
- package/dist/blueprints/documentation-writer.js.map +1 -0
- package/dist/blueprints/documentation.d.ts +26 -29
- package/dist/blueprints/documentation.d.ts.map +1 -1
- package/dist/blueprints/documentation.js +106 -1064
- package/dist/blueprints/documentation.js.map +1 -1
- package/dist/blueprints/magazine-copy-en.d.ts +13 -0
- package/dist/blueprints/magazine-copy-en.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy-en.js +553 -0
- package/dist/blueprints/magazine-copy-en.js.map +1 -0
- package/dist/blueprints/magazine-copy-fr.d.ts +14 -0
- package/dist/blueprints/magazine-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy-fr.js +568 -0
- package/dist/blueprints/magazine-copy-fr.js.map +1 -0
- package/dist/blueprints/magazine-copy.d.ts +166 -0
- package/dist/blueprints/magazine-copy.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy.js +58 -0
- package/dist/blueprints/magazine-copy.js.map +1 -0
- package/dist/blueprints/magazine-credits.d.ts +19 -0
- package/dist/blueprints/magazine-credits.d.ts.map +1 -0
- package/dist/blueprints/magazine-credits.js +109 -0
- package/dist/blueprints/magazine-credits.js.map +1 -0
- package/dist/blueprints/magazine.d.ts +30 -78
- package/dist/blueprints/magazine.d.ts.map +1 -1
- package/dist/blueprints/magazine.js +269 -690
- package/dist/blueprints/magazine.js.map +1 -1
- package/dist/blueprints/portfolio-copy-en.d.ts +4 -0
- package/dist/blueprints/portfolio-copy-en.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy-en.js +745 -0
- package/dist/blueprints/portfolio-copy-en.js.map +1 -0
- package/dist/blueprints/portfolio-copy-fr.d.ts +9 -0
- package/dist/blueprints/portfolio-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy-fr.js +737 -0
- package/dist/blueprints/portfolio-copy-fr.js.map +1 -0
- package/dist/blueprints/portfolio-copy.d.ts +204 -0
- package/dist/blueprints/portfolio-copy.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy.js +15 -0
- package/dist/blueprints/portfolio-copy.js.map +1 -0
- package/dist/blueprints/portfolio-credits.d.ts +18 -0
- package/dist/blueprints/portfolio-credits.d.ts.map +1 -0
- package/dist/blueprints/portfolio-credits.js +239 -0
- package/dist/blueprints/portfolio-credits.js.map +1 -0
- package/dist/blueprints/portfolio-typography.d.ts +19 -0
- package/dist/blueprints/portfolio-typography.d.ts.map +1 -0
- package/dist/blueprints/portfolio-typography.js +41 -0
- package/dist/blueprints/portfolio-typography.js.map +1 -0
- package/dist/blueprints/portfolio.d.ts +68 -157
- package/dist/blueprints/portfolio.d.ts.map +1 -1
- package/dist/blueprints/portfolio.js +475 -727
- package/dist/blueprints/portfolio.js.map +1 -1
- package/dist/blueprints/restaurant-copy-en.d.ts +3 -0
- package/dist/blueprints/restaurant-copy-en.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy-en.js +641 -0
- package/dist/blueprints/restaurant-copy-en.js.map +1 -0
- package/dist/blueprints/restaurant-copy-fr.d.ts +3 -0
- package/dist/blueprints/restaurant-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy-fr.js +665 -0
- package/dist/blueprints/restaurant-copy-fr.js.map +1 -0
- package/dist/blueprints/restaurant-copy.d.ts +234 -0
- package/dist/blueprints/restaurant-copy.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy.js +15 -0
- package/dist/blueprints/restaurant-copy.js.map +1 -0
- package/dist/blueprints/restaurant-credits.d.ts +18 -0
- package/dist/blueprints/restaurant-credits.d.ts.map +1 -0
- package/dist/blueprints/restaurant-credits.js +121 -0
- package/dist/blueprints/restaurant-credits.js.map +1 -0
- package/dist/blueprints/restaurant.d.ts +55 -128
- package/dist/blueprints/restaurant.d.ts.map +1 -1
- package/dist/blueprints/restaurant.js +425 -770
- package/dist/blueprints/restaurant.js.map +1 -1
- package/dist/blueprints/saas-copy-en.d.ts +3 -0
- package/dist/blueprints/saas-copy-en.d.ts.map +1 -0
- package/dist/blueprints/saas-copy-en.js +901 -0
- package/dist/blueprints/saas-copy-en.js.map +1 -0
- package/dist/blueprints/saas-copy-fr.d.ts +3 -0
- package/dist/blueprints/saas-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/saas-copy-fr.js +912 -0
- package/dist/blueprints/saas-copy-fr.js.map +1 -0
- package/dist/blueprints/saas-copy.d.ts +266 -0
- package/dist/blueprints/saas-copy.d.ts.map +1 -0
- package/dist/blueprints/saas-copy.js +18 -0
- package/dist/blueprints/saas-copy.js.map +1 -0
- package/dist/blueprints/saas-credits.d.ts +22 -0
- package/dist/blueprints/saas-credits.d.ts.map +1 -0
- package/dist/blueprints/saas-credits.js +44 -0
- package/dist/blueprints/saas-credits.js.map +1 -0
- package/dist/blueprints/saas.d.ts +59 -203
- package/dist/blueprints/saas.d.ts.map +1 -1
- package/dist/blueprints/saas.js +505 -1108
- package/dist/blueprints/saas.js.map +1 -1
- package/dist/blueprints/store-copy-en.d.ts +4 -0
- package/dist/blueprints/store-copy-en.d.ts.map +1 -0
- package/dist/blueprints/store-copy-en.js +521 -0
- package/dist/blueprints/store-copy-en.js.map +1 -0
- package/dist/blueprints/store-copy-fr.d.ts +4 -0
- package/dist/blueprints/store-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/store-copy-fr.js +521 -0
- package/dist/blueprints/store-copy-fr.js.map +1 -0
- package/dist/blueprints/store-copy.d.ts +219 -0
- package/dist/blueprints/store-copy.d.ts.map +1 -0
- package/dist/blueprints/store-copy.js +16 -0
- package/dist/blueprints/store-copy.js.map +1 -0
- package/dist/blueprints/store-credits.d.ts +3 -0
- package/dist/blueprints/store-credits.d.ts.map +1 -0
- package/dist/blueprints/store-credits.js +117 -0
- package/dist/blueprints/store-credits.js.map +1 -0
- package/dist/blueprints/store.d.ts +43 -231
- package/dist/blueprints/store.d.ts.map +1 -1
- package/dist/blueprints/store.js +466 -789
- package/dist/blueprints/store.js.map +1 -1
- package/dist/blueprints/vitrine-copy-en.d.ts.map +1 -1
- package/dist/blueprints/vitrine-copy-en.js +1 -0
- package/dist/blueprints/vitrine-copy-en.js.map +1 -1
- package/dist/blueprints/vitrine-copy-fr.d.ts.map +1 -1
- package/dist/blueprints/vitrine-copy-fr.js +1 -0
- package/dist/blueprints/vitrine-copy-fr.js.map +1 -1
- package/dist/blueprints/vitrine-copy.d.ts +1 -0
- package/dist/blueprints/vitrine-copy.d.ts.map +1 -1
- package/dist/blueprints/vitrine.d.ts +1 -0
- package/dist/blueprints/vitrine.d.ts.map +1 -1
- package/dist/blueprints/vitrine.js +24 -3
- package/dist/blueprints/vitrine.js.map +1 -1
- package/package.json +10 -10
- package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-portrait.jpg +0 -0
- package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow.png +0 -0
- package/dist/blueprints/assets/photos/magazine/archive-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/banquet-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/city-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/downtown-towers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/gallery-opening.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/garden-volunteers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/station-platform.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/theater-stage.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-branch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-stationery.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-sardines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-shelf.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-grid.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-salt-year.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-spines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-brochure.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-carnival.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-sibelius.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-series.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-gate.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-pictograms.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-totem.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-data.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-report-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-room.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-components.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-departures.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/beetroot-goats-curd.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/ceps-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/duck-leg-red-cabbage.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hake-green-peppercorns.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/vegetable-broth.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/adrian-tan.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync.png +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor.png +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim.png +0 -0
- package/dist/blueprints/assets/photos/store/brushed-cotton-overshirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cable-knit-beanie.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-shoulder-bag.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/heavyweight-t-shirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
- package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
- package/dist/blueprints/assets/photos/store/porcelain-pour-over-set.jpg +0 -0
- package/dist/blueprints/assets/photos/store/striped-wool-blanket.jpg +0 -0
|
@@ -2,10 +2,21 @@ import { CogentaError } from '@cogenta/core';
|
|
|
2
2
|
import { createContentStore, createTaxonomyStore, defineCollection, defineTaxonomy, f, validateCollectionSet, validateTaxonomySet, } from '@cogenta/schema';
|
|
3
3
|
import { coverArt } from '../demo-art/compositions.js';
|
|
4
4
|
import { definePageCollection, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
|
|
5
|
+
import { PORTFOLIO_COPY_EN } from './portfolio-copy-en.js';
|
|
6
|
+
import { PORTFOLIO_COPY_FR } from './portfolio-copy-fr.js';
|
|
7
|
+
import { PORTFOLIO_PHOTO_CREDITS } from './portfolio-credits.js';
|
|
5
8
|
import { STARTING_SKINS } from './starting-skins.js';
|
|
6
9
|
/**
|
|
7
|
-
* The `portfolio` blueprint: an independent
|
|
8
|
-
*
|
|
10
|
+
* The `portfolio` blueprint: an independent photography studio that works on
|
|
11
|
+
* commission for nature reserves, regional parks, growers, ports and heritage
|
|
12
|
+
* bodies (L9 task 8; richened by L25; rewritten to studio level by L27;
|
|
13
|
+
* rewritten as a bilingual photography portfolio on real photographs).
|
|
14
|
+
*
|
|
15
|
+
* Written in French and in English, not translated at render time: the
|
|
16
|
+
* installer picks the copy for the site's default locale, and with it the
|
|
17
|
+
* addresses (`/projets/…` in French, `/work/…` in English), the taxonomy
|
|
18
|
+
* names the project page labels its fact sheet with, and every word of demo
|
|
19
|
+
* content.
|
|
9
20
|
*
|
|
10
21
|
* The model is shaped by what `@cogenta/theme-portfolio` shows:
|
|
11
22
|
*
|
|
@@ -15,114 +26,113 @@ import { STARTING_SKINS } from './starting-skins.js';
|
|
|
15
26
|
* field holds an id.
|
|
16
27
|
* - `client`, `disciplines` and `team` are also real taxonomies (ADR-0022).
|
|
17
28
|
* They give every client, discipline and member of the studio a public
|
|
18
|
-
* archive
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* finished.
|
|
29
|
+
* archive, and they are what a project page lists in its fact sheet,
|
|
30
|
+
* labelled with each taxonomy's own name. The year in the fact sheet is the
|
|
31
|
+
* project's `publishedAt`, the date the work was finished.
|
|
22
32
|
*
|
|
23
|
-
*
|
|
24
|
-
* (
|
|
25
|
-
* screens, tins, exhibition graphics), rendered once with open-licence fonts
|
|
26
|
-
* and bundled under `assets/photos/portfolio/`. Nothing is generated at
|
|
27
|
-
* scaffold time.
|
|
33
|
+
* Every photograph is a real one under an open licence, credited on its own
|
|
34
|
+
* page (`portfolio-credits.ts`). Nothing is generated at scaffold time.
|
|
28
35
|
*
|
|
29
36
|
* The copy names the studio the person scaffolding it chose
|
|
30
37
|
* (`SeedContext.siteName`), falling back to a fictional name only outside a
|
|
31
38
|
* real scaffold. Every client, person, place and figure is fictional.
|
|
32
39
|
*/
|
|
33
40
|
export const DEFAULT_STUDIO_NAME = 'Studio Hale';
|
|
41
|
+
/** The copy for a site's default locale: French for any `fr*` locale, English otherwise. */
|
|
42
|
+
export function portfolioCopyFor(locale) {
|
|
43
|
+
return locale.toLowerCase().startsWith('fr') ? PORTFOLIO_COPY_FR : PORTFOLIO_COPY_EN;
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Content model
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
34
48
|
const TAXONOMY_PERMISSIONS = {
|
|
35
49
|
read: ['public'],
|
|
36
50
|
create: ['editor', 'admin'],
|
|
37
51
|
update: ['editor', 'admin'],
|
|
38
52
|
delete: ['admin'],
|
|
39
53
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
admin: {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
export const PORTFOLIO_COLLECTIONS = [project, page];
|
|
123
|
-
export const PORTFOLIO_TAXONOMIES = [client, disciplines, team];
|
|
124
|
-
validateCollectionSet(PORTFOLIO_COLLECTIONS);
|
|
125
|
-
validateTaxonomySet(PORTFOLIO_TAXONOMIES, PORTFOLIO_COLLECTIONS);
|
|
54
|
+
function taxonomyOf(key, copy) {
|
|
55
|
+
const own = copy.schema.taxonomies[key];
|
|
56
|
+
const en = PORTFOLIO_COPY_EN.schema.taxonomies[key];
|
|
57
|
+
const fr = PORTFOLIO_COPY_FR.schema.taxonomies[key];
|
|
58
|
+
return defineTaxonomy({
|
|
59
|
+
name: own.name,
|
|
60
|
+
labels: {
|
|
61
|
+
singular: { en: en.singular, fr: fr.singular },
|
|
62
|
+
plural: { en: en.plural, fr: fr.plural },
|
|
63
|
+
},
|
|
64
|
+
hierarchical: false,
|
|
65
|
+
permissions: TAXONOMY_PERMISSIONS,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** The content model, named and routed in the copy's language. */
|
|
69
|
+
export function portfolioSchema(copy) {
|
|
70
|
+
const { schema } = copy;
|
|
71
|
+
const client = taxonomyOf('client', copy);
|
|
72
|
+
const disciplines = taxonomyOf('disciplines', copy);
|
|
73
|
+
const team = taxonomyOf('team', copy);
|
|
74
|
+
const project = defineCollection({
|
|
75
|
+
name: 'project',
|
|
76
|
+
versioning: { drafts: true, history: true },
|
|
77
|
+
labels: schema.labels,
|
|
78
|
+
routing: { pattern: schema.routes.project },
|
|
79
|
+
fields: {
|
|
80
|
+
title: f.text({ required: true, max: 200 }),
|
|
81
|
+
slug: f.slug({ from: 'title', unique: true }),
|
|
82
|
+
summary: f.text({
|
|
83
|
+
max: 300,
|
|
84
|
+
multiline: true,
|
|
85
|
+
admin: { label: schema.fields.statement, help: schema.fields.statementHelp },
|
|
86
|
+
}),
|
|
87
|
+
client: f.text({
|
|
88
|
+
max: 120,
|
|
89
|
+
admin: { label: schema.fields.client, help: schema.fields.clientHelp },
|
|
90
|
+
}),
|
|
91
|
+
discipline: f.text({ max: 60, admin: { label: schema.fields.discipline } }),
|
|
92
|
+
// Read by `entryImage` (`@cogenta/theme-kit`) for every listing and as the
|
|
93
|
+
// lead visual of the project page. Cut at 3:2, subject in the middle.
|
|
94
|
+
coverImage: f.media({ accept: ['image'] }),
|
|
95
|
+
// The date the work was finished: the year in the caption and the fact
|
|
96
|
+
// sheet, and the order a reader expects the index in.
|
|
97
|
+
publishedAt: f.datetime(),
|
|
98
|
+
clientArchive: f.taxonomy({
|
|
99
|
+
of: client.name,
|
|
100
|
+
many: false,
|
|
101
|
+
admin: { label: schema.fields.clientArchive, help: schema.fields.clientArchiveHelp },
|
|
102
|
+
}),
|
|
103
|
+
disciplines: f.taxonomy({ of: disciplines.name, many: true }),
|
|
104
|
+
team: f.taxonomy({ of: team.name, many: true }),
|
|
105
|
+
blocks: f.blocks({ required: true }),
|
|
106
|
+
...SEO_FIELDS,
|
|
107
|
+
},
|
|
108
|
+
indexes: [['slug']],
|
|
109
|
+
permissions: {
|
|
110
|
+
read: ['public'],
|
|
111
|
+
create: ['editor', 'admin'],
|
|
112
|
+
update: ['editor', 'admin'],
|
|
113
|
+
delete: ['admin'],
|
|
114
|
+
publish: ['admin'],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const page = definePageCollection('/:slug');
|
|
118
|
+
const collections = [project, page];
|
|
119
|
+
const taxonomies = [client, disciplines, team];
|
|
120
|
+
validateCollectionSet(collections);
|
|
121
|
+
validateTaxonomySet(taxonomies, collections);
|
|
122
|
+
return { client, disciplines, team, project, page, collections, taxonomies };
|
|
123
|
+
}
|
|
124
|
+
const ENGLISH_SCHEMA = portfolioSchema(PORTFOLIO_COPY_EN);
|
|
125
|
+
/** The English model, which the existing exports and the English pack use. */
|
|
126
|
+
export const client = ENGLISH_SCHEMA.client;
|
|
127
|
+
export const disciplines = ENGLISH_SCHEMA.disciplines;
|
|
128
|
+
export const team = ENGLISH_SCHEMA.team;
|
|
129
|
+
export const project = ENGLISH_SCHEMA.project;
|
|
130
|
+
export const page = ENGLISH_SCHEMA.page;
|
|
131
|
+
export const PORTFOLIO_COLLECTIONS = ENGLISH_SCHEMA.collections;
|
|
132
|
+
export const PORTFOLIO_TAXONOMIES = ENGLISH_SCHEMA.taxonomies;
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Blocks, written as data
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
126
136
|
const BLOCK_VERSION = '1.0.0';
|
|
127
137
|
let nodeKey = 0;
|
|
128
138
|
function nextKey(prefix) {
|
|
@@ -218,47 +228,42 @@ function testimonial(text, name, role) {
|
|
|
218
228
|
attribution: { name, role },
|
|
219
229
|
};
|
|
220
230
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
{ name: 'Tom Reyes', slug: 'tom-reyes' },
|
|
258
|
-
{ name: 'Priya Nair', slug: 'priya-nair' },
|
|
259
|
-
{ name: 'Sam Whitford', slug: 'sam-whitford' },
|
|
260
|
-
{ name: 'Ada Brennan', slug: 'ada-brennan' },
|
|
261
|
-
];
|
|
231
|
+
/**
|
|
232
|
+
* Turns the parts of the copy into contract-B blocks. Consecutive headings
|
|
233
|
+
* and paragraphs become one `prose` block; a picture that is absent (a caller
|
|
234
|
+
* that never seeded any media) is left out, never replaced.
|
|
235
|
+
*/
|
|
236
|
+
function blocksOf(parts, media) {
|
|
237
|
+
const out = [];
|
|
238
|
+
let run = [];
|
|
239
|
+
const flush = () => {
|
|
240
|
+
if (run.length > 0)
|
|
241
|
+
out.push(prose(...run));
|
|
242
|
+
run = [];
|
|
243
|
+
};
|
|
244
|
+
for (const part of parts) {
|
|
245
|
+
if ('h2' in part)
|
|
246
|
+
run.push(h2(part.h2));
|
|
247
|
+
else if ('p' in part)
|
|
248
|
+
run.push(p(part.p));
|
|
249
|
+
else {
|
|
250
|
+
flush();
|
|
251
|
+
if ('figure' in part)
|
|
252
|
+
out.push(...figure(media, part.figure, part.caption, part.align));
|
|
253
|
+
else if ('gallery' in part)
|
|
254
|
+
out.push(...gallery(media, part.gallery, part.layout));
|
|
255
|
+
else if ('quote' in part)
|
|
256
|
+
out.push(quote(part.quote, part.author, part.role));
|
|
257
|
+
else
|
|
258
|
+
out.push(testimonial(part.testimonial, part.name, part.role));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
flush();
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// The studio: addresses and the work, oldest first
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
262
267
|
/** `hello@studiohale.com` for "Studio Hale": the studio's own name, as a mailbox. */
|
|
263
268
|
export function studioEmail(siteName, mailbox = 'hello') {
|
|
264
269
|
const domain = siteName
|
|
@@ -268,246 +273,83 @@ export function studioEmail(siteName, mailbox = 'hello') {
|
|
|
268
273
|
.replace(/[^a-z0-9]+/g, '');
|
|
269
274
|
return `${mailbox}@${domain === '' ? 'studio' : domain}.com`;
|
|
270
275
|
}
|
|
271
|
-
function
|
|
272
|
-
return {
|
|
276
|
+
function mailboxes(studio) {
|
|
277
|
+
return {
|
|
278
|
+
hello: studioEmail(studio),
|
|
279
|
+
jobs: studioEmail(studio, 'jobs'),
|
|
280
|
+
press: studioEmail(studio, 'press'),
|
|
281
|
+
};
|
|
273
282
|
}
|
|
274
|
-
|
|
275
|
-
{
|
|
276
|
-
title: 'Warp and Weft',
|
|
277
|
-
slug: 'warp-and-weft',
|
|
278
|
-
client: { name: 'Bradwell Mill Museum', slug: 'bradwell-mill-museum' },
|
|
279
|
-
discipline: 'Exhibitions',
|
|
280
|
-
disciplines: ['exhibitions', 'identity'],
|
|
281
|
-
team: ['mara-lindgren', 'tom-reyes', 'priya-nair'],
|
|
282
|
-
publishedAt: '2022-04-12T09:00:00.000Z',
|
|
283
|
-
summary: 'Graphics for an exhibition about two centuries of cotton weaving, in a museum that occupies the mill itself: a woven title wall, room panels and three hundred object labels.',
|
|
284
|
-
cover: 'warp-and-weft-cover',
|
|
285
|
-
body: (_copy, media) => [
|
|
286
|
-
prose(h2('The brief'), p('Bradwell Mill Museum occupies the old spinning and weaving sheds of a mill that closed in 1981. For its fortieth year the museum planned the largest exhibition in its history: 340 objects in four galleries, from pattern books and loom parts to recordings of the people who worked there.'), p('The museum asked us for all of the graphics: the title wall, the room panels, the object labels and the posters that would bring the town in. The galleries are the mill’s own sheds, with iron columns, uneven light and walls that could not be drilled.')),
|
|
287
|
-
prose(h2('Woven, then written'), p('The identity is a plain weave drawn in the dyes the mill used: indigo, madder and weld yellow, over undyed cotton. At the entrance the weave is the size of a wall. On the posters, the room panels and the labels it shrinks to a single woven strip along the top edge.'), p('Most of the work was writing. With the curators we set a limit of 120 words for a room panel and 40 for a label, and every label begins with what the object was for before it says what it is made of. The type is Barlow Condensed, set large enough to be read without stepping over the rope, and every panel was proofed at full size on the gallery wall before it went to print.')),
|
|
288
|
-
...figure(media, 'warp-and-weft-room', 'Room 2, the weaving shed: panel and object label.', 'end'),
|
|
289
|
-
prose(h2('Result'), p('The exhibition ran from April to November 2022 and drew 61,000 visitors, the museum’s highest attendance in a single year. The room panels are now part of the permanent display, and the museum’s own staff write new labels to the same limits.')),
|
|
290
|
-
],
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
title: 'Four tins for a family cannery',
|
|
294
|
-
slug: 'gannet-and-sons',
|
|
295
|
-
client: { name: 'Gannet & Sons', slug: 'gannet-and-sons' },
|
|
296
|
-
discipline: 'Packaging',
|
|
297
|
-
disciplines: ['packaging', 'identity'],
|
|
298
|
-
team: ['joel-okafor', 'ines-carvalho'],
|
|
299
|
-
publishedAt: '2023-04-20T09:00:00.000Z',
|
|
300
|
-
summary: 'Packaging for a hundred-year-old cannery selling four of its tins under its own name for the first time: a fish for each variety, drawn from three shapes, and a label that says where the fish came from.',
|
|
301
|
-
cover: 'gannet-cover',
|
|
302
|
-
body: (_copy, media) => [
|
|
303
|
-
prose(h2('The brief'), p('Gannet & Sons has packed fish in Aldermouth since 1923. For most of that time its tins were sold under supermarket labels, and the family’s name appeared only in small print on the back. When the fourth generation decided to sell under their own name, in delicatessens and food halls, they had four products, a small budget and a shelf of competitors with beautiful tins.')),
|
|
304
|
-
prose(h2('Four fish'), p('Each variety has a colour and a fish of its own, drawn from the same three shapes: a lens for the body, a forked triangle for the tail and a dot for the eye. The sardine is cream on blue with a row of red spots, the mackerel carries its stripes, the anchovy is long and thin, and the sprats come in threes. The family name runs across the top of every tin in spaced capitals, with the town and the year the cannery opened.'), p('The band around the tin carries what the lid has no room for: where and when the fish was caught, how soon after landing it was packed, and what to do with a tin once it is open. Ines Carvalho wrote it with the family, in their words.')),
|
|
305
|
-
...gallery(media, ['gannet-sardines', 'gannet-shelf'], 'grid'),
|
|
306
|
-
prose(h2('Result'), p('The tins were printed in runs of 20,000 per variety. Within a year they were stocked by 140 shops, from Edinburgh to Penzance, and the cannery added a fifth variety, smoked mackerel, which the family drew themselves from the same three shapes.')),
|
|
307
|
-
],
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
title: 'A ticketing app for regional rail',
|
|
311
|
-
slug: 'westmoor-rail-app',
|
|
312
|
-
client: { name: 'Westmoor Rail', slug: 'westmoor-rail' },
|
|
313
|
-
discipline: 'Digital products',
|
|
314
|
-
disciplines: ['digital'],
|
|
315
|
-
team: ['ada-brennan', 'tom-reyes'],
|
|
316
|
-
publishedAt: '2023-11-06T09:00:00.000Z',
|
|
317
|
-
summary: 'An app for a regional rail operator that lets a passenger plan, buy and show a ticket in under a minute, built around the departures board passengers already trust.',
|
|
318
|
-
cover: 'westmoor-rail-cover',
|
|
319
|
-
body: (_copy, media) => [
|
|
320
|
-
prose(h2('The brief'), p('Westmoor Rail runs 410 trains a day between Westmoor Central, Aldermouth and Kirkby Fell. In 2022 a third of its tickets were still bought from machines at the station, many by passengers who had downloaded the operator’s previous app and given up at the payment screen.'), p('The operator wanted a new app, and a shorter queue at the Westmoor Central machines between seven and nine in the morning.')),
|
|
321
|
-
...figure(media, 'westmoor-rail-departures', 'Live departures at Westmoor Central.', 'end'),
|
|
322
|
-
prose(h2('Start from the board'), p('We watched passengers read the departures boards and saw that they read them in the same order every time: the time, the destination, the platform. The main screen of the app repeats that order exactly, with the platform number in the same yellow square the stations use. Calling points and the service status sit under the destination in a smaller size, where a passenger who needs them looks.'), p('Buying a ticket takes three screens: where from and to, which ticket, and payment. The app keeps the last three journeys on its first screen, because that is how most commuters travel, and a season ticket renews from its own reminder.'), p('Ada Brennan led the design with four engineers at the operator, in two-week cycles, and every version was tried by passengers on the platform at Carrick Bridge before it shipped.')),
|
|
323
|
-
...figure(media, 'westmoor-rail-components', 'Components: buttons, platform numbers, service status and the departure row.', 'wide'),
|
|
324
|
-
prose(h2('Result'), p('The app launched in November 2023. A year later it sold 41 percent of the operator’s tickets, a purchase took 52 seconds on average, and the morning queue at Westmoor Central had shortened enough for two of the eight machines to be moved to smaller stations.')),
|
|
325
|
-
],
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
title: 'A cover system for paperback fiction',
|
|
329
|
-
slug: 'hollis-and-crane-paperbacks',
|
|
330
|
-
client: { name: 'Hollis & Crane', slug: 'hollis-and-crane' },
|
|
331
|
-
discipline: 'Book design',
|
|
332
|
-
disciplines: ['editorial'],
|
|
333
|
-
team: ['joel-okafor', 'priya-nair', 'sam-whitford'],
|
|
334
|
-
publishedAt: '2024-03-14T09:00:00.000Z',
|
|
335
|
-
summary: 'A cover system for the paperback fiction list of an independent publisher in Leith: one typographic rule, a cut-paper image for every book, and a shelf that reads as one list.',
|
|
336
|
-
cover: 'hollis-crane-cover',
|
|
337
|
-
body: ({ siteName }, media) => [
|
|
338
|
-
prose(h2('The brief'), p('Hollis & Crane publishes around twenty novels and story collections a year from a converted print works in Leith. Its hardbacks had a following. Its paperbacks, where most readers actually meet the list, were designed title by title and sold on the strength of the author alone. Booksellers told the sales team that they could not find the imprint on a table of new fiction.'), p('Ruth Crane, the publisher, wanted the paperbacks to be recognisable from three metres away without making twenty books look alike. The covers also had to be produced in a week by a production team of two, for print runs that are often no more than three thousand copies.')),
|
|
339
|
-
...figure(media, 'hollis-crane-salt-year', '*The Salt Year* by Nora Pell, the first cover in the series.', 'start'),
|
|
340
|
-
prose(h2('One rule, many images'), p('The system has one fixed rule and a great deal of freedom. The author’s name sits at the top in small capitals. The title sits below it in EB Garamond, ranged left and never longer than three lines. At the foot there is a thin line and the name of the imprint in italic. Those positions never move, whatever the book.'), p('Between them is the image, and here every book gets its own. Each one is cut from paper in two colours at most, from a single idea in the book: a sun sinking into the sea for a novel set on a salt marsh, one lit window among dark ones for a story about neighbours, a crescent for a book written at night. The images are commissioned from Ida Moss, an illustrator in Glasgow, and scanned flat. Two colours keep the covers calm on a table crowded with photography, and they keep the printing cheap.'), p('The spines follow the same logic turned on its side: the title in Garamond, the author in capitals, the imprint’s mark at the base. Shelved together, a year’s titles read as a list, which is what a publisher’s backlist should look like in a good bookshop.')),
|
|
341
|
-
...figure(media, 'hollis-crane-spines', 'Twelve spines from the 2024 list.', 'wide'),
|
|
342
|
-
...figure(media, 'hollis-crane-grid', 'The cover grid, as given to the production team.', 'end'),
|
|
343
|
-
prose(h2('In the shops'), p('The first six paperbacks were published in March 2024. By the end of the year two national chains and eleven independent shops had given the list a table of its own, and the publisher’s paperback sales had risen by 22 percent in a year when fiction sales fell across the country.'), p(`The production team now briefs Ida Moss directly and sends ${siteName} each finished cover for a last look. It takes about ten minutes, and most weeks there is nothing to add.`)),
|
|
344
|
-
testimonial('We used to argue about every cover. Now we argue about the image, which is the right thing to argue about, and the rest of the cover looks after itself.', 'Ruth Crane', 'Publisher, Hollis & Crane'),
|
|
345
|
-
],
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
title: 'Saltmarsh Quay ferry terminal',
|
|
349
|
-
slug: 'saltmarsh-quay',
|
|
350
|
-
client: { name: 'Aldermouth Harbour Board', slug: 'aldermouth-harbour-board' },
|
|
351
|
-
discipline: 'Wayfinding',
|
|
352
|
-
disciplines: ['wayfinding'],
|
|
353
|
-
team: ['tom-reyes', 'mara-lindgren', 'sam-whitford'],
|
|
354
|
-
publishedAt: '2024-09-02T09:00:00.000Z',
|
|
355
|
-
summary: 'Wayfinding for a rebuilt ferry terminal serving three islands: a family of signs, twelve pictograms and a number for every gate, tested with passengers before a single sign was made.',
|
|
356
|
-
cover: 'saltmarsh-quay-cover',
|
|
357
|
-
body: (_copy, media) => [
|
|
358
|
-
prose(h2('The brief'), p('Saltmarsh Quay carries 1.4 million passengers a year on ferries to Brenn, Holm and Sulvay, and a freight service that runs through the night. When the Aldermouth Harbour Board rebuilt the terminal in 2023, the new building doubled the waiting space and moved every gate. It also inherited forty years of signs, added one at a time, in four typefaces and three shades of blue.'), p('The board asked for a complete wayfinding system for the terminal, its car park and the walk to the bus station, to be installed in the eight weeks between the end of the winter timetable and the first summer sailing.')),
|
|
359
|
-
...figure(media, 'saltmarsh-quay-gate', 'Gate sign in the departures hall.', 'wide'),
|
|
360
|
-
prose(h2('Who reads a sign'), p('Before drawing anything we spent four days in the old terminal, following passengers from the car park to the boats. Two things stood out. Most passengers were in no hurry, but they were anxious, carrying bags and children and trying to read a sign while walking. And about one in five on a summer weekend were visiting the islands for the first time and knew the island they were going to, never the name of the ferry.'), p('So the system is organised around the islands. Every gate has a number, set in a yellow square that can be seen from the entrance, and the name of its island in Atkinson Hyperlegible, a typeface designed for readers with low vision. The number and the name are repeated on every sign between the door and the gate, in the same order, so that a passenger never has to translate one sign into the next.')),
|
|
361
|
-
...gallery(media, ['saltmarsh-quay-totem', 'saltmarsh-quay-pictograms'], 'grid'),
|
|
362
|
-
prose(h2('Pictograms and testing'), p('We drew twelve pictograms on a grid of one hundred units and tested each on paper with passengers waiting for the 11.15 to Sulvay. The first ferry was read as a factory. The first waiting room, a figure standing behind a bench, was read as a desk. Both were redrawn until nine people in ten named them correctly without a word beside them.'), p('Full-size cardboard versions of the overhead signs hung in the new building for a week before fabrication, and Tom Reyes walked the route with members of the Aldermouth Sight Loss Council. Two signs moved. One was taken out altogether, because it hid the gate numbers behind it.')),
|
|
363
|
-
prose(h2('Result'), p('All 214 signs were installed on time. In the first summer season the information desk recorded 38 percent fewer questions about where to go, and the harbour board has since adopted the system at its two smaller piers.')),
|
|
364
|
-
quote('Passengers used to queue at the desk to ask for gate three. Now the desk gets asked about the weather on Sulvay.', 'Callum Rennie', 'Terminal manager, Aldermouth Harbour Board'),
|
|
365
|
-
],
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
title: 'Annual report 2024',
|
|
369
|
-
slug: 'tidewater-trust-annual-report',
|
|
370
|
-
client: { name: 'Tidewater Trust', slug: 'tidewater-trust' },
|
|
371
|
-
discipline: 'Print and editorial',
|
|
372
|
-
disciplines: ['editorial'],
|
|
373
|
-
team: ['priya-nair', 'sam-whitford'],
|
|
374
|
-
publishedAt: '2025-04-24T09:00:00.000Z',
|
|
375
|
-
summary: 'The annual report of a coastal conservation charity, designed and edited: forty-eight pages that open with what changed on the coast and keep the accounts complete and readable.',
|
|
376
|
-
cover: 'tidewater-cover',
|
|
377
|
-
body: (_copy, media) => [
|
|
378
|
-
prose(h2('The brief'), p('Tidewater Trust looks after 3,100 hectares of saltmarsh, mudflat and dune on the north-east coast. Its annual report has three readers: 6,400 members, the grant-makers who fund most of its restoration work, and the charity regulator. The earlier reports were written for the last of the three, and opened with eleven pages of governance before a single bird was mentioned.')),
|
|
379
|
-
...figure(media, 'tidewater-report-cover', 'The cover, printed on uncoated recycled stock.', 'start'),
|
|
380
|
-
prose(h2('Coast first'), p('We reversed the order. The report now opens on the year’s work, one spread per site, each led by a single figure a member would care about: 412 hectares of saltmarsh returned to the tide, 140 wintering redshank at Hollow Marsh. The governance and the accounts follow in full, in the same typefaces and on the same grid, so that they read as part of the year.'), p('Libre Caslon sets the headlines and the large figures, and Work Sans sets the text and every table. The colours come from the estuary at low tide and code every chart the same way: dark blue for restoration, marsh green for the reserves, pale blue for learning.'), p('Priya Nair edited the text with the trust’s director over six weeks, taking it from 31,000 words to 14,000 without losing a number the auditors needed.')),
|
|
381
|
-
...figure(media, 'tidewater-data', 'Finance and volunteering, pages 28 and 29.', 'wide'),
|
|
382
|
-
prose(h2('Result'), p('The report was printed in an edition of 7,000 and published online as a set of pages rather than a download. Two new funders quoted it in their award letters, and the trust has asked us to design the next three.')),
|
|
383
|
-
],
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
title: 'Fenmore Building Society',
|
|
387
|
-
slug: 'fenmore-building-society',
|
|
388
|
-
client: { name: 'Fenmore Building Society', slug: 'fenmore-building-society' },
|
|
389
|
-
discipline: 'Identity',
|
|
390
|
-
disciplines: ['identity', 'wayfinding'],
|
|
391
|
-
team: ['ines-carvalho', 'joel-okafor'],
|
|
392
|
-
publishedAt: '2025-06-16T09:00:00.000Z',
|
|
393
|
-
summary: 'A new identity for a building society with 38 branches across the fens: a warm serif, a mark of five reeds and letters that sound like the person behind the counter.',
|
|
394
|
-
cover: 'fenmore-cover',
|
|
395
|
-
body: (_copy, media) => [
|
|
396
|
-
prose(h2('The brief'), p('Fenmore was founded in Ely in 1872 by farm workers who wanted somewhere safe to keep their wages. It now holds the savings of 190,000 members and the mortgages of 26,000 households, nearly all of them within forty miles of its head office. Its identity, a green shield with a sheaf of wheat, dated from a merger in 1994 and made the society look like a bank from somewhere larger.'), p('The board wanted an identity that looked local without looking old, and that its own staff could apply to everything from a branch fascia to a letter about a change of rate.')),
|
|
397
|
-
...figure(media, 'fenmore-stationery', 'Letterhead and business cards.', 'start'),
|
|
398
|
-
prose(h2('Reeds and a serif'), p('The mark is five reeds of different heights standing on a line of water, drawn with rounded ends so that it survives being embroidered on a uniform or engraved on a cash machine. The name is set in Young Serif, heavy and warm, whose curves sit well beside the reeds. Everything else is set in Schibsted Grotesk, chosen for its clear figures on a statement.'), p('Four colours cover every use: Fen green, Reed, Chalk and Slate. Branch windows carry three words, savings, mortgages and advice, large enough to read from the other side of a market square.'), p('With Ines Carvalho the society also rewrote its forty-three standard letters, so that each one begins with what has happened to the member’s money and ends with the name of a person to ask.')),
|
|
399
|
-
...figure(media, 'fenmore-branch', 'Branch front, Littleport.', 'wide'),
|
|
400
|
-
prose(h2('Rollout'), p('The identity went live on the website and on statements in June 2025, and branch by branch over the following nine months, each fascia replaced when the old one was due for repainting. Complaints about unclear letters halved in the first quarter.')),
|
|
401
|
-
testimonial('They listened to our branch staff before they showed us a single drawing, and the staff recognise their own suggestions in what we use every day.', 'Claire Denholm', 'Head of marketing, Fenmore Building Society'),
|
|
402
|
-
],
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
title: 'The 2025/26 concert season',
|
|
406
|
-
slug: 'rookery-hall-season',
|
|
407
|
-
client: { name: 'Rookery Hall', slug: 'rookery-hall' },
|
|
408
|
-
discipline: 'Identity',
|
|
409
|
-
disciplines: ['identity', 'editorial'],
|
|
410
|
-
team: ['mara-lindgren', 'ines-carvalho', 'sam-whitford'],
|
|
411
|
-
publishedAt: '2025-09-01T09:00:00.000Z',
|
|
412
|
-
summary: 'A season identity for a concert hall in Manchester: four series, four colour pairs and one poster grid that the hall’s own team now fills in for sixty-two concerts a year.',
|
|
413
|
-
cover: 'rookery-hall-cover',
|
|
414
|
-
body: ({ siteName }, media) => [
|
|
415
|
-
prose(h2('The brief'), p('Rookery Hall presents sixty-two concerts a year, from the resident orchestra’s symphony nights to Saturday mornings for families. Until 2024 every concert was designed on its own, by whichever freelance designer was free that month, and the season brochure ran to forty pages that audiences told the box office they never finished.'), p('Helen Marsh, the hall’s director of programming, asked for something more modest than a rebrand. The name, the building and the orchestra were never the problem. What the hall lacked was a way of making sixty-two concerts look like one season, which its marketing team of three could keep up without calling a designer every week.')),
|
|
416
|
-
...figure(media, 'rookery-hall-series', 'Four series, four colour pairs, four figures.', 'end'),
|
|
417
|
-
prose(h2('The system'), p('We started by sorting the season before drawing it. Every concert already belonged to one of four series, Symphonic, Chamber, New Music and Family, but the series were named only in small print. We made them the organising idea. Each series has a pair of colours and a single geometric figure: rings for the orchestra, five raised voices for chamber music, a climbing stair for new work and a scatter of circles for families.'), p('The type does the rest. Big Shoulders Display, a condensed face first drawn for the signs of Chicago, sets the composer’s name as large as the poster allows, so that a name of eight letters and a name of five both fill the width. Everything else sits on a strip of three columns at the foot of the poster: when, who and how much. The grid is fixed. A poster for a Tuesday lunchtime recital and a poster for the opening night differ only in their words, their series and their figure.'), p('We built the templates in the software the hall already used and wrote a twelve-page guide together with the marketing team. It covers the decisions they actually face: what to do with a title that is too long, how to credit a soloist, which photographs of a conductor may appear, and where.')),
|
|
418
|
-
...gallery(media, ['rookery-hall-poster-sibelius', 'rookery-hall-poster-carnival'], 'grid'),
|
|
419
|
-
prose(h2('The brochure'), p('The season brochure went from forty pages to twenty-four. The first half introduces the season series by series; the second half is a calendar with one line per concert, coded by series so that a reader can find every chamber concert with one glance down the page. The hall printed 30,000 copies and handed them out through libraries, cafés and its members instead of posting them.')),
|
|
420
|
-
...figure(media, 'rookery-hall-brochure', 'The season brochure, calendar spread.', 'wide'),
|
|
421
|
-
quote('The team can make a poster on a Tuesday afternoon and it looks like the season. That was the whole point.', 'Helen Marsh', 'Director of programming, Rookery Hall'),
|
|
422
|
-
prose(h2('The outcome'), p('The identity launched with the 2025/26 season in May. In the first four months the hall’s team produced 118 posters, banners and programme covers without outside help. Advance sales for the season were 14 percent higher than the year before, and sales for the New Music series, which the hall had considered cutting, rose by a third.'), p(`${siteName} still designs one poster a season, for the opening night. Everything else belongs to the hall.`)),
|
|
423
|
-
],
|
|
424
|
-
},
|
|
425
|
-
];
|
|
426
|
-
/** The blocks of one project page, in order. Media that is absent (a caller that never seeded any) is left out, never replaced. */
|
|
283
|
+
/** The blocks of one project page, in order. Media that is absent is left out, never replaced. */
|
|
427
284
|
export function portfolioProjectBlocks(demo, copy, media) {
|
|
428
|
-
return demo.body(
|
|
285
|
+
return blocksOf(demo.body(copy.siteName), media);
|
|
429
286
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
'Fenmore Building Society',
|
|
436
|
-
'Gannet & Sons',
|
|
437
|
-
'Hollis & Crane',
|
|
438
|
-
'Kestrel Opera Company',
|
|
439
|
-
'Lindley Public Library',
|
|
440
|
-
'Marrick Architects',
|
|
441
|
-
'Oakwright Joinery',
|
|
442
|
-
'Rookery Hall',
|
|
443
|
-
'Tidewater Trust',
|
|
444
|
-
'Westmoor Rail',
|
|
445
|
-
];
|
|
446
|
-
function disciplineRows(key) {
|
|
287
|
+
function pagesCopy(copy, siteName) {
|
|
288
|
+
return copy.pages(siteName, mailboxes(siteName));
|
|
289
|
+
}
|
|
290
|
+
function disciplineRows(copy, key, title) {
|
|
291
|
+
const archive = copy.schema.taxonomies.disciplines.name;
|
|
447
292
|
return {
|
|
448
293
|
_key: key,
|
|
449
294
|
_type: 'featureGrid',
|
|
450
295
|
_version: BLOCK_VERSION,
|
|
451
|
-
title
|
|
452
|
-
items:
|
|
296
|
+
title,
|
|
297
|
+
items: copy.disciplines.map((demo) => ({
|
|
453
298
|
_key: `${key}-${demo.slug}`,
|
|
454
299
|
title: demo.name,
|
|
455
300
|
text: demo.description,
|
|
456
|
-
link: { href:
|
|
301
|
+
link: { href: `/${archive}/${demo.slug}` },
|
|
457
302
|
})),
|
|
458
303
|
};
|
|
459
304
|
}
|
|
460
|
-
export function buildPortfolioHomeBlocks(options = {}) {
|
|
461
|
-
const
|
|
305
|
+
export function buildPortfolioHomeBlocks(copy, options = {}) {
|
|
306
|
+
const siteName = options.siteName?.trim() || DEFAULT_STUDIO_NAME;
|
|
307
|
+
const { hello } = mailboxes(siteName);
|
|
308
|
+
const { home } = pagesCopy(copy, siteName);
|
|
462
309
|
return [
|
|
463
310
|
{
|
|
464
311
|
_key: 'home-statement',
|
|
465
312
|
_type: 'hero',
|
|
466
313
|
_version: BLOCK_VERSION,
|
|
467
|
-
title:
|
|
468
|
-
subtitle:
|
|
469
|
-
actions: [{ label:
|
|
314
|
+
title: home.heroTitle,
|
|
315
|
+
subtitle: home.heroSubtitle,
|
|
316
|
+
actions: [{ label: home.heroAction, target: { href: `/${copy.pageSlugs.work}` } }],
|
|
470
317
|
},
|
|
471
318
|
{
|
|
472
319
|
_key: 'home-work',
|
|
473
320
|
_type: 'collectionList',
|
|
474
321
|
_version: BLOCK_VERSION,
|
|
475
|
-
title:
|
|
322
|
+
title: home.workTitle,
|
|
476
323
|
collection: 'project',
|
|
477
324
|
sort: { field: 'createdAt', direction: 'desc' },
|
|
478
325
|
limit: 6,
|
|
479
326
|
layout: 'grid',
|
|
480
327
|
},
|
|
481
|
-
disciplineRows('home-disciplines'),
|
|
328
|
+
disciplineRows(copy, 'home-disciplines', home.disciplinesTitle),
|
|
482
329
|
{
|
|
483
330
|
_key: 'home-figures',
|
|
484
331
|
_type: 'stats',
|
|
485
332
|
_version: BLOCK_VERSION,
|
|
486
|
-
title:
|
|
487
|
-
items:
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
value: '31',
|
|
493
|
-
label: 'clients who came back for a second project',
|
|
494
|
-
},
|
|
495
|
-
{ _key: 'home-figures-signs', value: '1,900', label: 'signs installed and still standing' },
|
|
496
|
-
],
|
|
333
|
+
title: home.figuresTitle,
|
|
334
|
+
items: home.figures.map((figureCopy, index) => ({
|
|
335
|
+
_key: `home-figures-${index}`,
|
|
336
|
+
value: figureCopy.value,
|
|
337
|
+
label: figureCopy.label,
|
|
338
|
+
})),
|
|
497
339
|
},
|
|
498
|
-
quote(
|
|
340
|
+
quote(home.quote.text, home.quote.author, home.quote.role),
|
|
499
341
|
{
|
|
500
342
|
_key: 'home-clients',
|
|
501
343
|
_type: 'featureGrid',
|
|
502
344
|
_version: BLOCK_VERSION,
|
|
503
|
-
title:
|
|
504
|
-
items:
|
|
345
|
+
title: home.clientsTitle,
|
|
346
|
+
items: home.clients.map((name, index) => ({ _key: `home-clients-${index}`, title: name })),
|
|
505
347
|
},
|
|
506
348
|
{
|
|
507
349
|
_key: 'home-index',
|
|
508
350
|
_type: 'collectionList',
|
|
509
351
|
_version: BLOCK_VERSION,
|
|
510
|
-
title:
|
|
352
|
+
title: home.indexTitle,
|
|
511
353
|
collection: 'project',
|
|
512
354
|
sort: { field: 'createdAt', direction: 'desc' },
|
|
513
355
|
limit: 12,
|
|
@@ -517,23 +359,62 @@ export function buildPortfolioHomeBlocks(options = {}) {
|
|
|
517
359
|
_key: 'home-contact',
|
|
518
360
|
_type: 'cta',
|
|
519
361
|
_version: BLOCK_VERSION,
|
|
520
|
-
title:
|
|
521
|
-
text:
|
|
522
|
-
actions: [{ label:
|
|
362
|
+
title: home.ctaTitle,
|
|
363
|
+
text: home.ctaText,
|
|
364
|
+
actions: [{ label: hello, target: { href: `mailto:${hello}` }, emphasis: 'primary' }],
|
|
523
365
|
},
|
|
524
366
|
];
|
|
525
367
|
}
|
|
526
|
-
|
|
368
|
+
/** The photo credits as a bulleted rich-text list: title, author, licence and source, linked. */
|
|
369
|
+
function creditsBlock(words) {
|
|
370
|
+
const intro = words.intro.map((part) => 'h2' in part ? h2(part.h2) : 'p' in part ? p(part.p) : null);
|
|
371
|
+
const items = PORTFOLIO_PHOTO_CREDITS.map((credit) => {
|
|
372
|
+
const key = nextKey('studio-credit');
|
|
373
|
+
const markDefs = [];
|
|
374
|
+
const link = (text, href, name) => {
|
|
375
|
+
const mark = `${key}-${name}`;
|
|
376
|
+
markDefs.push({ _key: mark, _type: 'link', href });
|
|
377
|
+
return { _key: `${key}-${name}-s`, _type: 'span', text, marks: [mark] };
|
|
378
|
+
};
|
|
379
|
+
const plain = (text, name) => ({
|
|
380
|
+
_key: `${key}-${name}-s`,
|
|
381
|
+
_type: 'span',
|
|
382
|
+
text,
|
|
383
|
+
marks: [],
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
_key: key,
|
|
387
|
+
_type: 'block',
|
|
388
|
+
style: 'normal',
|
|
389
|
+
listItem: 'bullet',
|
|
390
|
+
level: 1,
|
|
391
|
+
children: [
|
|
392
|
+
plain(`${credit.title} · ${credit.author} · `, 'a'),
|
|
393
|
+
credit.licenceUrl === undefined
|
|
394
|
+
? plain(credit.licence, 'b')
|
|
395
|
+
: link(credit.licence, credit.licenceUrl, 'b'),
|
|
396
|
+
plain(' · ', 'c'),
|
|
397
|
+
link(words.sourceWord, credit.source, 'd'),
|
|
398
|
+
],
|
|
399
|
+
markDefs,
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
return prose(...intro.filter((node) => node !== null), ...items);
|
|
403
|
+
}
|
|
404
|
+
export function buildPortfolioDemoPages(copy, options = {}) {
|
|
527
405
|
const media = options.media ?? {};
|
|
528
406
|
const siteName = options.siteName?.trim() || DEFAULT_STUDIO_NAME;
|
|
529
|
-
const {
|
|
530
|
-
const
|
|
531
|
-
const
|
|
407
|
+
const { hello } = mailboxes(siteName);
|
|
408
|
+
const words = pagesCopy(copy, siteName);
|
|
409
|
+
const slug = copy.pageSlugs;
|
|
410
|
+
const teamArchive = copy.schema.taxonomies.team.name;
|
|
411
|
+
const disciplineArchive = copy.schema.taxonomies.disciplines.name;
|
|
412
|
+
const { studio, contact } = words;
|
|
532
413
|
return [
|
|
533
|
-
{ title:
|
|
414
|
+
{ title: words.titles.home, slug: slug.home, blocks: buildPortfolioHomeBlocks(copy, options) },
|
|
534
415
|
{
|
|
535
|
-
title:
|
|
536
|
-
slug:
|
|
416
|
+
title: words.titles.work,
|
|
417
|
+
slug: slug.work,
|
|
537
418
|
blocks: [
|
|
538
419
|
{
|
|
539
420
|
_key: 'work-grid',
|
|
@@ -548,165 +429,106 @@ export function buildPortfolioDemoPages(options = {}) {
|
|
|
548
429
|
_key: 'work-disciplines',
|
|
549
430
|
_type: 'featureGrid',
|
|
550
431
|
_version: BLOCK_VERSION,
|
|
551
|
-
title:
|
|
552
|
-
items:
|
|
432
|
+
title: words.work.disciplinesTitle,
|
|
433
|
+
items: copy.disciplines.map((demo) => ({
|
|
553
434
|
_key: `work-disciplines-${demo.slug}`,
|
|
554
435
|
title: demo.name,
|
|
555
|
-
link: { href:
|
|
436
|
+
link: { href: `/${disciplineArchive}/${demo.slug}` },
|
|
556
437
|
})),
|
|
557
438
|
},
|
|
558
439
|
],
|
|
559
440
|
},
|
|
560
441
|
{
|
|
561
|
-
title:
|
|
562
|
-
slug:
|
|
442
|
+
title: words.titles.studio,
|
|
443
|
+
slug: slug.studio,
|
|
563
444
|
blocks: [
|
|
564
|
-
|
|
445
|
+
...blocksOf(studio.about, media),
|
|
565
446
|
...gallery(media, [
|
|
566
|
-
'
|
|
567
|
-
'
|
|
568
|
-
'
|
|
569
|
-
'
|
|
570
|
-
'
|
|
571
|
-
'
|
|
447
|
+
'towers-cover',
|
|
448
|
+
'lagoon-cover',
|
|
449
|
+
'vines-cover',
|
|
450
|
+
'glacier-cover',
|
|
451
|
+
'forest-cover',
|
|
452
|
+
'port-cover',
|
|
572
453
|
], 'carousel'),
|
|
573
|
-
|
|
454
|
+
...blocksOf(studio.how, media),
|
|
574
455
|
{
|
|
575
456
|
_key: 'studio-principles',
|
|
576
457
|
_type: 'featureGrid',
|
|
577
458
|
_version: BLOCK_VERSION,
|
|
578
|
-
title:
|
|
579
|
-
items:
|
|
580
|
-
{
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
_key: 'studio-principles-year',
|
|
587
|
-
title: 'Design for the second year',
|
|
588
|
-
text: 'A launch is the easy part. We design for the tenth poster, the fortieth letter and the sign added after we have left.',
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
_key: 'studio-principles-proof',
|
|
592
|
-
title: 'Proof on the real thing',
|
|
593
|
-
text: 'Signs go on the wall, labels on the tin and type on the paper it will be printed on. A screen flatters everything.',
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
_key: 'studio-principles-write',
|
|
597
|
-
title: 'Write it down',
|
|
598
|
-
text: 'Every decision comes with a sentence that explains it, so a client can defend it in a meeting we are not in.',
|
|
599
|
-
},
|
|
600
|
-
],
|
|
459
|
+
title: studio.principlesTitle,
|
|
460
|
+
items: studio.principles.map((item, index) => ({
|
|
461
|
+
_key: `studio-principles-${index}`,
|
|
462
|
+
title: item.title,
|
|
463
|
+
text: item.text,
|
|
464
|
+
})),
|
|
601
465
|
},
|
|
602
466
|
{
|
|
603
467
|
_key: 'studio-numbers',
|
|
604
468
|
_type: 'statCounter',
|
|
605
469
|
_version: BLOCK_VERSION,
|
|
606
|
-
title:
|
|
607
|
-
stats:
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
{
|
|
613
|
-
_key: 'studio-numbers-languages',
|
|
614
|
-
value: '9',
|
|
615
|
-
label: 'Languages our work has been set in',
|
|
616
|
-
},
|
|
617
|
-
],
|
|
470
|
+
title: studio.numbersTitle,
|
|
471
|
+
stats: studio.numbers.map((item, index) => ({
|
|
472
|
+
_key: `studio-numbers-${index}`,
|
|
473
|
+
value: item.value,
|
|
474
|
+
label: item.label,
|
|
475
|
+
})),
|
|
618
476
|
},
|
|
619
477
|
{
|
|
620
478
|
_key: 'studio-partners',
|
|
621
479
|
_type: 'featureGrid',
|
|
622
480
|
_version: BLOCK_VERSION,
|
|
623
|
-
title:
|
|
624
|
-
items:
|
|
625
|
-
{
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
_key: 'studio-partners-joel',
|
|
633
|
-
title: 'Joel Okafor',
|
|
634
|
-
text: 'Partner since 2017. Identity and packaging. He teaches typography one day a week at an art school in London.',
|
|
635
|
-
link: { href: '/team/joel-okafor' },
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
_key: 'studio-partners-ines',
|
|
639
|
-
title: 'Ines Carvalho',
|
|
640
|
-
text: 'Partner since 2017. Identity and writing. She was art director at a publisher in Lisbon for eight years before joining.',
|
|
641
|
-
link: { href: '/team/ines-carvalho' },
|
|
642
|
-
},
|
|
643
|
-
],
|
|
481
|
+
title: studio.partnersTitle,
|
|
482
|
+
items: studio.partners.map((item) => ({
|
|
483
|
+
_key: `studio-partners-${item.slug}`,
|
|
484
|
+
title: item.name,
|
|
485
|
+
text: item.text,
|
|
486
|
+
link: { href: `/${teamArchive}/${item.slug}` },
|
|
487
|
+
})),
|
|
644
488
|
},
|
|
645
|
-
testimonial(
|
|
489
|
+
testimonial(studio.testimonial.text, studio.testimonial.name, studio.testimonial.role),
|
|
646
490
|
{
|
|
647
491
|
_key: 'studio-faq',
|
|
648
492
|
_type: 'faq',
|
|
649
493
|
_version: BLOCK_VERSION,
|
|
650
|
-
title:
|
|
651
|
-
items: [
|
|
652
|
-
{
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
p('With a conversation and a written brief, usually after a visit. We then send a proposal with a fixed fee for the first stage, research and one direction, so that a client can stop there if the fit is wrong.'),
|
|
657
|
-
],
|
|
658
|
-
},
|
|
659
|
-
{
|
|
660
|
-
_key: 'studio-faq-cost',
|
|
661
|
-
question: 'What does a project cost?',
|
|
662
|
-
answer: [
|
|
663
|
-
p('Identities start at around £45,000 and wayfinding systems at around £60,000 before fabrication. Books, reports and exhibitions are quoted project by project. We give these ranges because a first conversation goes better when both sides know them.'),
|
|
664
|
-
],
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
_key: 'studio-faq-abroad',
|
|
668
|
-
question: 'Do you work outside the UK?',
|
|
669
|
-
answer: [
|
|
670
|
-
p('About a quarter of our work is for clients elsewhere in Europe. We travel for research and installation and work from London in between.'),
|
|
671
|
-
],
|
|
672
|
-
},
|
|
673
|
-
{
|
|
674
|
-
_key: 'studio-faq-placements',
|
|
675
|
-
question: 'Do you offer placements?',
|
|
676
|
-
answer: [
|
|
677
|
-
p('Two paid placements a year, starting in January and July. We reply to every application.'),
|
|
678
|
-
],
|
|
679
|
-
},
|
|
680
|
-
],
|
|
494
|
+
title: studio.faqTitle,
|
|
495
|
+
items: studio.faq.map(([question, answer], index) => ({
|
|
496
|
+
_key: `studio-faq-${index}`,
|
|
497
|
+
question,
|
|
498
|
+
answer: [p(answer)],
|
|
499
|
+
})),
|
|
681
500
|
},
|
|
682
501
|
],
|
|
683
502
|
},
|
|
684
503
|
{
|
|
685
|
-
title:
|
|
686
|
-
slug:
|
|
504
|
+
title: words.titles.contact,
|
|
505
|
+
slug: slug.contact,
|
|
687
506
|
blocks: [
|
|
688
507
|
{
|
|
689
508
|
_key: 'contact-new-work',
|
|
690
509
|
_type: 'cta',
|
|
691
510
|
_version: BLOCK_VERSION,
|
|
692
|
-
title:
|
|
693
|
-
text:
|
|
511
|
+
title: contact.ctaTitle,
|
|
512
|
+
text: contact.ctaText,
|
|
694
513
|
actions: [
|
|
695
|
-
{ label:
|
|
696
|
-
{ label:
|
|
514
|
+
{ label: hello, target: { href: `mailto:${hello}` }, emphasis: 'primary' },
|
|
515
|
+
{ label: contact.phoneLabel, target: { href: contact.phoneHref } },
|
|
697
516
|
],
|
|
698
517
|
},
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
518
|
+
...blocksOf(contact.visit, media),
|
|
519
|
+
...blocksOf(contact.jobs, media),
|
|
520
|
+
...blocksOf(contact.press, media),
|
|
702
521
|
],
|
|
703
522
|
},
|
|
704
523
|
{
|
|
705
|
-
title:
|
|
706
|
-
slug:
|
|
707
|
-
blocks:
|
|
708
|
-
|
|
709
|
-
|
|
524
|
+
title: words.titles.privacy,
|
|
525
|
+
slug: slug.privacy,
|
|
526
|
+
blocks: blocksOf(words.privacy, media),
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
title: words.titles.credits,
|
|
530
|
+
slug: slug.credits,
|
|
531
|
+
blocks: [creditsBlock(words.credits)],
|
|
710
532
|
},
|
|
711
533
|
];
|
|
712
534
|
}
|
|
@@ -730,204 +552,117 @@ function portfolioPalette() {
|
|
|
730
552
|
return skin.color;
|
|
731
553
|
}
|
|
732
554
|
/**
|
|
733
|
-
*
|
|
734
|
-
*
|
|
555
|
+
* Every slot names a bundled file under `assets/photos/portfolio/`. Covers
|
|
556
|
+
* are cut at 3:2 with their subject in the middle three quarters of the
|
|
735
557
|
* height, because the work grid shows its full-width places at 2:1; details
|
|
736
|
-
* keep the shape of the thing they show.
|
|
558
|
+
* keep the shape of the thing they show. The procedural `spec` is only the
|
|
559
|
+
* fallback `seedDemoMedia` uses if a file is ever missing.
|
|
737
560
|
*/
|
|
738
|
-
export
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
{
|
|
748
|
-
name: 'rookery-hall-poster-carnival',
|
|
749
|
-
alt: 'A green family concert poster for The Carnival of the Animals, with orange circles above the title',
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
name: 'rookery-hall-series',
|
|
753
|
-
alt: 'Four colour tiles showing the Symphonic, Chamber, New Music and Family series, each with its own figure',
|
|
754
|
-
},
|
|
755
|
-
{
|
|
756
|
-
name: 'rookery-hall-brochure',
|
|
757
|
-
alt: 'An open season brochure: a red page reading 25/26 beside a calendar of concerts from October to March',
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
name: 'hollis-crane-cover',
|
|
761
|
-
alt: 'Four Hollis & Crane paperback covers in a row, each a flat colour with a cut-paper image under the title',
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
name: 'hollis-crane-salt-year',
|
|
765
|
-
alt: 'The cover of The Salt Year by Nora Pell: a pale sun sinking into a band of dark sea on sand-coloured paper',
|
|
766
|
-
},
|
|
767
|
-
{
|
|
768
|
-
name: 'hollis-crane-spines',
|
|
769
|
-
alt: 'Twelve paperback spines standing side by side, each with its title in a serif and the H&C mark at the base',
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
name: 'hollis-crane-grid',
|
|
773
|
-
alt: 'A cover with red construction lines beside notes on the typefaces, image field and margins of the series',
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
name: 'saltmarsh-quay-cover',
|
|
777
|
-
alt: 'Two navy overhead signs with white pictograms directing passengers to tickets, the café, gates, ferries and buses',
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
name: 'saltmarsh-quay-totem',
|
|
781
|
-
alt: 'A tall navy sign listing gates 1 to 4 in yellow squares with island names, and services with arrows below',
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
name: 'saltmarsh-quay-pictograms',
|
|
785
|
-
alt: 'Twelve white pictograms on navy squares, from ferries and buses to left luggage and the waiting room',
|
|
786
|
-
},
|
|
787
|
-
{
|
|
788
|
-
name: 'saltmarsh-quay-gate',
|
|
789
|
-
alt: 'A gate sign with a large yellow number 3 beside the destination Sulvay and the day’s sailing times',
|
|
790
|
-
},
|
|
791
|
-
{
|
|
792
|
-
name: 'fenmore-cover',
|
|
793
|
-
alt: 'The Fenmore Building Society identity: the reed mark and name on green, colour swatches and type specimens',
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
name: 'fenmore-stationery',
|
|
797
|
-
alt: 'A Fenmore letter confirming a savings account, with a green business card and a cream card beside it',
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
name: 'fenmore-branch',
|
|
801
|
-
alt: 'A branch front with the Fenmore name on a green fascia and the words savings, mortgages and advice in the window',
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
name: 'tidewater-cover',
|
|
805
|
-
alt: 'An annual report spread: the figure 412 on a blue page beside an article and a bar chart of redshank counts',
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
name: 'tidewater-report-cover',
|
|
809
|
-
alt: 'The Tidewater Trust Annual Report 2024 cover, sand-coloured above horizontal bands of blue and green',
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
name: 'tidewater-data',
|
|
813
|
-
alt: 'A report spread with a spending breakdown on the left and a chart of volunteers by year on the right',
|
|
814
|
-
},
|
|
815
|
-
{
|
|
816
|
-
name: 'westmoor-rail-cover',
|
|
817
|
-
alt: 'Three phone screens from the Westmoor Rail app: journey planner, a ticket with its code, and live departures',
|
|
818
|
-
},
|
|
819
|
-
{
|
|
820
|
-
name: 'westmoor-rail-departures',
|
|
821
|
-
alt: 'The live departures screen for Westmoor Central, listing five trains with their status and platform numbers',
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
name: 'westmoor-rail-components',
|
|
825
|
-
alt: 'A sheet of app components: buttons, yellow platform numbers, service status labels and a departure row',
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
name: 'gannet-cover',
|
|
829
|
-
alt: 'Four Gannet & Sons tins seen from above: sardines on blue, mackerel on yellow, anchovies on red, sprats on green',
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
name: 'gannet-sardines',
|
|
833
|
-
alt: 'The sardine tin with its cream fish on blue, above the unrolled paper band describing the catch',
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
name: 'gannet-shelf',
|
|
837
|
-
alt: 'Three shop shelves stocked with Gannet & Sons tins in four colours, priced at £3.40 a tin',
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
name: 'warp-and-weft-cover',
|
|
841
|
-
alt: 'The exhibition title wall: a woven pattern of indigo, red and yellow bands beside the words Warp and Weft',
|
|
842
|
-
},
|
|
843
|
-
{
|
|
844
|
-
name: 'warp-and-weft-room',
|
|
845
|
-
alt: 'A museum panel headed The weaving shed, with an object label for Jacquard loom cards beneath it',
|
|
846
|
-
},
|
|
847
|
-
].map((item, index) => ({
|
|
848
|
-
...item,
|
|
849
|
-
photo: `portfolio/${item.name}.jpg`,
|
|
850
|
-
spec: coverArt(portfolioPalette(), index + 1),
|
|
851
|
-
}));
|
|
561
|
+
export function portfolioMediaSpecs(copy) {
|
|
562
|
+
const palette = portfolioPalette();
|
|
563
|
+
return copy.media.map((item, index) => ({
|
|
564
|
+
name: item.name,
|
|
565
|
+
spec: coverArt(palette, index + 1),
|
|
566
|
+
alt: item.alt,
|
|
567
|
+
photo: `portfolio/${item.file}`,
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
852
570
|
/** Header and footer navigation. Every path is a page this blueprint seeds. */
|
|
853
|
-
export
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
{ label: 'Work', url: '/work' },
|
|
861
|
-
{ label: 'Studio', url: '/studio' },
|
|
862
|
-
{ label: 'Contact', url: '/contact' },
|
|
863
|
-
{ label: 'Privacy', url: '/privacy' },
|
|
864
|
-
],
|
|
865
|
-
};
|
|
866
|
-
const PROJECT_PAGE = { kind: 'collection', collection: 'project' };
|
|
867
|
-
const SEARCH_RESULTS = { kind: 'search' };
|
|
868
|
-
const HOME = { kind: 'home' };
|
|
869
|
-
const archiveOf = (taxonomy) => ({
|
|
870
|
-
kind: 'taxonomy',
|
|
871
|
-
taxonomy,
|
|
872
|
-
});
|
|
873
|
-
const pagePath = (path) => ({ kind: 'path', path });
|
|
571
|
+
export function portfolioMenus(copy) {
|
|
572
|
+
const item = (key) => ({
|
|
573
|
+
label: copy.menus.labels[key],
|
|
574
|
+
url: `/${copy.pageSlugs[key]}`,
|
|
575
|
+
});
|
|
576
|
+
return { header: copy.menus.header.map(item), footer: copy.menus.footer.map(item) };
|
|
577
|
+
}
|
|
874
578
|
function shownOn(mode, ...targets) {
|
|
875
579
|
return { pages: { mode, targets } };
|
|
876
580
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
body: 'When it has to be ready and who it is for: Mara Lindgren replies to every enquiry within two working days.',
|
|
913
|
-
label: 'Start a project',
|
|
914
|
-
href: '/contact',
|
|
581
|
+
/**
|
|
582
|
+
* The widgets a studio's site places, and where.
|
|
583
|
+
*
|
|
584
|
+
* A project page is the work, shown large across the whole grid: nothing
|
|
585
|
+
* stands beside it. After it, the three projects that share the most
|
|
586
|
+
* disciplines, clients and people with it, each at its cover, so a visitor
|
|
587
|
+
* who reaches the end of a case study has somewhere to go next.
|
|
588
|
+
*
|
|
589
|
+
* Beside the index of one discipline, client or member of the team, the
|
|
590
|
+
* other terms of that same taxonomy, so a visitor can move from one
|
|
591
|
+
* discipline to another without going back to the work page; beside search
|
|
592
|
+
* results, the disciplines, the one way into the work a query does not give.
|
|
593
|
+
*
|
|
594
|
+
* In the footer, the studio's invitation to new work, on every page that
|
|
595
|
+
* does not already end on one: the home page closes on its own invitation and
|
|
596
|
+
* the contact page is that invitation.
|
|
597
|
+
*
|
|
598
|
+
* No widget names the studio's e-mail address: the pages derive it from the
|
|
599
|
+
* site's name, and a widget's settings cannot, so it would contradict them
|
|
600
|
+
* on any site not called Studio Hale.
|
|
601
|
+
*/
|
|
602
|
+
export function portfolioWidgets(copy) {
|
|
603
|
+
const taxonomies = copy.schema.taxonomies;
|
|
604
|
+
const archiveOf = (key) => ({
|
|
605
|
+
kind: 'taxonomy',
|
|
606
|
+
taxonomy: taxonomies[key].name,
|
|
607
|
+
});
|
|
608
|
+
const contact = `/${copy.pageSlugs.contact}`;
|
|
609
|
+
return [
|
|
610
|
+
{
|
|
611
|
+
area: 'sidebar',
|
|
612
|
+
type: 'terms',
|
|
613
|
+
title: copy.widgets.disciplines,
|
|
614
|
+
settings: { taxonomy: taxonomies.disciplines.name, showCounts: true, hierarchical: false },
|
|
615
|
+
visibility: shownOn('only', archiveOf('disciplines'), { kind: 'search' }),
|
|
915
616
|
},
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
{
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
617
|
+
{
|
|
618
|
+
area: 'sidebar',
|
|
619
|
+
type: 'terms',
|
|
620
|
+
title: copy.widgets.clients,
|
|
621
|
+
settings: { taxonomy: taxonomies.client.name, showCounts: false, hierarchical: false },
|
|
622
|
+
visibility: shownOn('only', archiveOf('client')),
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
area: 'sidebar',
|
|
626
|
+
type: 'terms',
|
|
627
|
+
title: copy.widgets.team,
|
|
628
|
+
settings: { taxonomy: taxonomies.team.name, showCounts: true, hierarchical: false },
|
|
629
|
+
visibility: shownOn('only', archiveOf('team')),
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
area: 'content-after',
|
|
633
|
+
type: 'relatedEntries',
|
|
634
|
+
title: copy.widgets.moreWork,
|
|
635
|
+
settings: { count: 3, showImage: true, showDate: false },
|
|
636
|
+
visibility: shownOn('only', { kind: 'collection', collection: 'project' }),
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
area: 'footer-1',
|
|
640
|
+
type: 'cta',
|
|
641
|
+
title: copy.widgets.ctaTitle,
|
|
642
|
+
settings: {
|
|
643
|
+
heading: copy.widgets.ctaHeading,
|
|
644
|
+
body: copy.widgets.ctaBody,
|
|
645
|
+
label: copy.widgets.ctaLabel,
|
|
646
|
+
href: contact,
|
|
647
|
+
},
|
|
648
|
+
visibility: shownOn('except', { kind: 'home' }, { kind: 'path', path: contact }),
|
|
649
|
+
},
|
|
650
|
+
];
|
|
651
|
+
}
|
|
652
|
+
export function portfolioSiteSettings(copy) {
|
|
653
|
+
return {
|
|
654
|
+
'general.tagline': copy.settings.tagline,
|
|
655
|
+
'general.socialLinks': [
|
|
656
|
+
{ label: 'Instagram', url: 'https://instagram.com/example' },
|
|
657
|
+
{ label: 'LinkedIn', url: 'https://linkedin.com/company/example' },
|
|
658
|
+
{ label: 'Bluesky', url: 'https://bsky.app/profile/example.bsky.social' },
|
|
659
|
+
],
|
|
660
|
+
'general.footerNote': copy.settings.footerNote,
|
|
661
|
+
// A studio's case studies are not a discussion: no comment form under the
|
|
662
|
+
// work. An editor can open comments again per collection or per entry.
|
|
663
|
+
'discussion.enabled': false,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
931
666
|
export const PORTFOLIO_RECOMMENDED_AGENTS = [
|
|
932
667
|
{
|
|
933
668
|
name: 'performanceAgent',
|
|
@@ -952,81 +687,94 @@ export const PORTFOLIO_RECOMMENDED_AGENTS = [
|
|
|
952
687
|
* finished, so the `createdAt` order every list sorts on agrees with the
|
|
953
688
|
* years a visitor reads.
|
|
954
689
|
*/
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
});
|
|
969
|
-
disciplineIds.set(demo.slug, term.id);
|
|
970
|
-
}
|
|
971
|
-
const teamIds = new Map();
|
|
972
|
-
for (const demo of PORTFOLIO_DEMO_TEAM) {
|
|
973
|
-
const term = await teamStore.create({ slug: demo.slug, labels: { [defaultLocale]: demo.name } });
|
|
974
|
-
teamIds.set(demo.slug, term.id);
|
|
975
|
-
}
|
|
976
|
-
const clientIds = new Map();
|
|
977
|
-
for (const demo of PORTFOLIO_DEMO_PROJECTS) {
|
|
978
|
-
if (clientIds.has(demo.client.slug))
|
|
979
|
-
continue;
|
|
980
|
-
const term = await clientStore.create({
|
|
981
|
-
slug: demo.client.slug,
|
|
982
|
-
labels: { [defaultLocale]: demo.client.name },
|
|
983
|
-
});
|
|
984
|
-
clientIds.set(demo.client.slug, term.id);
|
|
985
|
-
}
|
|
986
|
-
for (const demo of PORTFOLIO_DEMO_PROJECTS) {
|
|
987
|
-
const cover = media[demo.cover];
|
|
988
|
-
await projectStore.create({
|
|
989
|
-
status: 'published',
|
|
990
|
-
createdBy: adminId,
|
|
991
|
-
values: {
|
|
992
|
-
title: demo.title,
|
|
690
|
+
function seedPortfolioDemoContent(copy) {
|
|
691
|
+
return async (ctx) => {
|
|
692
|
+
const { db, defaultLocale, adminId, media } = ctx;
|
|
693
|
+
const siteName = ctx.siteName?.trim() || DEFAULT_STUDIO_NAME;
|
|
694
|
+
const model = portfolioSchema(copy);
|
|
695
|
+
const clientStore = createTaxonomyStore({ db, taxonomy: model.client });
|
|
696
|
+
const disciplineStore = createTaxonomyStore({ db, taxonomy: model.disciplines });
|
|
697
|
+
const teamStore = createTaxonomyStore({ db, taxonomy: model.team });
|
|
698
|
+
const projectStore = createContentStore({ db, collection: model.project, defaultLocale });
|
|
699
|
+
const pageStore = createContentStore({ db, collection: model.page, defaultLocale });
|
|
700
|
+
const disciplineIds = new Map();
|
|
701
|
+
for (const demo of copy.disciplines) {
|
|
702
|
+
const term = await disciplineStore.create({
|
|
993
703
|
slug: demo.slug,
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
704
|
+
labels: { [defaultLocale]: demo.name },
|
|
705
|
+
});
|
|
706
|
+
disciplineIds.set(demo.slug, term.id);
|
|
707
|
+
}
|
|
708
|
+
const teamIds = new Map();
|
|
709
|
+
for (const demo of copy.team) {
|
|
710
|
+
const term = await teamStore.create({
|
|
711
|
+
slug: demo.slug,
|
|
712
|
+
labels: { [defaultLocale]: demo.name },
|
|
713
|
+
});
|
|
714
|
+
teamIds.set(demo.slug, term.id);
|
|
715
|
+
}
|
|
716
|
+
const clientIds = new Map();
|
|
717
|
+
for (const demo of copy.projects) {
|
|
718
|
+
if (clientIds.has(demo.client.slug))
|
|
719
|
+
continue;
|
|
720
|
+
const term = await clientStore.create({
|
|
721
|
+
slug: demo.client.slug,
|
|
722
|
+
labels: { [defaultLocale]: demo.client.name },
|
|
723
|
+
});
|
|
724
|
+
clientIds.set(demo.client.slug, term.id);
|
|
725
|
+
}
|
|
726
|
+
for (const demo of copy.projects) {
|
|
727
|
+
const cover = media[demo.cover];
|
|
728
|
+
await projectStore.create({
|
|
729
|
+
status: 'published',
|
|
730
|
+
createdBy: adminId,
|
|
731
|
+
values: {
|
|
732
|
+
title: demo.title,
|
|
733
|
+
slug: demo.slug,
|
|
734
|
+
summary: demo.summary,
|
|
735
|
+
client: demo.client.name,
|
|
736
|
+
discipline: demo.discipline,
|
|
737
|
+
publishedAt: demo.publishedAt,
|
|
738
|
+
clientArchive: clientIds.get(demo.client.slug) ?? null,
|
|
739
|
+
disciplines: demo.disciplines
|
|
740
|
+
.map((slug) => disciplineIds.get(slug))
|
|
741
|
+
.filter((id) => id !== undefined),
|
|
742
|
+
team: demo.team
|
|
743
|
+
.map((slug) => teamIds.get(slug))
|
|
744
|
+
.filter((id) => id !== undefined),
|
|
745
|
+
...(cover === undefined ? {} : { coverImage: cover }),
|
|
746
|
+
},
|
|
747
|
+
blocks: {
|
|
748
|
+
blocks: portfolioProjectBlocks(demo, { siteName }, media).map(toBlockZoneEntry),
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
for (const demo of buildPortfolioDemoPages(copy, { media, siteName })) {
|
|
753
|
+
await pageStore.create({
|
|
754
|
+
status: 'published',
|
|
755
|
+
createdBy: adminId,
|
|
756
|
+
values: { title: demo.title, slug: demo.slug },
|
|
757
|
+
blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
};
|
|
1020
761
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
762
|
+
/** The whole blueprint, in one language. */
|
|
763
|
+
export function createPortfolioContentPack(locale) {
|
|
764
|
+
const copy = portfolioCopyFor(locale);
|
|
765
|
+
const model = portfolioSchema(copy);
|
|
766
|
+
return {
|
|
767
|
+
collections: model.collections,
|
|
768
|
+
taxonomies: model.taxonomies,
|
|
769
|
+
recommendedAgents: PORTFOLIO_RECOMMENDED_AGENTS,
|
|
770
|
+
seedDemoContent: seedPortfolioDemoContent(copy),
|
|
771
|
+
defaultTheme: '@cogenta/theme-portfolio',
|
|
772
|
+
menus: portfolioMenus(copy),
|
|
773
|
+
widgets: portfolioWidgets(copy),
|
|
774
|
+
siteSettings: portfolioSiteSettings(copy),
|
|
775
|
+
mediaSpecs: portfolioMediaSpecs(copy),
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/** The English pack, which `BLUEPRINT_CONTENT_PACKS` lists; `contentPackFor` picks by locale. */
|
|
779
|
+
export const portfolioContentPack = createPortfolioContentPack('en');
|
|
1032
780
|
//# sourceMappingURL=portfolio.js.map
|