Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
Creative Commons Attribution 4.0 International Public License

By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License").
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License").

You can read the full license text at:
https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
https://creativecommons.org/licenses/by/4.0/legalcode

Summary (not a substitute for the full license):

You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material
- for any purpose, even commercially

Under the following terms:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial — You may not use the material for commercial purposes.
- ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.

No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
6 changes: 5 additions & 1 deletion posts/foreign-buyer-tax-vancouver-toronto/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"published": "2026-09-08",
"license": "CC-BY-4.0",
"protected": false,
"cardImage": "rent-gap",
"geography": [
"Canada",
Expand All @@ -19,6 +20,7 @@
"urban",
"housing",
"tax",
"immigration",
"policy"
],
"documents": [
Expand All @@ -41,7 +43,9 @@
"School of Cities"
],
"year": 2026,
"title": "Foreign buyer taxes and the rental affordability gap"
"title": "Foreign buyer taxes and the rental affordability gap",
"link": "https://schoolofcities.utoronto.ca/wp-content/uploads/2026/09/SofC-Policy-Brief-6-Foreign-Buyer-Taxes-and-the-Rental-Affordability-Gap.pdf",
"linkType": "PDF"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div class="text">

<p>
Placeholder text — replace with the post's introduction.

</p>

</div>
Expand Down
4 changes: 3 additions & 1 deletion posts/spatial-access-to-public-libraries-toronto/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const charts = {
'spatial-access-map': {
title: 'Spatial access to public libraries in Toronto',
subtitle: 'Shortest travel time to the nearest library by public transit and walking',
source: 'Toronto Public Libraries, City of Toronto, Toronto Transit Commission (2026)',
source:
'Toronto Public Libraries, City of Toronto, Toronto Transit Commission, OpenStreetMap (2026)',
note: 'Public transit travel times were based on average on Tuesday morning 10-1030am and Saturday 10-1030am',
alt: 'Two maps of Toronto showing travel time to the nearest public library in three bands, under 15 minutes, 15 to 30 minutes, and 30 minutes or more, by walking and by public transit, with library locations and major transit lines overlaid.',
widths: [1080],
graphicVerb: 'created'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,90 @@
// plus a 112px lift so it sits near the map's colored area rather than down in
// the empty margin below the coastline.
const legends = [
{ modeText: 'walking', bottom: 'calc(51.5% + 112px)' },
{ modeText: 'public transit', bottom: '112px' }
{ modeText: 'walking', bottom: 'calc(51.5% + 32px)' },
{ modeText: 'public transit', bottom: '32px' }
];

// Ward number label positions, in the source PNG's native pixel space (2160x1200 per
// panel). The lon/lat-to-pixel fit (from the ward boundary line color) has a few
// degrees of unmodeled skew, so points from it alone can land close to a real boundary
// line for some wards — every position here was checked against (and a few corrected
// against) the actual boundary-line pixels in the rendered PNG, not just the fitted
// polygon, so labels stay off ward lines and out of blank/water areas (e.g. 3, 10)
// that the ward polygon technically covers but the map doesn't render.
// Not hand-placed — re-derive and re-verify against the real pixels if the base map
// is ever re-rendered. Both panels share the same extent/scale, so the transit panel
// is just +1200px in y.
const panelHeight = 1200;
const wards = [
{ code: 1, x: 259.0, y: 380.0 },
{ code: 2, x: 360.0, y: 670.0 },
{ code: 3, x: 320.0, y: 890.0 },
{ code: 4, x: 600.0, y: 850.0 },
{ code: 5, x: 550, y: 550 },
{ code: 6, x: 751.0, y: 250 },
{ code: 7, x: 520, y: 280 },
{ code: 8, x: 850, y: 570 },
{ code: 9, x: 700, y: 740 },
{ code: 10, x: 890, y: 960 },
{ code: 11, x: 870.0, y: 790.0 },
{ code: 12, x: 870, y: 670 },
{ code: 13, x: 955, y: 905 },
{ code: 14, x: 1110, y: 790 },
{ code: 15, x: 1050, y: 500 },
{ code: 16, x: 1210.0, y: 500.0 },
{ code: 17, x: 1200.0, y: 250.0 },
{ code: 18, x: 950, y: 220 },
{ code: 19, x: 1230, y: 770.0 },
{ code: 20, x: 1480, y: 720.0 },
{ code: 21, x: 1458.7, y: 497.3 },
{ code: 22, x: 1398.3, y: 278.7 },
{ code: 23, x: 1600, y: 291.4 },
{ code: 24, x: 1690.0, y: 490.0 },
{ code: 25, x: 1880.0, y: 357.0 }
];
</script>

<div class="map">
<img src={mapImage} width="1080" height="1200" alt="" />

<svg class="ward-labels" viewBox="0 0 2160 2400" preserveAspectRatio="none">
{#each wards as ward}
<text x={ward.x} y={ward.y}>{ward.code.toString().padStart(2, '0')}</text>
<text x={ward.x} y={ward.y + panelHeight}>{ward.code.toString().padStart(2, '0')}</text>
{/each}
</svg>

{#each legends as legend}
<div class="legend" style="bottom: {legend.bottom};">
<div class="legend-row extra-legend-row first-legend-row">
<span class="library-swatch"></span>
<span class="extra-legend-label">Public library</span>
</div>
<div class="legend-title">
Travel time by <span class="mode-underline">{legend.modeText}</span> to the nearest library (minutes)
</div>
<div class="legend-row">
<div class="legend-bar">
<span class="legend-dot" title="Library"></span>
{#each bandColors as color}
<div class="legend-segment" style="background: {color};"></div>
{/each}
<span class="legend-tick" style="left: 33.333%;">15</span>
<span class="legend-tick" style="left: 66.667%;">30</span>
</div>
</div>
<div class="legend-row extra-legend-row after-bar-row">
<svg class="ward-swatch" viewBox="0 0 16 16">
<polygon
points="3,2 10,1 15,5 13,11 14,15 7,14 1,10 2,5"
fill="none"
stroke="#000826"
stroke-width="1.4"
stroke-linejoin="round"
/>
</svg>
<span class="extra-legend-label">Municipal ward boundaries</span>
</div>
</div>
{/each}
</div>
Expand All @@ -48,9 +109,29 @@
height: auto;
}

.ward-labels {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}

.ward-labels text {
font-family: OpenSansBold;
font-weight: normal;
font-size: 26px;
text-anchor: middle;
dominant-baseline: central;
fill: var(--brandGray90);
paint-order: stroke fill;
stroke: rgba(255, 255, 255, 0.85);
stroke-width: 5px;
stroke-linejoin: round;
}

.legend {
position: absolute;
right: 12px;
right: 32px;
width: 220px;
box-sizing: border-box;
padding: 10px 12px 18px;
Expand All @@ -59,12 +140,12 @@
}

.legend-title {
font-family: OpenSansBold;
font-family: OpenSans;
font-weight: normal;
font-size: 12px;
line-height: 15px;
color: var(--brandGray90);
margin-bottom: 8px;
margin-bottom: 4px;
}

.mode-underline {
Expand All @@ -77,18 +158,6 @@
gap: 6px;
}

.legend-dot {
position: absolute;
left: 0;
top: 50%;
transform: translate(-50%, -50%);
width: 9px;
height: 9px;
border-radius: 50%;
background: #ffdc14;
box-shadow: 0 0 0 1px var(--brandWhite);
}

.legend-bar {
position: relative;
display: flex;
Expand All @@ -106,9 +175,44 @@
position: absolute;
top: 100%;
transform: translateX(-50%);
margin-top: 3px;
margin-top: 2px;
font-family: OpenSans;
font-size: 11px;
font-size: 12px;
color: var(--brandGray90);
}

.extra-legend-row {
margin-top: 12px;
}

.first-legend-row {
margin-top: 0;
margin-bottom: 10px;
}

.after-bar-row {
margin-top: 20px;
}

.library-swatch {
width: 9px;
height: 9px;
box-sizing: border-box;
border-radius: 50%;
background: #ffdc14;
box-shadow: 0 0 0 1px var(--brandWhite);
flex-shrink: 0;
}

.ward-swatch {
width: 14px;
height: 14px;
flex-shrink: 0;
}

.extra-legend-label {
font-family: OpenSans;
font-size: 12px;
color: var(--brandGray90);
}
</style>
1 change: 1 addition & 0 deletions posts/spatial-access-to-public-libraries-toronto/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"authors": ["Polina Gorn", "Jeff Allen"],
"published": "2026-09-02",
"license": "CC-BY-4.0",
"protected": true,
"geography": ["Toronto"],
"tags": ["urban", "public transit", "accessibility", "public libraries"],
"documents": [
Expand Down
4 changes: 4 additions & 0 deletions scripts/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ function checkMeta(slug, meta) {
error(slug, `meta.json published "${meta.published}" is not a valid ISO date (YYYY-MM-DD)`);
}

if (typeof meta.protected !== 'boolean') {
error(slug, `meta.json "protected" must be true or false, got ${JSON.stringify(meta.protected)}`);
}

for (const entry of meta.changelog ?? []) {
if (!isValidDate(entry.date)) {
error(slug, `meta.json changelog date "${entry.date}" is not a valid ISO date (YYYY-MM-DD)`);
Expand Down
Loading
Loading