.jcd-music-host { position: relative; width: 100%; height: 100%; min-height: 60vh; background: #000; }
.jcd-music-host canvas { display: block; width: 100%; height: 100%; }

/*
 * ===========================================================================
 * THE MENU IS ONLY THERE WHEN YOU FACE THE HORIZON.
 *
 * John, 2026-08-09: "menu with disco logo should only appear on the main
 * horizon (with music gallery) the other side of the scene is just the same
 * road/mountain preset into the horizon but without a sun/vinyl."
 *
 * ONE NUMBER DRIVES ALL OF IT. look-around.js publishes
 * --jcd-music-view-facing once per frame -- 1 fully facing the main horizon, 0
 * fully turned away, a smoothstep between 35 and 90 degrees off-axis -- onto
 * the host AND onto documentElement, and data-djjc-facing beside it. Same shape
 * as --scene-engine-transition-progress, which scene-engine-sequence.js already
 * pushes into CSS. The menu, the logo, the player and the carousel all read
 * that one number, so they cannot desync.
 *
 * NO JS TOUCHES THIS MARKUP. The player is built by scene-engine-player.js and
 * the menu will be the engine's own render of a WordPress menu; neither is
 * mutated from the scene. All the scene does is publish a number, and an
 * Elementor update, a theme change or a class rename cannot break a stylesheet
 * rule the way it would break a querySelector.
 *
 * IN THIS FILE AND NOT IN scene-engine-runtime.css, WHERE .jcd-player'S OWN
 * RULES LIVE -- and the split is deliberate. runtime.css is enqueued on every
 * page including the homepage, and the disco scene is LOCKED. This stylesheet
 * is enqueued only by the music module ('style_handles' => ['scene-engine-music']
 * in module.php), so the homepage never even parses these rules. Co-location
 * would have been tidier; a rule the disco room cannot see is safer, and safer
 * wins here.
 *
 * ===========================================================================
 * THE ACCESSIBILITY DECISIONS, WRITTEN DOWN BECAUSE THEY ARE DECISIONS.
 *
 * A menu that disappears based on where a pointer has dragged the view is a
 * menu some people cannot reach. Four cases, four answers:
 *
 *   THE POINTER NEVER MOVES. facing rests at 1 and nothing fades. The feature
 *   can only ever remove interface as the result of a gesture somebody made,
 *   and the same gesture reverses it. Nothing is hidden by default, ever.
 *
 *   THE PROPERTY IS ABSENT -- another route, a frame before the first publish,
 *   a browser that failed to run the module, the scene torn down. Every read
 *   below carries `, 1`, so absent resolves to FULLY PRESENT. This is why the
 *   fallback is asserted in tests/music-look-contract.test.js rather than left
 *   to review: a missing one would make "no music scene" mean "no menu".
 *
 *   KEYBOARD ONLY. Turning the view is a pointer drag and nothing else -- the
 *   arrows, Home and End belong to the carousel -- so a keyboard-only visitor
 *   can never reach a state where the menu is gone. For them this feature does
 *   not exist, which is the correct outcome rather than a gap.
 *
 *   MIXED INPUT: dragged away, now tabbing. This is the case that decides the
 *   shape of the rules. Fading with opacity alone would leave the transport in
 *   the tab order and under the pointer -- focus would land on something
 *   invisible and a click at the bottom of the screen would hit a button that
 *   is not there. So at facing 0 the container goes visibility:hidden, which
 *   removes it from BOTH. Tab skips it cleanly; there is no invisible focus
 *   target to trap anyone, and dragging back brings it straight back.
 *
 * REDUCED MOTION OPTS OUT ENTIRELY. Interface that dissolves as the view is
 * dragged is motion-coupled interface. Someone who asked for less motion should
 * not have to perform a gesture to get a menu back, so under
 * prefers-reduced-motion the player and the menu simply stay. That is a real
 * departure from John's framing for those users, and it is the defensible
 * direction: an always-present menu is never worse than a sometimes-absent one.
 * The 3D half is untouched by it -- the carousel is off frustum long before it
 * is hidden, so hiding it is not motion anybody sees.
 * ===========================================================================
 */

/*
 * The documented resting value. Inline styles from look-around.js win over it,
 * so this is the state before the first publish and after the last -- and it is
 * PRESENT. Same device as [data-djjc-scene-placement="1"] declaring
 * --scene-engine-transition-progress: 0 in scene-engine-runtime.css.
 */
:root { --jcd-music-view-facing: 1; }

/*
 * THE MENU IS NOT ON THESE RULES YET, AND THAT IS THE GATE'S DOING RATHER THAN
 * AN OVERSIGHT. The engine's own menu render is decided but unbuilt --
 * docs/superpowers/handover/2026-08-07-music-transition-and-player-decisions.md
 * section 5, "the Scene Engine gets a Menu selector". A jcd-music-menu selector
 * was written here first and tests/dead-code-contract.test.js failed it by
 * name: nothing renders that class, and zero dead classes is the contract. It
 * was removed rather than excepted.
 *
 * (And the class is named here WITHOUT its leading dot on purpose --
 * scripts/dead-code.mjs scans this stylesheet with a regex and does not strip
 * comments, so a dotted mention in prose reds the gate exactly as a real rule
 * would.)
 *
 * SO WHOEVER BUILDS THE MENU ADDS IT TO EXACTLY THREE SELECTOR LISTS BELOW --
 * the opacity, the visibility, and the reduced-motion pair -- and nothing else.
 * The number is already published and already correct; the menu joins the
 * player as a second reader of it, which is the whole design.
 */
.jcd-player {
    opacity: var(--jcd-music-view-facing, 1);
}

/*
 * FULLY TURNED AWAY: out of the picture, out of the tab order, out of the hit
 * test. Driven by the attribute and not the number, because CSS cannot branch
 * on a number and these two questions have no in-between.
 *
 * The attribute is written on documentElement and on the host, so this matches
 * whichever ancestor carries it. `="false"` and not `:not([...="true"])`: a
 * page with no attribute at all must fall through to visible.
 */
[data-djjc-facing="false"] .jcd-player {
    visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
    .jcd-player {
        opacity: 1;
    }

    [data-djjc-facing="false"] .jcd-player {
        visibility: visible;
    }
}
