@port60/template-kit 0.11.0 → 0.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@port60/template-kit",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Build Port60 site templates locally: scaffold, live-preview, validate against the platform contract, and package for studio upload. AI-agent ready — every scaffold ships AGENTS.md and validate emits machine-readable JSON.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -892,6 +892,12 @@
892
892
  "kind": "text",
893
893
  "required": false,
894
894
  "description": "Optional introduction."
895
+ },
896
+ {
897
+ "name": "mapSlug",
898
+ "kind": "text",
899
+ "required": false,
900
+ "description": "WHICH map renders here, by its address (the Pages editor's placement picker writes it; the Maps area is where maps exist). Absent = the tenant's first published map."
895
901
  }
896
902
  ],
897
903
  "minimal": {},
@@ -12,6 +12,9 @@
12
12
  { "name": "--accent", "description": "Action accent (the donate CTA family)." },
13
13
  { "name": "--accent-strong", "description": "Darker accent." },
14
14
  { "name": "--gold", "description": "Highlight tint (marks, ratings)." },
15
+ { "name": "--marker", "description": "Impact-map pin colour (the live-map markers are a zoom-scaling circle layer that take this colour). Defaults to --accent." },
16
+ { "name": "--map-mode", "description": "Impact-map basemap colour mode read by the map island: auto (derive the map's paper/ink from the theme), light, dark, or muted. Default auto." },
17
+ { "name": "--map-projection", "description": "Impact-map projection: flat (default) or globe — the planet as a rotatable sphere with atmosphere." },
15
18
  { "name": "--line", "description": "Hairline borders." },
16
19
  { "name": "--line-strong", "description": "Emphasised borders (selected, focus)." },
17
20
  { "name": "--danger", "description": "Errors and destructive affordances." },
@@ -20,6 +20,15 @@
20
20
  --accent: #ef6044;
21
21
  --accent-strong: #d94d33;
22
22
  --gold: #f3b13b;
23
+ /* Impact map. --marker = the map pin colour (a deliberate brand accent by default; the live-map
24
+ markers are a zoom-scaling MapLibre circle layer that takes this colour). --map-mode = the
25
+ basemap colour mode read by the map island: `auto` derives the map's paper/ink from the theme
26
+ neutrals below, `light`/`dark`/`muted` force a base. Template-owned — set in theme.css or exposed
27
+ as Appearance knobs; the tenant never edits map colour directly. */
28
+ --marker: var(--accent);
29
+ --map-mode: auto;
30
+ /* flat (default) or globe — MapLibre's own globe projection, a rotatable sphere. */
31
+ --map-projection: flat;
23
32
  --line: rgba(31, 42, 51, 0.1);
24
33
  --line-strong: rgba(18, 135, 122, 0.28);
25
34
  --danger: #c0392b;
@@ -953,6 +962,22 @@ textarea.p60-form-control {
953
962
  gap: 1rem;
954
963
  }
955
964
 
965
+ /* MOBILE BREAKOUT (owner, 2026-08-17): on phones the nesting stack squashes the card badly —
966
+ measured on Sanctuary: 390px viewport → 346 container → 294 aside → 249px card → ~195px of
967
+ actual controls. A donate form is a working control surface; it gets the full screen width
968
+ (edge to edge, side borders/radius dropped, its own padding kept). The negative-margin trick
969
+ escapes ANY wrapper depth; layouts here are symmetric, so the sheet lands exactly 0..100vw. */
970
+ @media (max-width: 640px) {
971
+ .donate-card {
972
+ width: 100vw;
973
+ margin-left: calc(50% - 50vw);
974
+ border-radius: 0;
975
+ border-left: 0;
976
+ border-right: 0;
977
+ padding: 1.4rem 1.1rem;
978
+ }
979
+ }
980
+
956
981
  /* Optional cause image banner at the top of the widget (set per-cause in tenant-admin). */
957
982
  .donate-cause-img {
958
983
  width: 100%;
@@ -1966,6 +1991,30 @@ textarea.p60-form-control {
1966
1991
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
1967
1992
  gap: 1.5rem;
1968
1993
  }
1994
+ /* MOBILE: the stacked grid becomes an edge-to-edge SWIPE ROW with a peek (owner, 2026-08-17) —
1995
+ the scroller bleeds to the screen edges, each card keeps its padding, the first card aligns to
1996
+ the text gutter, and the cut-off next card IS the "swipe me" affordance (the reference maps'
1997
+ card idiom). Snap keeps cards settling cleanly; the scrollbar is hidden, the peek replaces it. */
1998
+ @media (max-width: 640px) {
1999
+ .article-grid {
2000
+ display: flex;
2001
+ width: 100vw;
2002
+ margin-left: calc(50% - 50vw);
2003
+ overflow-x: auto;
2004
+ -webkit-overflow-scrolling: touch;
2005
+ scroll-snap-type: x proximity;
2006
+ gap: 0.9rem;
2007
+ padding: 0.2rem 22px 0.6rem;
2008
+ scroll-padding-left: 22px;
2009
+ scrollbar-width: none;
2010
+ }
2011
+ .article-grid::-webkit-scrollbar { display: none; }
2012
+ .article-grid > .article-card {
2013
+ flex: 0 0 78%;
2014
+ max-width: 330px;
2015
+ scroll-snap-align: start;
2016
+ }
2017
+ }
1969
2018
  .article-card {
1970
2019
  display: flex;
1971
2020
  flex-direction: column;
@@ -2355,6 +2404,26 @@ textarea.p60-form-control {
2355
2404
  align-items: center;
2356
2405
  gap: 0.5rem;
2357
2406
  }
2407
+ /* MOBILE: the rotator is MEDIA — it bleeds edge to edge like the hero and the map, with the
2408
+ arrows overlaid on the image instead of eating width beside it. */
2409
+ @media (max-width: 640px) {
2410
+ .events-carousel {
2411
+ position: relative;
2412
+ width: 100vw;
2413
+ margin-left: calc(50% - 50vw);
2414
+ gap: 0;
2415
+ }
2416
+ .events-carousel .carousel-slide { border-radius: 0; }
2417
+ .events-carousel > .carousel-arrow {
2418
+ position: absolute;
2419
+ top: 50%;
2420
+ transform: translateY(-50%);
2421
+ z-index: 2;
2422
+ opacity: 0.9;
2423
+ }
2424
+ .events-carousel > .carousel-arrow:first-of-type { left: 10px; }
2425
+ .events-carousel > .carousel-arrow:last-of-type { right: 10px; }
2426
+ }
2358
2427
  .carousel-slide {
2359
2428
  position: relative;
2360
2429
  flex: 1;
@@ -4228,23 +4297,80 @@ textarea.p60-form-control {
4228
4297
  .people-grid { grid-template-columns: 1fr; }
4229
4298
  }
4230
4299
 
4231
- /* ── Impact map island (WP4) — baseline look; templates restyle via the styling API. Colours ride
4232
- currentColor and the theme's tokens so the island wears the template's clothes. */
4300
+ /* ── Impact map island (WP4) — the OVERLAY borrows the theme: pins wear --marker (a brand accent by
4301
+ default), the card is a themed --surface. The BASEMAP colour is derived by the map island from the
4302
+ theme + --map-mode (see lib/mapTheme.ts). Brand on the overlay, neutrals on the map — the recipe
4303
+ that stays legible on any theme. Templates restyle any of this via the tokens or these classes. */
4233
4304
  .impact-map { margin: 24px 0; }
4234
4305
  .impact-map-head h2 { margin: 0 0 6px; }
4235
4306
  .impact-map-intro { margin: 0 0 14px; opacity: 0.85; }
4236
- .impact-map-canvas { position: relative; min-height: 340px; border-radius: 14px; overflow: hidden;
4237
- background: linear-gradient(160deg, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.55)), currentColor; }
4307
+ .impact-map-stats { margin: 0 0 14px; color: var(--muted); font-weight: 600; letter-spacing: 0.01em; }
4308
+ /* The stable stage, shaped like THE WORLD BAND ITSELF: the island's planet view is all 360° of
4309
+ longitude by 84°N–58°S (whole Greenland in, only the polar void and Antarctica trimmed), which in
4310
+ Web Mercator is a 1.497:1 box — so a frame with that exact ratio holds the full planet edge to
4311
+ edge, full width AND full height, at any screen size. A ratio wider than the band (a max-height
4312
+ clamp once made it so on large screens) turns the fit height-constrained and the world renders
4313
+ NARROWER than the frame — dead bars at the sides; a narrower ratio (min-height on phones) just
4314
+ adds benign vertical margin. Relative + clipped so MapLibre always has a real box and can never
4315
+ escape it. Keep this ratio in step with the island's planet bounds. */
4316
+ .impact-map-stage { position: relative; width: 100%; aspect-ratio: 1.497; min-height: 300px;
4317
+ border-radius: 14px; overflow: hidden; }
4318
+ /* width:100% is LOAD-BEARING: with height pinned by min-height and width left auto, CSS derives
4319
+ width FROM the aspect-ratio (300 x 1.497 = 449px) — on phones the stage overflowed the page
4320
+ sideways, shoving every other section into a lopsided "left with a right gutter" layout. Width
4321
+ pinned, the ratio yields height and min-height only ever makes the stage TALLER. */
4322
+ /* MOBILE BREAKOUT: a map is a canvas — every pixel is information, nothing needs a margin — so on
4323
+ phones it bleeds edge to edge (media bleeds, prose gutters: the hero already sets the rhythm).
4324
+ Cooperative gestures keep page-scrolling safe over the full-width canvas. */
4325
+ @media (max-width: 640px) {
4326
+ .impact-map-stage {
4327
+ width: 100vw;
4328
+ margin-left: calc(50% - 50vw);
4329
+ border-radius: 0;
4330
+ }
4331
+ }
4332
+ .impact-map-canvas { position: absolute; inset: 0;
4333
+ background: linear-gradient(160deg, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.55)), var(--muted); }
4238
4334
  .impact-map-pin { position: absolute; width: 16px; height: 16px; border-radius: 50%;
4239
- transform: translate(-50%, -50%); border: 2px solid #fff; background: currentColor;
4335
+ transform: translate(-50%, -50%); border: 2px solid var(--surface); background: var(--marker);
4240
4336
  cursor: pointer; padding: 0; transition: transform 120ms ease; }
4241
4337
  .impact-map-pin:hover, .impact-map-pin:focus-visible { transform: translate(-50%, -50%) scale(1.35); }
4242
- .impact-map-pin--active { box-shadow: 0 0 0 5px rgb(255 255 255 / 28%); }
4243
- .impact-map-card { margin-top: 14px; border: 1px solid rgb(127 127 127 / 30%); border-radius: 12px;
4338
+ .impact-map-pin--active { box-shadow: 0 0 0 5px var(--primary-soft); }
4339
+ .impact-map-card { margin-top: 14px; background: var(--surface); color: var(--ink);
4340
+ border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow-sm);
4244
4341
  padding: 16px 18px; max-width: 34rem; }
4245
4342
  .impact-map-card-kind { display: inline-block; font-size: 11px; font-weight: 700;
4246
- letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.65; margin-bottom: 4px; }
4343
+ letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
4247
4344
  .impact-map-card-title { margin: 0 0 6px; }
4248
4345
  .impact-map-card-summary { margin: 0 0 10px; opacity: 0.9; }
4249
- .impact-map-card-cta { font-weight: 600; }
4346
+ .impact-map-card-cta { font-weight: 600; color: var(--primary); }
4250
4347
  .impact-map-empty { opacity: 0.7; }
4348
+ /* The live tile layer (slice 2): fills the stage from first render so MapLibre measures a real box;
4349
+ the pin canvas overlays it until (unless) the map is ready, then unmounts to reveal the map.
4350
+ Sized with width/height (NOT inset) because MapLibre forces position:relative on this element,
4351
+ which would defeat absolute insets and collapse the box to zero height. */
4352
+ .impact-map-gl { width: 100%; height: 100%; }
4353
+ /* Live-map markers are a MapLibre circle layer (WebGL) — they scale with zoom and take their colour
4354
+ from --marker (circle-color) — so there is no DOM marker element to style here. The pin canvas
4355
+ fallback above keeps its own .impact-map-pin dots for the no-tiles case. */
4356
+ /* The INLINE marker card (a MapLibre popup anchored at the clicked marker — where "See more"
4357
+ lives): the shell takes the theme's surface; the tip is recoloured per anchor side to match. */
4358
+ .impact-map-popup .maplibregl-popup-content { background: var(--surface); color: var(--ink);
4359
+ border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow);
4360
+ padding: 14px 16px; }
4361
+ .impact-map-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
4362
+ .impact-map-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
4363
+ .impact-map-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: var(--surface); }
4364
+ .impact-map-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip,
4365
+ .impact-map-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
4366
+ .impact-map-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: var(--surface); }
4367
+ .impact-map-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--surface); }
4368
+ .impact-map-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--surface); }
4369
+ .impact-map-popup-card .impact-map-card-title { margin: 4px 0 6px; font-size: 1.02rem; }
4370
+ .impact-map-popup-card .impact-map-card-summary { margin: 0 0 8px; }
4371
+ /* The point's image (when it has one): bleeds to the popup's top edge; plain rounded in the
4372
+ fallback card. Absent image = the card simply starts at the kind line. */
4373
+ .impact-map-card-image { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
4374
+ border-radius: 10px; margin: 0 0 10px; }
4375
+ .impact-map-popup-card .impact-map-card-image { width: calc(100% + 32px); margin: -14px -16px 10px;
4376
+ border-radius: calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px) 0 0; }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starter",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "format": "port60-liquid@1",
5
5
  "label": "Starter",
6
6
  "description": "The reference template for the Port60 dialect, the developer docs' worked example and the base to copy when building your own. 1.1.0 adds the layout slot: the template owns the header, navigation and footer chrome.",
@@ -15,12 +15,14 @@
15
15
  "values",
16
16
  "cta",
17
17
  "people",
18
- "campaigns"
18
+ "campaigns",
19
+ "impactMap"
19
20
  ],
20
21
  "islands": [
21
22
  "donation_widget",
22
23
  "member_menu",
23
- "hero_carousel"
24
+ "hero_carousel",
25
+ "map"
24
26
  ],
25
27
  "layout": true,
26
28
  "heroImagery": true,
@@ -46,7 +48,7 @@
46
48
  }
47
49
  ]
48
50
  },
49
- "changelog": "Demonstrates the behaviour catalogue: the values rail reveals with a stagger and a multi-photo standard hero becomes an engine-wired carousel.",
51
+ "changelog": "Adds the impact map section: pin your located work on a themed map of where you work.",
50
52
  "imagery": {
51
53
  "hero": {
52
54
  "idealAspect": "16:9",
@@ -0,0 +1,5 @@
1
+ <section class="section">
2
+ <div class="container">
3
+ {% island 'map' %}
4
+ </div>
5
+ </section>