@bongos/core 1.24.30 → 1.24.32
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/.bongos-core.json +153 -103
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/api/openapi.json +139 -1
- package/docs/api-reference.md +7 -3
- package/docs/copy-inventory.md +192 -187
- package/docs/copy-registry.json +530 -458
- package/docs/module-api-changelog.md +4 -2
- package/docs/page-inventory.json +15 -35
- package/docs/page-readings.json +228 -265
- package/modules/copy-desk/module.json +1 -1
- package/modules/copy-desk/routes/copy-desk-studio.js +103 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +2 -2
- package/modules/hall-ui/public/ai-studio.html +1 -1
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/craft-mode.js +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/hall-render.js +62 -86
- package/modules/hall-ui/public/home-city-draw.js +360 -52
- package/modules/hall-ui/public/home-city.css +47 -11
- package/modules/hall-ui/public/home-city.js +170 -40
- package/modules/hall-ui/public/home-gallery.css +129 -0
- package/modules/hall-ui/public/home-gallery.js +349 -0
- package/modules/hall-ui/public/home-studio.css +72 -0
- package/modules/hall-ui/public/home-studio.js +203 -0
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +4 -1
- package/modules/hall-ui/public/market-watch-item.html +1 -1
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch-settings.js +6 -1
- package/modules/hall-ui/public/market-watch.html +141 -54
- package/modules/hall-ui/public/market-watch.js +54 -0
- package/modules/hall-ui/public/market-watch.states.json +2 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +2 -3
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +9 -1
- package/modules/hall-ui/public/style.css +74 -27
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/home-city.md +133 -0
- package/modules/hall-ui/records/loader.md +1 -1
- package/modules/hall-ui/records/market-watch-settings.md +16 -3
- package/modules/hall-ui/records/market-watch.md +3 -1
- package/modules/lifecycle/home-city.js +58 -10
- package/modules/lifecycle/home-gallery.js +320 -0
- package/modules/lifecycle/migrations/lifecycle_020_work_appreciations.sql +36 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/routes/home-gallery.js +37 -0
- package/modules/market-watch/routes/settings.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +35 -1
- package/scripts/bongos/leak-scan-allowlist.js +8 -0
- package/scripts/hall-preview/README.md +1 -0
- package/scripts/hall-preview/server.js +10 -0
- package/src/bongos/serve-internal.js +17 -3
- package/src/module-api.js +1 -1
- package/tests/hall_builders_page_pass.mjs +18 -27
- package/tests/hall_loading.mjs +1 -1
- package/tests/hall_nav.mjs +11 -4
- package/tests/hall_page_gate_map.mjs +7 -4
- package/tests/home_city.mjs +200 -5
- package/tests/home_city_widget.mjs +187 -27
- package/tests/home_gallery.mjs +255 -0
- package/tests/home_gallery_widget.mjs +220 -0
- package/tests/home_studio.mjs +169 -0
- package/tests/home_studio_widget.mjs +157 -0
- package/tests/market_watch_pages.mjs +162 -0
- package/tests/nav_permission_atoms.mjs +2 -2
- package/modules/hall-ui/public/market-watch-settings.html +0 -115
- package/modules/hall-ui/public/market-watch-settings.states.json +0 -14
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* modules/hall-ui/public/home-city.css — Home's project city (task 1005054).
|
|
1
|
+
/* modules/hall-ui/public/home-city.css — Home's project city (task 1005054; v16, task 1005058).
|
|
2
2
|
The scene card, the ladder of builders beside it and the time slider under it
|
|
3
3
|
(home-city.js; record modules/hall-ui/records/home-city.md). Every colour is a
|
|
4
4
|
hall token or a mix of one; the tower faces are drawing data in
|
|
@@ -12,7 +12,32 @@
|
|
|
12
12
|
.hc-svg .hc-wait__n { font: 700 11px var(--font-display); fill: var(--accent-on); }
|
|
13
13
|
.hc-svg .hc-av__i { font: 600 13px var(--font-display); }
|
|
14
14
|
.hc-svg [data-tip] { cursor: default; }
|
|
15
|
+
.hc-svg a[href] { cursor: pointer; }
|
|
16
|
+
.hc-svg a:focus { outline: none; }
|
|
17
|
+
.hc-svg a:focus-visible .hc-face { filter: brightness(1.35); }
|
|
15
18
|
.hc-svg .hc-tower:hover .hc-face { filter: brightness(1.3); }
|
|
19
|
+
/* the core and the museum stay quiet on the builder's Home so the new buildings lead;
|
|
20
|
+
each comes back to full colour under the pointer or the keyboard */
|
|
21
|
+
.hc-svg .hc-quiet { opacity: .3; filter: saturate(0) brightness(.85); transition: opacity .3s, filter .3s; }
|
|
22
|
+
.hc-svg .hc-quiet:hover, .hc-svg .hc-quiet:focus-visible { opacity: .95; filter: none; }
|
|
23
|
+
.hc-svg .hc-core:hover .hc-face, .hc-svg .hc-museum:hover .hc-face { filter: brightness(1.15); }
|
|
24
|
+
.hc-svg .hc-sky:hover circle, .hc-svg .hc-sky:focus-visible circle { opacity: 1; }
|
|
25
|
+
|
|
26
|
+
/* the ideator's sky (task 1005059): night falls over the same city, seen from the street.
|
|
27
|
+
The sky is the page's own ground tinted toward the sea, deeper at the horizon; the
|
|
28
|
+
skyline's outlines are hidden until one of the three groups is pointed at or focused,
|
|
29
|
+
then the whole group lights at once. */
|
|
30
|
+
.home-city.hc-is-sky { background: linear-gradient(color-mix(in srgb, var(--bg) 86%, var(--sea)), color-mix(in srgb, var(--bg-card) 86%, var(--accent))); }
|
|
31
|
+
html[data-theme="dark"] .home-city.hc-is-sky { background: linear-gradient(color-mix(in srgb, var(--bg) 92%, var(--sea)), var(--bg) 40%, color-mix(in srgb, var(--bg) 58%, var(--sea))); }
|
|
32
|
+
@media (prefers-color-scheme: dark) {
|
|
33
|
+
html:not([data-theme]) .home-city.hc-is-sky { background: linear-gradient(color-mix(in srgb, var(--bg) 92%, var(--sea)), var(--bg) 40%, color-mix(in srgb, var(--bg) 58%, var(--sea))); }
|
|
34
|
+
}
|
|
35
|
+
.hc-is-sky .hc-time { background: color-mix(in srgb, var(--bg-card) 70%, transparent); }
|
|
36
|
+
.hc-svg .hc-sky__label { font: 500 11px var(--font-display); letter-spacing: .06em; }
|
|
37
|
+
.hc-svg .hc-ol { stroke-opacity: 0; transition: stroke-opacity .25s; }
|
|
38
|
+
.hc-svg:has(.hc-hit[data-group="area"]:hover, .hc-hit[data-group="area"]:focus-visible) .hc-ol-area,
|
|
39
|
+
.hc-svg:has(.hc-hit[data-group="civic"]:hover, .hc-hit[data-group="civic"]:focus-visible) .hc-ol-civic,
|
|
40
|
+
.hc-svg:has(.hc-hit[data-group="museum"]:hover, .hc-hit[data-group="museum"]:focus-visible) .hc-ol-museum { stroke-opacity: .9; }
|
|
16
41
|
|
|
17
42
|
/* the ladder: a small face and one thin line split by craft */
|
|
18
43
|
.hc-ladder { position: relative; flex: none; width: 230px; margin: 24px 28px 24px 0; }
|
|
@@ -32,20 +57,30 @@
|
|
|
32
57
|
.hc-seg:hover { color: var(--ink); }
|
|
33
58
|
.hc-seg:hover i { background: var(--ink); }
|
|
34
59
|
|
|
35
|
-
/* the time slider */
|
|
36
|
-
.hc-time { display: flex; align-items: center; gap:
|
|
37
|
-
.hc-
|
|
38
|
-
.hc-
|
|
39
|
-
.hc-
|
|
40
|
-
.hc-
|
|
41
|
-
.hc-
|
|
42
|
-
.hc-
|
|
60
|
+
/* the time slider: live at its right end, a quiet player beside it */
|
|
61
|
+
.hc-time { display: flex; align-items: center; gap: 12px; padding: 18px 24px 16px; background: var(--bg-card); }
|
|
62
|
+
.hc-player { display: flex; gap: 2px; flex: none; }
|
|
63
|
+
.hc-p { width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--ink-faint); cursor: pointer; display: grid; place-items: center; }
|
|
64
|
+
.hc-p svg { width: 13px; height: 13px; }
|
|
65
|
+
.hc-p:hover { color: var(--ink); background: var(--rule-soft); }
|
|
66
|
+
.hc-p[aria-pressed="true"] { color: var(--ink); }
|
|
67
|
+
.hc-p:disabled { opacity: .3; cursor: default; background: transparent; }
|
|
68
|
+
.hc-p-play .hc-play__off, .hc-p-play.is-playing .hc-play__on { display: none; }
|
|
69
|
+
.hc-p-play.is-playing .hc-play__off { display: block; }
|
|
70
|
+
.hc-p:focus-visible, .hc-slider:focus-visible, .hc-live:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
71
|
+
.hc-track { position: relative; flex: 1; min-width: 0; }
|
|
72
|
+
.hc-when { position: absolute; top: -18px; transform: translateX(-50%); font: 600 11px/1 var(--font-display); padding: 3px 8px; border-radius: var(--radius-pill); background: var(--ink); color: var(--bg-card); opacity: 0; transition: opacity .25s; white-space: nowrap; pointer-events: none; }
|
|
73
|
+
.hc-when.is-on { opacity: 1; }
|
|
74
|
+
.hc-live { display: inline-flex; align-items: center; gap: 6px; min-height: 24px; padding: 0; border: 0; background: transparent; cursor: pointer; font: 600 11px var(--font-display); color: var(--ink-soft); }
|
|
75
|
+
.hc-live i { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); }
|
|
76
|
+
.hc-live.is-live { color: var(--ink); }
|
|
77
|
+
.hc-live.is-live i { background: var(--ok); animation: hc-live 2.4s ease-out infinite; }
|
|
43
78
|
.hc-slider { width: 100%; margin: 0; appearance: none; -webkit-appearance: none; background: transparent; height: 24px; cursor: pointer; }
|
|
44
79
|
.hc-slider::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--rule-soft); }
|
|
45
80
|
.hc-slider::-moz-range-track { height: 4px; border-radius: 2px; background: var(--rule-soft); }
|
|
46
81
|
.hc-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: var(--ink); margin-top: -8px; border: 3px solid var(--bg-card); }
|
|
47
82
|
.hc-slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--ink); border: 3px solid var(--bg-card); }
|
|
48
|
-
.hc-ticks { display: flex; justify-content: space-between; font-size: 11px; color: var(--ink-faint); margin-top: 2px; }
|
|
83
|
+
.hc-ticks { display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: var(--ink-faint); margin-top: 2px; }
|
|
49
84
|
|
|
50
85
|
.hc-tip { position: fixed; pointer-events: none; z-index: var(--z-modal); background: var(--ink); color: var(--bg-card); font-size: 12px; line-height: 1.35; padding: 8px 11px; border-radius: 9px; max-width: 260px; opacity: 0; transform: translate(-50%, calc(-100% - 12px)); }
|
|
51
86
|
.hc-tip.is-on { opacity: 1; }
|
|
@@ -54,8 +89,9 @@
|
|
|
54
89
|
/* live, and nothing more: a crane's slab climbs, this week's ideas glow (20s+) */
|
|
55
90
|
@keyframes hc-lift { 0%, 12% { transform: translateY(0); } 55%, 70% { transform: translateY(-24px); } 100% { transform: translateY(0); } }
|
|
56
91
|
@keyframes hc-glow { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
|
|
92
|
+
@keyframes hc-live { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 55%, transparent); } 100% { box-shadow: 0 0 0 8px transparent; } }
|
|
57
93
|
.hc-slab { animation: hc-lift 20s ease-in-out infinite; }
|
|
58
|
-
.hc-orb { animation: hc-glow 24s ease-in-out infinite; }
|
|
94
|
+
.hc-orb, .hc-twinkle { animation: hc-glow 24s ease-in-out infinite; }
|
|
59
95
|
|
|
60
96
|
/* a phone: the ladder runs full width under the city */
|
|
61
97
|
@media (max-width: 700px) {
|
|
@@ -7,9 +7,25 @@
|
|
|
7
7
|
// rule tests/home_bounded_reads.mjs holds Home to. The drawing is
|
|
8
8
|
// home-city-draw.js (pure); this file owns the read, the DOM, the slider and hover.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// v16 (task 1005058): the city is the one city of every craft (home-city-draw.js), so
|
|
11
|
+
// this file also hands the drawing the Governor City (window.GovCity, city-draw.js)
|
|
12
|
+
// and turns its district doors into craft switches. The time slider is LIVE at its
|
|
13
|
+
// right end; moving it, or the quiet player under it (rewind, play, fast forward),
|
|
14
|
+
// glides the city through every week in between — the owner asked for a rewind, not
|
|
15
|
+
// a jump. That glide is the one motion here driven from script (a requestAnimationFrame
|
|
16
|
+
// loop that redraws the city at a fractional week); under prefers-reduced-motion it
|
|
17
|
+
// does not run at all and every control jumps straight to where it was going. The
|
|
18
|
+
// ladder's rows and a crane's slab still move in CSS, which the hall's rule stops.
|
|
19
|
+
//
|
|
20
|
+
// The ideator's Home (task 1005059): while the craft is Ideating (OTBCraftMode, its
|
|
21
|
+
// otb:craft event) the same widget draws the sky over the city's skyline
|
|
22
|
+
// (home-city-draw.js skySvg) from the same read, and the ladder ranks the week's
|
|
23
|
+
// ideators — ideas filed, votes cast, ideas that became work. Every other craft keeps
|
|
24
|
+
// the builder's city exactly as it was.
|
|
25
|
+
//
|
|
26
|
+
// The governor's Home is the street (task 1005064; the v16 round's v16-governing.html):
|
|
27
|
+
// choosing Governing on Home, or opening Home with it chosen, goes to the street page
|
|
28
|
+
// (GovCity.STREET_HREF), which is never redrawn here.
|
|
13
29
|
(function () {
|
|
14
30
|
'use strict';
|
|
15
31
|
if (!window.OTB || typeof window.OTB.contributeHallWidget !== 'function') return;
|
|
@@ -17,16 +33,31 @@
|
|
|
17
33
|
if (!Draw) return;
|
|
18
34
|
const API = '/api/bongos';
|
|
19
35
|
const WEEKS = 10;
|
|
20
|
-
const STEP_MS = 800;
|
|
21
36
|
const RUNG = 40;
|
|
37
|
+
// the player's speeds, in weeks per frame at 60 fps: rewind and fast forward cross the
|
|
38
|
+
// whole slider in about five and four seconds; play is about a week every 1.6 s
|
|
39
|
+
const SPEED = { rw: -1 / 34, play: 1 / 96, ff: 1 / 26 };
|
|
40
|
+
const STILL = () => !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
22
41
|
const CRAFTS = [
|
|
23
42
|
['art', 'artistry', 'Art', 'pages designed'],
|
|
24
43
|
['build', 'building', 'Build', 'shipped'],
|
|
25
44
|
['idea', 'ideating', 'Ideas', 'ideas'],
|
|
26
45
|
['gov', 'governing', 'Govern', 'votes and decisions'],
|
|
27
46
|
];
|
|
47
|
+
// the ideator's line: a lightbulb, a star, and an arrow into the work
|
|
48
|
+
const IDEATOR = [
|
|
49
|
+
['idea', 'ideating', 'Ideas', 'ideas filed'],
|
|
50
|
+
['vote', '#vote', 'Votes', 'votes cast'],
|
|
51
|
+
['work', '#work', 'Became work', 'ideas became work'],
|
|
52
|
+
];
|
|
53
|
+
const GLYPH = {
|
|
54
|
+
'#vote': '<path d="M8 2.2l1.7 3.6 3.9.5-2.9 2.7.8 3.9L8 11l-3.5 1.9.8-3.9L2.4 6.3l3.9-.5z"/>',
|
|
55
|
+
'#work': '<path d="M2.5 2.5l7 7M9.5 9.5l-1 4 5-5-4 1z"/>',
|
|
56
|
+
};
|
|
57
|
+
const SKY_LABEL = 'The project\'s ideas as a night sky over the city\'s real skyline, with this week\'s ideators ranked beside it';
|
|
58
|
+
const CITY_LABEL = 'The project as a city: one tower per working area, as tall as what has shipped there, with the builders of the week beside it';
|
|
28
59
|
|
|
29
|
-
let host = null, model = null, viewerId = null,
|
|
60
|
+
let host = null, model = null, viewerId = null, t = 0, target = 0, raf = null, run = null, lastWeek = -1, shown = false, gallery = null;
|
|
30
61
|
const rungs = new Map();
|
|
31
62
|
|
|
32
63
|
function mode() {
|
|
@@ -35,6 +66,7 @@
|
|
|
35
66
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
36
67
|
}
|
|
37
68
|
|
|
69
|
+
function atLive(v) { return !model || v >= model.W - 1 - 1e-3; }
|
|
38
70
|
function weekName(i) {
|
|
39
71
|
if (!model || i === model.W - 1) return 'this week';
|
|
40
72
|
const w = model.weeks[i];
|
|
@@ -42,7 +74,15 @@
|
|
|
42
74
|
return d && !isNaN(d) ? 'week of ' + d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' }) : 'that week';
|
|
43
75
|
}
|
|
44
76
|
|
|
77
|
+
// Ideating draws the sky; every other craft (or none) the builder's city.
|
|
78
|
+
function craft() {
|
|
79
|
+
const CM = window.OTBCraftMode;
|
|
80
|
+
const m = CM && typeof CM.current === 'function' ? CM.current() : document.documentElement.dataset.craft;
|
|
81
|
+
return m === 'ideating' ? 'ideating' : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
function craftIcon(key) {
|
|
85
|
+
if (GLYPH[key]) return '<svg class="hc-ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + GLYPH[key] + '</svg>';
|
|
46
86
|
const CM = window.OTBCraftMode;
|
|
47
87
|
return CM && typeof CM.iconHtml === 'function' ? CM.iconHtml(key, 'hc-ico') : '';
|
|
48
88
|
}
|
|
@@ -59,7 +99,12 @@
|
|
|
59
99
|
}
|
|
60
100
|
function drawScene() {
|
|
61
101
|
const svg = host.querySelector('.hc-svg');
|
|
62
|
-
const
|
|
102
|
+
const sky = craft() === 'ideating';
|
|
103
|
+
const draw = sky ? Draw.skySvg : Draw.citySvg;
|
|
104
|
+
const out = draw(model, atLive(t) ? model.W - 1 : t, { mode: mode(), gov: window.GovCity || null });
|
|
105
|
+
host.classList.toggle('hc-is-sky', sky);
|
|
106
|
+
svg.setAttribute('aria-label', sky ? SKY_LABEL : CITY_LABEL);
|
|
107
|
+
host.querySelector('#home-city-h').textContent = sky ? 'The project\'s ideas this week' : 'The project this week';
|
|
63
108
|
svg.setAttribute('viewBox', out.viewBox);
|
|
64
109
|
svg.replaceChildren(...out.nodes.map(build));
|
|
65
110
|
}
|
|
@@ -91,8 +136,12 @@
|
|
|
91
136
|
}
|
|
92
137
|
|
|
93
138
|
function drawLadder() {
|
|
94
|
-
const
|
|
139
|
+
const week = Math.floor(t + 1e-6);
|
|
140
|
+
const who = craft();
|
|
141
|
+
const rows = Draw.ladderFor(model, week, viewerId, who);
|
|
142
|
+
const kinds = who === 'ideating' ? IDEATOR : CRAFTS;
|
|
95
143
|
const ladder = host.querySelector('.hc-ladder');
|
|
144
|
+
ladder.setAttribute('aria-label', who === 'ideating' ? 'This week\'s ideators, ranked by what they did' : 'This week\'s builders, ranked by what they did');
|
|
96
145
|
ladder.style.height = (Math.max(1, rows.length) * RUNG) + 'px';
|
|
97
146
|
const top = Math.max(1, ...rows.map((r) => r.total));
|
|
98
147
|
const room = Math.max(60, ladder.clientWidth - 40);
|
|
@@ -107,7 +156,7 @@
|
|
|
107
156
|
r.dataset.tipBody = weekName(week);
|
|
108
157
|
const line = r.querySelector('.hc-line');
|
|
109
158
|
line.textContent = '';
|
|
110
|
-
const crafts =
|
|
159
|
+
const crafts = kinds.filter((c) => row.v[c[0]] > 0);
|
|
111
160
|
line.style.width = Math.max(crafts.length * 29, room * row.total / top).toFixed(1) + 'px';
|
|
112
161
|
crafts.forEach((c) => {
|
|
113
162
|
const seg = document.createElement('span');
|
|
@@ -125,29 +174,73 @@
|
|
|
125
174
|
rungs.forEach((r, id) => { if (!shown.has(id)) r.hidden = true; });
|
|
126
175
|
}
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
|
|
177
|
+
// ---- time: live at the right end; the city glides to wherever the slider is sent ----
|
|
178
|
+
function when(v) {
|
|
179
|
+
if (atLive(v)) return 'Live';
|
|
180
|
+
const w0 = model.weeks[0] && model.weeks[0].start ? Date.parse(model.weeks[0].start + 'T00:00:00Z') : NaN;
|
|
181
|
+
return isNaN(w0) ? weekName(Math.floor(v)) : new Date(w0 + v * 7 * 864e5).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' });
|
|
182
|
+
}
|
|
183
|
+
function setT(v) {
|
|
184
|
+
t = Math.max(0, Math.min(model.W - 1, v));
|
|
185
|
+
const live = atLive(t);
|
|
130
186
|
const s = host.querySelector('.hc-slider');
|
|
131
|
-
s.value = String(
|
|
132
|
-
s.setAttribute('aria-valuetext',
|
|
187
|
+
s.value = String(t);
|
|
188
|
+
s.setAttribute('aria-valuetext', live ? 'Live' : when(t));
|
|
189
|
+
const pill = host.querySelector('.hc-when');
|
|
190
|
+
pill.textContent = when(t);
|
|
191
|
+
pill.style.left = (t / Math.max(1, model.W - 1) * 100) + '%';
|
|
192
|
+
pill.classList.toggle('is-on', !live);
|
|
193
|
+
const l = host.querySelector('.hc-live');
|
|
194
|
+
l.classList.toggle('is-live', live);
|
|
195
|
+
l.querySelector('span').textContent = live ? 'Live' : 'Back to live';
|
|
133
196
|
drawScene();
|
|
134
|
-
|
|
197
|
+
const w = Math.floor(t + 1e-6);
|
|
198
|
+
if (w !== lastWeek) { lastWeek = w; drawLadder(); }
|
|
199
|
+
buttons();
|
|
135
200
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
201
|
+
function buttons() {
|
|
202
|
+
const b = (k) => host.querySelector(`.hc-p-${k}`);
|
|
203
|
+
b('play').classList.toggle('is-playing', run === 'play');
|
|
204
|
+
b('play').setAttribute('aria-label', run === 'play' ? 'Pause' : atLive(t) ? 'Play from the start' : 'Play');
|
|
205
|
+
['rw', 'play', 'ff'].forEach((k) => b(k).setAttribute('aria-pressed', String(run === k)));
|
|
206
|
+
b('rw').disabled = t <= 1e-3 && run !== 'rw';
|
|
207
|
+
b('ff').disabled = atLive(t) && run !== 'ff';
|
|
208
|
+
}
|
|
209
|
+
function halt() { if (raf) cancelAnimationFrame(raf); raf = null; run = null; target = t; if (host) buttons(); }
|
|
210
|
+
function tick() {
|
|
211
|
+
raf = requestAnimationFrame(() => {
|
|
212
|
+
let v;
|
|
213
|
+
if (run) { v = t + SPEED[run]; if (v <= 0 || v >= model.W - 1) { v = Math.max(0, Math.min(model.W - 1, v)); run = null; } target = v; }
|
|
214
|
+
else { v = t + (target - t) * 0.16; if (Math.abs(target - v) < 0.004) v = target; }
|
|
215
|
+
setT(v);
|
|
216
|
+
if (run || v !== target) tick(); else { raf = null; buttons(); }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function glideTo(v) {
|
|
220
|
+
run = null; target = Math.max(0, Math.min(model.W - 1, v));
|
|
221
|
+
if (STILL()) { const v = target; halt(); setT(v); return; }
|
|
222
|
+
if (!raf) tick();
|
|
223
|
+
}
|
|
224
|
+
function press(k) {
|
|
225
|
+
if (run === k) { halt(); return; }
|
|
226
|
+
if (k === 'play' && atLive(t)) setT(0);
|
|
227
|
+
if (STILL()) { halt(); setT(k === 'rw' ? 0 : model.W - 1); return; }
|
|
228
|
+
run = k; buttons();
|
|
229
|
+
if (!raf) tick();
|
|
143
230
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
231
|
+
|
|
232
|
+
// ---- the doors: the museum and the stars switch the craft (the craft's own Home); the
|
|
233
|
+
// core is a plain link to the street and a building a plain link to its goal ----
|
|
234
|
+
function wireDoors() {
|
|
235
|
+
host.addEventListener('click', (e) => {
|
|
236
|
+
const d = e.target.closest('[data-craft-door]');
|
|
237
|
+
if (!d || !host.contains(d)) return;
|
|
238
|
+
const CM = window.OTBCraftMode;
|
|
239
|
+
if (!CM || typeof CM.choose !== 'function') return;
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
CM.choose(d.getAttribute('data-craft-door'));
|
|
242
|
+
window.scrollTo(0, 0);
|
|
243
|
+
});
|
|
151
244
|
}
|
|
152
245
|
|
|
153
246
|
// ---- one tooltip for the whole widget; labels are ledger data, so text only ----
|
|
@@ -169,12 +262,14 @@
|
|
|
169
262
|
}
|
|
170
263
|
|
|
171
264
|
function ticks() {
|
|
172
|
-
const
|
|
265
|
+
const tk = host.querySelector('.hc-ticks');
|
|
173
266
|
const first = model.weeks[0] && model.weeks[0].start ? new Date(model.weeks[0].start + 'T00:00:00Z') : null;
|
|
174
267
|
const mid = model.weeks[Math.floor(model.W / 2)] && model.weeks[Math.floor(model.W / 2)].start ? new Date(model.weeks[Math.floor(model.W / 2)].start + 'T00:00:00Z') : null;
|
|
175
268
|
const mon = (d) => (d && !isNaN(d) ? d.toLocaleDateString('en-GB', { month: 'short', timeZone: 'UTC' }) : '');
|
|
176
|
-
|
|
177
|
-
|
|
269
|
+
const live = host.querySelector('.hc-live');
|
|
270
|
+
tk.textContent = '';
|
|
271
|
+
[mon(first), mon(mid)].forEach((s) => { const sp = document.createElement('span'); sp.textContent = s; tk.appendChild(sp); });
|
|
272
|
+
tk.appendChild(live);
|
|
178
273
|
}
|
|
179
274
|
|
|
180
275
|
async function load() {
|
|
@@ -189,29 +284,64 @@
|
|
|
189
284
|
const s = host.querySelector('.hc-slider');
|
|
190
285
|
s.max = String(model.W - 1);
|
|
191
286
|
ticks();
|
|
192
|
-
|
|
193
|
-
|
|
287
|
+
shown = true;
|
|
288
|
+
craftHome();
|
|
289
|
+
target = model.W - 1;
|
|
290
|
+
setT(model.W - 1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ---- the artist's Home (task 1005060): in Artistry the same section is the gallery,
|
|
294
|
+
// home-gallery.js, the bench in the museum the city's art district opens onto ----
|
|
295
|
+
function craftHome() {
|
|
296
|
+
const art = !!gallery && document.documentElement.dataset.craft === 'artistry';
|
|
297
|
+
host.classList.toggle('hc--gallery', art);
|
|
298
|
+
if (gallery) gallery.show(art);
|
|
299
|
+
host.hidden = !(art || shown);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---- the governor's Home is the street; tests swap the hop for a recorder ----
|
|
303
|
+
function streetHop() {
|
|
304
|
+
const href = window.GovCity && window.GovCity.STREET_HREF;
|
|
305
|
+
if (document.documentElement.dataset.craft !== 'governing' || !href) return false;
|
|
306
|
+
(window.OTBHomeStreetHop || ((h) => window.location.assign(h)))(href);
|
|
307
|
+
return true;
|
|
194
308
|
}
|
|
195
309
|
|
|
196
310
|
function mount(hostEl) {
|
|
311
|
+
document.addEventListener('otb:craft', streetHop);
|
|
312
|
+
if (streetHop()) return; // leaving for the street: build nothing, read nothing
|
|
197
313
|
host = hostEl;
|
|
198
314
|
host.innerHTML =
|
|
199
315
|
'<h2 id="home-city-h" class="sr-only">The project this week</h2>' +
|
|
200
316
|
'<div class="hc-stage">' +
|
|
201
|
-
'<svg class="hc-svg" role="img"
|
|
317
|
+
'<svg class="hc-svg" role="img"></svg>' +
|
|
202
318
|
'<div class="hc-ladder" aria-label="This week\'s builders, ranked by what they did"></div>' +
|
|
203
319
|
'</div>' +
|
|
204
320
|
'<div class="hc-time">' +
|
|
205
|
-
'<
|
|
206
|
-
'<
|
|
207
|
-
'<
|
|
208
|
-
|
|
209
|
-
|
|
321
|
+
'<div class="hc-player" role="group" aria-label="Time">' +
|
|
322
|
+
'<button type="button" class="hc-p hc-p-rw" aria-label="Rewind"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3.5v9L2.5 8zM14 3.5v9L8.5 8z" fill="currentColor"/></svg></button>' +
|
|
323
|
+
'<button type="button" class="hc-p hc-p-play" aria-label="Play">' +
|
|
324
|
+
'<svg class="hc-play__on" viewBox="0 0 16 16" aria-hidden="true"><path d="M4.5 3v10l8-5z" fill="currentColor"/></svg>' +
|
|
325
|
+
'<svg class="hc-play__off" viewBox="0 0 16 16" aria-hidden="true"><path d="M4.5 3h2.4v10H4.5zM9.1 3h2.4v10H9.1z" fill="currentColor"/></svg>' +
|
|
326
|
+
'</button>' +
|
|
327
|
+
'<button type="button" class="hc-p hc-p-ff" aria-label="Fast forward"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 3.5v9L7.5 8zM8 3.5v9L13.5 8z" fill="currentColor"/></svg></button>' +
|
|
328
|
+
'</div>' +
|
|
329
|
+
'<div class="hc-track"><div class="hc-when" aria-hidden="true"></div>' +
|
|
330
|
+
'<input type="range" class="hc-slider" min="0" max="0" step="any" value="0" aria-label="Time, from the start of the version to live">' +
|
|
331
|
+
'<div class="hc-ticks"></div>' +
|
|
332
|
+
'<button type="button" class="hc-live is-live"><i aria-hidden="true"></i><span>Live</span></button>' +
|
|
333
|
+
'</div>' +
|
|
210
334
|
'</div>';
|
|
211
|
-
host.querySelector('.hc-slider').addEventListener('input', (e) => {
|
|
212
|
-
host.querySelector(
|
|
335
|
+
host.querySelector('.hc-slider').addEventListener('input', (e) => { if (model) glideTo(Number(e.target.value)); });
|
|
336
|
+
['rw', 'play', 'ff'].forEach((k) => host.querySelector(`.hc-p-${k}`).addEventListener('click', () => { if (model) press(k); }));
|
|
337
|
+
host.querySelector('.hc-live').addEventListener('click', () => { if (model) glideTo(model.W - 1); });
|
|
213
338
|
wireTip();
|
|
339
|
+
wireDoors();
|
|
340
|
+
if (window.OTBHomeGallery) gallery = window.OTBHomeGallery.attach(host);
|
|
341
|
+
document.addEventListener('otb:craft', craftHome);
|
|
342
|
+
craftHome();
|
|
214
343
|
new MutationObserver(() => { if (model) drawScene(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
344
|
+
document.addEventListener('otb:craft', () => { if (model) { drawScene(); drawLadder(); } });
|
|
215
345
|
window.addEventListener('resize', () => { if (model) drawLadder(); });
|
|
216
346
|
load();
|
|
217
347
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* modules/hall-ui/public/home-gallery.css — the artist's Home: a bench in the museum
|
|
2
|
+
(task 1005060; mock docs/design/mocks/hall/home/v16-artistry.html, record
|
|
3
|
+
modules/hall-ui/records/home-city.md § "The gallery"). A quiet wall, a floor and a
|
|
4
|
+
bench barely there, and the art the largest thing in the room in a thin frame.
|
|
5
|
+
Every colour is a hall token or a mix of one; the only literal is black with alpha,
|
|
6
|
+
as a shadow. The tokens carry both modes, so no rule here is written per mode.
|
|
7
|
+
No :root; motion is CSS and the hall's reduced-motion rule stops it. */
|
|
8
|
+
|
|
9
|
+
/* home-city.js shows the gallery in Artistry and the city otherwise */
|
|
10
|
+
.home-city.hc--gallery > :not(.hg) { display: none; }
|
|
11
|
+
.home-city:not(.hc--gallery) > .hg { display: none; }
|
|
12
|
+
|
|
13
|
+
.hg-room {
|
|
14
|
+
--hg-wall: color-mix(in srgb, var(--bg-card) 94%, var(--accent));
|
|
15
|
+
--hg-wall-low: color-mix(in srgb, var(--bg) 90%, var(--accent-deep));
|
|
16
|
+
--hg-floor: color-mix(in srgb, var(--bg-deep) 86%, var(--accent-deep));
|
|
17
|
+
--hg-spot: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
18
|
+
--hg-frame: rgba(0, 0, 0, .82);
|
|
19
|
+
--hg-shadow: rgba(0, 0, 0, .45);
|
|
20
|
+
position: relative; height: 760px; overflow: hidden; user-select: none; touch-action: pan-y; outline: none;
|
|
21
|
+
color: var(--ink);
|
|
22
|
+
background: linear-gradient(var(--hg-wall), var(--hg-wall-low) 88%, transparent 88%), var(--hg-floor);
|
|
23
|
+
}
|
|
24
|
+
.hg-room:focus-visible { box-shadow: inset 0 0 0 2px var(--focus, var(--accent)); }
|
|
25
|
+
.hg-room::before { content: ""; position: absolute; inset: 0 0 12% 0; background: radial-gradient(ellipse 40% 64% at 46% 44%, var(--hg-spot), transparent 70%); pointer-events: none; }
|
|
26
|
+
/* the skirting board and the floorboards */
|
|
27
|
+
.hg-room::after { content: ""; position: absolute; left: 0; right: 0; top: 88%; bottom: 0; border-top: 2px solid color-mix(in srgb, var(--hg-wall-low) 82%, var(--ink)); background: repeating-linear-gradient(90deg, transparent 0 160px, color-mix(in srgb, var(--ink) 5%, transparent) 160px 161px); pointer-events: none; }
|
|
28
|
+
.hg-bench { position: absolute; left: 50%; bottom: -30px; width: 46%; height: 44px; transform: translateX(-50%); border-radius: 14px 14px 0 0; background: linear-gradient(color-mix(in srgb, var(--ink) 70%, var(--accent-deep)), var(--ink)); opacity: .22; filter: blur(1.2px); z-index: 3; pointer-events: none; }
|
|
29
|
+
|
|
30
|
+
/* the wall title, in vinyl lettering */
|
|
31
|
+
.hg-vinyl { position: absolute; left: 44px; top: 34px; z-index: 4; }
|
|
32
|
+
.hg-h { margin: 0; font: 300 30px/1.2 var(--font-display); letter-spacing: .01em; color: var(--ink); }
|
|
33
|
+
.hg-sub { margin: 6px 0 0; font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-soft); }
|
|
34
|
+
.hg-sort { display: flex; gap: 18px; margin-top: 16px; }
|
|
35
|
+
.hg-sort button, .hg-way { all: unset; cursor: pointer; box-sizing: border-box; min-height: 24px; min-width: 24px; display: inline-flex; align-items: center; color: var(--ink-soft); }
|
|
36
|
+
.hg-sort button { font-size: 12.5px; letter-spacing: .04em; border-bottom: 1px solid transparent; }
|
|
37
|
+
.hg-sort button[aria-pressed="true"] { color: var(--ink); border-color: var(--ink); }
|
|
38
|
+
.hg-ways { display: flex; flex-wrap: wrap; gap: 4px 22px; margin-top: 22px; }
|
|
39
|
+
.hg-way { font-size: 13px; letter-spacing: .01em; text-decoration: none; gap: 6px; }
|
|
40
|
+
.hg-way:hover, .hg-way[aria-pressed="true"] { color: var(--ink); }
|
|
41
|
+
.hg-sort button:focus-visible, .hg-way:focus-visible { outline: 2px solid var(--focus, var(--accent)); outline-offset: 3px; }
|
|
42
|
+
|
|
43
|
+
/* the wall, one work at a time along it */
|
|
44
|
+
.hg-wall { position: absolute; inset: 0 0 12% 0; overflow: hidden; }
|
|
45
|
+
.hg-track { position: absolute; inset: 0; display: flex; transition: transform .7s cubic-bezier(.22, .7, .2, 1); }
|
|
46
|
+
.hg-track.is-drag { transition: none; }
|
|
47
|
+
.hg-hang { flex: 0 0 100%; position: relative; }
|
|
48
|
+
.hg-work { position: absolute; left: 49%; top: 188px; transform: translateX(-50%); width: min(700px, 56%); transition: opacity .3s; }
|
|
49
|
+
.hg-canvas { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: var(--bg-deep); border: 3px solid var(--hg-frame); box-shadow: 0 18px 34px -20px var(--hg-shadow); }
|
|
50
|
+
.hg-canvas img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center; pointer-events: none; -webkit-user-drag: none; }
|
|
51
|
+
|
|
52
|
+
/* the wall label: small, at the work's foot to its right; click to read it closer */
|
|
53
|
+
.hg-label { position: absolute; left: calc(100% + 28px); bottom: 0; width: 158px; font-size: 10px; line-height: 1.4; color: var(--ink); }
|
|
54
|
+
.hg-text { display: block; cursor: zoom-in; transform-origin: 100% 100%; transition: transform .35s cubic-bezier(.2, .7, .2, 1); border-radius: 4px; outline-offset: 6px; }
|
|
55
|
+
.hg-text:focus-visible { outline: 2px solid var(--focus, var(--accent)); }
|
|
56
|
+
.hg-who { display: block; font-weight: 700; font-size: 10.5px; }
|
|
57
|
+
.hg-bio { display: block; margin-bottom: 6px; color: var(--ink-soft); }
|
|
58
|
+
.hg-title { display: block; }
|
|
59
|
+
.hg-medium, .hg-place { display: block; color: var(--ink-soft); }
|
|
60
|
+
.hg-words, .hg-credit { display: none; margin: 8px 0 0; }
|
|
61
|
+
.hg-credit { color: var(--ink-soft); }
|
|
62
|
+
.hg-hang.is-reading .hg-text { transform: scale(1.85); cursor: zoom-out; }
|
|
63
|
+
.hg-hang.is-reading .hg-words, .hg-hang.is-reading .hg-credit { display: block; }
|
|
64
|
+
.hg-hang.is-reading .hg-work > .hg-canvas { opacity: .4; }
|
|
65
|
+
/* the signature, in the artist's hand, on the wall below the label; the heart never changes size */
|
|
66
|
+
.hg-sigline { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
|
|
67
|
+
.hg-sig { font: 500 17px/1 "Caveat", "Segoe Script", "Bradley Hand", "Snell Roundhand", cursive; color: var(--ink); opacity: .82; transform: rotate(-2deg); white-space: nowrap; }
|
|
68
|
+
.hg-heart { all: unset; cursor: pointer; flex: none; width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%; color: var(--ink-soft); }
|
|
69
|
+
.hg-heart svg { width: 15px; height: 15px; }
|
|
70
|
+
.hg-heart:hover { color: var(--ink); }
|
|
71
|
+
.hg-heart[aria-pressed="true"] { color: var(--accent-deep); }
|
|
72
|
+
.hg-heart[aria-pressed="true"] path { fill: currentColor; }
|
|
73
|
+
.hg-heart:focus-visible { outline: 2px solid var(--focus, var(--accent)); }
|
|
74
|
+
|
|
75
|
+
/* moving along the wall */
|
|
76
|
+
.hg-edge { all: unset; position: absolute; top: 0; bottom: 12%; width: 9%; min-width: 44px; z-index: 4; cursor: pointer; display: grid; place-items: center; color: var(--ink-soft); opacity: .55; transition: opacity .2s; }
|
|
77
|
+
.hg-edge:hover, .hg-edge:focus-visible { opacity: 1; }
|
|
78
|
+
.hg-edge:focus-visible svg { outline: 2px solid var(--focus, var(--accent)); outline-offset: 6px; border-radius: 50%; }
|
|
79
|
+
.hg-edge svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }
|
|
80
|
+
.hg-prev { left: 0; } .hg-next { right: 0; }
|
|
81
|
+
.hg-edge[disabled] { opacity: 0; pointer-events: none; }
|
|
82
|
+
.hg-count { position: absolute; left: 49%; top: calc(88% - 30px); transform: translateX(-50%); z-index: 4; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); }
|
|
83
|
+
.hg-empty { position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%); width: min(440px, 80%); margin: 0; text-align: center; font-size: 14px; color: var(--ink-soft); }
|
|
84
|
+
.hg-room.is-empty .hg-edge, .hg-room.is-empty .hg-fsbtn { display: none; }
|
|
85
|
+
|
|
86
|
+
/* full screen: only the art, its caption, the signature and the heart */
|
|
87
|
+
.hg-fsbtn { all: unset; position: absolute; right: 16px; top: 16px; z-index: 5; width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; color: var(--ink-soft); cursor: pointer; opacity: .6; }
|
|
88
|
+
.hg-fsbtn:hover, .hg-fsbtn:focus-visible { opacity: 1; }
|
|
89
|
+
.hg-fsbtn:focus-visible { outline: 2px solid var(--focus, var(--accent)); }
|
|
90
|
+
.hg-fsbtn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
|
|
91
|
+
.hg-cap { display: none; position: absolute; left: 50%; bottom: 22px; transform: translateX(-50%); font-size: 12px; color: var(--ink-soft); white-space: nowrap; max-width: 60vw; overflow: hidden; text-overflow: ellipsis; }
|
|
92
|
+
.hg-room:fullscreen, .hg-room.is-fs { --hg-spot: transparent; background: var(--hg-wall-low); }
|
|
93
|
+
.hg-room.is-fs { position: fixed; inset: 0; height: auto; z-index: 1000; }
|
|
94
|
+
.hg-room:fullscreen::after, .hg-room.is-fs::after, .hg-room:fullscreen .hg-bench, .hg-room.is-fs .hg-bench,
|
|
95
|
+
.hg-room:fullscreen .hg-vinyl, .hg-room.is-fs .hg-vinyl, .hg-room:fullscreen .hg-count, .hg-room.is-fs .hg-count { display: none; }
|
|
96
|
+
.hg-room:fullscreen .hg-wall, .hg-room.is-fs .hg-wall, .hg-room:fullscreen .hg-edge, .hg-room.is-fs .hg-edge { bottom: 0; }
|
|
97
|
+
.hg-room:fullscreen .hg-work, .hg-room.is-fs .hg-work { left: 50%; top: 50%; transform: translate(-50%, calc(-50% - 22px)); width: min(86vw, calc((100vh - 130px) * 1.6)); }
|
|
98
|
+
.hg-room:fullscreen .hg-canvas, .hg-room.is-fs .hg-canvas { border: 0; box-shadow: none; opacity: 1; }
|
|
99
|
+
.hg-room:fullscreen .hg-label, .hg-room.is-fs .hg-label { position: absolute; left: auto; right: 0; top: calc(100% + 10px); bottom: auto; width: auto; margin: 0; }
|
|
100
|
+
.hg-room:fullscreen .hg-text, .hg-room.is-fs .hg-text { display: none; }
|
|
101
|
+
.hg-room:fullscreen .hg-cap, .hg-room.is-fs .hg-cap { display: block; }
|
|
102
|
+
|
|
103
|
+
/* the full gallery: every work on the wall at once, hung salon-style */
|
|
104
|
+
.hg-salon { position: absolute; left: 44px; right: 44px; top: 210px; bottom: calc(12% + 20px); overflow: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 34px 30px; align-content: start; padding: 4px; z-index: 4; }
|
|
105
|
+
.hg-salon[hidden] { display: none; }
|
|
106
|
+
.hg-room.is-all .hg-wall, .hg-room.is-all .hg-edge, .hg-room.is-all .hg-count, .hg-room.is-all .hg-fsbtn { display: none; }
|
|
107
|
+
.hg-piece { all: unset; cursor: pointer; display: block; width: 100%; }
|
|
108
|
+
.hg-pc { position: relative; display: block; aspect-ratio: 16 / 10; overflow: hidden; background: var(--bg-deep); border: 2px solid var(--hg-frame); box-shadow: 0 12px 22px -16px var(--hg-shadow); transition: transform .25s; }
|
|
109
|
+
.hg-pc img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center; }
|
|
110
|
+
.hg-piece:hover .hg-pc, .hg-piece:focus-visible .hg-pc { transform: translateY(-3px); }
|
|
111
|
+
.hg-piece:focus-visible { outline: 2px solid var(--focus, var(--accent)); outline-offset: 6px; }
|
|
112
|
+
.hg-pt { display: block; margin-top: 9px; font-size: 10.5px; color: var(--ink); }
|
|
113
|
+
.hg-pl { display: flex; align-items: center; gap: 5px; margin-top: 2px; font-size: 10px; color: var(--ink-soft); }
|
|
114
|
+
.hg-pl svg { width: 11px; height: 11px; }
|
|
115
|
+
|
|
116
|
+
@media (max-width: 860px) {
|
|
117
|
+
.hg-room { height: 660px; }
|
|
118
|
+
.hg-vinyl { left: 20px; right: 20px; top: 20px; }
|
|
119
|
+
.hg-h { font-size: 24px; }
|
|
120
|
+
.hg-work { top: 196px; left: 50%; width: 86%; }
|
|
121
|
+
.hg-label { position: static; width: auto; margin-top: 14px; }
|
|
122
|
+
/* a phone has no room to scale the label: read closer, it opens over the dimmed picture */
|
|
123
|
+
.hg-hang.is-reading .hg-text { transform: none; }
|
|
124
|
+
.hg-hang.is-reading .hg-label { position: absolute; left: 0; right: 0; top: 0; margin: 0; font-size: 12px; }
|
|
125
|
+
.hg-hang.is-reading .hg-who { font-size: 12.5px; }
|
|
126
|
+
.hg-hang.is-reading .hg-work > .hg-canvas { opacity: 0; }
|
|
127
|
+
.hg-count { left: 50%; }
|
|
128
|
+
.hg-salon { left: 20px; right: 20px; top: 196px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 22px 16px; }
|
|
129
|
+
}
|