:root {
  color-scheme: dark;
  --overlay-bg: rgba(8, 8, 9, 0.88);
  --fg: #f5f5f7;
  --muted: #b8b8bf;
  /* ART-AR brand accent (gold), matching art-ar-platform's static/common.css
     - #F6C32C/#ffb40b there, same gradient stops. Both the primary button
     and the loading spinner read these two custom properties. A project's
     loading-screen branding redefines them (and the --brand-* properties
     used below) in a <style> injected by server/loadingScreen/render.js. */
  --accent: #F6C32C;
  --accent-strong: #F7A906;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000;
  color: var(--fg);
  font-family: 'Lato', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
}

#app {
  position: fixed;
  inset: 0;
}

/* Camera canvas / 8th Wall render target sits behind every overlay */
#camera-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The model gestures (model-gestures.js) need every touch move; without
     this the browser takes drags and pinches for scrolling and zooming. */
  touch-action: none;
}

.screen {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
  background: var(--overlay-bg);
}

.screen {
  transition: opacity 0.3s ease;
}

.screen.fade-out {
  opacity: 0;
}

.screen[hidden] {
  display: none;
}

/* Shared page-background texture, matching art-ar-platform's body
   background (static/common.css's `body` rule - same image, same
   color/size/position/repeat/attachment). There, it's one rule on
   <body> since normal page content flows over it; here each of these
   three screens is its own fixed, fully-covering overlay, so the same
   recipe is applied to each directly instead - #screen-not-found and
   #screen-incompatible are left on the plain --overlay-bg fill for now. */
#screen-loading,
#screen-start,
#screen-camera-opening,
#screen-webcam-fail {
  background: #131313 url('/img/bgtextures.png') no-repeat bottom center / cover fixed;
}

.screen h1 {
  font-size: 20px;
  margin: 0 0 8px;
  font-weight: 700;
}

.screen p {
  color: var(--muted);
  margin: 4px 0;
  max-width: 320px;
}

.screen img.screen-icon {
  width: 160px;
  max-width: 50vw;
  height: auto;
  margin-bottom: 16px;
}

/* Loading-screen layout: absolutely positioned rather than flex-centered
   like the other screens, matching webar-site's own loading screen - see
   its src/static/Shared/Styles/style.css (#loading-panel-logo /
   #loading-progress-text-div) and loading.css (the spinner wrapper's
   top: 90%) for the reference positions this mirrors: logo above center,
   status text and spinner lower, near the bottom edge. */
#screen-loading {
  display: block;
  padding: 0;
}

/* The ID rule above outranks `.screen[hidden]`, so without this the loading
   screen never actually hides and shows through when overlays above it fade. */
#screen-loading[hidden] {
  display: none;
}

#loading-logo {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  margin: 0;
  width: 240px; /* 1.5x the shared .screen-icon default (160px) */
  max-width: 75vw; /* scaled the same 1.5x, so it still fits narrow screens */
}

#loading-status {
  position: absolute;
  left: 50%;
  top: 80%;
  transform: translate(-50%, -50%);
}

/* Pulsates like webar-site's own #loading-progress-text-animator
   (2s linear, scale 1 -> 1.1 -> 1) - a separate wrapper from the
   position one above, since transform is already used there for
   centering and the two would otherwise clobber each other. */
#loading-title-animator {
  animation: pulsate 2s linear infinite;
}

@keyframes pulsate {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.1);
  }
}

/* Per-project loading-screen branding (server/loadingScreen/render.js
   injects the --brand-* values; unbranded pages never render these two
   elements). Mirrored by art-ar-platform's editor preview - see its
   static/css/loading-screen-editor.css - so keep the two in sync. Positions
   are the element's center, as % of the screen. */
#loading-brand-logo {
  position: absolute;
  left: var(--brand-logo-x);
  top: var(--brand-logo-y);
  transform: translate(-50%, -50%) scale(var(--brand-logo-scale));
  width: 240px; /* same base size as the default #loading-logo */
  max-width: 75vw;
  height: auto;
}

#loading-brand-title {
  position: absolute;
  left: var(--brand-title-x);
  top: var(--brand-title-y);
  transform: translate(-50%, -50%);
  margin: 0;
  width: max-content;
  max-width: 90%;
  text-align: center;
  font-family: var(--brand-title-font), 'Lato', sans-serif;
  font-size: var(--brand-title-size);
  font-weight: 700;
  line-height: 1.2;
  color: var(--brand-text);
}

/* --brand-text is only defined on branded pages; the fallback keeps the
   default look (.screen p's muted color). */
#loading-title,
#start-text {
  color: var(--brand-text, var(--muted));
}

#loading-title {
  margin: 0;
  font-size: 19px; /* matches webar-site's #loading-progress-text */
  width: max-content;
  max-width: 80vw;
}

/* Start ("tap to begin") screen: minimal, matches webar-site's own
   #start-panel - the whole screen is the tap target (see the click
   listener on screens.start in app.js, not a separate <button>), with
   just a single pulsing "Нажмите" label centered on it (.screen's
   existing flex centering does the layout work). */
#screen-start {
  cursor: pointer;
}

#screen-start:active {
  color: rgba(245, 245, 247, 0.6);
}

#start-text {
  margin: 0;
  font-size: 25px; /* matches webar-site's #start-text */
  font-weight: 700;
}

#start-text-animator {
  animation: start-text-pulse 2s ease-in-out infinite;
}

@keyframes start-text-pulse {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.1);
  }
}

.btn {
  margin-top: 20px;
  padding: 12px 28px;
  border-radius: 999px;
  border: none;
  background: linear-gradient(180deg, var(--accent) 0%, var(--accent-strong) 100%);
  color: #1a1400;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
}

.btn:active {
  filter: brightness(0.92);
}

.support-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 24px;
  color: var(--accent);
  text-decoration: none;
}

/* Loading spinner - same 8-dot fading-ring structure/animation as
   webar-site's reference loading spinner (see its
   src/static/Shared/Styles/loading.css, class ldio-yzaezf3dcml), just
   recolored from white to the ART-AR gold accent and reparented into a
   normal flex-flow box instead of an absolutely-positioned panel. */
.brand-spinner {
  position: absolute;
  left: 50%;
  top: 90%; /* matches webar-site's spinner wrapper position */
  transform: translate(-50%, -50%);
  width: 80px; /* matches webar-site's effective spinner size (200px scaled 0.4) */
  height: 80px;
  margin: 0;
}

.brand-spinner-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 200px;
  height: 200px;
  transform-origin: 0 0;
  transform: scale(0.4);
  backface-visibility: hidden;
}

.brand-spinner-inner div {
  box-sizing: content-box;
}

.brand-spinner-inner div > div {
  position: absolute;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  animation: brand-spinner-fade 1s linear infinite;
}

@keyframes brand-spinner-fade {
  0% {
    opacity: 1;
    backface-visibility: hidden;
    transform: translateZ(0) scale(1.5, 1.5);
  }
  100% {
    opacity: 0;
    backface-visibility: hidden;
    transform: translateZ(0) scale(1, 1);
  }
}

.brand-spinner-inner div:nth-child(1) > div { left: 148px; top: 88px; animation-delay: -0.875s; }
.brand-spinner-inner > div:nth-child(1) { transform-origin: 160px 100px; }
.brand-spinner-inner div:nth-child(2) > div { left: 130px; top: 130px; animation-delay: -0.75s; }
.brand-spinner-inner > div:nth-child(2) { transform-origin: 142px 142px; }
.brand-spinner-inner div:nth-child(3) > div { left: 88px; top: 148px; animation-delay: -0.625s; }
.brand-spinner-inner > div:nth-child(3) { transform-origin: 100px 160px; }
.brand-spinner-inner div:nth-child(4) > div { left: 46px; top: 130px; animation-delay: -0.5s; }
.brand-spinner-inner > div:nth-child(4) { transform-origin: 58px 142px; }
.brand-spinner-inner div:nth-child(5) > div { left: 28px; top: 88px; animation-delay: -0.375s; }
.brand-spinner-inner > div:nth-child(5) { transform-origin: 40px 100px; }
.brand-spinner-inner div:nth-child(6) > div { left: 46px; top: 46px; animation-delay: -0.25s; }
.brand-spinner-inner > div:nth-child(6) { transform-origin: 58px 58px; }
.brand-spinner-inner div:nth-child(7) > div { left: 88px; top: 28px; animation-delay: -0.125s; }
.brand-spinner-inner > div:nth-child(7) { transform-origin: 100px 40px; }
.brand-spinner-inner div:nth-child(8) > div { left: 130px; top: 46px; animation-delay: 0s; }
.brand-spinner-inner > div:nth-child(8) { transform-origin: 142px 58px; }

/* Camera-permission error panel: deliberately NOT ART-AR branded (no
   logo, no gold accent) - styled instead to read as a neutral,
   system-level dialog card, since this is a device/OS-level failure
   rather than part of the ART-AR experience. It still gets the shared
   page-texture backdrop below, same as loading/start - only the dialog
   card itself (and its button) stay unbranded. */

#screen-webcam-fail .system-dialog {
  background: #ffffff;
  color: #1c1c1e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  border-radius: 14px;
  padding: 24px 20px;
  max-width: 280px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
}

#screen-webcam-fail h1 {
  font-size: 17px;
  color: #1c1c1e;
  font-weight: 600;
  margin: 0 0 12px; /* overrides .screen h1's 0 0 8px */
}

/* Subtitle: margin zeroed out (overriding .screen p's 4px 0) so the only
   gaps around it are the h1's margin-bottom above and the .btn's
   margin-top below - kept equal (12px each) so it sits vertically
   centered between the title and the button instead of hugging the
   title. */
#screen-webcam-fail p {
  color: #6e6e73;
  max-width: none;
  margin: 0;
}

#screen-webcam-fail .btn {
  margin-top: 12px;
  padding: 10px 24px;
  border-radius: 8px;
  border: none;
  background: #007aff;
  color: #ffffff;
  font-weight: 600;
}

#screen-webcam-fail .btn:active {
  filter: brightness(0.9);
}

/* Point-at-marker hint, shown once camera session is live */
#marker-hint {
  position: fixed;
  z-index: 10; /* above the engine-managed camera canvas */
  left: 50%;
  bottom: 8%;
  width: max-content;
  max-width: 90vw;
  text-align: center;
  color: var(--fg);
  font-size: 20px;
  font-weight: 700;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  /* Slightly transparent + gentle scale pulse, like webar-unity's
     guide text (ScalePulse). */
  animation: marker-hint-pulse 2s ease-in-out infinite;
}

@keyframes marker-hint-pulse {
  0%, 100% {
    transform: translateX(-50%) scale(1);
    opacity: 0.55;
  }
  50% {
    transform: translateX(-50%) scale(1.08);
    opacity: 0.8;
  }
}

#marker-hint[hidden] {
  display: none;
}

/* Tap-to-enable-sound prompt (see media-unlock.js). The whole screen is
   the tap target; the pill just says so. */
#sound-prompt {
  position: fixed;
  z-index: 10; /* above the camera canvas, below #button-overlay */
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

#sound-prompt[hidden] {
  display: none;
}

#sound-prompt-text {
  max-width: 90vw;
  padding: 12px 22px;
  border-radius: 999px;
  background: var(--overlay-bg);
  border: 1px solid var(--accent);
  color: var(--accent);
  font-size: 18px;
  font-weight: 700;
  text-align: center;
}

/* Fullscreen video player (video-player.js). Above the AR view and the
   sound prompt, below the marker buttons (#button-overlay), which must
   stay visible over the video. */
#video-player {
  position: fixed;
  z-index: 20;
  inset: 0;
  background: #000;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

#video-player[hidden] {
  display: none;
}

#video-player .vp-stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

#video-player video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Controls layer: covers the player but lets taps through to the stage
   except on the controls themselves. Hidden controls take no taps. */
#video-player .vp-controls {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0s 0.2s;
}

#video-player.vp-show-controls .vp-controls {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.2s;
}

#video-player .vp-controls > * {
  pointer-events: auto;
}

#video-player .vp-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}

#video-player .vp-button svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

#video-player .vp-button svg .vp-fill {
  fill: currentColor;
  stroke: none;
}

/* The control bar. Its measured height is --vp-bar-height (on <html>),
   which the marker buttons' bottom row sits on while it's shown. */
#video-player .vp-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 24px max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
}

#video-player .vp-time,
#video-player .vp-duration {
  flex: none;
  min-width: 36px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--fg);
}

/* Seek bar: native range input, restyled to look the same everywhere. */
#video-player .vp-seek {
  flex: 1;
  min-width: 0;
  height: 28px;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}

#video-player .vp-seek::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.35);
}

#video-player .vp-seek::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.35);
}

#video-player .vp-seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -6px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
}

#video-player .vp-seek::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
}

/* Play/pause and sound icons follow the video's state. */
#video-player .vp-icon-pause,
#video-player.vp-playing .vp-icon-play,
#video-player .vp-icon-muted,
#video-player.vp-muted .vp-icon-sound {
  display: none;
}

#video-player.vp-playing .vp-icon-pause,
#video-player.vp-muted .vp-icon-muted {
  display: block;
}

/* A video with no audio track: the sound button shows "muted", dimmed,
   and can't be pressed. */
#video-player.vp-no-audio .vp-icon-sound {
  display: none;
}

#video-player.vp-no-audio .vp-icon-muted {
  display: block;
}

#video-player .vp-button:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Marker buttons (buttons-overlay.js), mirroring art-ar-app's
   MarkerButtonsPanel.prefab. Sizes are the app's canvas units: its
   CanvasScaler uses a 750x1624 reference matched to screen height, so one
   unit is 1/1624 of the screen height (--bu). */
/* --bu, the marker buttons' size unit: 1/1624 of the page height, as in
   the app. On a touch screen, buttons-overlay.js sets it in px from the
   height last measured in portrait and holds it in landscape (the video
   player rotates the screen for a landscape video), so the buttons keep
   their portrait size. Until a portrait size is known (a phone opened in
   landscape), a touch screen uses its longer side, which is the height in
   portrait. A desktop (fine pointer) just follows the window's height. It's
   on :root because the video player's bar uses it too. */
:root {
  --bu: calc(100vh / 1624);
}

@supports (height: 100dvh) {
  :root {
    --bu: calc(100dvh / 1624);
  }
}

@media (pointer: coarse) {
  :root {
    --bu: calc(max(100vw, 100vh) / 1624);
  }

  @supports (height: 100dvh) {
    :root {
      --bu: calc(max(100dvw, 100dvh) / 1624);
    }
  }
}

#button-overlay {
  position: fixed;
  z-index: 30; /* above #sound-prompt and the video player */
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

#button-overlay[hidden] {
  display: none;
}

/* One panel per edge: 4 cells of 150 units with 20 between them, 660
   in all, like the app's layout groups. Hidden panels sit off-screen,
   and showing slides them in over 0.5s, linear, like the app's animator
   transitions (IsShowed.anim/IsHidden.anim). */
.button-panel {
  position: absolute;
  display: flex;
  gap: calc(20 * var(--bu));
  transition: transform 0.5s linear;
}

.button-panel[data-edge="left"],
.button-panel[data-edge="right"] {
  top: 50%;
  width: calc(150 * var(--bu));
  height: calc(660 * var(--bu));
  margin-top: calc(-330 * var(--bu));
  flex-direction: column;
}

/* In landscape the side columns, at full portrait size, reach down into
   the video player's control bar at the bottom corners, and there isn't
   room above the bar for four full-size buttons. So instead the bar's
   contents are inset by the column width on each side that has buttons,
   keeping play and close clear of them. The columns then overlap only the
   bar's empty gradient. */
@media (orientation: landscape) {
  html:has(#button-overlay.is-shown .button-panel[data-edge="left"] .button-cell:not([hidden])) #video-player .vp-bar {
    padding-left: calc(150 * var(--bu) + 8px);
  }

  html:has(#button-overlay.is-shown .button-panel[data-edge="right"] .button-cell:not([hidden])) #video-player .vp-bar {
    padding-right: calc(150 * var(--bu) + 8px);
  }
}

.button-panel[data-edge="top"],
.button-panel[data-edge="bottom"] {
  left: 50%;
  width: calc(660 * var(--bu));
  height: calc(150 * var(--bu));
  margin-left: calc(-330 * var(--bu));
  flex-direction: row;
}

/* Shown: side panels flush with their edge, top/bottom 25 units in. The
   safe-area insets are this page's addition, to keep them clear of a
   notch in landscape. */
.button-panel[data-edge="left"] {
  left: env(safe-area-inset-left, 0px);
  transform: translateX(calc(-235 * var(--bu) - env(safe-area-inset-left, 0px)));
}

.button-panel[data-edge="right"] {
  right: env(safe-area-inset-right, 0px);
  transform: translateX(calc(235 * var(--bu) + env(safe-area-inset-right, 0px)));
}

.button-panel[data-edge="top"] {
  top: calc(25 * var(--bu) + env(safe-area-inset-top, 0px));
  transform: translateY(calc(-260 * var(--bu) - env(safe-area-inset-top, 0px)));
}

.button-panel[data-edge="bottom"] {
  bottom: calc(25 * var(--bu) + env(safe-area-inset-bottom, 0px));
  transform: translateY(calc(260 * var(--bu) + env(safe-area-inset-bottom, 0px)));
  /* The bottom offset changes with the video player's controls (below),
     in step with their 0.2s fade. The slide stays 0.5s. */
  transition: transform 0.5s linear, bottom 0.2s ease;
}

/* In landscape, iPhone reports a bottom inset for the home indicator but
   none at the top, so adding it lifted the bottom row visibly higher
   above its edge than the top row sits below its own. The indicator only
   takes swipes up from the very edge, not taps on the buttons, so here
   the bottom row is 25 units in like the top one. The side insets stay:
   they keep the side columns clear of the notch. */
@media (orientation: landscape) {
  .button-panel[data-edge="bottom"] {
    bottom: calc(25 * var(--bu));
    transform: translateY(calc(260 * var(--bu)));
  }
}

/* While the video player's controls are up, the bottom row sits right on
   top of its control bar instead of overlapping it, and drops back to
   the screen edge when they hide (video-player.js sets these on <html>). */
.vp-open.vp-controls-shown .button-panel[data-edge="bottom"] {
  bottom: var(--vp-bar-height, 0px);
}

#button-overlay.is-shown .button-panel {
  transform: none;
}

/* A layout-group slot. The cells share the panel's length equally, and
   each button sits at the start of its cell, so a panel with fewer
   buttons spreads them out from its start, as in the app. Unused
   positions are removed from the layout entirely. */
.button-cell {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  align-items: flex-start;
  justify-content: flex-start;
}

.button-cell[hidden] {
  display: none;
}

/* MarkerButton.prefab: 150x150 units, a 100x100 image at the top center
   and a label in the bottom 50 units. Only the image takes taps, like
   the app, where it's the only raycast target. */
.marker-button {
  position: relative;
  flex: none;
  width: calc(150 * var(--bu));
  height: calc(150 * var(--bu));
  pointer-events: none;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.marker-button img {
  position: absolute;
  top: 0;
  left: calc(25 * var(--bu));
  width: calc(100 * var(--bu));
  height: calc(100 * var(--bu));
  object-fit: fill; /* the app doesn't preserve the image's aspect */
  pointer-events: auto;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* The app's Button color tint: 0.784 while pressed. */
.marker-button:active img {
  filter: brightness(0.784);
}

.marker-button-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(50 * var(--bu));
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible; /* the app lets long labels overflow vertically */
  text-align: center;
  color: #fff;
  font-family: 'Raleway', sans-serif;
  font-weight: 900;
  font-size: calc(22 * var(--bu));
  line-height: 1;
}

/* Temporary on-screen debug console - see public/js/debug-console.js.
   Not wired up (installDebugConsole() isn't called from app.js), so this
   sits inactive; kept ready in case phone debugging is needed again. */
#debug-console {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 35vh;
  overflow-y: auto;
  background: rgba(0, 0, 0, 0.85);
  color: #0f0;
  font-family: monospace;
  font-size: 10px;
  line-height: 1.4;
  padding: 6px 8px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom));
  z-index: 9999;
  pointer-events: none;
}

#debug-console:empty {
  display: none;
}

.debug-line {
  white-space: pre-wrap;
  word-break: break-all;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.debug-warn {
  color: #ff0;
}

.debug-error {
  color: #f55;
}

/* Camera-opening screen has only the spinner, so center it instead of the
   loading screen's near-bottom (90%) position. */
#screen-camera-opening .brand-spinner {
  top: 50%;
}
