@a2f0/skyline 0.2.18 → 0.2.19

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.
@@ -38,6 +38,7 @@ const messageTopFloors = zones.map(([, , count], zone) => h.floors.slice(0, zone
38
38
  + Math.floor((count + 5) / 2) - 1).reverse().slice(0, 3);
39
39
  export const blueCrossFloors = [...zones.flatMap(([from, to, count]) => Array.from({ length: count }, (_, i) => from + (to - from) * i / count)), h.occupied];
40
40
  const spandrelBelow = 0.3, spandrelAbove = 0.9;
41
+ const southInfillFront = 0.52, emblemClearance = 0.1;
41
42
  const color = (hex) => new THREE.Color(hex);
42
43
  const glassTones = [0x414141, 0x464646, 0x3c3c3c, 0x4a4a4a].map(color);
43
44
  const litGlass = color(0x8a8a8a), dimGlass = color(0x5c5c5c);
@@ -81,6 +82,9 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
81
82
  const wall = kit.batch("Blue Cross · glass, spandrels and bands", kit.material(0xffffff, { vertexColors: true }));
82
83
  const mullions = kit.batch("Blue Cross · mullions", kit.material(0x2c2c2c));
83
84
  const bars = kit.batch("Blue Cross · band columns and emblems", kit.material(0xb4b4b4));
85
+ const infill = kit.batch("Blue Cross · south band infill", kit.material(0xffffff, { vertexColors: true }));
86
+ const transoms = kit.batch("Blue Cross · south transoms", kit.material(0xb4b4b4));
87
+ const aluminium = color(0xb4b4b4);
84
88
  const windows = [];
85
89
  let windowColumns = 0;
86
90
  const outline = (way) => orient(polygonOf(projectPlan(record.parts.find((part) => part.way === way).coordinates)));
@@ -197,20 +201,22 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
197
201
  skin(bay, h.lobbyTop, middle[0], i + 1, (run) => !lies(run, block));
198
202
  skin(bay, middle[1], h.wings, i + 1, (run) => !lies(run, block));
199
203
  });
200
- // An emblem: a plate standing 7 to 30 cm off the screen, cut to an outline given across and
204
+ // An emblem: a plate standing ahead of the screen infill, cut to an outline given across and
201
205
  // up from its centre. Its faces fan from the centre, which sees the whole outline, and its
202
206
  // sides follow the outline's edges.
203
207
  const emblem = (centre, t, n, y, shape) => {
208
+ // The corrected screen panels stand ahead of the old backing plane.
204
209
  const point = ([u, v], depth) => [centre[0] + t[0] * u + n[0] * depth, y + v, centre[1] + t[1] * u + n[1] * depth];
210
+ const backDepth = southInfillFront + emblemClearance, frontDepth = backDepth + 0.23;
205
211
  const out = [n[0], 0, n[1]], back = [-n[0], 0, -n[1]];
206
212
  shape.forEach((p, i) => {
207
213
  const q = shape[(i + 1) % shape.length];
208
- kit.triangle(bars, [point([0, 0], 0.3), point(p, 0.3), point(q, 0.3)], [out, out, out]);
209
- kit.triangle(bars, [point([0, 0], 0.07), point(q, 0.07), point(p, 0.07)], [back, back, back]);
214
+ kit.triangle(bars, [point([0, 0], frontDepth), point(p, frontDepth), point(q, frontDepth)], [out, out, out]);
215
+ kit.triangle(bars, [point([0, 0], backDepth), point(q, backDepth), point(p, backDepth)], [back, back, back]);
210
216
  // The edge's side faces away from the centre.
211
217
  const sign = Math.sign((q[1] - p[1]) * (p[0] + q[0]) - (q[0] - p[0]) * (p[1] + q[1])), length = Math.hypot(q[0] - p[0], q[1] - p[1]);
212
218
  const [across, up] = [sign * (q[1] - p[1]) / length, -sign * (q[0] - p[0]) / length];
213
- kit.quad(bars, [point(p, 0.07), point(q, 0.07), point(q, 0.3), point(p, 0.3)], [[t[0] * across, up, t[1] * across]]);
219
+ kit.quad(bars, [point(p, backDepth), point(q, backDepth), point(q, frontDepth), point(p, frontDepth)], [[t[0] * across, up, t[1] * across]]);
214
220
  });
215
221
  };
216
222
  // The company's emblems, 4.2 m tall: a cross, its arms a third of its span, and a shield,
@@ -232,9 +238,54 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
232
238
  for (const [lo, hi] of [...h.bands, [h.roof + 0.4, h.top - 0.3]])
233
239
  kit.box(bars, at((i + 0.5) * pitch), n, 0.45, 0.07, 0.4, lo + 0.1, hi - 0.1);
234
240
  }
235
- // The south face runs west to east; the emblems stand in its first two bays.
236
- if (n[1] > 0.9)
241
+ if (n[1] > 0.9) {
242
+ // FID-BCBS-002: the 2010 south elevation has eight narrow column slots,
243
+ // with curtain-wall panels between them, not an uninterrupted dark band.
244
+ // Restrict this reconstruction to the photographed face: the north atrium
245
+ // has a different structural rhythm. Keep the mapped chain's small bends.
246
+ const panel = (target, s0, s1, levels, columns, back, front, paint, sides) => {
247
+ const k0 = chain.starts.findLastIndex((s) => s <= s0);
248
+ const k1 = chain.starts.findLastIndex((s) => s < s1);
249
+ chainSkin(kit, target, plan, chain, k0, k1, s0, s1, columns, levels, back, front, paint, sides);
250
+ };
251
+ // Two, three and two glazed rows in the mechanical bands; nine shallow
252
+ // courses in the crown. The 1.9 m slots and thin framing are photo ratios,
253
+ // not surveyed dimensions (docs/blue-cross-reference.md).
254
+ const bands = [
255
+ ...h.bands.map(([lo, hi], i) => [lo, hi, i === 1 ? 3 : 2, false]),
256
+ [h.roof, h.top, 9, true],
257
+ ];
258
+ for (const [lo, hi, count, crown] of bands) {
259
+ const levels = [lo + 0.02], paints = [];
260
+ const append = (height, paint) => { levels.push(height); paints.push(paint); };
261
+ append(lo + 0.18, aluminium);
262
+ for (let row = 1; row < count; row += 1) {
263
+ const y = lo + (hi - lo) * row / count;
264
+ append(y - (crown ? 0.08 : 0.55), crown ? screen : glassTones[1]);
265
+ if (!crown) {
266
+ append(y - 0.4, aluminium);
267
+ append(y + 0.4, spandrel);
268
+ }
269
+ append(y + (crown ? 0.08 : 0.55), aluminium);
270
+ }
271
+ append(hi - 0.18, crown ? screen : glassTones[1]);
272
+ append(hi - 0.02, aluminium);
273
+ for (let bay = 0; bay <= 8; bay += 1) {
274
+ const s0 = bay === 0 ? 0.02 : (bay - 0.5) * pitch + 0.95;
275
+ const s1 = bay === 8 ? chain.length - 0.02 : (bay + 0.5) * pitch - 0.95;
276
+ panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2, southInfillFront, (cell, row) => cell === 1 || cell === 3 ? aluminium : paints[row], aluminium);
277
+ }
278
+ }
279
+ // Pale rails frame the office ribbons. They sit just ahead of the vertical
280
+ // mullions and never cross the exposed column slots or celebration panes.
281
+ for (const row of rows.filter(({ kind }) => kind === "glass")) {
282
+ for (const [lo, hi] of [[row.lo - 0.18, row.lo], [row.hi, row.hi + 0.18]]) {
283
+ panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [], 0.17, 0.22, () => aluminium, aluminium);
284
+ }
285
+ }
286
+ // The south face runs west to east; the emblems stand in its first two bays.
237
287
  [cross, shield].forEach((shape, i) => emblem(at((i + 1) * pitch), t, n, h.roof + 6.7, shape));
288
+ }
238
289
  });
239
290
  const model = kit.finish({ height: h.top, outlines: [shell, mullions], opacity: 0.16 });
240
291
  const glass = model.building.getObjectByName(wall.name);
@@ -113,10 +113,12 @@ export const colourMaterials = {
113
113
  "antenna mast": ["stainless"],
114
114
  },
115
115
  "building-blue-cross-blue-shield": {
116
- shell: ["glass", { 0x3a3a3a: "neutral" }],
117
- "glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
116
+ shell: ["glass", { 0x3a3a3a: "neutral", 0x383838: "opaque spandrel" }],
117
+ "glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x383838: "opaque spandrel", 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
118
118
  mullions: ["dark metal"],
119
119
  "band columns and emblems": ["band lights"],
120
+ "south band infill": ["glass", { 0x383838: "opaque spandrel", 0x3e3e3e: "screen", 0xb4b4b4: "aluminium" }],
121
+ "south transoms": ["aluminium"],
120
122
  },
121
123
  // Blue-green glass, and the south face's white concrete frame.
122
124
  "building-340-on-the-park": {
@@ -271,7 +273,7 @@ export function batchOf(buildingId, meshName) {
271
273
  }
272
274
  // A grey's tone: the hex the model wrote it as, from its linear channel.
273
275
  export const toneOf = (grey) => new THREE.Color(grey, grey, grey).getHex();
274
- // Historical comparisons below predate sky reflections and FID-KEM-002/003.
276
+ // Historical comparisons below predate sky reflections, FID-KEM-002/003 and FID-BCBS-003.
275
277
  // The measured colours scale to the scene by this factor in linear light, so the skyline view's
276
278
  // faces show about the photograph's brightness under the day's lights. At 1.4 the skyline view's
277
279
  // 1600 x 900 render, its pixels chosen by each photograph sample's rule, reaches 0.97 (Two
@@ -62,8 +62,14 @@ export const measuredColours = {
62
62
  // docs/blue-cross-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg and a close-up.
63
63
  "building-blue-cross-blue-shield": {
64
64
  ...glass(0x7d8992),
65
+ // FID-BCBS-003: the panorama mixes panes and opaque spandrels. Retain its
66
+ // measured hue provisionally for both, keeping their grey contrast and
67
+ // separate reflection masks through the shared glass swatch.
68
+ "opaque spandrel": "glass",
65
69
  screen: 0x334a60,
66
70
  "band lights": 0xb0bdcd,
71
+ // Inferred same pale metal as the columns; independent calibration is open.
72
+ aluminium: "band lights",
67
73
  "dark metal": null,
68
74
  neutral: null,
69
75
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2f0/skyline",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "Interactive Chicago skyline viewer and reusable Three.js building models",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -38,6 +38,7 @@ const messageTopFloors = zones.map(([, , count], zone) => h.floors.slice(0, zone
38
38
  + Math.floor((count + 5) / 2) - 1).reverse().slice(0, 3);
39
39
  export const blueCrossFloors = [...zones.flatMap(([from, to, count]) => Array.from({ length: count }, (_, i) => from + (to - from) * i / count)), h.occupied];
40
40
  const spandrelBelow = 0.3, spandrelAbove = 0.9;
41
+ const southInfillFront = 0.52, emblemClearance = 0.1;
41
42
  const color = (hex) => new THREE.Color(hex);
42
43
  const glassTones = [0x414141, 0x464646, 0x3c3c3c, 0x4a4a4a].map(color);
43
44
  const litGlass = color(0x8a8a8a), dimGlass = color(0x5c5c5c);
@@ -81,6 +82,9 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
81
82
  const wall = kit.batch("Blue Cross · glass, spandrels and bands", kit.material(0xffffff, { vertexColors: true }));
82
83
  const mullions = kit.batch("Blue Cross · mullions", kit.material(0x2c2c2c));
83
84
  const bars = kit.batch("Blue Cross · band columns and emblems", kit.material(0xb4b4b4));
85
+ const infill = kit.batch("Blue Cross · south band infill", kit.material(0xffffff, { vertexColors: true }));
86
+ const transoms = kit.batch("Blue Cross · south transoms", kit.material(0xb4b4b4));
87
+ const aluminium = color(0xb4b4b4);
84
88
  const windows = [];
85
89
  let windowColumns = 0;
86
90
  const outline = (way) => orient(polygonOf(projectPlan(record.parts.find((part) => part.way === way).coordinates)));
@@ -197,20 +201,22 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
197
201
  skin(bay, h.lobbyTop, middle[0], i + 1, (run) => !lies(run, block));
198
202
  skin(bay, middle[1], h.wings, i + 1, (run) => !lies(run, block));
199
203
  });
200
- // An emblem: a plate standing 7 to 30 cm off the screen, cut to an outline given across and
204
+ // An emblem: a plate standing ahead of the screen infill, cut to an outline given across and
201
205
  // up from its centre. Its faces fan from the centre, which sees the whole outline, and its
202
206
  // sides follow the outline's edges.
203
207
  const emblem = (centre, t, n, y, shape) => {
208
+ // The corrected screen panels stand ahead of the old backing plane.
204
209
  const point = ([u, v], depth) => [centre[0] + t[0] * u + n[0] * depth, y + v, centre[1] + t[1] * u + n[1] * depth];
210
+ const backDepth = southInfillFront + emblemClearance, frontDepth = backDepth + 0.23;
205
211
  const out = [n[0], 0, n[1]], back = [-n[0], 0, -n[1]];
206
212
  shape.forEach((p, i) => {
207
213
  const q = shape[(i + 1) % shape.length];
208
- kit.triangle(bars, [point([0, 0], 0.3), point(p, 0.3), point(q, 0.3)], [out, out, out]);
209
- kit.triangle(bars, [point([0, 0], 0.07), point(q, 0.07), point(p, 0.07)], [back, back, back]);
214
+ kit.triangle(bars, [point([0, 0], frontDepth), point(p, frontDepth), point(q, frontDepth)], [out, out, out]);
215
+ kit.triangle(bars, [point([0, 0], backDepth), point(q, backDepth), point(p, backDepth)], [back, back, back]);
210
216
  // The edge's side faces away from the centre.
211
217
  const sign = Math.sign((q[1] - p[1]) * (p[0] + q[0]) - (q[0] - p[0]) * (p[1] + q[1])), length = Math.hypot(q[0] - p[0], q[1] - p[1]);
212
218
  const [across, up] = [sign * (q[1] - p[1]) / length, -sign * (q[0] - p[0]) / length];
213
- kit.quad(bars, [point(p, 0.07), point(q, 0.07), point(q, 0.3), point(p, 0.3)], [[t[0] * across, up, t[1] * across]]);
219
+ kit.quad(bars, [point(p, backDepth), point(q, backDepth), point(q, frontDepth), point(p, frontDepth)], [[t[0] * across, up, t[1] * across]]);
214
220
  });
215
221
  };
216
222
  // The company's emblems, 4.2 m tall: a cross, its arms a third of its span, and a shield,
@@ -232,9 +238,54 @@ export function createBlueCrossGeographicBuilding(record, projectPlan, offset) {
232
238
  for (const [lo, hi] of [...h.bands, [h.roof + 0.4, h.top - 0.3]])
233
239
  kit.box(bars, at((i + 0.5) * pitch), n, 0.45, 0.07, 0.4, lo + 0.1, hi - 0.1);
234
240
  }
235
- // The south face runs west to east; the emblems stand in its first two bays.
236
- if (n[1] > 0.9)
241
+ if (n[1] > 0.9) {
242
+ // FID-BCBS-002: the 2010 south elevation has eight narrow column slots,
243
+ // with curtain-wall panels between them, not an uninterrupted dark band.
244
+ // Restrict this reconstruction to the photographed face: the north atrium
245
+ // has a different structural rhythm. Keep the mapped chain's small bends.
246
+ const panel = (target, s0, s1, levels, columns, back, front, paint, sides) => {
247
+ const k0 = chain.starts.findLastIndex((s) => s <= s0);
248
+ const k1 = chain.starts.findLastIndex((s) => s < s1);
249
+ chainSkin(kit, target, plan, chain, k0, k1, s0, s1, columns, levels, back, front, paint, sides);
250
+ };
251
+ // Two, three and two glazed rows in the mechanical bands; nine shallow
252
+ // courses in the crown. The 1.9 m slots and thin framing are photo ratios,
253
+ // not surveyed dimensions (docs/blue-cross-reference.md).
254
+ const bands = [
255
+ ...h.bands.map(([lo, hi], i) => [lo, hi, i === 1 ? 3 : 2, false]),
256
+ [h.roof, h.top, 9, true],
257
+ ];
258
+ for (const [lo, hi, count, crown] of bands) {
259
+ const levels = [lo + 0.02], paints = [];
260
+ const append = (height, paint) => { levels.push(height); paints.push(paint); };
261
+ append(lo + 0.18, aluminium);
262
+ for (let row = 1; row < count; row += 1) {
263
+ const y = lo + (hi - lo) * row / count;
264
+ append(y - (crown ? 0.08 : 0.55), crown ? screen : glassTones[1]);
265
+ if (!crown) {
266
+ append(y - 0.4, aluminium);
267
+ append(y + 0.4, spandrel);
268
+ }
269
+ append(y + (crown ? 0.08 : 0.55), aluminium);
270
+ }
271
+ append(hi - 0.18, crown ? screen : glassTones[1]);
272
+ append(hi - 0.02, aluminium);
273
+ for (let bay = 0; bay <= 8; bay += 1) {
274
+ const s0 = bay === 0 ? 0.02 : (bay - 0.5) * pitch + 0.95;
275
+ const s1 = bay === 8 ? chain.length - 0.02 : (bay + 0.5) * pitch - 0.95;
276
+ panel(infill, s0, s1, levels, [s0, s0 + 0.12, s1 - 0.12, s1], 0.2, southInfillFront, (cell, row) => cell === 1 || cell === 3 ? aluminium : paints[row], aluminium);
277
+ }
278
+ }
279
+ // Pale rails frame the office ribbons. They sit just ahead of the vertical
280
+ // mullions and never cross the exposed column slots or celebration panes.
281
+ for (const row of rows.filter(({ kind }) => kind === "glass")) {
282
+ for (const [lo, hi] of [[row.lo - 0.18, row.lo], [row.hi, row.hi + 0.18]]) {
283
+ panel(transoms, 0.02, chain.length - 0.02, [lo, hi], [], 0.17, 0.22, () => aluminium, aluminium);
284
+ }
285
+ }
286
+ // The south face runs west to east; the emblems stand in its first two bays.
237
287
  [cross, shield].forEach((shape, i) => emblem(at((i + 1) * pitch), t, n, h.roof + 6.7, shape));
288
+ }
238
289
  });
239
290
  const model = kit.finish({ height: h.top, outlines: [shell, mullions], opacity: 0.16 });
240
291
  const glass = model.building.getObjectByName(wall.name);
@@ -113,10 +113,12 @@ export const colourMaterials = {
113
113
  "antenna mast": ["stainless"],
114
114
  },
115
115
  "building-blue-cross-blue-shield": {
116
- shell: ["glass", { 0x3a3a3a: "neutral" }],
117
- "glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
116
+ shell: ["glass", { 0x3a3a3a: "neutral", 0x383838: "opaque spandrel" }],
117
+ "glass, spandrels and bands": ["glass", { ...windows(0x8a8a8a, 0x5c5c5c), 0x383838: "opaque spandrel", 0x2a2a2a: "neutral", 0x3e3e3e: "screen" }],
118
118
  mullions: ["dark metal"],
119
119
  "band columns and emblems": ["band lights"],
120
+ "south band infill": ["glass", { 0x383838: "opaque spandrel", 0x3e3e3e: "screen", 0xb4b4b4: "aluminium" }],
121
+ "south transoms": ["aluminium"],
120
122
  },
121
123
  // Blue-green glass, and the south face's white concrete frame.
122
124
  "building-340-on-the-park": {
@@ -271,7 +273,7 @@ export function batchOf(buildingId, meshName) {
271
273
  }
272
274
  // A grey's tone: the hex the model wrote it as, from its linear channel.
273
275
  export const toneOf = (grey) => new THREE.Color(grey, grey, grey).getHex();
274
- // Historical comparisons below predate sky reflections and FID-KEM-002/003.
276
+ // Historical comparisons below predate sky reflections, FID-KEM-002/003 and FID-BCBS-003.
275
277
  // The measured colours scale to the scene by this factor in linear light, so the skyline view's
276
278
  // faces show about the photograph's brightness under the day's lights. At 1.4 the skyline view's
277
279
  // 1600 x 900 render, its pixels chosen by each photograph sample's rule, reaches 0.97 (Two
@@ -62,8 +62,14 @@ export const measuredColours = {
62
62
  // docs/blue-cross-reference.md, 2026-10-09 — Daytime colours from Chicago.jpg and a close-up.
63
63
  "building-blue-cross-blue-shield": {
64
64
  ...glass(0x7d8992),
65
+ // FID-BCBS-003: the panorama mixes panes and opaque spandrels. Retain its
66
+ // measured hue provisionally for both, keeping their grey contrast and
67
+ // separate reflection masks through the shared glass swatch.
68
+ "opaque spandrel": "glass",
65
69
  screen: 0x334a60,
66
70
  "band lights": 0xb0bdcd,
71
+ // Inferred same pale metal as the columns; independent calibration is open.
72
+ aluminium: "band lights",
67
73
  "dark metal": null,
68
74
  neutral: null,
69
75
  },
@@ -16,7 +16,7 @@
16
16
  <span class="visually-hidden">Preparing the skyline…</span>
17
17
  <!-- skyline-loading:start -->
18
18
  <svg class="skyline-trace" viewBox="0 0 1000 390.01" aria-hidden="true" focusable="false">
19
- <defs><path id="skyline-loading-outline" pathLength="1" d="M0 390.01 L24 390.01 L24 326.71 L36.78 320.28 L76.29 320.28 L78.85 321.63 L79.08 320.54 L87.68 320.54 L87.92 316.21 L133.24 316.74 L133.47 317.75 L133.94 317.75 L134.17 316.74 L164.38 316.74 L164.85 317.75 L165.08 321.6 L181.12 321.6 L181.35 329.03 L189.48 329.03 L190.41 330.18 L207.85 330.18 L208.08 328.36 L208.54 328.36 L209.01 331.14 L212.26 338.16 L212.49 334.77 L213.89 334.77 L214.12 344.27 L216.21 344.27 L216.45 348.52 L234.57 348.52 L234.81 318.86 L238.53 318.86 L238.76 308.7 L242.48 308.7 L242.71 280.86 L244.57 280.86 L244.8 273.62 L245.03 272.91 L245.73 272.91 L245.96 272.02 L249.68 272.02 L249.91 273.17 L251.54 273.17 L251.77 272.02 L253.87 272.02 L254.1 273.17 L255.26 272.91 L255.49 272.02 L257.58 272.02 L257.82 272.91 L258.75 272.91 L258.98 280.86 L260.84 280.86 L261.07 318.86 L261.3 316.21 L263.63 316.21 L263.86 318.86 L265.02 318.86 L265.25 316.21 L268.04 316.21 L268.28 333.8 L272.69 333.8 L272.92 321.07 L274.55 321.07 L274.78 314 L288.96 314 L289.19 317.54 L300.58 317.54 L300.81 310.9 L311.51 310.9 L311.74 229.87 L323.13 229.87 L323.36 222.27 L330.8 222.27 L331.03 219.97 L349.39 220.16 L349.62 229.87 L352.41 229.87 L352.64 249.49 L382.16 249.49 L382.39 383.11 L386.81 383.11 L387.04 262.54 L406.57 233.23 L407.26 233.23 L410.05 252.75 L410.52 252.78 L410.75 233.23 L411.21 233.23 L439.11 263.31 L439.34 313.74 L444.22 313.74 L444.45 202.79 L513.71 202.79 L513.95 228.1 L528.36 228.1 L528.59 76.89 L548.34 76.8 L548.58 67.61 L561.59 67.52 L562.29 16 L563.68 16 L564.61 67.52 L571.82 67.61 L572.05 83.08 L572.98 83.08 L573.21 152.98 L579.49 143.92 L580.88 121.96 L582.04 143.91 L595.53 163.38 L598.55 173.04 L598.78 176.62 L600.41 176.62 L600.64 180.12 L602.03 180.12 L602.27 183.62 L603.66 183.62 L603.89 187.12 L605.52 187.12 L605.75 336.98 L606.22 379.83 L608.77 379.83 L609.01 89.53 L622.72 89.53 L622.95 83.96 L641.78 83.96 L642.01 69.64 L642.71 69.64 L642.94 83.96 L661.77 83.96 L662 89.53 L675.25 89.53 L675.48 202.65 L675.94 189.66 L748.46 189.66 L748.69 202.65 L755.43 202.65 L755.66 208.92 L800.75 208.92 L801.22 295.71 L806.33 295.71 L806.56 284.84 L818.42 284.84 L818.65 282.28 L837.48 282.28 L837.71 284.84 L848.17 284.84 L848.4 291.4 L848.87 291.4 L849.1 295.71 L854.91 295.71 L855.14 266.9 L894.19 266.9 L894.42 390.01 L937.42 390.01 L937.65 306.26 L939.51 306.26 L939.74 290.76 L953.69 290.76 L953.92 306.26 L958.57 306.26 L958.8 295.48 L974.14 295.48 L974.37 306.26 L976 306.26 L976 390.01 L1000 390.01" /></defs>
19
+ <defs><path id="skyline-loading-outline" pathLength="1" d="M0 390.01 L24 390.01 L24 326.71 L36.78 320.28 L76.29 320.28 L78.85 321.63 L79.08 320.54 L87.68 320.54 L87.92 316.21 L133.24 316.74 L133.47 317.75 L133.94 317.75 L134.17 316.74 L164.38 316.74 L164.85 317.75 L165.08 321.6 L181.12 321.6 L181.35 329.03 L189.48 329.03 L190.41 330.18 L207.85 330.18 L208.08 328.36 L208.54 328.36 L209.01 331.14 L212.26 338.16 L212.49 334.77 L213.89 334.77 L214.12 344.27 L216.21 344.27 L216.45 348.52 L234.57 348.52 L234.81 318.86 L238.53 318.86 L238.76 308.7 L242.48 308.7 L242.71 280.86 L244.57 280.86 L244.8 273.62 L245.03 272.91 L245.73 272.91 L245.96 272.02 L249.68 272.02 L249.91 273.17 L251.54 273.17 L251.77 272.02 L253.87 272.02 L254.1 273.17 L255.26 272.91 L255.49 272.02 L257.58 272.02 L257.82 272.91 L258.75 272.91 L258.98 280.86 L260.84 280.86 L261.07 318.86 L261.3 316.21 L263.63 316.21 L263.86 318.86 L265.02 318.86 L265.25 316.21 L268.04 316.21 L268.28 333.8 L272.69 333.8 L272.92 321.07 L274.55 321.07 L274.78 314 L288.96 314 L289.19 317.54 L300.58 317.54 L300.81 310.9 L311.51 310.9 L311.74 229.87 L323.13 229.87 L323.36 222.27 L330.8 222.27 L331.03 219.97 L349.39 220.16 L349.62 229.87 L352.41 229.87 L352.64 249.49 L382.16 249.49 L382.39 383.11 L386.81 383.11 L387.04 262.54 L406.57 233.23 L407.26 233.23 L410.05 252.75 L410.52 252.78 L410.75 233.23 L411.21 233.23 L439.11 263.31 L439.34 313.74 L444.22 313.74 L444.45 202.79 L513.71 202.79 L513.95 228.1 L528.36 228.1 L528.59 76.89 L548.34 76.8 L548.58 67.61 L561.59 67.52 L562.29 16 L563.68 16 L564.61 67.52 L571.82 67.61 L572.05 83.08 L572.98 83.08 L573.21 152.98 L579.49 143.92 L580.88 121.96 L582.04 143.91 L595.53 163.38 L598.55 173.04 L598.78 176.62 L600.41 176.62 L600.64 180.12 L602.03 180.12 L602.27 183.62 L603.66 183.62 L603.89 187.12 L605.52 187.12 L605.75 336.98 L606.22 379.83 L608.77 379.83 L609.01 89.53 L622.72 89.53 L622.95 83.96 L641.78 83.96 L642.01 69.64 L642.71 69.64 L642.94 83.96 L661.77 83.96 L662 89.53 L675.25 89.53 L675.48 202.65 L675.71 189.68 L748.46 189.66 L748.69 202.65 L755.43 202.65 L755.66 208.92 L800.75 208.92 L801.22 295.71 L806.33 295.71 L806.56 284.84 L818.42 284.84 L818.65 282.28 L837.48 282.28 L837.71 284.84 L848.17 284.84 L848.4 291.4 L848.87 291.4 L849.1 295.71 L854.91 295.71 L855.14 266.9 L894.19 266.9 L894.42 390.01 L937.42 390.01 L937.65 306.26 L939.51 306.26 L939.74 290.76 L953.69 290.76 L953.92 306.26 L958.57 306.26 L958.8 295.48 L974.14 295.48 L974.37 306.26 L976 306.26 L976 390.01 L1000 390.01" /></defs>
20
20
  <use class="skyline-trace-rail" href="#skyline-loading-outline" />
21
21
  <use class="skyline-trace-line" href="#skyline-loading-outline" />
22
22
  <use class="skyline-trace-tip" href="#skyline-loading-outline" />