/* ============================================================================
   Colombini Group — Intro Virtual Tour

   LAYER STACK. The composition is built as explicit strata; every z-index in
   this file comes from the block below and nowhere else, so the order can be
   read in one place instead of reverse-engineered from scattered values.

     L0  background      GLSL full-screen quad   ─┐ all three are drawn into
     L1  dust + halo     GLSL points / quad       ├─ the single #viewer canvas
     L2  the ring        GLB, real geometry + PBR ─┘  (see viewer/main.js)
     L3  markers         .label-renderer — CSS2D DOM over the canvas
     L4  panels          #group-sidebar, #hotspot-panel
     L5  chrome          #chrome (logo, hamburger, session)
     L6  pop-up          #media-viewer — tours and videos over everything
     L7  loader          #loader

   Palette: near-black warm ground with a champagne accent, plus the cream and
   brown of the ring itself. Keep in step with the COLOR table in
   viewer/main.js, which feeds the same values to the shaders.
   ========================================================================= */
:root {
  /* ground — black and gold, per the client's direction. Named for the role,
     not the hue: these were navy until the palette changed. */
  --ink-900:   #07070a;
  --ink-800:   #12100d;
  --ink-700:   #1b1813;

  /* the ring */
  --cream:     #f0e0c8;
  --champagne: #cba468;
  --brown:     #4a3a30;

  /* text */
  --text:      #f2f2f0;
  --text-dim:  rgba(242, 242, 240, 0.58);
  --text-faint:rgba(242, 242, 240, 0.34);

  /* glass */
  --panel-bg:      rgba(14, 12, 10, 0.76);
  --panel-border:  rgba(255, 255, 255, 0.11);
  --panel-shadow:  0 24px 70px rgba(0, 0, 0, 0.55);

  /* layer order */
  --z-viewer: 0;
  --z-labels: 10;
  --z-panels: 20;
  --z-chrome: 30;
  --z-modal:  35;
  --z-loader: 40;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--ink-900);
  color: var(--text);
  font-family: "Helvetica Neue", "Segoe UI", Arial, sans-serif;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ── L0-L2 — the canvas ─────────────────────────────────────── */
#viewer {
  position: fixed;
  inset: 0;
  z-index: var(--z-viewer);
  /* Only shows through before the first frame; the shader paints the real one. */
  background: var(--ink-900);
  cursor: grab;
  /* A finger dragging over the ring is ours, not a page pan or a pinch. Without
     this the browser claimed the gesture and cancelled it, which is why the
     ring could not be dragged on an iPad (see "Touch" in viewer/main.js). */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
#viewer.dragging { cursor: grabbing; }
#viewer canvas { display: block; }

/* ── L3 — hotspot markers ───────────────────────────────────── */
.label-renderer {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-labels);
  pointer-events: none;   /* individual markers opt back in below */
}

/* CSS2DRenderer writes its own inline transform on .hs-marker every frame to
   place it on the projected 3D point, and that beats anything set here. So the
   wrapper stays zero-sized and its children are positioned absolutely against
   it: the orb lands exactly on the ring surface, the wordmark floats beside. */
.hs-marker {
  position: relative;
  width: 0;
  height: 0;
  transition: opacity 0.35s var(--ease);
}

.hs-orb {
  position: absolute;
  top: -15px;
  left: -15px;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  pointer-events: auto;
  background: none;
  /* A light, not a bead. The gradient is opaque only at the very centre and
     falls away to nothing well before the edge, so the orb reads as a glow
     sitting on the ring rather than a solid sphere stuck to it. The tight
     inner stops keep a defined hot core; without them the whole thing turns
     into a soft blur with no focus. */
  background-image: radial-gradient(circle at 50% 50%,
    rgba(255, 255, 255, 1)    0%,
    rgba(255, 255, 255, 0.98) 12%,
    rgba(255, 253, 246, 0.72) 26%,
    rgba(255, 248, 232, 0.34) 42%,
    rgba(246, 226, 188, 0.14) 60%,
    rgba(203, 164, 104, 0.05) 78%,
    rgba(203, 164, 104, 0)    100%);
  /* The small outer halo the client liked, kept as a separate soft ring. */
  filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.55))
          drop-shadow(0 0 16px rgba(203, 164, 104, 0.35));
  /* Slow enough to match the camera's glide between points: the orb swelling
     faster than the shot moves made the two read as separate events. */
  transition: transform 0.42s var(--ease), filter 0.42s var(--ease);
}

/* Halo ring that breathes, so the markers read as interactive without needing
   a hover to discover them. */
.hs-orb::after {
  content: "";
  position: absolute;
  inset: -13px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.30);
  opacity: 0;
  animation: hs-pulse 3.4s ease-out infinite;
}
@keyframes hs-pulse {
  0%   { transform: scale(0.65); opacity: 0; }
  35%  { opacity: 0.75; }
  100% { transform: scale(1.35); opacity: 0; }
}

.hs-orb:hover,
.hs-orb:focus-visible {
  transform: scale(1.18);
  filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.85))
          drop-shadow(0 0 26px rgba(203, 164, 104, 0.5));
  outline: none;
}

/* ── Brand wordmarks ────────────────────────────────────────── */
/* The label beside each orb — see buildWordmark() in viewer/hotspots.js. The
   official logos (.hs-logo, below) when a brand has one; the typographic
   stand-in otherwise. */
.hs-wordmark {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  line-height: 1.1;
  cursor: pointer;
  pointer-events: auto;
  user-select: none;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.7);
  transition: opacity 0.48s var(--ease);
}

/* Which side the lettering sits on is decided per frame from the marker's
   projected position (updateMarkerFacing), so as the ring turns the labels
   always stay on the outside instead of crossing over the mesh. */
.hs-marker.hs-left  .hs-wordmark { right: 26px; text-align: right; }
.hs-marker.hs-right .hs-wordmark { left: 26px;  text-align: left; }

/* Stacked above the orb instead of beside it. Applied by main.js when a label
   would otherwise run past the edge of the viewport — at the reference's type
   size the longest one ("COLOMBINI GROUP CONTRACT") does not fit alongside on
   a narrow window, and the client asked for it to move rather than shrink. */
.hs-marker.hs-stacked .hs-wordmark {
  left: 50%;
  right: auto;
  top: auto;
  bottom: 22px;
  /* --wm-nudge keeps it on screen when the orb is near an edge (hotspots.js). */
  transform: translateX(calc(-50% + var(--wm-nudge, 0px)));
  text-align: center;
}

/* Sized against reference/colomb1.png, where the wordmarks are roughly a
   sixth of the ring's width — much larger than they were. They are the brand
   names, so they carry the composition. */
.hs-wm-line { display: block; font-size: clamp(15px, 1.65vw, 26px); }
.hs-wm-sub {
  display: block;
  font-size: clamp(8px, 0.72vw, 12px);
  letter-spacing: 0.30em;
  color: var(--text-faint);
  margin-top: 3px;
}

.hs-wm-caps .hs-wm-line { text-transform: uppercase; font-weight: 300; letter-spacing: 0.045em; }
.hs-wm-caps .hs-wm-lead { color: var(--text); }
.hs-wm-caps .hs-wm-tail { color: rgba(242, 242, 240, 0.62); }

.hs-wm-title .hs-wm-line { font-weight: 700; letter-spacing: -0.005em; }
.hs-wm-lower .hs-wm-line { font-weight: 700; text-transform: lowercase; letter-spacing: -0.015em; }

/* Official logos (assets/logos/brands/). Sized by height so they sit at the
   same optical size as the lettering they replace; --logo-k comes from `scale`
   in hotspots.json and evens out thin one-liners against bold or two-line
   lockups. The drop-shadow does the job text-shadow did for the type. */
.hs-wm-logo { line-height: 0; }
.hs-logo {
  --logo-h: clamp(18px, 1.9vw, 30px);
  display: block;
  height: calc(var(--logo-h) * var(--logo-k, 1));
  width: auto;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.7));
  transition: height 0.48s var(--ease);
}
.hs-marker.hs-left    .hs-logo { margin-left: auto; }
.hs-marker.hs-stacked .hs-logo { margin: 0 auto; }
.hs-marker.hs-active  .hs-logo { --logo-h: clamp(20px, 2.1vw, 33px); }

/* Marker on the far side of the ring. CSS2D elements are DOM and get no depth
   testing, so without this they'd punch straight through the mesh. */
.hs-marker.hs-behind { opacity: 0.28; }
.hs-marker.hs-behind .hs-orb { filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.25)); }

/* Another brand is open. Hidden outright rather than dimmed: the focus view is
   a deep crop of the ring, so a faint marker from a brand that is half
   off-frame reads as a stray dot rather than as context. Matches the fourth
   frame of reference/HUB CG.png, which shows one brand alone. */
.hs-marker.hs-dimmed { opacity: 0; pointer-events: none; }
.hs-marker.hs-active .hs-orb { transform: scale(1.3); }
.hs-marker.hs-active .hs-wm-line { font-size: clamp(17px, 1.85vw, 29px); }

/* ── L4 — glass panels ──────────────────────────────────────── */
/* Solo la colocacion. El cristal lo pone .hs-card, que es la capa que se
   inclina bajo el puntero: si los dos vivieran en el mismo elemento estarian
   peleandose por el mismo `transform`. */
.panel {
  position: fixed;
  z-index: var(--z-panels);
}

/* ── Group hub menu ─────────────────────────────────────────── */
/* Bottom-left, as loose typography on the background rather than in a box —
   in reference/colomb1.png it is set directly on the navy, and boxing it made
   the composition feel much heavier. The group mark and the gold "Colombini
   Group" heading that used to top it are gone (client, 29 September 2026):
   the group's identity is the logo in the header now. On a phone the same
   list becomes the drop-down behind the hamburger — see the phone layout at
   the end of this file. */
#group-sidebar {
  position: fixed;
  z-index: var(--z-panels);
  left: 44px;
  bottom: 40px;
  max-width: 250px;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.7s var(--ease) 0.25s, transform 0.7s var(--ease) 0.25s;
}
body.is-ready #group-sidebar { opacity: 1; transform: none; }
/* Out of the way while a brand is open — at that zoom the ring fills the
   frame and the group menu would sit on top of it. */
body.is-focused #group-sidebar { opacity: 0; transform: translateY(6px); pointer-events: none; }

.gs-list { list-style: none; margin: 0; padding: 0; }
.gs-item {
  padding: 9px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  cursor: default;
  opacity: 0;
  transform: translateY(6px);
  animation: gs-in 0.55s var(--ease) forwards;
  animation-delay: calc(0.4s + var(--i) * 0.07s);
}
@keyframes gs-in { to { opacity: 1; transform: none; } }

.gs-item-label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.gs-item-note {
  display: block;
  font-size: 10.5px;
  color: var(--text-faint);
  margin-top: 3px;
  line-height: 1.4;
}

/* One doc, one link — a voice like "Chi siamo" carries several. Same
   grow-from-the-left hover as .hs-item in the brand panel, scaled down for
   the sidebar's smaller type. A document still "in sviluppo" renders as
   .gs-link-pending: same size and position, but plain text — no fake
   affordance for a click that would do nothing.

   Size in longhands, never `font: 9.5px/1.4 inherit`. That shorthand is
   invalid — `inherit` cannot be combined with anything else — so the browser
   dropped the whole line: the buttons fell back to the platform's own button
   font and the pending ones, being <span>s, to the page's 16px. That is why
   "VT FABBRICA R1" and "VIDEO MANIFESTO CG" came out bigger than their
   neighbours (client correction, 29 September 2026). Capitals come from here
   too, so a label typed in any case reads like the rest. */
.gs-links { list-style: none; margin: 5px 0 0; padding: 0; }
.gs-link {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 13px;              /* what the buttons were rendering at anyway */
  line-height: 1.4;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 4px 0;
  position: relative;
  transition: color 0.2s var(--ease), padding-left 0.2s var(--ease);
}
.gs-link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 0;
  height: 1px;
  background: var(--champagne);
  transition: width 0.2s var(--ease);
}
.gs-link:hover,
.gs-link:focus-visible {
  color: var(--text);
  padding-left: 12px;
  outline: none;
}
.gs-link:hover::before,
.gs-link:focus-visible::before { width: 8px; }
.gs-link-pending { color: var(--text-faint); cursor: default; }

/* ── Brand sub-menu panel ───────────────────────────────────── */
/* The <section> owns where the card is and how it arrives. Everything visible
   is on .hs-card inside it. */
#hotspot-panel {
  top: 50%;
  right: 48px;
  width: 340px;
  max-height: 74vh;
  /* Flex, so the card is an item that can be shrunk to fit. With the section
     left at auto height the card grows with its content and a fourteen-item
     brand runs off the screen instead of scrolling its list. Column, so that
     `flex: 1 1 auto` on the card governs its HEIGHT: in a row container it
     would govern the width and the card would keep its content height, which
     is exactly how a fourteen-item brand overflowed the sheet on a phone. */
  display: flex;
  flex-direction: column;
  transform: translateY(-50%) translateX(var(--hs-slide, 28px));
  transition:
    opacity 0.28s var(--ease),
    transform 0.36s var(--ease),
    right 0.44s var(--ease),
    width 0.44s var(--ease),
    height 0.44s var(--ease);
}
#hotspot-panel.hs-hidden  { opacity: 0; pointer-events: none; }
#hotspot-panel.hs-visible { opacity: 1; pointer-events: auto; --hs-slide: 0px; }

/* ── The card itself ───────────────────────────────────────────
   The tilt, the scale and the specular are all driven by custom properties
   that card-fx.js writes from the pointer. Keeping them as properties rather
   than as a transform string built in JS means the easing, the rest state and
   the reduced-motion override all stay here in CSS where they belong. */
.hs-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  flex: 1 1 auto;
  min-height: 0;
  max-height: 100%;
  border-radius: 8px;
  padding: 30px 28px;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  backdrop-filter: blur(16px) saturate(120%);
  -webkit-backdrop-filter: blur(16px) saturate(120%);
  box-shadow: var(--panel-shadow);
  transform-origin: var(--hs-ox, 50%) var(--hs-oy, 50%);
  transform:
    perspective(1200px)
    rotateX(var(--hs-rx, 0deg))
    rotateY(var(--hs-ry, 0deg))
    scale(var(--hs-scale, 1));
  /* Short: this has to feel like the card is under your hand, not like it is
     catching up with it. */
  transition: transform 0.16s var(--ease), border-radius 0.44s var(--ease),
              padding 0.44s var(--ease);
  will-change: transform;
}

/* A gradient hairline along the leading edge, so the glass has a lit rim
   instead of a flat 1px box. */
.hs-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 1px;
  z-index: 2;
  background: linear-gradient(to bottom,
    transparent, rgba(203, 164, 104, 0.55) 35%, rgba(203, 164, 104, 0.55) 65%, transparent);
}
/* The specular. A pool of light that follows the cursor across the glass — the
   thing that makes a flat panel read as a surface being tilted rather than a
   rectangle being scaled. */
.hs-card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(460px circle at var(--hs-mx, 50%) var(--hs-my, 0%),
    rgba(255, 255, 255, 0.06), transparent 60%);
  opacity: var(--hs-lit, 0);
  transition: opacity 0.35s var(--ease);
}

/* The two buttons have a row of their own above the heading. They used to
   float over its top-right corner, which was fine for two bare icons; now the
   expand button says what it does and needs the room. Pulled up into the
   card's padding, towards the corner, so the card grows as little as it can. */
.hs-tools {
  position: relative;
  z-index: 3;
  flex: none;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  margin: -16px -14px 8px 0;
}
.hs-tools button {
  background: none;
  border: none;
  color: var(--text-faint);
  cursor: pointer;
  padding: 5px 8px;
  border-radius: 3px;
  line-height: 1;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
              background-color 0.2s var(--ease);
}
.hs-tools button:hover { color: var(--text); }
.hs-tools button:focus-visible { outline: 1px solid var(--champagne); color: var(--text); }
#hs-close { font-size: 22px; }

/* "Ingrandisci" in words beside the brackets, framed like the header's Esci.
   The brackets alone were two faint marks nobody read as a button: the client
   only found the large view by tapping them by accident (29 September 2026).
   The label turns to "Riduci" once expanded (setPanelExpanded). */
#hs-expand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 11px 6px 9px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
#hs-expand:hover {
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.45);
  background-color: rgba(255, 255, 255, 0.04);
}

/* Corner brackets. They point outwards to expand and fold inwards to come
   back, which needs no icon file and animates between the two states. */
#hs-expand .hs-ico {
  display: block;
  flex: none;
  position: relative;
  width: 11px;
  height: 11px;
}
#hs-expand .hs-ico::before,
#hs-expand .hs-ico::after {
  content: "";
  position: absolute;
  width: 5px;
  height: 5px;
  border: 1.5px solid currentColor;
  transition: inset 0.3s var(--ease);
}
#hs-expand .hs-ico::before { top: 0; left: 0; border-width: 1.5px 0 0 1.5px; }
#hs-expand .hs-ico::after { bottom: 0; right: 0; border-width: 0 1.5px 1.5px 0; }
body.is-expanded #hs-expand .hs-ico::before { top: 3px; left: 3px; }
body.is-expanded #hs-expand .hs-ico::after { bottom: 3px; right: 3px; }

/* ── Contents ──────────────────────────────────────────────── */
.hs-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;          /* so the list, not the card, is what scrolls */
}

#hs-title {
  font-size: 19px;
  font-weight: 400;
  margin: 0 0 4px;
  letter-spacing: 0.01em;
  transition: font-size 0.44s var(--ease);
}
#hs-title.hs-title-logo { line-height: 0; }
.hs-title-img {
  display: block;
  height: calc(22px * var(--logo-k, 1));
  max-width: 100%;
  width: auto;
  object-fit: contain;
  object-position: left center;
  transition: height 0.44s var(--ease);
}
body.is-expanded .hs-title-img { height: calc(30px * var(--logo-k, 1)); }
#hs-title::after {
  content: "";
  display: block;
  width: 34px;
  height: 1px;
  background: var(--champagne);
  margin: 14px 0 4px;
}

#hs-list {
  list-style: none;
  margin: 0;
  padding: 0;
  min-height: 0;
  overflow-y: auto;
  /* The default bar is a grey slab against the glass. Thin and champagne, or
     nothing at all where the platform allows it. */
  scrollbar-width: thin;
  scrollbar-color: rgba(203, 164, 104, 0.38) transparent;
}
#hs-list::-webkit-scrollbar { width: 4px; }
#hs-list::-webkit-scrollbar-track { background: transparent; }
#hs-list::-webkit-scrollbar-thumb {
  background: rgba(203, 164, 104, 0.38);
  border-radius: 2px;
}
#hs-list::-webkit-scrollbar-thumb:hover { background: rgba(203, 164, 104, 0.6); }
#hs-list li {
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  /* Staggered entrance; --i is capped in openHotspotPanel so a 14-item brand
     doesn't take two seconds to finish arriving. */
  opacity: 0;
  transform: translateX(10px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
  transition-delay: calc(var(--i) * 0.026s + 0.10s);
}
#hs-list li:first-child { border-top: none; }
.hs-visible #hs-list li { opacity: 1; transform: none; }

/* Real buttons rather than plain <li> text: they lead somewhere, and it is what
   makes them respond to the pointer and reachable from the keyboard. The
   labels are the client's own document names, in capitals as in their sheet.
   Size in longhands for the same reason as .gs-link: `font: 13px/1.45 inherit`
   was invalid and left every item in the platform's button font, whose size an
   iPad does not share with a desktop. */
.hs-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.45;
  letter-spacing: 0.02em;
  padding: 10px 0;
  position: relative;
  transition: color 0.22s var(--ease), padding-left 0.22s var(--ease);
}
.hs-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 0;
  height: 1px;
  background: var(--champagne);
  transition: width 0.22s var(--ease);
}
.hs-item:hover,
.hs-item:focus-visible {
  color: var(--text);
  padding-left: 18px;
  outline: none;
}
.hs-item:hover::before,
.hs-item:focus-visible::before { width: 12px; }

/* ── The video slot ────────────────────────────────────────────
   Laid out now and empty until the files arrive, so dropping a <video> in
   later changes nothing about the composition. */
.hs-media { display: none; }
body.is-expanded .hs-media {
  display: block;
  position: relative;
  align-self: start;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: linear-gradient(152deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.006));
}
.hs-media-mark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 52px;
  height: 52px;
  margin: -32px 0 0 -26px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
}
.hs-media-mark::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -7px 0 0 -4px;
  border: 7px solid transparent;
  border-left: 11px solid rgba(255, 255, 255, 0.34);
}
.hs-media-note {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  margin-top: 30px;
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ── Expanded ──────────────────────────────────────────────────
   About 70% of the window, centred. Anchored from the right in both states so
   the width and the position can transition together instead of the card
   jumping when the positioning scheme changes. */
body.is-expanded #hotspot-panel {
  right: calc(50% - min(35vw, 550px));
  width: min(70vw, 1100px);
  height: min(74vh, 780px);
  max-height: none;
}
body.is-expanded .hs-card { border-radius: 12px; padding: 38px 42px; }
body.is-expanded .hs-body {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: 40px;
  row-gap: 22px;
}
body.is-expanded #hs-title {
  grid-column: 1 / -1;
  font-size: 27px;
}
body.is-expanded #hs-list { grid-column: 2; grid-row: 2; }
body.is-expanded .hs-item { font-size: 13.5px; padding: 11px 0; }

/* ── L5 — chrome ────────────────────────────────────────────── */
.chrome {
  position: fixed;
  z-index: var(--z-chrome);
  top: 34px;
  left: 44px;
  right: 44px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  pointer-events: none;   /* the ring stays draggable underneath */
}
.chrome button { pointer-events: auto; }

.chrome-start {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

/* The group's own mark where its name used to be written (client, 29 September
   2026). The first pass put the silver lettering here; it is the half-ring
   (assets/logos/colombini-group.svg) that was meant, so that is what sits in the
   corner. The lettering stays on the login, where it is the title. It stands
   alone: the "/ Intro Virtual Tour" that used to follow it was taken out on
   30 September, at the client's word. */
.eyebrow {
  display: flex;
  align-items: center;
  min-width: 0;
}
.eyebrow-logo {
  display: block;
  flex: none;
  height: clamp(18px, 1.4vw, 26px);
  width: auto;
  user-select: none;
}

/* The hamburger. Phones only (see the phone layout at the end of the file);
   three bars that fold into a cross while the menu is open. */
.menu-toggle {
  display: none;
  flex: none;
  position: relative;
  width: 36px;
  height: 36px;
  margin: -8px 0 -8px -8px;     /* a big target, without pushing the logo away */
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.menu-toggle:focus-visible { outline: 1px solid var(--champagne); border-radius: 3px; }
.menu-toggle-bars,
.menu-toggle-bars::before,
.menu-toggle-bars::after {
  position: absolute;
  left: 9px;
  width: 18px;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.menu-toggle-bars { top: 17px; }
.menu-toggle-bars::before,
.menu-toggle-bars::after { content: ""; left: 0; }
.menu-toggle-bars::before { transform: translateY(-6px); }
.menu-toggle-bars::after { transform: translateY(6px); }
body.menu-open .menu-toggle-bars { background: transparent; }
body.menu-open .menu-toggle-bars::before { transform: rotate(45deg); }
body.menu-open .menu-toggle-bars::after { transform: rotate(-45deg); }

.session {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.session-logout {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 3px;
  color: var(--text-dim);
  font: inherit;
  letter-spacing: 0.12em;
  padding: 6px 13px;
  cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.session-logout:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.4); }
.session-logout:focus-visible { outline: 1px solid var(--champagne); }

/* ── L6 — the pop-up ────────────────────────────────────────── */
/* Tours and videos open here, over the ring, in the same tab (client,
   29 September 2026) — see viewer/media-viewer.js for which items do. A dark
   veil over the ring, and a window as large as the screen allows: a virtual
   tour wants every pixel it can get. The ring stops drawing underneath. */
.mv {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: clamp(12px, 3vw, 44px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), visibility 0s linear 0.3s;
}
.mv.mv-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.3s var(--ease), visibility 0s;
}
.mv-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(5, 5, 7, 0.8);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.mv-window {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(100%, 1360px);
  height: min(100%, 880px);
  overflow: hidden;
  border-radius: 12px;
  background: rgba(14, 12, 10, 0.94);
  border: 1px solid var(--panel-border);
  box-shadow: var(--panel-shadow);
  transform: translateY(14px) scale(0.985);
  transition: transform 0.38s var(--ease);
}
.mv.mv-open .mv-window { transform: none; }

.mv-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 58px;
  padding: 10px 12px 10px 24px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.mv-brand { flex: none; line-height: 0; }
.mv-brand:empty { display: none; }
/* Same --logo-k as on the ring, so COLOMBINI reads at one size here too. */
.mv-brand img {
  display: block;
  height: calc(16px * var(--logo-k, 1));
  max-width: 190px;
  width: auto;
  object-fit: contain;
  object-position: left center;
}
.mv-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mv-brand:not(:empty) + .mv-title {
  padding-left: 16px;
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.mv-external {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 3px;
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.mv-external:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.45); }
.mv-external:focus-visible { outline: 1px solid var(--champagne); }
/* An arrow leaving a box, drawn in CSS like the expand brackets: the box is
   open at its top-right corner, and the arrow comes out through the gap. */
.mv-external-ico {
  flex: none;
  position: relative;
  width: 11px;
  height: 11px;
  border-left: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
}
.mv-external-ico::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 1px;
  width: 11px;
  height: 1.5px;
  background: currentColor;
  transform: rotate(-45deg);
  transform-origin: left center;
}
.mv-external-ico::after {
  content: "";
  position: absolute;
  top: -3px;
  right: -3px;
  width: 6px;
  height: 6px;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
}
.mv-close {
  flex: none;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 26px;
  line-height: 1;
  padding: 4px 10px;
  border-radius: 3px;
  cursor: pointer;
  transition: color 0.2s var(--ease);
}
.mv-close:hover { color: var(--text); }
.mv-close:focus-visible { outline: 1px solid var(--champagne); color: var(--text); }

.mv-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: #000;
}
.mv-stage iframe,
.mv-stage video,
.mv-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: #000;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.mv-stage.mv-loaded iframe,
.mv-stage.mv-loaded video,
.mv-stage.mv-loaded img { opacity: 1; }
.mv-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -17px 0 0 -17px;
}
.mv-stage.mv-loaded .mv-spinner,
.mv-stage.mv-failed .mv-spinner { display: none; }
.mv-error {
  display: none;
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  margin: 0;
  padding: 0 24px;
  transform: translateY(-50%);
  text-align: center;
  font-size: 13px;
  color: var(--text-dim);
}
.mv-stage.mv-failed .mv-error { display: block; }

/* Corporate video: the same warm glass as the expanded brand cards. */
.mv-versions[hidden], .mv-video-tools[hidden] { display: none; }
.mv-video-card .mv-backdrop { background: rgba(5, 5, 7, 0.44); backdrop-filter: blur(3px); }
.mv-video-card .mv-window {
  width: min(84vw, 1100px);
  height: auto;
  max-height: 100%;
  padding: clamp(20px, 3vw, 42px);
  background: radial-gradient(ellipse at 35% 70%, rgba(119, 87, 48, 0.14), transparent 65%), var(--panel-bg);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
}
.mv-video-card .mv-head { padding: 0 0 22px; border: 0; }
.mv-video-card .mv-title { font-size: clamp(22px, 3vw, 34px); letter-spacing: -0.035em; text-transform: none; color: var(--text); }
.mv-video-card .mv-title::after { content: ''; display: block; width: 32px; height: 1px; margin-top: 14px; background: var(--champagne); }
.mv-video-card .mv-external { display: none; }
.mv-video-card .mv-stage { flex: 0 1 auto; width: 100%; aspect-ratio: 16 / 9; min-height: 100px; border: 1px solid var(--panel-border); border-radius: 4px; overflow: hidden; }
.mv-versions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; margin-bottom: 12px; }
.mv-version, .mv-fullscreen {
  font: inherit; font-size: 11px; letter-spacing: 0.06em; color: var(--text-dim);
  background: transparent; border: 1px solid var(--panel-border); border-radius: 3px;
  padding: 9px 12px; cursor: pointer; min-height: 38px;
}
.mv-version:hover, .mv-fullscreen:hover { color: var(--text); border-color: var(--champagne); }
.mv-version[aria-pressed="true"] { color: var(--champagne); border-color: var(--champagne); background: rgba(203, 164, 104, 0.08); }
.mv-version:focus-visible, .mv-fullscreen:focus-visible { outline: 2px solid var(--champagne); outline-offset: 3px; }
.mv-video-tools { display: flex; justify-content: flex-end; align-items: center; gap: 12px; padding-top: 12px; }
.mv-video-status { flex: 1; font-size: 11px; color: var(--text-dim); }
.mv-fullscreen { flex: none; }
.mv-stage:fullscreen { width: 100%; height: 100%; max-height: none; border: 0; border-radius: 0; }
@media (max-width: 720px) {
  .mv-video-card .mv-window { width: 100%; height: auto; border: 1px solid var(--panel-border); border-radius: 12px; padding: 20px 16px; }
  .mv-video-card .mv-head { gap: 8px; }
  .mv-versions { justify-content: flex-start; }
}
@media (max-height: 520px) and (orientation: landscape) {
  .mv-video-card .mv-window { width: min(100%, 800px); padding: 12px 20px; }
  .mv-video-card .mv-head { padding-bottom: 8px; min-height: 32px; }
  .mv-video-card .mv-title { font-size: 20px; }
  .mv-video-card .mv-title::after { display: none; }
  .mv-video-card .mv-stage { max-height: calc(100dvh - 175px); }
  .mv-versions { margin-bottom: 8px; }
  .mv-video-tools { padding-top: 6px; }
}

/* ── L7 — loader ────────────────────────────────────────────── */
#loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-loader);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  background: var(--ink-900);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-size: 10px;
  color: var(--text-faint);
  transition: opacity 0.5s var(--ease);
}
#loader.fade-out { opacity: 0; }
#loader.is-error { color: #e2716a; letter-spacing: 0.04em; text-transform: none; font-size: 13px; }
#loader.is-error .loader-ring { display: none; }

.loader-ring {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-top-color: var(--champagne);
  animation: loader-spin 1.1s linear infinite;
}
@keyframes loader-spin { to { transform: rotate(360deg); } }

/* ── Login page ─────────────────────────────────────── */
/* The card sits over the same layered composition as the hub — background
   shader and dust, mounted by viewer/login-scene.js. It used to be a flat CSS
   gradient and nothing else. */
.login-page {
  overflow: auto;
  /* Kept as the fallback ground: if WebGL or the shaders are unavailable the
     scene never mounts and this is all there is. */
  background:
    radial-gradient(circle at 50% 32%, var(--ink-700) 0%, var(--ink-900) 68%);
}
.login-page #viewer {
  /* Decoration only. The canvas must not swallow clicks meant for the form,
     and the pointer listeners live on window, so they still hear everything. */
  pointer-events: none;
  cursor: default;
}
.login-shell {
  position: relative;          /* lifts the card above #viewer, which is z 0 */
  z-index: var(--z-panels);
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 32px 20px;
}

/* ── The sheen ─────────────────────────────────────
   A narrow band of light crossing the lettering diagonally, from the top right
   down to the bottom left, once every few seconds. Taken from the reference
   frames the client supplied (Estilo plateado para el titulo del login.jpeg,
   and the arrows drawn over Entrada de luz para el efecto plateado.jpeg): the
   metal reads dark until the band passes, and the band blows out to near white
   with a bloom around it.

   One clock drives three things — the letters, a wash over the card, and the
   border. They share a duration and a start, so they stay in step with no JS
   at all. Keep --sheen-cycle in one place or they drift apart. */
.login-card,
.login-card::before { --sheen-cycle: 4.2s; }

.login-card {
  position: relative;
  /* So the title can be sized against the CARD instead of the viewport. Between
     640px and about 900px the card is already at its 600px cap while a vw-based
     size is still shrinking, and the heading ends up floating in the middle of
     a card it should be filling. Browsers without container queries drop the
     cqw rule below and fall back to the clamp, which is close enough. */
  container-type: inline-size;
  width: 100%;
  /* 360px originally, then 600px (the brief asked for 60-80% larger), then a
     flat 500px. Flat was the problem: 500px is a quarter of an FHD screen and
     well over a third of a 1280-wide laptop, so the card looked much bigger
     there. Now it keeps roughly the same share of the window as it has at
     1920 (26vw), with a floor so the fields stay usable and the 500px cap so
     it never grows past what the client approved. */
  max-width: clamp(400px, 29vw, 500px);
  background: var(--panel-bg);
  /* The visible edge is drawn by ::after, so it can open a gap where the
     emblem crosses it. Width 0 here, but the colour still animates and
     ::after inherits it, which keeps the sheen on the edge in step. */
  border: 0 solid var(--panel-border);
  border-radius: 10px;
  box-shadow: var(--panel-shadow);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  /* The top is taller than it was: the mark and its rule used to sit in this
     space, and now the lower half of the emblem does. */
  padding: clamp(46px, 5.6vw, 62px) clamp(24px, 3.8vw, 42px) clamp(26px, 3.4vw, 38px);
  animation: sheen-card-edge var(--sheen-cycle) linear infinite;
}
/* The wash. Much weaker and much wider than the band on the letters: the point
   is that the whole card is briefly lit from the same source, not that it
   flashes. */
.login-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 28%,
    rgba(255, 255, 255, 0.045) 46%,
    rgba(255, 255, 255, 0.085) 50%,
    rgba(255, 255, 255, 0.045) 54%,
    transparent 72%);
  background-size: 260% 100%;
  background-repeat: no-repeat;
  animation: sheen-sweep var(--sheen-cycle) linear infinite;
}
.login-card > * { position: relative; }

/* The edge, with a break in the top where the emblem sits. That break is what
   puts the mark on a layer of its own: the line runs up to it and stops, as if
   passing behind it — no shadow or glow needed. */
.login-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid;
  border-color: inherit;
  pointer-events: none;
  --gap: calc(var(--emblem-w) / 2 + 14px);
  -webkit-mask: radial-gradient(var(--gap) 18px at 50% 0, transparent 98%, #000 100%);
  mask: radial-gradient(var(--gap) 18px at 50% 0, transparent 98%, #000 100%);
}

/* ── The emblem ─────────────────────────────────────
   The group mark, centred on the top edge of the card and half out of it, so it
   reads as sitting on a layer above the glass rather than printed on it. It is
   the card's own child, positioned against it, which keeps it attached through
   every breakpoint without any JS. No shadow and no halo (removed at the
   client's request, 16 Sep 2026): the layering comes from the gap it opens in
   the card's edge, see .login-card::after. */
/* Sized against the CARD (cqw), not the window: tied to vw it kept its size
   while the card shrank on a laptop and came out oversized on top of it. */
.login-card { --emblem-w: clamp(84px, 28cqw, 142px); }
.login-card > .login-emblem {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 2;
  transform: translate(-50%, -82%);
  line-height: 0;
  pointer-events: none;
}
.login-card .login-emblem img {
  display: block;
  width: var(--emblem-w);
  height: auto;
}

/* The title is the client's chrome artwork (assets/logos/), not type any
   more: it carries its own metal, bevel and highlights, so the CSS sheen that
   used to sweep the lettering — and the gradient clipped to the glyphs — are
   gone. The card's own wash and edge sheen stay. The alt text is the heading
   for anything that does not load or render the image. */
.login-title {
  margin: 0;
  line-height: 0;
  text-align: center;
}
.login-title img {
  display: block;
  width: 84%;
  max-width: 400px;
  height: auto;
  margin: 0 auto;
}

@keyframes sheen-sweep {
  0%, 64%  { background-position: -10% 0; }
  100%     { background-position: 110% 0; }
}
@keyframes sheen-card-edge {
  0%, 64%, 100% {
    border-color: var(--panel-border);
    box-shadow: var(--panel-shadow);
  }
  82% {
    border-color: rgba(255, 255, 255, 0.30);
    box-shadow: var(--panel-shadow), 0 0 48px rgba(230, 236, 245, 0.11);
  }
}

/* The card used to carry a subtitle line under the heading. Taken out on
   14 September 2026 — the brand name says it, and the line was only diluting
   it. The space it left goes to the first field instead. */
.login-field { display: block; margin-bottom: clamp(14px, 1.8vw, 20px); }
.login-field:first-of-type { margin-top: clamp(24px, 3.2vw, 36px); }
.login-field span {
  display: block;
  font-size: clamp(10px, 0.85vw, 11.5px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 7px;
}
.login-field input {
  width: 100%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--panel-border);
  border-radius: 3px;
  color: var(--text);
  font: clamp(14px, 1.15vw, 16px)/1.4 inherit;
  padding: clamp(11px, 1.15vw, 14px) clamp(12px, 1.2vw, 16px);
}
.login-field input:focus-visible {
  outline: none;
  border-color: var(--champagne);
  background: rgba(255, 255, 255, 0.08);
}

.login-error { color: #e2716a; font-size: 12.5px; margin: 0 0 14px; }

.login-submit {
  width: 100%;
  background: var(--champagne);
  border: 1px solid var(--champagne);
  border-radius: 3px;
  color: #1a1206;
  font: 600 clamp(13px, 1.05vw, 14.5px)/1 inherit;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: clamp(13px, 1.35vw, 17px);
  cursor: pointer;
  transition: background 0.2s var(--ease), opacity 0.2s var(--ease);
}
.login-submit:hover:not(:disabled) { background: #d9b57e; }
.login-submit:disabled { opacity: 0.55; cursor: progress; }

.login-help {
  font-size: clamp(11px, 0.95vw, 12.5px);
  color: var(--text-faint);
  text-align: center;
  margin: clamp(18px, 2.2vw, 26px) 0 0;
  line-height: 1.5;
}

/* Shallow windows. The emblem stands above the card, so the card plus the
   emblem is what has to fit, and at 600px tall the top of the mark was being
   cut off by the edge of the window. Smaller mark, more room above the card. */
@media (max-height: 700px) {
  .login-shell { padding-top: 96px; }
  .login-card { --emblem-w: clamp(80px, 22cqw, 112px); }
}

/* Very short windows — a phone held sideways, or a desktop window squashed
   down. The card plus the emblem above it is taller than the viewport there,
   and the page cannot clip it: the login is the only way in. So the emblem
   shrinks, the shell stops forcing a full-height centre, and the page is
   allowed to scroll. (`overflow: hidden` on html/body is there for the hub,
   where the canvas fills the window; :has() scopes the release to the login,
   and the class on body covers browsers without it.) */
@media (max-height: 560px) {
  html:has(body.login-page),
  body.login-page { overflow-y: auto; }
  .login-shell { min-height: auto; padding: 84px 20px 28px; }
  .login-card { --emblem-w: 78px; padding-top: 32px; }
  .login-help { margin-top: 14px; }
}

/* Portrait tablets. Width is what is scarce there and height is plentiful, so
   sizing the card off the window width alone (29vw) left it looking lost in the
   middle of an iPad or a Surface held upright. A share of the width instead,
   still capped at the approved 500px. Phones are handled by the 640px
   breakpoint below, which gives the card the full width. */
@media (orientation: portrait) and (min-width: 641px) {
  .login-card { max-width: min(500px, 64vw); }
}

/* The login had no breakpoints at all before, because at 360px wide it never
   needed any. At 600 it does. */
@media (max-width: 640px) {
  .login-shell { padding: 24px 16px; }
  .login-card { border-radius: 10px; padding: 30px 24px 26px; max-width: 100%; }
  .login-title img { width: 90%; }
  .login-shell { padding-top: 48px; }
  .login-card { padding-top: 44px; }
  .login-card { --emblem-w: 26cqw; }
}

/* ── Responsive ─────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .chrome { top: 24px; left: 28px; right: 28px; }
  #group-sidebar { left: 28px; bottom: 28px; max-width: 210px; }
  #hotspot-panel { right: 28px; width: 310px; }
  body.is-expanded #hotspot-panel { right: calc(50% - min(42vw, 550px)); width: min(84vw, 1100px); }
  body.is-expanded .hs-body { column-gap: 26px; }
  .hs-wm-line { font-size: 15px; }
  .hs-logo, .hs-marker.hs-active .hs-logo { --logo-h: 17px; }
}

@media (max-width: 720px) {
  .chrome { top: 16px; left: 16px; right: 16px; }
  #hotspot-panel {
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: auto;
    max-height: 58vh;
    transform: none;
  }
  /* Slides up from the bottom on a phone rather than in from the right —
     there is no room beside the ring at this width. */
  #hotspot-panel.hs-hidden  { transform: translateY(24px); }
  #hotspot-panel.hs-visible { transform: none; }
  .hs-card { border-radius: 10px 10px 0 0; padding: 22px 20px 26px; }
  .hs-tools { margin: -10px -10px 6px 0; }
  .hs-card::before { inset: 0 0 auto 0; width: auto; height: 1px;
    background: linear-gradient(to right, transparent, rgba(203,164,104,0.55) 35%,
                                rgba(203,164,104,0.55) 65%, transparent); }
  /* No tilt on a touch screen: there is no hover, and a card that leans when
     you tap it just reads as a glitch. */
  .hs-card { transform: none; }
  /* Expanded on a phone is a tall sheet, not a centred window — two columns
     at this width would be four words each. */
  body.is-expanded #hotspot-panel {
    right: 0; left: 0; width: auto; height: 88vh; max-height: none;
  }
  body.is-expanded .hs-card { border-radius: 12px 12px 0 0; padding: 26px 22px; }
  body.is-expanded .hs-body { display: flex; }
  body.is-expanded #hs-title { font-size: 21px; }
  body.is-expanded .hs-media { margin-bottom: 18px; }
  .hs-wm-line { font-size: 12.5px; }
  .hs-logo, .hs-marker.hs-active .hs-logo { --logo-h: 14px; }
  .hs-wm-sub  { font-size: 8px; letter-spacing: 0.2em; }
  /* No room beside the orb at this width — a wordmark like "COLOMBINI GROUP
     CONTRACT" runs straight off the edge. Stack it above the orb and centre
     it instead, which also stops it overlapping the ring. */
  .hs-marker.hs-left  .hs-wordmark,
  .hs-marker.hs-right .hs-wordmark {
    left: 50%;
    right: auto;
    top: auto;
    bottom: 18px;
    transform: translateX(calc(-50% + var(--wm-nudge, 0px)));
    text-align: center;
  }
  /* A pop-up on a phone is the whole screen: every pixel goes to the tour. */
  .mv { padding: 0; }
  .mv-window { width: 100%; height: 100%; border-radius: 0; border: 0; }
  .mv-head { padding-left: 16px; gap: 12px; }
  .mv-brand { display: none; }
  .mv-brand:not(:empty) + .mv-title { padding-left: 0; border-left: 0; }
  .mv-external { padding: 9px 10px; }
  .mv-external-text { display: none; }
}

@media (max-width: 480px) {
  /* The hamburger and the logo need the width; who is signed in can go. */
  .session-name { display: none; }
}

@media (max-width: 420px) {
  .hs-wm-line { font-size: 11px; }
  .hs-logo, .hs-marker.hs-active .hs-logo { --logo-h: 12px; }
}

/* ── Short viewports ────────────────────────────────────────── */
/* The breakpoints above only watch width, but the group menu is a tall block
   anchored to the bottom: on a laptop in a shallow window (or any 16:10 at
   1024x640) it climbs into the ring and collides with the wordmarks. */
@media (max-height: 780px) {
  #group-sidebar { bottom: 26px; }
  #group-sidebar .gs-item-note { display: none; }
  #group-sidebar .gs-item { padding: 6px 0; }
}

@media (max-height: 620px) {
  #group-sidebar { bottom: 16px; max-width: 178px; }
  #group-sidebar .gs-item { padding: 4px 0; }
  .gs-item-label { font-size: 11.5px; }
  .chrome { top: 18px; }
}

/* ── The phone menu ─────────────────────────────────────────── */
/* On a phone the group menu does not fit bottom-left: it sat over the ring and
   Febal Casa could not be tapped through it (client, 29 September 2026). It
   hides behind the hamburger and drops down from the top, full width, and the
   ring slides down out of its way (main.js). Also for a phone held sideways,
   where the height runs out long before the width. Same query as MENU_QUERY in
   viewer/main.js — keep the two in step. */
@media (max-width: 720px), (max-height: 520px) {
  .menu-toggle { display: block; }

  #group-sidebar,
  body.is-ready #group-sidebar,
  body.is-focused #group-sidebar {
    top: 0;
    left: 0;
    right: 0;
    bottom: auto;
    max-width: none;
    max-height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Clear of the header row, which stays on top of the menu (L5 over L4). */
    padding: 66px 22px 14px;
    background: rgba(12, 10, 8, 0.9);
    backdrop-filter: blur(18px) saturate(120%);
    -webkit-backdrop-filter: blur(18px) saturate(120%);
    border-bottom: 1px solid var(--panel-border);
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-14px);
    transition: opacity 0.3s var(--ease), transform 0.45s var(--ease),
                visibility 0s linear 0.45s;
  }
  body.menu-open #group-sidebar {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    transition: opacity 0.3s var(--ease), transform 0.45s var(--ease), visibility 0s;
  }
  /* One column on a phone upright, side by side when there is the width. */
  #group-sidebar .gs-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    column-gap: 28px;
  }
  #group-sidebar .gs-item,
  #group-sidebar .gs-item:first-child { padding: 12px 0 10px; }
  #group-sidebar .gs-item-label { font-size: 13px; letter-spacing: 0.13em; }
  #group-sidebar .gs-link { font-size: 13px; padding: 6px 0; }
  /* The page-load entrance is for the menu that is already on screen; this
     one replays each time the drop-down opens. */
  #group-sidebar .gs-item { animation: none; opacity: 1; transform: none; }
  body.menu-open #group-sidebar .gs-item {
    animation: gs-drop 0.4s var(--ease) both;
    animation-delay: calc(0.08s + var(--i) * 0.05s);
  }
}
@keyframes gs-drop { from { opacity: 0; transform: translateY(-6px); } }

/* ── Reduced motion ─────────────────────────────────────────── */
/* The 3D layers read this too (viewer/quality.js drops to the lite tier), so
   this only has to settle the DOM animations. */
@media (prefers-reduced-motion: reduce) {
  .hs-orb::after { animation: none; }
  .loader-ring { animation: none; border-top-color: rgba(255, 255, 255, 0.12); }
  .gs-item,
  body.menu-open #group-sidebar .gs-item { animation: none; opacity: 1; transform: none; }
  #hs-list li { transition-delay: 0s; }
  /* The sheen is an animation, not a transition, so the blanket rule below
     does not reach it. */
  .login-card,
  .login-card::before { animation: none; }
  /* The card keeps its glass and its specular, but stops leaning. */
  .hs-card { transform: none; }
  * { transition-duration: 0.01ms !important; }
}
