@bongos/core 1.24.30 → 1.24.31
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 +2 -2
- package/docs/page-inventory.json +15 -35
- package/docs/page-readings.json +226 -263
- 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 +156 -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 +123 -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 +29 -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 +167 -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,21 @@
|
|
|
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.
|
|
13
25
|
(function () {
|
|
14
26
|
'use strict';
|
|
15
27
|
if (!window.OTB || typeof window.OTB.contributeHallWidget !== 'function') return;
|
|
@@ -17,16 +29,31 @@
|
|
|
17
29
|
if (!Draw) return;
|
|
18
30
|
const API = '/api/bongos';
|
|
19
31
|
const WEEKS = 10;
|
|
20
|
-
const STEP_MS = 800;
|
|
21
32
|
const RUNG = 40;
|
|
33
|
+
// the player's speeds, in weeks per frame at 60 fps: rewind and fast forward cross the
|
|
34
|
+
// whole slider in about five and four seconds; play is about a week every 1.6 s
|
|
35
|
+
const SPEED = { rw: -1 / 34, play: 1 / 96, ff: 1 / 26 };
|
|
36
|
+
const STILL = () => !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
22
37
|
const CRAFTS = [
|
|
23
38
|
['art', 'artistry', 'Art', 'pages designed'],
|
|
24
39
|
['build', 'building', 'Build', 'shipped'],
|
|
25
40
|
['idea', 'ideating', 'Ideas', 'ideas'],
|
|
26
41
|
['gov', 'governing', 'Govern', 'votes and decisions'],
|
|
27
42
|
];
|
|
43
|
+
// the ideator's line: a lightbulb, a star, and an arrow into the work
|
|
44
|
+
const IDEATOR = [
|
|
45
|
+
['idea', 'ideating', 'Ideas', 'ideas filed'],
|
|
46
|
+
['vote', '#vote', 'Votes', 'votes cast'],
|
|
47
|
+
['work', '#work', 'Became work', 'ideas became work'],
|
|
48
|
+
];
|
|
49
|
+
const GLYPH = {
|
|
50
|
+
'#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"/>',
|
|
51
|
+
'#work': '<path d="M2.5 2.5l7 7M9.5 9.5l-1 4 5-5-4 1z"/>',
|
|
52
|
+
};
|
|
53
|
+
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';
|
|
54
|
+
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
55
|
|
|
29
|
-
let host = null, model = null, viewerId = null,
|
|
56
|
+
let host = null, model = null, viewerId = null, t = 0, target = 0, raf = null, run = null, lastWeek = -1, shown = false, gallery = null;
|
|
30
57
|
const rungs = new Map();
|
|
31
58
|
|
|
32
59
|
function mode() {
|
|
@@ -35,6 +62,7 @@
|
|
|
35
62
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
36
63
|
}
|
|
37
64
|
|
|
65
|
+
function atLive(v) { return !model || v >= model.W - 1 - 1e-3; }
|
|
38
66
|
function weekName(i) {
|
|
39
67
|
if (!model || i === model.W - 1) return 'this week';
|
|
40
68
|
const w = model.weeks[i];
|
|
@@ -42,7 +70,15 @@
|
|
|
42
70
|
return d && !isNaN(d) ? 'week of ' + d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' }) : 'that week';
|
|
43
71
|
}
|
|
44
72
|
|
|
73
|
+
// Ideating draws the sky; every other craft (or none) the builder's city.
|
|
74
|
+
function craft() {
|
|
75
|
+
const CM = window.OTBCraftMode;
|
|
76
|
+
const m = CM && typeof CM.current === 'function' ? CM.current() : document.documentElement.dataset.craft;
|
|
77
|
+
return m === 'ideating' ? 'ideating' : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
45
80
|
function craftIcon(key) {
|
|
81
|
+
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
82
|
const CM = window.OTBCraftMode;
|
|
47
83
|
return CM && typeof CM.iconHtml === 'function' ? CM.iconHtml(key, 'hc-ico') : '';
|
|
48
84
|
}
|
|
@@ -59,7 +95,12 @@
|
|
|
59
95
|
}
|
|
60
96
|
function drawScene() {
|
|
61
97
|
const svg = host.querySelector('.hc-svg');
|
|
62
|
-
const
|
|
98
|
+
const sky = craft() === 'ideating';
|
|
99
|
+
const draw = sky ? Draw.skySvg : Draw.citySvg;
|
|
100
|
+
const out = draw(model, atLive(t) ? model.W - 1 : t, { mode: mode(), gov: window.GovCity || null });
|
|
101
|
+
host.classList.toggle('hc-is-sky', sky);
|
|
102
|
+
svg.setAttribute('aria-label', sky ? SKY_LABEL : CITY_LABEL);
|
|
103
|
+
host.querySelector('#home-city-h').textContent = sky ? 'The project\'s ideas this week' : 'The project this week';
|
|
63
104
|
svg.setAttribute('viewBox', out.viewBox);
|
|
64
105
|
svg.replaceChildren(...out.nodes.map(build));
|
|
65
106
|
}
|
|
@@ -91,8 +132,12 @@
|
|
|
91
132
|
}
|
|
92
133
|
|
|
93
134
|
function drawLadder() {
|
|
94
|
-
const
|
|
135
|
+
const week = Math.floor(t + 1e-6);
|
|
136
|
+
const who = craft();
|
|
137
|
+
const rows = Draw.ladderFor(model, week, viewerId, who);
|
|
138
|
+
const kinds = who === 'ideating' ? IDEATOR : CRAFTS;
|
|
95
139
|
const ladder = host.querySelector('.hc-ladder');
|
|
140
|
+
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
141
|
ladder.style.height = (Math.max(1, rows.length) * RUNG) + 'px';
|
|
97
142
|
const top = Math.max(1, ...rows.map((r) => r.total));
|
|
98
143
|
const room = Math.max(60, ladder.clientWidth - 40);
|
|
@@ -107,7 +152,7 @@
|
|
|
107
152
|
r.dataset.tipBody = weekName(week);
|
|
108
153
|
const line = r.querySelector('.hc-line');
|
|
109
154
|
line.textContent = '';
|
|
110
|
-
const crafts =
|
|
155
|
+
const crafts = kinds.filter((c) => row.v[c[0]] > 0);
|
|
111
156
|
line.style.width = Math.max(crafts.length * 29, room * row.total / top).toFixed(1) + 'px';
|
|
112
157
|
crafts.forEach((c) => {
|
|
113
158
|
const seg = document.createElement('span');
|
|
@@ -125,29 +170,73 @@
|
|
|
125
170
|
rungs.forEach((r, id) => { if (!shown.has(id)) r.hidden = true; });
|
|
126
171
|
}
|
|
127
172
|
|
|
128
|
-
|
|
129
|
-
|
|
173
|
+
// ---- time: live at the right end; the city glides to wherever the slider is sent ----
|
|
174
|
+
function when(v) {
|
|
175
|
+
if (atLive(v)) return 'Live';
|
|
176
|
+
const w0 = model.weeks[0] && model.weeks[0].start ? Date.parse(model.weeks[0].start + 'T00:00:00Z') : NaN;
|
|
177
|
+
return isNaN(w0) ? weekName(Math.floor(v)) : new Date(w0 + v * 7 * 864e5).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' });
|
|
178
|
+
}
|
|
179
|
+
function setT(v) {
|
|
180
|
+
t = Math.max(0, Math.min(model.W - 1, v));
|
|
181
|
+
const live = atLive(t);
|
|
130
182
|
const s = host.querySelector('.hc-slider');
|
|
131
|
-
s.value = String(
|
|
132
|
-
s.setAttribute('aria-valuetext',
|
|
183
|
+
s.value = String(t);
|
|
184
|
+
s.setAttribute('aria-valuetext', live ? 'Live' : when(t));
|
|
185
|
+
const pill = host.querySelector('.hc-when');
|
|
186
|
+
pill.textContent = when(t);
|
|
187
|
+
pill.style.left = (t / Math.max(1, model.W - 1) * 100) + '%';
|
|
188
|
+
pill.classList.toggle('is-on', !live);
|
|
189
|
+
const l = host.querySelector('.hc-live');
|
|
190
|
+
l.classList.toggle('is-live', live);
|
|
191
|
+
l.querySelector('span').textContent = live ? 'Live' : 'Back to live';
|
|
133
192
|
drawScene();
|
|
134
|
-
|
|
193
|
+
const w = Math.floor(t + 1e-6);
|
|
194
|
+
if (w !== lastWeek) { lastWeek = w; drawLadder(); }
|
|
195
|
+
buttons();
|
|
135
196
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
197
|
+
function buttons() {
|
|
198
|
+
const b = (k) => host.querySelector(`.hc-p-${k}`);
|
|
199
|
+
b('play').classList.toggle('is-playing', run === 'play');
|
|
200
|
+
b('play').setAttribute('aria-label', run === 'play' ? 'Pause' : atLive(t) ? 'Play from the start' : 'Play');
|
|
201
|
+
['rw', 'play', 'ff'].forEach((k) => b(k).setAttribute('aria-pressed', String(run === k)));
|
|
202
|
+
b('rw').disabled = t <= 1e-3 && run !== 'rw';
|
|
203
|
+
b('ff').disabled = atLive(t) && run !== 'ff';
|
|
143
204
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
function halt() { if (raf) cancelAnimationFrame(raf); raf = null; run = null; target = t; if (host) buttons(); }
|
|
206
|
+
function tick() {
|
|
207
|
+
raf = requestAnimationFrame(() => {
|
|
208
|
+
let v;
|
|
209
|
+
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; }
|
|
210
|
+
else { v = t + (target - t) * 0.16; if (Math.abs(target - v) < 0.004) v = target; }
|
|
211
|
+
setT(v);
|
|
212
|
+
if (run || v !== target) tick(); else { raf = null; buttons(); }
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function glideTo(v) {
|
|
216
|
+
run = null; target = Math.max(0, Math.min(model.W - 1, v));
|
|
217
|
+
if (STILL()) { const v = target; halt(); setT(v); return; }
|
|
218
|
+
if (!raf) tick();
|
|
219
|
+
}
|
|
220
|
+
function press(k) {
|
|
221
|
+
if (run === k) { halt(); return; }
|
|
222
|
+
if (k === 'play' && atLive(t)) setT(0);
|
|
223
|
+
if (STILL()) { halt(); setT(k === 'rw' ? 0 : model.W - 1); return; }
|
|
224
|
+
run = k; buttons();
|
|
225
|
+
if (!raf) tick();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---- the doors: the museum and the stars switch the craft (the craft's own Home); the
|
|
229
|
+
// core is a plain link to the street and a building a plain link to its goal ----
|
|
230
|
+
function wireDoors() {
|
|
231
|
+
host.addEventListener('click', (e) => {
|
|
232
|
+
const d = e.target.closest('[data-craft-door]');
|
|
233
|
+
if (!d || !host.contains(d)) return;
|
|
234
|
+
const CM = window.OTBCraftMode;
|
|
235
|
+
if (!CM || typeof CM.choose !== 'function') return;
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
CM.choose(d.getAttribute('data-craft-door'));
|
|
238
|
+
window.scrollTo(0, 0);
|
|
239
|
+
});
|
|
151
240
|
}
|
|
152
241
|
|
|
153
242
|
// ---- one tooltip for the whole widget; labels are ledger data, so text only ----
|
|
@@ -169,12 +258,14 @@
|
|
|
169
258
|
}
|
|
170
259
|
|
|
171
260
|
function ticks() {
|
|
172
|
-
const
|
|
261
|
+
const tk = host.querySelector('.hc-ticks');
|
|
173
262
|
const first = model.weeks[0] && model.weeks[0].start ? new Date(model.weeks[0].start + 'T00:00:00Z') : null;
|
|
174
263
|
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
264
|
const mon = (d) => (d && !isNaN(d) ? d.toLocaleDateString('en-GB', { month: 'short', timeZone: 'UTC' }) : '');
|
|
176
|
-
|
|
177
|
-
|
|
265
|
+
const live = host.querySelector('.hc-live');
|
|
266
|
+
tk.textContent = '';
|
|
267
|
+
[mon(first), mon(mid)].forEach((s) => { const sp = document.createElement('span'); sp.textContent = s; tk.appendChild(sp); });
|
|
268
|
+
tk.appendChild(live);
|
|
178
269
|
}
|
|
179
270
|
|
|
180
271
|
async function load() {
|
|
@@ -189,8 +280,19 @@
|
|
|
189
280
|
const s = host.querySelector('.hc-slider');
|
|
190
281
|
s.max = String(model.W - 1);
|
|
191
282
|
ticks();
|
|
192
|
-
|
|
193
|
-
|
|
283
|
+
shown = true;
|
|
284
|
+
craftHome();
|
|
285
|
+
target = model.W - 1;
|
|
286
|
+
setT(model.W - 1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ---- the artist's Home (task 1005060): in Artistry the same section is the gallery,
|
|
290
|
+
// home-gallery.js, the bench in the museum the city's art district opens onto ----
|
|
291
|
+
function craftHome() {
|
|
292
|
+
const art = !!gallery && document.documentElement.dataset.craft === 'artistry';
|
|
293
|
+
host.classList.toggle('hc--gallery', art);
|
|
294
|
+
if (gallery) gallery.show(art);
|
|
295
|
+
host.hidden = !(art || shown);
|
|
194
296
|
}
|
|
195
297
|
|
|
196
298
|
function mount(hostEl) {
|
|
@@ -198,20 +300,34 @@
|
|
|
198
300
|
host.innerHTML =
|
|
199
301
|
'<h2 id="home-city-h" class="sr-only">The project this week</h2>' +
|
|
200
302
|
'<div class="hc-stage">' +
|
|
201
|
-
'<svg class="hc-svg" role="img"
|
|
303
|
+
'<svg class="hc-svg" role="img"></svg>' +
|
|
202
304
|
'<div class="hc-ladder" aria-label="This week\'s builders, ranked by what they did"></div>' +
|
|
203
305
|
'</div>' +
|
|
204
306
|
'<div class="hc-time">' +
|
|
205
|
-
'<
|
|
206
|
-
'<
|
|
207
|
-
'<
|
|
208
|
-
|
|
209
|
-
|
|
307
|
+
'<div class="hc-player" role="group" aria-label="Time">' +
|
|
308
|
+
'<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>' +
|
|
309
|
+
'<button type="button" class="hc-p hc-p-play" aria-label="Play">' +
|
|
310
|
+
'<svg class="hc-play__on" viewBox="0 0 16 16" aria-hidden="true"><path d="M4.5 3v10l8-5z" fill="currentColor"/></svg>' +
|
|
311
|
+
'<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>' +
|
|
312
|
+
'</button>' +
|
|
313
|
+
'<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>' +
|
|
314
|
+
'</div>' +
|
|
315
|
+
'<div class="hc-track"><div class="hc-when" aria-hidden="true"></div>' +
|
|
316
|
+
'<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">' +
|
|
317
|
+
'<div class="hc-ticks"></div>' +
|
|
318
|
+
'<button type="button" class="hc-live is-live"><i aria-hidden="true"></i><span>Live</span></button>' +
|
|
319
|
+
'</div>' +
|
|
210
320
|
'</div>';
|
|
211
|
-
host.querySelector('.hc-slider').addEventListener('input', (e) => {
|
|
212
|
-
host.querySelector(
|
|
321
|
+
host.querySelector('.hc-slider').addEventListener('input', (e) => { if (model) glideTo(Number(e.target.value)); });
|
|
322
|
+
['rw', 'play', 'ff'].forEach((k) => host.querySelector(`.hc-p-${k}`).addEventListener('click', () => { if (model) press(k); }));
|
|
323
|
+
host.querySelector('.hc-live').addEventListener('click', () => { if (model) glideTo(model.W - 1); });
|
|
213
324
|
wireTip();
|
|
325
|
+
wireDoors();
|
|
326
|
+
if (window.OTBHomeGallery) gallery = window.OTBHomeGallery.attach(host);
|
|
327
|
+
document.addEventListener('otb:craft', craftHome);
|
|
328
|
+
craftHome();
|
|
214
329
|
new MutationObserver(() => { if (model) drawScene(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
330
|
+
document.addEventListener('otb:craft', () => { if (model) { drawScene(); drawLadder(); } });
|
|
215
331
|
window.addEventListener('resize', () => { if (model) drawLadder(); });
|
|
216
332
|
load();
|
|
217
333
|
}
|
|
@@ -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
|
+
}
|