chenlei 5 mesi fa
commit
5138a51e46
100 ha cambiato i file con 13778 aggiunte e 0 eliminazioni
  1. 22 0
      .gitignore
  2. 2 0
      .npmrc
  3. 8 0
      .prettierignore
  4. 3 0
      .vscode/extensions.json
  5. 10 0
      .vscode/settings.json
  6. 1 0
      README.md
  7. 16 0
      package.json
  8. 14 0
      packages/base/package.json
  9. 2 0
      packages/base/src/index.js
  10. 63 0
      packages/base/src/usePagination.js
  11. 27 0
      packages/base/src/utils.js
  12. 1 0
      packages/mobile/.env.development
  13. 1 0
      packages/mobile/.env.production
  14. 30 0
      packages/mobile/.gitignore
  15. 17 0
      packages/mobile/index.html
  16. 8 0
      packages/mobile/jsconfig.json
  17. 36 0
      packages/mobile/package.json
  18. 22 0
      packages/mobile/postcss.config.js
  19. 6515 0
      packages/mobile/public/4dage.js
  20. BIN
      packages/mobile/public/favicon.ico
  21. BIN
      packages/mobile/public/img/map.png
  22. 4 0
      packages/mobile/public/lib/jquery-2.1.1.min.js
  23. 14 0
      packages/mobile/public/lib/krpano/js/tour.js
  24. 5 0
      packages/mobile/public/lib/krpano/plugins/bingmaps.js
  25. 493 0
      packages/mobile/public/lib/krpano/plugins/combobox.xml
  26. 5 0
      packages/mobile/public/lib/krpano/plugins/googlemaps.js
  27. 5 0
      packages/mobile/public/lib/krpano/plugins/gyro2.js
  28. 19 0
      packages/mobile/public/lib/krpano/plugins/scrollarea.js
  29. 5 0
      packages/mobile/public/lib/krpano/plugins/soundinterface.js
  30. 5 0
      packages/mobile/public/lib/krpano/plugins/videoplayer.js
  31. 5 0
      packages/mobile/public/lib/krpano/plugins/webvr.js
  32. 897 0
      packages/mobile/public/lib/krpano/plugins/webvr.xml
  33. BIN
      packages/mobile/public/lib/krpano/plugins/webvr_cursor_80x80_17f.png
  34. 21 0
      packages/mobile/public/lib/krpano/skin/img_doticon_01.svg
  35. BIN
      packages/mobile/public/lib/krpano/skin/rotate_device.png
  36. BIN
      packages/mobile/public/lib/krpano/skin/vtourskin.png
  37. 1297 0
      packages/mobile/public/lib/krpano/skin/vtourskin.xml
  38. 28 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_117.xml
  39. 31 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_117round.xml
  40. 71 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_black.xml
  41. 42 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_flat_light.xml
  42. 40 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_glass.xml
  43. 58 0
      packages/mobile/public/lib/krpano/skin/vtourskin_design_ultra_light.xml
  44. BIN
      packages/mobile/public/lib/krpano/skin/vtourskin_hotspot.png
  45. BIN
      packages/mobile/public/lib/krpano/skin/vtourskin_light.png
  46. BIN
      packages/mobile/public/lib/krpano/skin/vtourskin_mapspot.png
  47. BIN
      packages/mobile/public/lib/krpano/skin/vtourskin_mapspotactive.png
  48. 127 0
      packages/mobile/public/lib/krpano/tour.xml
  49. 207 0
      packages/mobile/public/lib/krpano/tour_test1.xml
  50. 734 0
      packages/mobile/public/lib/leaflet.css
  51. 6 0
      packages/mobile/public/lib/leaflet.js
  52. 48 0
      packages/mobile/public/model.html
  53. 19 0
      packages/mobile/src/App.vue
  54. 37 0
      packages/mobile/src/api/index.js
  55. BIN
      packages/mobile/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF
  56. BIN
      packages/mobile/src/assets/fonts/SOURCEHANSERIFCN-REGULAR.OTF
  57. BIN
      packages/mobile/src/assets/images/3D.png
  58. BIN
      packages/mobile/src/assets/images/active-more.png
  59. BIN
      packages/mobile/src/assets/images/back-min.png
  60. BIN
      packages/mobile/src/assets/images/bg-min.jpg
  61. BIN
      packages/mobile/src/assets/images/button-min.png
  62. BIN
      packages/mobile/src/assets/images/close.png
  63. BIN
      packages/mobile/src/assets/images/danmu_close.png
  64. BIN
      packages/mobile/src/assets/images/danmu_open.png
  65. BIN
      packages/mobile/src/assets/images/danmu_top.png
  66. BIN
      packages/mobile/src/assets/images/logo-bg-min.png
  67. BIN
      packages/mobile/src/assets/images/logo-min.png
  68. BIN
      packages/mobile/src/assets/images/menu-min.png
  69. BIN
      packages/mobile/src/assets/images/more.png
  70. BIN
      packages/mobile/src/assets/images/nodata-min.png
  71. BIN
      packages/mobile/src/assets/images/search.png
  72. 112 0
      packages/mobile/src/assets/main.css
  73. 7 0
      packages/mobile/src/assets/utils.scss
  74. 80 0
      packages/mobile/src/components/RemarkPopup.vue
  75. 98 0
      packages/mobile/src/components/Sidebar.vue
  76. 64 0
      packages/mobile/src/components/TopNav.vue
  77. 26 0
      packages/mobile/src/configure.js
  78. 26 0
      packages/mobile/src/main.js
  79. 43 0
      packages/mobile/src/router/index.js
  80. 12 0
      packages/mobile/src/stores/counter.js
  81. 50 0
      packages/mobile/src/utils/broswer.js
  82. 74 0
      packages/mobile/src/views/Antiquity/Detail/index.scss
  83. 119 0
      packages/mobile/src/views/Antiquity/Detail/index.vue
  84. 139 0
      packages/mobile/src/views/Antiquity/components/DetailPopup.vue
  85. 151 0
      packages/mobile/src/views/Antiquity/components/List.vue
  86. 101 0
      packages/mobile/src/views/Antiquity/index.scss
  87. 104 0
      packages/mobile/src/views/Antiquity/index.vue
  88. 41 0
      packages/mobile/src/views/Exhibition/index.scss
  89. 33 0
      packages/mobile/src/views/Exhibition/index.vue
  90. 18 0
      packages/mobile/src/views/Home/index.scss
  91. 13 0
      packages/mobile/src/views/Home/index.vue
  92. BIN
      packages/mobile/src/views/Message/images/edit-min.png
  93. BIN
      packages/mobile/src/views/Message/images/msg-bg-min.jpg
  94. BIN
      packages/mobile/src/views/Message/images/success-min.png
  95. 161 0
      packages/mobile/src/views/Message/index.scss
  96. 132 0
      packages/mobile/src/views/Message/index.vue
  97. 215 0
      packages/mobile/src/views/Scene/components/AddressPopup.vue
  98. 373 0
      packages/mobile/src/views/Scene/components/Danmaku.vue
  99. 565 0
      packages/mobile/src/views/Scene/constants.js
  100. 0 0
      packages/mobile/src/views/Scene/images/address-1.png

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+npm-debug.log*
+yarn-error.log
+yarn.lock
+package-lock.json
+
+# production
+/es
+docs-dist
+
+# misc
+.DS_Store
+
+# ide
+/.idea
+
+dist
+dist-node
+build

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+registry=https://registry.npmmirror.com/
+@dage:registry=http://192.168.20.245:4873/

+ 8 - 0
.prettierignore

@@ -0,0 +1,8 @@
+build/
+public/
+**/*.png
+**/*.svg
+**/*.jpg
+.DS_Store
+.history
+package.json

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 10 - 0
.vscode/settings.json

@@ -0,0 +1,10 @@
+{
+  "explorer.fileNesting.enabled": true,
+  "explorer.fileNesting.patterns": {
+    "tsconfig.json": "tsconfig.*.json, env.d.ts",
+    "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
+    "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .prettier*, prettier*, .editorconfig"
+  },
+  "editor.formatOnSave": true,
+  "editor.defaultFormatter": "esbenp.prettier-vscode"
+}

+ 1 - 0
README.md

@@ -0,0 +1 @@
+南京陆军工程大学数字史馆 ZHS2106281-1

+ 16 - 0
package.json

@@ -0,0 +1,16 @@
+{
+  "name": "nanjin-military-museum",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "pinia": "^2.2.6",
+    "tslib": "^2.8.0"
+  }
+}

+ 14 - 0
packages/base/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "@nj/base",
+  "version": "1.0.0",
+  "description": "",
+  "sideEffects": false,
+  "module": "src/index.js",
+  "main": "src/index.js",
+  "files": [
+    "src"
+  ],
+  "keywords": [],
+  "author": "",
+  "license": "ISC"
+}

+ 2 - 0
packages/base/src/index.js

@@ -0,0 +1,2 @@
+export * from "./utils";
+export * from "./usePagination";

+ 63 - 0
packages/base/src/usePagination.js

@@ -0,0 +1,63 @@
+import { computed, ref } from "vue";
+
+export const PaginationType = {
+  DEFAULT: 0,
+  CONCAT: 1,
+};
+
+export const DEFAULT_PAGE_SIZE = 10;
+
+export const usePagination = (
+  handler,
+  type = PaginationType.DEFAULT,
+  size = DEFAULT_PAGE_SIZE
+) => {
+  const pageNum = ref(1);
+  const total = ref(0);
+  const loading = ref(false);
+  const inited = ref(false);
+  const list = ref([]);
+  const noMore = ref(false);
+  const noData = computed(() => !total.value && !loading.value && inited.value);
+
+  const getList = async () => {
+    try {
+      loading.value = true;
+
+      const data = await handler({
+        pageNum: pageNum.value,
+        pageSize: size,
+      });
+      total.value = data.total;
+
+      if (type === PaginationType.DEFAULT) {
+        list.value = data.records;
+      } else {
+        list.value =
+          pageNum.value > 1 ? list.value.concat(data.records) : data.records;
+      }
+
+      noMore.value = size * pageNum.value > data.total;
+    } finally {
+      loading.value = false;
+      inited.value = true;
+    }
+  };
+
+  const resetParams = () => {
+    pageNum.value = 1;
+    total.value = 0;
+    list.value = [];
+  };
+
+  return {
+    pageNum,
+    loading,
+    list,
+    total,
+    noData,
+    noMore,
+    getList,
+    resetParams,
+  };
+};

+ 27 - 0
packages/base/src/utils.js

@@ -0,0 +1,27 @@
+export const isDevelopment = import.meta.env.MODE === "development";
+
+export const BASE_URL = `${window.origin}/project/helong-memorial-hall`;
+
+export const getEnvImagePath = (path) => {
+  return isDevelopment
+    ? `http://192.168.20.245:8011${path}`
+    : `${BASE_URL}/base${path}`;
+};
+
+export function isMobile() {
+  const userAgent = navigator.userAgent.toLowerCase();
+  return /iphone|ipod|android|windows phone|blackberry|mobile/i.test(userAgent);
+}
+
+export function checkDeviceAndRedirect() {
+  const isMobileDevice = isMobile();
+  const currentPath = window.location.pathname;
+  const isInMobilePath = currentPath.includes("/mobile/");
+  const isInPCPath = currentPath.includes("/pc/");
+
+  if (isMobileDevice && !isInMobilePath) {
+    window.location.href = currentPath.replace("/pc/", "/mobile/");
+  } else if (!isMobileDevice && !isInPCPath) {
+    window.location.href = currentPath.replace("/mobile/", "/pc/");
+  }
+}

+ 1 - 0
packages/mobile/.env.development

@@ -0,0 +1 @@
+VITE_BASE_URL=http://192.168.20.245:8011

+ 1 - 0
packages/mobile/.env.production

@@ -0,0 +1 @@
+VITE_BASE_URL=

+ 30 - 0
packages/mobile/.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 17 - 0
packages/mobile/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <link rel="stylesheet" href="/lib/leaflet.css" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>南京陆军工程大学数字史馆门户网站</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+    <script src="/lib/krpano/js/tour.js"></script>
+    <script src="/lib/jquery-2.1.1.min.js"></script>
+    <script src="/lib/leaflet.js"></script>
+  </body>
+</html>

+ 8 - 0
packages/mobile/jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 36 - 0
packages/mobile/package.json

@@ -0,0 +1,36 @@
+{
+  "name": "@nj/mobile",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@dage/service": "^1.0.5",
+    "@nj/base": "workspace:*",
+    "animate.css": "^4.1.1",
+    "pinia": "^2.2.6",
+    "swiper": "^11.1.15",
+    "vant": "^4.9.15",
+    "video.js": "^8.21.0",
+    "vue": "^3.5.13",
+    "vue-chat-scroll": "^1.4.0",
+    "vue-router": "^4.4.5",
+    "vue3-video-play": "^1.3.2"
+  },
+  "devDependencies": {
+    "@vant/auto-import-resolver": "^1.2.1",
+    "@vitejs/plugin-vue": "^5.2.1",
+    "autoprefixer": "^10.4.20",
+    "less": "^4.2.2",
+    "postcss-px-to-viewport": "^1.1.1",
+    "sass": "^1.82.0",
+    "unplugin-auto-import": "^0.18.6",
+    "unplugin-vue-components": "^0.27.5",
+    "vite": "^6.0.1",
+    "vite-plugin-vue-devtools": "^7.6.5"
+  }
+}

+ 22 - 0
packages/mobile/postcss.config.js

@@ -0,0 +1,22 @@
+export default {
+  plugins: {
+    autoprefixer: {},
+    "postcss-px-to-viewport": {
+      unitToConvert: "px", // 需要转换的单位,默认为"px"
+      viewportWidth: 750, // 设计稿的视口宽度
+      unitPrecision: 5, // 单位转换后保留的精度
+      propList: ["*"], // 能转化为vw的属性列表
+      viewportUnit: "vw", // 希望使用的视口单位
+      fontViewportUnit: "vw", // 字体使用的视口单位
+      selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
+      minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
+      mediaQuery: true, // 媒体查询里的单位是否需要转换单位
+      replace: true, //  是否直接更换属性值,而不添加备用属性
+      exclude: /node_modules/i, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
+      include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
+      landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
+      landscapeUnit: "vw", // 横屏时使用的单位
+      landscapeWidth: 750, // 横屏时使用的视口宽度
+    },
+  },
+};

File diff suppressed because it is too large
+ 6515 - 0
packages/mobile/public/4dage.js


BIN
packages/mobile/public/favicon.ico


BIN
packages/mobile/public/img/map.png


File diff suppressed because it is too large
+ 4 - 0
packages/mobile/public/lib/jquery-2.1.1.min.js


File diff suppressed because it is too large
+ 14 - 0
packages/mobile/public/lib/krpano/js/tour.js


File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/bingmaps.js


+ 493 - 0
packages/mobile/public/lib/krpano/plugins/combobox.xml

@@ -0,0 +1,493 @@
+<krpano>
+
+	<!--
+		combobox.xml Plugin - krpano 1.19-pr13
+
+		- This plugin converts <combobox> elements in the current xml
+		  into <layer> container, scrollarea and textfield elements.
+		- Additionally it's also possible to add and remove combobox
+		  elements also dynamically.
+		- The full xml implementation allows many ways of customizing
+		  for own needs - custom designs/styles, custom functionality.
+		- The plugin works automatically the same for HTML5 and Flash.
+		- It's possible to use this plugin as replacement for the old
+		  combobox.swf/combobox.js plugins, the action interfaces are
+		  the same.
+
+
+		Syntax for Static XML Code:
+
+			<combobox name="..." design="..." ...any layer settings...>
+				<item name="..." caption="..." onclick="..." />
+				<item name="..." caption="..." onclick="..." />
+			</combobox>
+
+		Syntax for Dynamic XML Code:
+
+		 - Global Actions:
+
+			addComboboxLayer(cbname, design*)
+			removeComboboxLayer(cbname);
+
+		 - Combobox Layer Actions:
+
+			layer[cbname].addItem(caption, onclick)
+			layer[cbname].addNamedItem(name, caption, onclick)
+			layer[cbname].addIdItem(name, caption, onclick);       same as addNamedItem (for combobox.js compatibility)
+			layer[cbname].selectItem(caption)
+			layer[cbname].selectItemByName(name_or_index)
+			layer[cbname].selectIdItem(name_or_index)              same as selectItemByName (for combobox.js compatibility)
+			layer[cbname].removeAll()
+			layer[cbname].openList()
+			layer[cbname].closeList()
+
+		 - Events/Callbacks:
+
+			layer[cbname].onChange
+
+		- Combobox Layer Attributes:
+
+			layer[cbname].item              - krpano Array of the items
+			layer[cbname].selecteditemindex - current selected item index
+	-->
+
+	<!-- path to the scrollarea plugin -->
+	<combobox_scrollareaplugin
+		url.html5="%SWFPATH%/plugins/scrollarea.js"
+		url.flash="%SWFPATH%/plugins/scrollarea.swf"
+		/>
+
+	<!-- core internal layer styles -->
+	<style name="combobox_container_style" type="container" maskchildren="true" bgcapture="true" visible="false" onclick="combobox_onclick_event();" mergedalpha="false" alpha="1.0" />
+	<style name="combobox_marker_style" type="text" align="righttop" edge="center" html="▼" havemarkersize="false" onautosized="set(havemarkersize,true);" mergedalpha="false" alpha="1.0" />
+	<style name="combobox_item_style" type="text" wordwrap="false" vcenter="true" align="lefttop" onover="if(!combbox_item_pressed,onoveritem());asyncloop(hovering,,if(!combbox_item_pressed,onoutitem()));" ondown="onoveritem(); set(combbox_item_pressed,true);" onup="onoutitem(); set(combbox_item_pressed,false);" onoveritem="set(bg,true);" onoutitem="set(bg,false);" mergedalpha="false" alpha="1.0" />
+
+	<!-- several pre-defined designs -->
+	<combobox_design name="default" margin="2" open_close_speed="0.25">
+		<!-- default design - white box with black text and blue selection -->
+		<style name="combobox_container_style" bgalpha="1.0" bgcolor="0xFFFFFF" bgborder="1 0xFFFFFF 0.5" bgroundedge="1" bgshadow="0 1 3 0x000000 1.0" />
+		<style name="combobox_marker_style" css="color:#FFFFFF;" bg="false" txtshadow="0 0 2 0x000000 1" />
+		<style name="combobox_item_style" css="color:#222222;" padding="4 4" bg="false" bgcolor="0xC7E4FC" bgalpha="1.0" bgroundedge="1" txtshadow="0 0 1 0xFFFFFF 1.0" />
+	</combobox_design>
+
+	<combobox_design name="vtour" margin="4" open_close_speed="0.25">
+		<!-- default vtourskin.xml design -->
+		<style name="combobox_container_style" bgalpha="0.8" bgcolor="0x2D3E50" bgborder="0" bgroundedge="1" bgshadow="0 4 10 0x000000 0.3" />
+		<style name="combobox_marker_style" css="color:#FFFFFF;" bg="false" txtshadow="0 0 2 0x000000 1" />
+		<style name="combobox_item_style" css="color:#FFFFFF;" padding="4 4" bg="false" bgcolor="0xFFFFFF" bgalpha="0.5" bgroundedge="0" txtshadow="0 0 2 0x000000 1" />
+	</combobox_design>
+
+
+	<!-- internal events -->
+	<events name="combobox_xml_plugin_events" keep="true"
+	        onxmlcomplete="combobox_parse_xml_elements();"
+	        onresize="combobox_closelist();"
+	        />
+
+
+	<!-- krpano version check -->
+	<action name="combobox_versioncheck" autorun="preinit">
+		if(build LT '2017-09-13',
+			error('combobox.xml - too old krpano version!');
+			set(events[combobox_xml_plugin_events].name, null);
+			set(action[addComboboxLayer].content, '');
+			set(action[removeComboboxLayer].content, '');
+		);
+	</action>
+
+
+	<!-- convert all <combobox> elements to layers -->
+	<action name="combobox_parse_xml_elements" scope="local">
+		if(global.combobox,
+			def(i, integer, 0);
+			def(cnt, integer, get(global.combobox.count));
+			copy(combobox_src, global.combobox);
+			delete(global.combobox);
+			if(cnt GT 0, loop(i LT cnt,
+				copy(cb, combobox_src[get(i)]);
+				if(cb AND cb.name AND cb.parsed != true,
+					set(cb.parsed, true);
+					addComboboxLayer(get(cb.name), get(cb.design));
+					copy(ly, layer[get(cb.name)]);
+					copyattributes(get(ly), get(cb));
+					set(ly.keep, true);
+					def(item_cnt, integer, get(cb.item.count));
+					if(item_cnt GT 0,
+						def(item_i, integer, 0);
+						loop(item_i LT  item_cnt,
+							combobox_additem(get(ly.name), get(cb.item[get(item_i)].name), get(cb.item[get(item_i)].caption), get(cb.item[get(item_i)].onclick));
+							inc(item_i);
+						);
+					);
+				);
+				inc(i);
+			));
+		);
+	</action>
+
+
+	<!-- dynamically add a combobox layer -->
+	<action name="addComboboxLayer" scope="local" args="cbname, design">
+		<!-- create the layer -->
+		addlayer(get(cbname));
+		copy(cb, layer[get(cbname)]);
+		set(cb.keep, true);
+		
+		<!-- copy the design settings (or set defaults) -->
+		if(!combobox_design[get(design)].name, set(design,'default'));
+		copy(cb.cbdesign, combobox_design[get(design)]);
+		calc(cb.margin, cb.cbdesign.margin !== null ? cb.cbdesign.margin : 2);
+		calc(cb.open_close_speed, cb.cbdesign.open_close_speed !== null ? cb.cbdesign.open_close_speed : 0.25);
+		<!-- load the styles and copy the design style settings -->
+		cb.loadstyle(combobox_container_style);
+		copyattributes(get(cb), get(cb.cbdesign.style[combobox_container_style]));
+
+		<!-- add/build/map actions -->
+		calc(cb.addItem,          'combobox_additem(' + cbname + ', null, "%%1", "%%2");');
+		calc(cb.addNamedItem,     'combobox_additem(' + cbname + ', "%%1", "%%2", "%%3");');
+		calc(cb.addIdItem,        'combobox_additem(' + cbname + ', "%%1", "%%2", "%%3");');
+		calc(cb.selectItem,       'combobox_finditem(' + cbname + ', "%%1", __cb_fi); if(__cb_fi GE 0, combobox_selectitem(' + cbname + ', get(__cb_fi))); delete(__cb_fi);');
+		calc(cb.selectItemByName, 'combobox_selectitem(' + cbname + ', "%%1");');
+		calc(cb.selectIdItem,     'combobox_selectitem(' + cbname + ', "%%1");');
+		calc(cb.removeAll,        'combobox_removeitems(' + cbname + ');');
+		calc(cb.openList,         'combobox_openlist(' + cbname + ');');
+		calc(cb.closeList,        'combobox_closelist(' + cbname + ');');
+
+		<!-- create sub-layers -->
+		def(saname, string, 'combobox_%1_scrollarea');
+		addlayer(get(saname));
+		copy(sa, layer[get(saname)]);
+		copy(sa.parent, cbname);
+		copy(sa.url, combobox_scrollareaplugin.url);
+		copy(sa.keep, true);
+		copy(sa.align, lefttop);
+		set(sa.direction, v);
+		set(sa.enabled, false);
+		set(sa.width, 100%);
+		set(sa.height, 100%);
+		copy(cb.scrollarea, sa);
+
+		def(mkname, string, 'combobox_%1_marker');
+		addlayer(get(mkname));
+		copy(mk, layer[get(mkname)]);
+		copy(mk.parent, saname);
+		copy(mk.keep, true);
+		mk.loadstyle(combobox_marker_style);
+		copyattributes(get(mk), get(cb.cbdesign.style[combobox_marker_style]));
+		copy(cb.marker, mk);
+
+		<!-- item data array -->
+		cb.createarray('item');
+
+		<!-- item autosizing information -->
+		set(cb.autosize_i, 0);
+		set(cb.autosize_cnt, 0);
+		set(cb.autosize_max_w, 0);
+		set(cb.autosize_max_h, 0);
+
+		set(cb.lastselecteditemindex, 0);
+		set(cb.selecteditemindex, 0);
+	</action>
+
+
+	<!-- dynamically remove a combobox element -->
+	<action name="removeComboboxLayer" scope="local" args="cbname">
+		if(layer[get(cbname)],
+			copy(cb, layer[get(cbname)]);
+			if(cb === global.openedcombobox, delete(global.openedcombobox));
+			if(cb,
+				removelayer(get(cbname), true);
+			);
+		);
+	</action>
+
+
+	<!-- default onclick event for combobox elements: open the list -->
+	<action name="combobox_onclick_event">
+		combobox_openlist(get(name));
+	</action>
+
+
+	<!-- dynamically add items -->
+	<action name="combobox_additem" scope="local" args="cbname, itemname, itemcaption, itemonclick">
+		copy(cb, layer[get(cbname)]);
+
+		<!-- when no item name is set, generate an automatic one -->
+		if(itemname === null, calc(itemname, 'autoname_' + cb.item.count); );
+
+		<!-- save the item caption and onclick event -->
+		copy(cb.item[get(itemname)].caption, itemcaption);
+		copy(cb.item[get(itemname)].onclick, itemonclick);
+
+		inc(cb.autosize_cnt);
+
+		<!-- create the item layer/textfield -->
+		calc(itemlayername, 'comboboxitem_' + cbname + '_' + itemname);
+		addlayer(get(itemlayername));
+		copy(li, layer[get(itemlayername)]);
+		li.loadstyle(combobox_item_style);
+		copyattributes(get(li), get(cb.cbdesign.style[combobox_item_style]));
+		copy(li.parent, cb.scrollarea.name);
+		copy(li.keep, true);
+		copy(li.cblayername, cb.name);
+		copy(li.itemname, itemname);
+		copy(li.html, itemcaption);
+		set(li.onautosized, delayedcall(0,combobox_item_autosize_update()) );
+		set(li.onclick, combobox_item_onclick() );
+
+		copy(cb.item[get(itemname)].itemlayername, itemlayername);
+	</action>
+
+
+	<!-- onautosized callback from the item textfield -->
+	<action name="combobox_item_autosize_update" scope="local">
+		copy(cb, layer[get(caller.cblayername)]);
+		inc(cb.autosize_i);
+		Math.max(cb.autosize_max_w, caller.width);
+		Math.max(cb.autosize_max_h, caller.height);
+		if(cb.autosize_i == cb.autosize_cnt, combobox_align_items(get(cb.name)); );
+	</action>
+
+
+	<!-- align the image and set the combobox size -->
+	<action name="combobox_align_items" scope="local" args="cbname">
+		copy(cb, layer[get(cbname)]);
+		if(cb.marker.havemarkersize == false OR cb.scrollarea.loaded == false,
+			<!-- wait until everything is ready -->
+			delayedcall(calc(cb.name + '_waitformarkersize'), 0.01, combobox_align_items(get(cbname)) );
+		  ,
+			<!-- set the item positions and the combobox size -->
+			if(global.openedcombobox === cb, combobox_closelist() );
+			copy(sa, cb.scrollarea);
+			calc(itemwidth, cb.margin GT 0 ? -2 * cb.margin : '100%');
+			copy(mk_w, cb.marker.width);
+			copy(item_cnt, cb.autosize_cnt);
+
+			for(def(item_i, integer, 0), item_i LT item_cnt, inc(item_i),
+				copy(li, layer[get(cb.item[get(item_i)].itemlayername)]);
+				set(li.x, get(cb.margin));
+				copy(li.width, itemwidth);
+				copy(li.height, cb.autosize_max_h);
+				calc(li.y, cb.margin + item_i * (cb.autosize_max_h + cb.margin));
+			);
+
+			if(cb.width == null OR cb.width == cb.lastautosizedwidth,
+				<!-- no combobox width (or an autosized width) set - set the largest item width -->
+				calc(cb.width, cb.margin + cb.autosize_max_w + 2 + mk_w + cb.margin);
+				copy(cb.lastautosizedwidth, cb.width);
+			);
+
+			calc(cb.height, 2*cb.margin + cb.autosize_max_h);
+			calc(sa.height, cb.margin + item_cnt*(cb.margin+cb.autosize_max_h));
+			calc(sa.y, -(cb.selecteditemindex * (cb.autosize_max_h + cb.margin)));
+			calc(cb.marker.x, cb.margin + mk_w/2);
+			tween(layer[get(cb.name)].marker.y, calc(cb.margin + cb.selecteditemindex*(cb.autosize_max_h + cb.margin) + cb.autosize_max_h/2), 0.1);
+
+			<!-- when all is done, show the combobox -->
+			delayedcall(0.1, set(layer[get(cb.name)].visible,true); );
+		);
+	</action>
+
+
+	<!-- default onclick event for items: select the current item, close the list and call the item onclick event -->
+	<action name="combobox_item_onclick" scope="local">
+		copy(cb, layer[get(caller.cblayername)]);
+		copy(itemname, caller.itemname);
+		combobox_selectitem(get(cb.name), get(itemname));
+
+		if(global.openedcombobox === cb, combobox_closelist() );
+
+		if(cb.item[get(itemname)].onclick,
+			<!-- call the onclick event after the combobox has closed -->
+			delayedcall(get(cb.open_close_speed),
+				copy(cb.curitem, cb.item[get(itemname)]);
+				callwith(cb,
+					cb.item[get(itemname)].onclick();
+				);
+			);
+		);
+	</action>
+
+
+	<!-- select an item -->
+	<action name="combobox_selectitem" scope="local" args="cbname, itemname">
+		if(global.combbox_item_pressed != true,
+			copy(cb, layer[get(cbname)]);
+			copy(cb.lastselecteditemindex, cb.selecteditemindex);
+			copy(cb.selecteditemindex, cb.item[get(itemname)].index);
+			<!-- call onchange event on selection change -->
+			if(cb.lastselecteditemindex != cb.selecteditemindex AND cb.onchange,
+				callwith(cb, onchange() );
+			);
+			if(global.openedcombobox === cb,
+				<!-- when opened, just close to the selected item -->
+				combobox_closelist();
+			  ,
+				if(layer[get(cbname)].scrollarea.loaded,
+					layer[get(cbname)].scrollarea.stopscrolling();
+					calc(offset, cb.selecteditemindex*(cb.autosize_max_h + cb.margin));
+					tween(layer[get(cbname)].marker.y, calc(cb.margin + offset + cb.autosize_max_h/2), 0);
+					tween(layer[get(cbname)].scrollarea.y, calc(-offset), 0, default, layer[get(cbname)].scrollarea.update(); );
+				);
+			);
+		);
+	</action>
+
+
+	<!-- find an item by its caption, the global variable defined in 'returnvariable' will contain the index  -->
+	<action name="combobox_finditem" scope="local" args="cbname, itemcaption, returnvariable">
+		copy(cb, layer[get(cbname)]);
+		copy(item_cnt, cb.item.count);
+		set(calc('global.' + returnvariable), -1);
+		for(def(item_i, integer, 0), item_i LT  item_cnt, inc(item_i),
+			if(cb.item[get(item_i)].caption == itemcaption,
+				copy(calc('global.' + returnvariable), item_i);
+				copy(item_i, item_cnt);
+			);
+		);
+	</action>
+
+
+	<!-- remove all items (to be able to add new ones) -->
+	<action name="combobox_removeitems" scope="local" args="cbname">
+		copy(cb, layer[get(cbname)]);
+		if(global.openedcombobox === cb, combobox_closelist() );
+
+		<!-- remove all item layers -->
+		calc(item_i, cb.item.count - 1);
+		loop(item_i GE 0,
+			removelayer(get(cb.item[get(item_i)].itemlayername));
+			dec(item_i);
+		);
+
+		<!-- reset the item information -->
+		set(cb.item.count, 0);
+		set(cb.autosize_i,0);
+		set(cb.autosize_cnt, 0);
+		set(cb.autosize_max_w, 0);
+		set(cb.autosize_max_h, 0);
+		set(cb.selecteditemindex, 0);
+		set(cb.lastselecteditemindex, 0);
+		if(cb.width == cb.lastautosizedwidth, set(cb.width, null));
+	</action>
+
+
+	<!-- open the combobox list -->
+	<action name="combobox_openlist" scope="local" args="cbname">
+		<!-- if another combobox is already open, close that one first -->
+		if(global.openedcombobox !== null, combobox_closelist() );
+
+		copy(cb, layer[get(cbname)]);
+		copy(global.openedcombobox, cb);
+
+		<!-- find the available screen space above or below the combobox -->
+		calc(cbheight, 2*cb.margin + cb.autosize_max_h);
+		set(lx1, 0);
+		set(ly1, 0);
+		copy(lx2, cb.pixelwidth);
+		copy(ly2, cbheight);
+		layertoscreen(get(cbname), lx1,ly1, lx1,ly1);
+		layertoscreen(get(cbname), lx2,ly2, lx2,ly2);
+		calc(space_above, ly1 - area.pixely);
+		calc(space_below, area.pixelheight - (ly2 - area.pixely));
+
+		<!-- the required space for full opening: -->
+		calc(openheight, cb.margin + cb.autosize_cnt*(cb.margin+cb.autosize_max_h) );
+
+		<!-- vertical centered alignment? -->
+		calc(cb_edge, cb.edge ? cb.edge : cb.align);
+		calc(iscentered, cb_edge == 'left' OR cb_edge == 'center' OR cb_edge == 'right');
+		if(iscentered,
+			calc(openheight_max, space_above + space_below);
+		  ,
+			Math.max(openheight_max, space_above, space_below);
+		);
+
+		<!-- limit the height to the available space (minus some margin) -->
+		Math.min(openheight, calc(openheight_max + cbheight - 20));
+
+		<!-- need vertical offset? (depending on the available space and the align/edge setting) -->
+		set(yoffset, null);
+		calc(top_overflow, -ly1 + area.pixely + openheight/2);
+		calc(bottom_overflow, ly2 - area.pixely + openheight/2 - area.pixelheight);
+		if(cb.parent,
+			<!-- no vertical offset inside other layers, do only a height clipping -->
+			Math.max(max_overflow, top_overflow, bottom_overflow, 0);
+			sub(openheight, max_overflow);
+		  ,
+			if(iscentered,
+				if(openheight GE (area.pixelheight - 20),
+					set(yoffset,0);
+				  ,
+					if(top_overflow GT 0, calc(yoffset, cb.y + top_overflow); );
+					if(bottom_overflow GT 0, calc(yoffset, cb.y - bottom_overflow); );
+				);
+			,
+				indexoftxt(isbottomalign, get(cb_edge), 'bottom');
+				if(space_above GT space_below,
+					if(isbottomalign LT 0, calc(yoffset, cb.y - openheight + cbheight); );
+				  ,
+					if(isbottomalign GE 0, calc(yoffset, cb.y - openheight + cbheight); );
+				);
+			);
+		);
+		if(yoffset != null,
+			copy(cb.ybackup, cb.y);
+			tween(layer[get(cbname)].y, calc(yoffset), get(cb.open_close_speed));
+		);
+
+		<!-- center the opened list at the selected item -->
+		calc(centeritem_y, -1 * (cb.margin + cb.selecteditemindex*(cb.margin+cb.autosize_max_h) + cb.autosize_max_h/2 - openheight/2));
+		clamp(centeritem_y, calc(openheight - cb.scrollarea.height), 0);
+
+		<!-- apply the changes now -->
+		tween(layer[get(cbname)].height, get(openheight), get(cb.open_close_speed));
+		tween(layer[get(cbname)].scrollarea.y, get(centeritem_y), get(cb.open_close_speed), default, layer[get(cbname)].scrollarea.update(); );
+
+		<!-- special html5/flash case:
+			 rotating textfields (the marker symbol here) are not possible in
+			 flash (a flashplayer limitation), so use a rotated symbol instead.
+		-->
+		if(device.html5,
+			tween(layer[get(cbname)].marker.rotate, 90, get(cb.open_close_speed));
+		  ,
+			set(layer[get(cbname)].marker.html, '◀');
+		);
+
+		<!-- enable the scrollarea to allow the user to drag it -->
+		set(cb.scrollarea.enabled, true);
+
+		<!-- install a global onmousedown event to close the list when clicking at the pano -->
+		set(events[combobox_xml_plugin_events].onmousedown, combobox_closelist() );
+	</action>
+
+
+	<!-- close the current open list -->
+	<action name="combobox_closelist" scope="local">
+		if(global.openedcombobox !== null,
+			copy(cb, global.openedcombobox);
+			delete(global.openedcombobox);
+			
+			<!-- clear the global onmousedown event -->
+			set(events[combobox_xml_plugin_events].onmousedown, null);
+
+			<!-- disable the dragging -->
+			set(cb.scrollarea.enabled, false);
+
+			<!-- closing animations -->
+			calc(offset, cb.selecteditemindex*(cb.autosize_max_h + cb.margin));
+			if(cb.ybackup !== null, tween(cb.y, get(cb.ybackup), get(cb.open_close_speed)));
+			layer[get(cb.name)].scrollarea.stopscrolling();
+			tween(layer[get(cb.name)].height, calc(2*cb.margin + cb.autosize_max_h), get(cb.open_close_speed));
+			tween(layer[get(cb.name)].scrollarea.y, calc(-offset), get(cb.open_close_speed), default, layer[get(cb.name)].scrollarea.update(); );
+			tween(layer[get(cb.name)].marker.y, calc(cb.margin + offset + cb.autosize_max_h/2), get(cb.open_close_speed));
+			<!-- special html5/flash case: rotate marker or change symbol -->
+			if(device.html5,
+				tween(layer[get(cb.name)].marker.rotate, 0, get(cb.open_close_speed));
+			  ,
+				set(layer[get(cb.name)].marker.html, '▼');
+			);
+		);
+	</action>
+
+</krpano>

File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/googlemaps.js


File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/gyro2.js


File diff suppressed because it is too large
+ 19 - 0
packages/mobile/public/lib/krpano/plugins/scrollarea.js


File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/soundinterface.js


File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/videoplayer.js


File diff suppressed because it is too large
+ 5 - 0
packages/mobile/public/lib/krpano/plugins/webvr.js


+ 897 - 0
packages/mobile/public/lib/krpano/plugins/webvr.xml

@@ -0,0 +1,897 @@
+<krpano>
+	<!--
+		webvr.xml
+		- krpano 1.19
+	-->
+
+	<!-- load the WebVR plugin and assign it to a 'webvr' variable for easier usage -->
+	<plugin name="WebVR" devices="html5" keep="true"
+	        url="webvr.js"
+	        onloaded="copy(webvr, plugin[WebVR]);"
+	        mousespeed="0.00125"
+	        multireslock="true"
+	        fullscreen_mirroring="true"
+	        mobilevr_support="true"
+	        mobilevr_ipd="63.5"
+	        mobilevr_screensize="auto"
+	        mobilevr_lens_overlap="1.0"
+	        mobilevr_lens_fov="96"
+	        mobilevr_lens_dist="0.6"
+	        mobilevr_lens_dist2="1|0|0|0"
+	        mobilevr_lens_ca="0.0"
+	        mobilevr_lens_vign="100"
+	        mobilevr_wakelock="true"
+	        mobilevr_sensor_mode="3"
+	        mobilevr_autocalibration="false"
+	        mobilevr_touch_support="true"
+	        mobilevr_fake_support="false"
+	        vr_cursor="hotspot[vr_cursor]"
+	        vr_cursor_enabled="true"
+	        vr_cursor_onover="if(handcursor, tween(hotspot[vr_cursor].scale,0.4,0.1); vr_auto_click(get(vr_timeout)); );"
+	        vr_cursor_onout="tween(hotspot[vr_cursor].scale,0.3,0.1);"
+	        onavailable="webvr_onavailable();"
+	        onunavailable=""
+	        onunknowndevice="webvr_onunknowndevice();"
+	        onentervr="webvr_onentervr();"
+	        onexitvr="webvr_onexitvr();"
+	        />
+
+	
+	<!-- a custom xml data structure with the supported VR headsets -->
+	<vrheadsets>
+		<headset name="cb1" caption="Cardboard A"   overlap="1.10" fov="96.0"  dist="1.00" dist2="1|0|0|0" ca="0.000" vig="100" />
+		<headset name="cb2" caption="Cardboard B"   overlap="1.00" fov="96.0"  dist="0.60" dist2="1|0|0|0" ca="0.000" vig="100" />
+		<headset name="gvr" caption="GearVR"        overlap="1.00" fov="112.0" dist="0.95" dist2="1|0|0|0" ca="0.090" vig="100" />
+		<headset name="hom" caption="HOMiDO"        overlap="1.00" fov="101.0" dist="1.10" dist2="1|0|0|0" ca="0.075" vig="100" />
+		<headset name="one" caption="VR ONE"        overlap="1.00" fov="109.9" dist="0.00" dist2="1.139|0.093|0.018|0.207" ca="0.090" vig="35" />
+		<headset name="ccr" caption="ColorCross VR" overlap="1.00" fov="70.0"  dist="0.65" dist2="1|0|0|0" ca="0.000" vig="100" />
+		<headset name="nod" caption="No Distortion" overlap="1.00" fov="96.0"  dist="0.00" dist2="1|0|0|0" ca="0.000" vig="100" />
+	</vrheadsets>
+
+
+	<!-- the VR cursor hotspot -->
+	<hotspot name="vr_cursor" keep="true"
+	         url="webvr_cursor_80x80_17f.png"
+	         visible="false"
+	         enabled="false"
+	         distorted="true"
+	         crop="0|0|80|80"
+	         scale="0.3"
+	         depth="1000"
+	         />
+
+
+	<!-- vr_auto_click() - call this action in the onover event of a
+	     hotspot to trigger automatically a click after some time.  -->
+	<action name="vr_auto_click">
+		if(webvr.isenabled,
+			if(%1 != null, set(vr_aclk_timeout, %1), set(vr_aclk_timeout, 2000));
+			copy(vr_aclk_t1, timertick);
+			set(vr_aclk_waiting, true);
+			copy(vr_aclk_hotspot, name);
+			set(hotspot[vr_cursor].crop,'0|0|80|80');
+
+			asyncloop(vr_aclk_waiting AND vr_aclk_hotspot == name,
+				sub(dt, timertick,vr_aclk_t1);
+
+				if(!hovering,
+					set(vr_aclk_waiting, false);
+					set(hotspot[vr_cursor].crop,'0|0|80|80');
+				  ,
+					div(f, dt, vr_aclk_timeout);
+					mul(f, 16);
+					roundval(f);
+					Math.min(f, 16);
+					mul(f, 80);
+
+					txtadd(hotspot[vr_cursor].crop,get(f),'|0|80|80');
+
+					<!-- wait another 100ms delay after finishing the animation before doing the click -->
+					sub(dt, 100);
+					if(dt GT vr_aclk_timeout,
+						set(vr_aclk_waiting,false);
+						set(hotspot[vr_cursor].crop,'0|0|80|80');
+						<!-- call onclick -->
+						onclick();
+					  );
+				  );
+				);
+		  );
+	</action>
+
+
+	<!-- by pressing SPACE the Oculus Rift could be re-centered -->
+	<events name="webvr_events" devices="html5" keep="true"
+	        onkeydown="if(webvr AND webvr.isenabled AND keycode==32, webvr.resetSensor() );"
+	        onmousedown="if(webvr AND webvr.isenabled, webvr_showbuttons() );"
+	        />
+
+
+	<!-- when WebVR support is available show an EnterVR button -->
+	<action name="webvr_onavailable">
+		webvr.loadsettings();
+		delayedcall(0.5, tween(layer[webvr_enterbutton].alpha,1.0); );
+	</action>
+	
+	
+	<action name="webvr_onunknowndevice">
+		if(webvr.isfake AND device.desktop AND webvr.havesettings == false,
+			<!-- set the 'no distortion' headset for fake desktop usage -->
+			set(webvr.mobilevr_lens_overlap, 1.0);
+			set(webvr.mobilevr_lens_fov, 96.0);
+			set(webvr.mobilevr_lens_dist, 0.0);
+			set(webvr.mobilevr_lens_dist2, '1|0|0|0');
+			set(webvr.mobilevr_lens_ca, 0.0);
+			set(webvr.mobilevr_lens_vign, 100);
+		  ,
+			set(ask_user_for_screensize,true);
+		  );
+	</action>
+
+
+	<action name="webvr_onentervr">
+		tween(layer[webvr_enterbutton].alpha,0,0);
+
+		webvr_showbuttons();
+		webvr_hide_all_non_vr_layers();
+
+		<!-- when the screen size is unknown an no custom size is set, open the setup screen on entering the VR mode -->
+		if(webvr.ismobilevr == true AND !webvr.isfake AND ask_user_for_screensize == true AND webvr.mobilevr_screensize == 'auto',
+			set(ask_user_for_screensize, false);
+			vr_setup();
+		  );
+		if(webvr.isfake,
+			webvr_show_fakemode_info(true);
+		  );
+	</action>
+
+
+	<action name="webvr_onexitvr">
+		stopdelayedcall(vr_button_fadeout);
+
+		tween(layer[webvr_enterbutton].alpha,1);
+		tween(layer[webvr_exitbutton].alpha,0);
+		tween(layer[webvr_setupbutton].alpha,0);
+		
+		webvr_show_fakemode_info(false);
+
+		webvr_restore_layers();
+	</action>
+
+
+	<action name="webvr_hide_all_non_vr_layers">
+		for(set(i,0), i LT layer.count, inc(i),
+			copy(lr, layer[get(i)]);
+			if(lr.vr !== true,
+				copy(lr.vr_backup_visible, lr.visible);
+				set(lr.visible, false);
+			  );
+		  );
+	</action>
+
+	<action name="webvr_restore_layers">
+		for(set(i,0), i LT layer.count, inc(i),
+			copy(lr, layer[get(i)]);
+			if(lr.vr_backup_visible,
+				copy(lr.visible, lr.vr_backup_visible);
+				delete(lr.vr_backup_visible);
+			  );
+		  );
+	</action>
+	
+	<action name="webvr_show_fakemode_info">
+		if('%1' == 'true',
+			addlayer(webvr_fakemode_info);
+			set(layer[webvr_fakemode_info].url, '%SWFPATH%/plugins/textfield.swf');
+			set(layer[webvr_fakemode_info].keep, true);
+			set(layer[webvr_fakemode_info].align, 'bottom');
+			set(layer[webvr_fakemode_info].y, 80);
+			set(layer[webvr_fakemode_info].background, false);
+			set(layer[webvr_fakemode_info].css, 'color:#FFFFFF;text-align:center;');
+			set(layer[webvr_fakemode_info].html, '[i][u]Simulated WebVR Mode![/u][/i][br]For real WebVR with headset tracking, either use a [a href="http://webvr.info" target="_blank" style="color:#FFFFFF;"]WebVR-API-capable[/a] desktop browser or a mobile device and a VR headset.');
+		  ,
+			removelayer(webvr_fakemode_info);
+		  );
+	</action>
+	
+	
+	<!-- ensure the same scaling on mobiles (regardless if mobilescale is 0.5 or 1.0) -->
+	<krpano webvr_setup_scale="calc:(1.0 + 1.0*(device.mobile AND stagescale LT 1.0)) / (1.0 + 1.0*device.mobile)"
+	        webvr_button_scale.normal="1.0"
+	        webvr_button_scale.mobile="1.6"
+	        />
+
+	
+	<!-- the EnterVR/ExitVR and SetupVR buttons -->
+	<style name="webvr_button_style"
+	       url="%SWFPATH%/plugins/textfield.swf"
+	       backgroundcolor="0x000000"
+	       backgroundalpha="0.5"
+	       roundedge="calc:9*webvr_setup_scale*webvr_button_scale"
+	       css="calc:'color:#FFFFFF;font-size:' + 20*webvr_setup_scale*webvr_button_scale + 'px;'"
+	       padding="calc:6*webvr_setup_scale*webvr_button_scale + ' ' + 10*webvr_setup_scale*webvr_button_scale"
+	       />
+	
+	<layer name="webvr_enterbutton" keep="true" vr="true"
+	       style="webvr_button_style"
+	       html="Enter VR"
+	       align="top" y="24"
+	       autoalpha="true" alpha="0.0"
+	       onclick="webvr.enterVR();"
+	       />
+
+	<layer name="webvr_exitbutton" keep="true" vr="true"
+	       style="webvr_button_style"
+	       html="Exit VR"
+	       align="top" y="24"
+	       autoalpha="true" alpha="0.0"
+	       onclick="webvr.exitVR();"
+	       />
+
+	<layer name="webvr_setupbutton" keep="true" vr="true"
+	       style="webvr_button_style"
+	       html="VR Setup"
+	       align="bottom" y="24"
+	       autoalpha="true" alpha="0.0"
+	       onclick="vr_setup()"
+	       />
+
+
+	<action name="webvr_showbuttons">
+		stopdelayedcall(vr_button_fadeout);
+		if(webvr.ismobilevr,
+			tween(layer[webvr_exitbutton].alpha|layer[webvr_setupbutton].alpha, 1.0|1.0, 0.25);
+			delayedcall(vr_button_fadeout, 3.0, tween(layer[webvr_exitbutton].alpha|layer[webvr_setupbutton].alpha, 0.0|0.0, 1.0); );
+		  ,
+			tween(layer[webvr_exitbutton].alpha, 1.0, 0.25);
+			delayedcall(vr_button_fadeout, 3.0, tween(layer[webvr_exitbutton].alpha, 0.0, 1.0); );
+		  );
+	</action>
+
+
+
+	<!--
+		VR Setup
+	-->
+
+	<action name="vr_setup">
+		<!-- disable cursor -->
+		set(webvr.vr_cursor_enabled, false);
+		
+		<!-- hide VR buttons -->
+		tween(layer[webvr_exitbutton].alpha,0);
+		tween(layer[webvr_setupbutton].alpha,0);
+		
+		<!-- create background layer -->
+		addlayer(vr_setup_bg);
+		set(layer[vr_setup_bg].type, container);
+		set(layer[vr_setup_bg].bgcolor, 0x000000);
+		set(layer[vr_setup_bg].bgalpha, 0.5);
+		set(layer[vr_setup_bg].bgcapture, true);
+		set(layer[vr_setup_bg].handcursor, false);
+		set(layer[vr_setup_bg].align, lefttop);
+		set(layer[vr_setup_bg].width, 100%);
+		set(layer[vr_setup_bg].height, 100%);
+		set(layer[vr_setup_bg].zorder, 99999);
+		
+		<!-- get and prepare device infos and settings -->
+		copy(i_screensize, webvr.mobilevr_screensize);
+		if(i_screensize == 'auto', copy(i_screensize, webvr.devicesize));
+		if(i_screensize LE 0, set(i_screensize, 5.0));
+		roundval(i_screensize, 1);
+		txtadd(i_screensize, ' inch');
+
+		copy(i_ipd, webvr.mobilevr_ipd);
+		roundval(i_ipd, 1);
+		txtadd(i_ipd, ' mm');
+
+		copy(i_fov, webvr.mobilevr_lens_fov);
+		roundval(i_fov, 1);
+
+		copy(i_dist, webvr.mobilevr_lens_dist);
+		roundval(i_dist, 2);
+		
+		copy(i_dist2, webvr.mobilevr_lens_dist2);
+		txtsplit(i_dist2, '|', i_dist2_k1, i_dist2_k2, i_dist2_k3, i_dist2_k4);
+		mul(i_dist2_k1,1);
+		mul(i_dist2_k2,10);
+		mul(i_dist2_k3,10);
+		mul(i_dist2_k4,10);
+		roundval(i_dist2_k1,2);
+		roundval(i_dist2_k2,2);
+		roundval(i_dist2_k3,2);
+		roundval(i_dist2_k4,2);
+
+		copy(i_vig, webvr.mobilevr_lens_vign);
+		roundval(i_vig, 0);
+		
+		copy(i_overlap, webvr.mobilevr_lens_overlap);
+		roundval(i_overlap, 2);
+		
+		copy(i_ca, webvr.mobilevr_lens_ca);
+		roundval(i_ca, 3);
+
+		set(i_headset, 'Custom');
+		for(set(i,0), i LT vrheadsets.headset.count, inc(i),
+			copy(hs, vrheadsets.headset[get(i)]);
+			if(i_overlap == hs.overlap AND i_fov == hs.fov AND i_dist == hs.dist AND i_dist2 == hs.dist2 AND i_ca == hs.ca AND i_vig == hs.vig , copy(i_headset, hs.caption));
+		   );
+
+		<!-- when the screen size is unknown, mark it red -->
+		set(known_size, true);
+		set(sizcol, #FFFFFF);
+		copy(i_devicename, webvr.devicename);
+		if(i_devicename == 'Unknown',
+			if(webvr.mobilevr_screensize == 'auto',
+				set(sizcol, #AA0000);
+				set(known_size, false);
+			  ,
+				set(i_devicename, 'Custom');
+			  );
+		  );
+
+		
+		<!-- create layer for the main menu -->
+		addlayer(vr_setup_m1);
+		set(layer[vr_setup_m1].type, container);
+		set(layer[vr_setup_m1].parent, vr_setup_bg);
+		set(layer[vr_setup_m1].align, lefttop);
+		set(layer[vr_setup_m1].width, 100%);
+		set(layer[vr_setup_m1].height, 100%);
+		
+		<!-- create layer for the headset customization menu -->
+		addlayer(vr_setup_m3);
+		set(layer[vr_setup_m3].type, container);
+		set(layer[vr_setup_m3].parent, vr_setup_bg);
+		set(layer[vr_setup_m3].align, lefttop);
+		set(layer[vr_setup_m3].width, 100%);
+		set(layer[vr_setup_m3].height, 100%);
+		set(layer[vr_setup_m3].visible, false);
+		
+		<!-- create layer for the calibration menu -->
+		addlayer(vr_setup_m2);
+		set(layer[vr_setup_m2].type, container);
+		set(layer[vr_setup_m2].parent, vr_setup_bg);
+		set(layer[vr_setup_m2].align, lefttop);
+		set(layer[vr_setup_m2].width, 100%);
+		set(layer[vr_setup_m2].height, 100%);
+		set(layer[vr_setup_m2].visible, false);
+		
+		<!-- create the text elements -->
+		set(vr_setup_text_parent, 'vr_setup_m1');
+		vr_setup_createtext(vr_setup_title, 'MOBILE VR SETUP',       center, center, 0, -225, #FFFFFF,     false);
+
+		vr_setup_createtext(vr_setup_dvn1, 'Device:',         center, right,  0, -145, #FFFFFF,     true, vr_setup_select('screen') );
+		vr_setup_createtext(vr_setup_dvn2, get(i_devicename), center, left,   0, -145, get(sizcol), true, vr_setup_select('screen') );
+		vr_setup_createtext(vr_setup_siz1, 'Screensize:',     center, right,  0, -105, #FFFFFF,     true, vr_setup_select('screen') );
+		vr_setup_createtext(vr_setup_siz2, get(i_screensize), center, left,   0, -105, get(sizcol), true, vr_setup_select('screen') );
+
+		vr_setup_createtext(vr_setup_ipd1, 'IPD:',            center, right,  0,  -35, #FFFFFF,     true, vr_setup_select('ipd') );
+		vr_setup_createtext(vr_setup_ipd2, get(i_ipd),        center, left,   0,  -35, #FFFFFF,     true, vr_setup_select('ipd') );
+
+		vr_setup_createtext(vr_setup_hmd1, 'VR Headset:',     center, right,  0,  +35, #FFFFFF,     true, vr_setup_select('headset') );
+		vr_setup_createtext(vr_setup_hmd2, get(i_headset),    center, left,   0,  +35, #FFFFFF,     true, vr_setup_select('headset') );
+		
+		vr_setup_createtext(vr_setup_hmd3, 'Customize',       center, center, 0,  +75, #FFFFFF,     true, set(background,true), set(background,false), vr_setup_customize_headset() );
+
+		
+
+		if(webvr.iswebvr == false,
+			vr_setup_createtext(vr_setup_cal, 'Calibrate Gyroscope',   center, center,    0, +145, #FFFFFF,     true, set(background,true), set(background,false), vr_setup_calibration() );
+		  );
+
+		vr_setup_createtext(vr_setup_sav, 'SAVE',          center, center, -200, +225, #FFFFFF,     true, set(background,true), set(background,false), vr_setup_save() );
+		vr_setup_createtext(vr_setup_rst, 'RESET',         center, center,    0, +225, #FFFFFF,     true, set(background,true), set(background,false), vr_setup_reset() );
+		vr_setup_createtext(vr_setup_cls, 'CLOSE',         center, center, +200, +225, #FFFFFF,     true, set(background,true), set(background,false), vr_setup_close() );
+		
+		<!-- and the adjusting buttons -->
+		vr_setup_createbutton(vr_setup_btn1, '&#60;', left,  left,  5%, -35, #FFFFFF, true, null);
+		vr_setup_createbutton(vr_setup_btn2, '&#62;', right, right, 5%, -35, #FFFFFF, true, null);
+		
+		
+		<!-- create the customize_headset text elements -->
+		set(vr_setup_text_parent, 'vr_setup_m3');
+		vr_setup_createtext(vr_setup_m31, 'VR HEADSET', center, center, 0, -225, #FFFFFF, false);
+		
+		vr_setup_createtext(vr_setup_fov1,  'FOV:',           center, right,  0,  -80,  #FFFFFF,    true, vr_setup_select('fov') );
+		vr_setup_createtext(vr_setup_fov2, get(i_fov),        center, left,   0,  -80,  #FFFFFF,    true, vr_setup_select('fov') );
+		vr_setup_createtext(vr_setup_dst1, 'Distortion:',     center, right,  0,  -32,  #FFFFFF,    true, vr_setup_select('dist') );
+		vr_setup_createtext(vr_setup_dst2, get(i_dist),       center, left,   0,  -32,  #FFFFFF,    true, vr_setup_select('dist') );
+		vr_setup_createtext(vr_setup_d2tx, 'Dist2:',          center, right,  0,  +16,  #FFFFFF,    true, vr_setup_select('dist2k1') );
+		vr_setup_createtext(vr_setup_d2k1, get(i_dist2_k1),   center, left,   0,  +16,  #FFFFFF,    true, vr_setup_select('dist2k1') );
+		vr_setup_createtext(vr_setup_d2k2, get(i_dist2_k2),   center, left, +100, +16,  #FFFFFF,    true, vr_setup_select('dist2k2') );
+		vr_setup_createtext(vr_setup_d2k3, get(i_dist2_k3),   center, left, +200, +16,  #FFFFFF,    true, vr_setup_select('dist2k3') );
+		vr_setup_createtext(vr_setup_d2k4, get(i_dist2_k4),   center, left, +300, +16,  #FFFFFF,    true, vr_setup_select('dist2k4') );
+		
+		
+		vr_setup_createtext(vr_setup_cac1, 'CA Corr:',        center, right,  0,  +64,  #FFFFFF,    true, vr_setup_select('ca') );
+		vr_setup_createtext(vr_setup_cac2, get(i_ca),         center, left,   0,  +64,  #FFFFFF,    true, vr_setup_select('ca') );
+		vr_setup_createtext(vr_setup_vig1, 'Vignette:',       center, right,  0, +112,  #FFFFFF,    true, vr_setup_select('vignette') );
+		vr_setup_createtext(vr_setup_vig2, get(i_vig),        center, left,   0, +112,  #FFFFFF,    true, vr_setup_select('vignette') );
+		vr_setup_createtext(vr_setup_olp1, 'Overlap:',        center, right,  0, +160,  #FFFFFF,    true, vr_setup_select('overlap') );
+		vr_setup_createtext(vr_setup_olp2, get(i_overlap),    center, left,   0, +160,  #FFFFFF,    true, vr_setup_select('overlap') );
+				
+		vr_setup_createtext(vr_setup_m35, 'CLOSE',       center, center, 0, +225, #FFFFFF, true, set(background,true), set(background,false), vr_setup_close_sub_menus() );
+		
+		
+		<!-- create the calibration text elements -->
+		set(vr_setup_text_parent, 'vr_setup_m2');
+		vr_setup_createtext(vr_setup_cb1, 'GYROSCOPE', center, center, 0, -225, #FFFFFF, false);
+		vr_setup_createtext(vr_setup_cb2, 'Place the device on a flat and[br]stable surface and tab calibrate[br]to correct a gyroscope drifting.', center, center, 0, -95, #FFFFFF, false, vr_setup_select('screen') );
+		vr_setup_createtext(vr_setup_cb3, 'CALIBRATE',   center, center, 0,  +55, #FFFFFF, true, set(background,true), set(background,false), vr_setup_do_calibration() );
+		vr_setup_createtext(vr_setup_cb4, 'RESET',       center, center, 0, +125, #FFFFFF, true, set(background,true), set(background,false), webvr.resetcalibration() );
+		vr_setup_createtext(vr_setup_cb5, 'CLOSE',       center, center, 0, +225, #FFFFFF, true, set(background,true), set(background,false), vr_setup_close_sub_menus() );
+		
+		vr_setup_createtext(vr_setup_cb6, 'Calibrating...',      bottom, center, 0, 40, #FFFFFF, false, null );
+		vr_setup_createtext(vr_setup_cb7, 'Calibration okay.',   bottom, center, 0, 40, #FFFFFF, false, null );
+		vr_setup_createtext(vr_setup_cb8, 'Calibration failed!', bottom, center, 0, 40, #FFFFFF, false, null );
+		set(layer[vr_setup_cb6].autoalpha, true);
+		set(layer[vr_setup_cb7].autoalpha, true);
+		set(layer[vr_setup_cb8].autoalpha, true);
+		set(layer[vr_setup_cb6].alpha, 0.0);
+		set(layer[vr_setup_cb7].alpha, 0.0);
+		set(layer[vr_setup_cb8].alpha, 0.0);
+		
+		
+		<!-- pre-select the screen size for adjusting when it is unknown, otherwise the IPD -->
+		if(known_size == false,
+			vr_setup_select('screen', true);
+		  ,
+			vr_setup_select('ipd', true);
+		  );
+	</action>
+
+
+	<action name="vr_setup_createtext">
+		<!--
+			%1 = name
+			%2 = text
+			%3 = align
+			%4 = edge
+			%5 = x
+			%6 = y
+			%7 = color
+			%8 = enabled
+			%9 = ondown
+			%10 = onup
+			%11 = onclick
+		-->
+		addlayer(%1);
+		set(layer[%1].parent, get(vr_setup_text_parent));
+		set(layer[%1].url, '%SWFPATH%/plugins/textfield.swf');
+		set(layer[%1].css, calc('text-align:%3;color:%7;font-size:'+40*webvr_setup_scale+'px;font-weight:bold;'));
+		set(layer[%1].padding, calc(0 + ' ' + 8*webvr_setup_scale));
+		set(layer[%1].roundedge, calc(8*webvr_setup_scale));
+		set(layer[%1].background, false);
+		set(layer[%1].backgroundcolor, 0xFFFFFF);
+		set(layer[%1].backgroundalpha, 0.25);
+		set(layer[%1].align, %3);
+		set(layer[%1].edge, %4);
+		set(layer[%1].x, calc(%5 * webvr_setup_scale));
+		set(layer[%1].y, calc(%6 * webvr_setup_scale));
+		set(layer[%1].html, %2);
+		set(layer[%1].enabled, %8);
+		set(layer[%1].ondown, %9);
+		set(layer[%1].onup, %10);
+		set(layer[%1].onclick, %11);
+	</action>
+
+
+	<action name="vr_setup_createbutton">
+		vr_setup_createtext(%1,%2,%3,%4,%5,%6,%7,%8,%9);
+		set(layer[%1].css, calc('vertical-align:middle;text-align:center;color:%7;font-size:'+60*webvr_setup_scale+'px;font-weight:bold;'));
+		set(layer[%1].background, true);
+		set(layer[%1].padding, 0);
+		set(layer[%1].roundedge, calc(40 * webvr_setup_scale));
+		set(layer[%1].width, calc(70 * webvr_setup_scale));
+		set(layer[%1].height, calc(70 * webvr_setup_scale));
+		set(layer[%1].vcenter, true);
+	</action>
+
+
+	<action name="vr_setup_reset">
+		<!-- reset to the defaults -->
+		set(webvr.mobilevr_screensize, 'auto');
+		copy(i_screensize, webvr.devicesize);
+		if(i_screensize LE 0, set(i_screensize, 5.0); );
+		roundval(i_screensize, 1);
+		set(layer[vr_setup_dvn2].html, get(webvr.devicename));
+		txtadd(layer[vr_setup_siz2].html, get(i_screensize), ' inch');
+
+		set(webvr.mobilevr_ipd, 63.5);
+		copy(i_ipd, webvr.mobilevr_ipd);
+		roundval(i_ipd, 1);
+		txtadd(layer[vr_setup_ipd2].html, get(i_ipd), ' mm');
+
+		<!-- set fake custom lens settings and call 'next' headset to switch to the default 'Cardboard' settings -->
+		set(webvr.mobilevr_lens_fov, 100);
+		set(webvr.mobilevr_lens_dist, 0.5);
+		set(webvr.mobilevr_lens_dist2, '1|0|0|0');
+		set(webvr.mobilevr_lens_vign, 100);
+		set(webvr.mobilevr_lens_overlap, 1.0);
+		set(webvr.mobilevr_lens_ca, 0.0);
+		
+		if(webvr.isfake AND device.desktop,
+			<!-- select 'no distortion' headset for fake desktop usage -->
+			vr_setup_change_headset(-1);
+		  ,
+			<!-- select 'Cardboard A' headset for Mobile-VR usage -->
+			vr_setup_change_headset(+1);	
+		  );
+
+		vr_setup_select(get(selected_var));
+	</action>
+
+
+	<action name="vr_setup_close">
+		<!-- 2. parameter == true => remove children elements too -->
+		removelayer(vr_setup_bg, true);
+		
+		<!-- enable cursor -->
+		set(webvr.vr_cursor_enabled, true);
+	</action>
+
+
+	<action name="vr_setup_save">
+		webvr.saveSettings();
+		vr_setup_close();
+	</action>
+	
+	
+	<action name="vr_setup_customize_headset">
+		set(layer[vr_setup_bg].bgalpha, 0.1);
+		
+		set(layer[vr_setup_m1].visible,false);
+		set(layer[vr_setup_m2].visible,false);
+		set(layer[vr_setup_m3].visible,true);
+		
+		set(layer[vr_setup_hmd1].parent, vr_setup_m3);
+		set(layer[vr_setup_hmd2].parent, vr_setup_m3);
+		set(layer[vr_setup_btn1].parent, vr_setup_m3);
+		set(layer[vr_setup_btn2].parent, vr_setup_m3);
+		
+		set(layer[vr_setup_hmd1].y, calc(-145 * webvr_setup_scale));
+		set(layer[vr_setup_hmd2].y, calc(-145 * webvr_setup_scale));
+		
+		copy(old_selection, selected_var);
+		vr_setup_select('headset');
+	</action>
+	
+	
+
+	<action name="vr_setup_calibration">
+		set(layer[vr_setup_m1].visible,false);
+		set(layer[vr_setup_m2].visible,true);
+	</action>
+	
+	<action name="vr_setup_close_sub_menus">
+		set(layer[vr_setup_bg].bgalpha, 0.5);
+		
+		set(layer[vr_setup_m1].visible,true);
+		set(layer[vr_setup_m2].visible,false);
+		set(layer[vr_setup_m3].visible,false);
+		
+		set(layer[vr_setup_hmd1].parent, vr_setup_m1);
+		set(layer[vr_setup_hmd2].parent, vr_setup_m1);
+		set(layer[vr_setup_btn1].parent, vr_setup_m1);
+		set(layer[vr_setup_btn2].parent, vr_setup_m1);
+		
+		set(layer[vr_setup_hmd1].y, calc(+35 * webvr_setup_scale));
+		set(layer[vr_setup_hmd2].y, calc(+35 * webvr_setup_scale));
+		
+		if(old_selection,
+			vr_setup_select(get(old_selection));
+			delete(old_selection);
+		  );
+	</action>
+	
+	<action name="vr_setup_do_calibration">
+		if(!webvr.isfake,
+			tween(layer[vr_setup_cb6].alpha, 1.0, 0.1);
+			tween(layer[vr_setup_cb7].alpha, 0.0, 0.1);
+			tween(layer[vr_setup_cb8].alpha, 0.0, 0.1);
+			webvr.calibrate(
+				tween(layer[vr_setup_cb6].alpha, 0.0, 0.1);
+				tween(layer[vr_setup_cb7].alpha, 1.0, 0.1);
+				delayedcall(2.0, tween(layer[vr_setup_cb7].alpha, 0.0, 0.25) );
+			  ,
+				tween(layer[vr_setup_cb6].alpha, 0.0, 0.1);
+				tween(layer[vr_setup_cb8].alpha, 1.0, 0.1);
+				delayedcall(2.0, tween(layer[vr_setup_cb8].alpha, 0.0, 0.25) );
+			  );
+		  );
+	</action>
+
+	<action name="vr_setup_update_dist2">
+		txtadd(webvr.mobilevr_lens_dist2, get(i_dist2_k1), '|', calc(i_dist2_k2/10.0), '|', calc(i_dist2_k3/10.0), '|', calc(i_dist2_k4/10.0));
+		vr_setup_change_headset(0);
+	</action>
+
+	<action name="vr_setup_select">
+		<!-- select a setting for adjusting -->
+		set(layer[vr_setup_siz2].background, false);
+		set(layer[vr_setup_ipd2].background, false);
+		set(layer[vr_setup_hmd2].background, false);
+		set(layer[vr_setup_fov2].background, false);
+		set(layer[vr_setup_dst2].background, false);
+		set(layer[vr_setup_d2k1].background, false);
+		set(layer[vr_setup_d2k2].background, false);
+		set(layer[vr_setup_d2k3].background, false);
+		set(layer[vr_setup_d2k4].background, false);
+		set(layer[vr_setup_vig2].background, false);
+		set(layer[vr_setup_cac2].background, false);
+		set(layer[vr_setup_olp2].background, false);
+
+		set(selected_setting, null);
+		delete(selected_var_value);
+
+		set(layer[vr_setup_btn1].ondown, vr_setup_change_ondown(-1) );
+		set(layer[vr_setup_btn2].ondown, vr_setup_change_ondown(+1) );
+		set(selected_var_callback, null);
+
+		set(selected_var, %1);
+
+		if(selected_var == 'screen',
+			set(selected_setting,      vr_setup_siz2);
+			set(selected_var_name,     'webvr.mobilevr_screensize');
+			set(selected_var_postfix,  ' inch');
+			copy(selected_var_value,   get(selected_var_name));
+			if(selected_var_value == 'auto', copy(selected_var_value, webvr.devicesize));
+			if(selected_var_value LE 0, set(selected_var_value, 5.0));
+			set(selected_var_step,     0.1);
+			set(selected_var_min,      4);
+			set(selected_var_max,      10);
+			set(selected_var_round,    1);
+			set(selected_var_callback, vr_setup_change_screen() );
+		  );
+
+		if(selected_var == 'ipd',
+			set(selected_setting,      vr_setup_ipd2);
+			set(selected_var_name,     'webvr.mobilevr_ipd');
+			set(selected_var_postfix,  ' mm');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.1);
+			set(selected_var_min,      40);
+			set(selected_var_max,      80);
+			set(selected_var_round,    1);
+		  );
+
+		if(selected_var == 'headset',
+			set(selected_setting,      vr_setup_hmd2);
+			set(layer[vr_setup_btn1].ondown, vr_setup_change_headset(-1) );
+			set(layer[vr_setup_btn2].ondown, vr_setup_change_headset(+1) );
+		  );
+
+		if(selected_var == 'fov',
+			set(selected_setting,      vr_setup_fov2);
+			set(selected_var_name,     'webvr.mobilevr_lens_fov');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.1);
+			set(selected_var_min,      40);
+			set(selected_var_max,      179);
+			set(selected_var_round,    1);
+			set(selected_var_callback, vr_setup_change_headset(0) );
+		  );
+
+		if(selected_var == 'dist',
+			set(selected_setting,      vr_setup_dst2);
+			set(selected_var_name,     'webvr.mobilevr_lens_dist');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      0);
+			set(selected_var_max,      5);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_change_headset(0) );
+		  );
+		
+		if(selected_var == 'dist2k1',
+			set(selected_setting,      vr_setup_d2k1);
+			set(selected_var_name,     'i_dist2_k1');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      -9);
+			set(selected_var_max,      +9);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_update_dist2() );
+		  );
+		
+		if(selected_var == 'dist2k2',
+			set(selected_setting,      vr_setup_d2k2);
+			set(selected_var_name,     'i_dist2_k2');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      -9);
+			set(selected_var_max,      +9);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_update_dist2() );
+		  );
+		
+		if(selected_var == 'dist2k3',
+			set(selected_setting,      vr_setup_d2k3);
+			set(selected_var_name,     'i_dist2_k3');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      -9);
+			set(selected_var_max,      +9);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_update_dist2() );
+		  );
+
+		if(selected_var == 'dist2k4',
+			set(selected_setting,      vr_setup_d2k4);
+			set(selected_var_name,     'i_dist2_k4');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      -9);
+			set(selected_var_max,      +9);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_update_dist2() );
+		  );
+
+		if(selected_var == 'vignette',
+			set(selected_setting,      vr_setup_vig2);
+			set(selected_var_name,     'webvr.mobilevr_lens_vign');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     1);
+			set(selected_var_min,      10);
+			set(selected_var_max,      200);
+			set(selected_var_round,    0);
+			set(selected_var_callback, vr_setup_change_headset(0) );
+		  );
+
+		if(selected_var == 'ca',
+			set(selected_setting,      vr_setup_cac2);
+			set(selected_var_name,     'webvr.mobilevr_lens_ca');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      -1.0);
+			set(selected_var_max,      +1.0);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_change_headset(0) );
+		  );
+		  
+		if(selected_var == 'overlap',
+			set(selected_setting,      vr_setup_olp2);
+			set(selected_var_name,     'webvr.mobilevr_lens_overlap');
+			set(selected_var_postfix,  '');
+			copy(selected_var_value,   get(selected_var_name));
+			set(selected_var_step,     0.01);
+			set(selected_var_min,      0.5);
+			set(selected_var_max,      2.0);
+			set(selected_var_round,    2);
+			set(selected_var_callback, vr_setup_change_headset(0) );
+		  );
+
+		if(selected_setting != null,
+			set(layer[get(selected_setting)].background, true);
+			if(%2 == true,
+				set(layer[vr_setup_btn1].y, get(layer[get(selected_setting)].y));
+				set(layer[vr_setup_btn2].y, get(layer[get(selected_setting)].y));
+			  ,
+				tween(layer[vr_setup_btn1].y, get(layer[get(selected_setting)].y));
+				tween(layer[vr_setup_btn2].y, get(layer[get(selected_setting)].y));
+			  );
+		  );
+	</action>
+
+
+	<action name="vr_setup_change_screen">
+		set(layer[vr_setup_dvn2].html, 'Custom');
+		set(layer[vr_setup_dvn2].css, calc('color:#FFFFFF;font-size:'+40*webvr_setup_scale+'px;font-weight:bold;'));
+		set(layer[vr_setup_siz2].css, calc('color:#FFFFFF;font-size:'+40*webvr_setup_scale+'px;font-weight:bold;'));
+	</action>
+
+
+	<action name="vr_setup_change_ondown">
+		copy(t0,timertick);
+		set(t1,0);
+		asyncloop(pressed,
+			copy(t2,timertick);
+			sub(dt,t2,t1);
+			if(dt GT 100,
+				copy(t1,t2);
+				sub(dt,t1,t0);
+				div(dt,1000);
+				Math.max(dt,1);
+				mul(dt,%1);
+				vr_setup_adjust(get(dt));
+			  );
+		  );
+	</action>
+
+
+	<action name="vr_setup_adjust">
+		if(selected_setting != null,
+			mul(change, selected_var_step, %1);
+			add(selected_var_value, change);
+			Math.max(selected_var_value, selected_var_min);
+			Math.min(selected_var_value, selected_var_max);
+			roundval(selected_var_value, get(selected_var_round));
+			tween(get(selected_var_name), get(selected_var_value), 0.1);
+			txtadd(layer[get(selected_setting)].html, get(selected_var_value), get(selected_var_postfix));
+			if(selected_var_callback != null, selected_var_callback());
+		  );
+	</action>
+
+
+	<action name="vr_setup_change_headset">
+		set(i_headset, 'Custom');
+		if(%1 != 0,
+			copy(i_fov, webvr.mobilevr_lens_fov);
+			roundval(i_fov, 1);
+			copy(i_dist, webvr.mobilevr_lens_dist);
+			roundval(i_dist, 2);
+			copy(i_dist2, webvr.mobilevr_lens_dist2);
+			copy(i_vig, webvr.mobilevr_lens_vign);
+			roundval(i_vig, 0);
+			copy(i_ca, webvr.mobilevr_lens_ca);
+			roundval(i_ca, 3);
+			copy(i_overlap, webvr.mobilevr_lens_overlap);
+			roundval(i_overlap, 2);
+			set(i_hsindex, -1);
+			copy(i_hscount, vrheadsets.headset.count);
+			for(set(i,0), i LT i_hscount, inc(i),
+				copy(hs, vrheadsets.headset[get(i)]);
+				if(i_overlap == hs.overlap AND i_fov == hs.fov AND i_dist == hs.dist AND i_dist2 == hs.dist2 AND i_ca == hs.ca AND i_vig == hs.vig , copy(i_hsindex, i); copy(i_headset, hs.caption); );
+			   );
+
+			if(%1 GT 0,
+				<!-- loop right -->
+				add(i_hsindex, 1);
+				if(i_hsindex GE i_hscount, set(i_hsindex,0));
+			  ,
+				<!-- loop left -->
+				sub(i_hsindex, 1);
+				if(i_hsindex LT 0, sub(i_hsindex,i_hscount,1));
+			  );
+
+			copy(hs, vrheadsets.headset[get(i_hsindex)]);
+			copy(i_headset, hs.caption);
+			copy(i_overlap, hs.overlap);
+			copy(i_fov,     hs.fov);
+			copy(i_dist,    hs.dist);
+			copy(i_dist2,   hs.dist2);
+			copy(i_ca,      hs.ca);
+			copy(i_vig,     hs.vig);
+		  );
+
+		copy(layer[vr_setup_hmd2].html, i_headset);
+		if(%1 != 0,
+			copy(webvr.mobilevr_lens_overlap, i_overlap);
+			copy(webvr.mobilevr_lens_fov, i_fov);
+			copy(webvr.mobilevr_lens_dist, i_dist);
+			copy(webvr.mobilevr_lens_dist2, i_dist2);
+			copy(webvr.mobilevr_lens_ca, i_ca);
+			copy(webvr.mobilevr_lens_vign, i_vig);
+			copy(layer[vr_setup_olp2].html, i_overlap);
+			copy(layer[vr_setup_fov2].html, i_fov);
+			copy(layer[vr_setup_dst2].html, i_dist);
+			
+			txtsplit(i_dist2, '|', i_dist2_k1, i_dist2_k2, i_dist2_k3, i_dist2_k4);
+			mul(i_dist2_k1,1);
+			mul(i_dist2_k2,10);
+			mul(i_dist2_k3,10);
+			mul(i_dist2_k4,10);
+			roundval(i_dist2_k1,2);
+			roundval(i_dist2_k2,2);
+			roundval(i_dist2_k3,2);
+			roundval(i_dist2_k4,2);
+			copy(layer[vr_setup_d2k1].html, i_dist2_k1);
+			copy(layer[vr_setup_d2k2].html, i_dist2_k2);
+			copy(layer[vr_setup_d2k3].html, i_dist2_k3);
+			copy(layer[vr_setup_d2k4].html, i_dist2_k4);
+			
+			copy(layer[vr_setup_cac2].html, i_ca);
+			copy(layer[vr_setup_vig2].html, i_vig);
+		  );
+	</action>
+
+</krpano>

BIN
packages/mobile/public/lib/krpano/plugins/webvr_cursor_80x80_17f.png


+ 21 - 0
packages/mobile/public/lib/krpano/skin/img_doticon_01.svg

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>img_doticon_01</title>
+    <g id="编辑器" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" transform="translate(-301.000000, -514.000000)" fill-rule="nonzero">
+            <g id="img_doticon_01" transform="translate(301.000000, 514.000000)">
+                <rect id="box" fill-opacity="0" fill="#FFFFFF" x="0" y="0" width="28" height="28"></rect>
+                <g id="组_3856" transform="translate(0.000000, 6.000000)">
+                    <path d="M14.001,7.555 L0.056,15.785 C0.0126054449,15.8112854 -0.00867245421,15.8627216 0.00347072632,15.9119816 C0.0156139069,15.9612417 0.058360381,15.9968951 0.109,16 L6.222,16 L13.999,11.41 L21.776,16 L27.89,16 C27.9406396,15.9968951 27.9833861,15.9612417 27.9955293,15.9119816 C28.0076725,15.8627216 27.9863946,15.8112854 27.943,15.785 L14.001,7.555 Z" id="路径_1339-2" fill="#000000" opacity="0.4"></path>
+                    <path d="M14.002,4.683 L19.961,7.875 L24.511,7.875 C24.5624468,7.87202014 24.6057597,7.83545696 24.6173305,7.78523975 C24.6289013,7.73502254 24.6059555,7.68319237 24.561,7.658 L14.002,2 L3.443,7.658 C3.39804452,7.68319237 3.37509869,7.73502254 3.38666948,7.78523975 C3.39824026,7.83545696 3.44155322,7.87202014 3.493,7.875 L8.043,7.875 L14.002,4.683 Z" id="路径_1340-2" fill="#000000" opacity="0.6"></path>
+                    <g id="编组" transform="translate(0.000000, 5.000000)" fill="#FFFFFF" opacity="0.8">
+                        <path d="M13.998,0.553 L0.051,8.796 C0.00871211887,8.82153017 -0.0116166444,8.87193307 0.00112551213,8.91965824 C0.0138676686,8.96738341 0.0566143459,9.00094578 0.106,9.002 L6.227,9.002 L13.996,4.411 L21.765,9.002 L27.888,9.002 C27.9373857,9.00094578 27.9801323,8.96738341 27.9928745,8.91965824 C28.0056166,8.87193307 27.9852879,8.82153017 27.943,8.796 L13.998,0.553 Z" id="路径_1339-3-2"></path>
+                    </g>
+                    <g id="编组" transform="translate(3.000000, 0.000000)" fill="#FFFFFF">
+                        <path d="M11.003,2.681 L16.956,5.873 L21.517,5.873 C21.567027,5.87169479 21.6099942,5.83707541 21.6219116,5.78847112 C21.633829,5.73986684 21.6117492,5.68929839 21.568,5.665 L11.003,-0.001 L0.437,5.665 C0.393250843,5.68929839 0.371170959,5.73986684 0.383088355,5.78847112 C0.395005751,5.83707541 0.437973037,5.87169479 0.488,5.873 L5.05,5.873 L11.003,2.681 Z" id="路径_1340-3-2"></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
packages/mobile/public/lib/krpano/skin/rotate_device.png


BIN
packages/mobile/public/lib/krpano/skin/vtourskin.png


File diff suppressed because it is too large
+ 1297 - 0
packages/mobile/public/lib/krpano/skin/vtourskin.xml


+ 28 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_117.xml

@@ -0,0 +1,28 @@
+<krpano>
+
+	<!-- Version 1.17 Design -->
+
+	<skin_settings layout_width="100%"
+	               layout_maxwidth=""
+	               controlbar_width="100%"
+	               controlbar_height.normal="40"
+	               controlbar_height.mobile="38"
+	               controlbar_offset.normal="20"
+	               controlbar_offset.mobile="0"
+	               controlbar_offset_closed="-2"
+	               controlbar_overlap="0"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0x000000"
+	               design_bgalpha="0.5"
+	               design_bgborder="1,0 0xFFFFFF 1"
+	               design_bgroundedge.no-ios="0"
+	               design_bgroundedge.ios="1"
+	               design_bgshadow="0 0 20 0x000000 1.0"
+	               design_thumbborder_bgborder="4 0xFFFFFF 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="3"
+	               design_text_css="color:#FFFFFF; font-family:Arial; font-weight:bold;"
+	               design_text_shadow="1"
+	               />
+
+</krpano>

+ 31 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_117round.xml

@@ -0,0 +1,31 @@
+<krpano>
+
+	<!-- Version 1.17 Round Design -->
+
+	<skin_settings layout_width="100%"
+	               layout_maxwidth.normal="900"
+	               layout_maxwidth.mobile=""
+	               controlbar_width.normal="-44"
+	               controlbar_width.mobile="100%"
+	               controlbar_height.normal="38"
+	               controlbar_height.mobile="34"
+	               controlbar_offset.normal="22"
+	               controlbar_offset.mobile="0"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap.normal="7"
+	               controlbar_overlap.mobile="2"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0x000000"
+	               design_bgalpha="0.5"
+	               design_bgborder="0 0xFFFFFF 1.0"
+	               design_bgroundedge.normal="9"
+	               design_bgroundedge.mobile="1"
+	               design_bgshadow="0 0 9 0xFFFFFF 0.5"
+	               design_thumbborder_bgborder="4 0xFFFFFF 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="5"
+	               design_text_css="color:#FFFFFF; font-family:Arial; font-weight:bold;"
+	               design_text_shadow="1"
+	               />
+
+</krpano>

+ 71 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_black.xml

@@ -0,0 +1,71 @@
+<krpano>
+
+	<!-- Flat Light Design -->
+
+	<!-- modify the <skin_settings> values -->
+	<skin_settings layout_width="100%"
+	               layout_maxwidth="100%"
+	               controlbar_width="100%"
+	               controlbar_height="40"
+	               controlbar_offset.normal="0"
+	               controlbar_offset.mobile="0"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap.no-fractionalscaling="0"
+	               controlbar_overlap.fractionalscaling="0"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0x000000"
+	               design_bgalpha="1.0"
+	               design_bgborder="0"
+	               design_bgroundedge="0"
+	               design_bgshadow="0 4 10 0xFFFFFF 0.3"
+	               design_thumbborder_bgborder="3 0xB2B2B2 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="0"
+	               design_text_css="color:#FFFFFF; font-family:Arial;"
+	               design_text_shadow="0"
+	               />
+
+	<!-- adjust the design of some skin elements  -->
+	<layer name="skin_layer">
+		<layer name="skin_loadingtext" width="100%" css="calc:skin_settings.design_text_css + ' text-align:center; font-size:20px;'" padding="4 6" textshadow="calc:2.0" textshadowrange="1.0" textshadowangle="90" textshadowcolor="0x2D3E50" textshadowalpha="1.0" />
+		<layer name="skin_control_bar" alpha="0.7" />
+	</layer>
+
+
+	<!-- use a fullscreen map -->
+	<action name="skin_showmap">
+		if(%1 == null, if(layer[skin_map].state == 'closed', set(show,true), set(show,false)); , set(show,%1); );
+		mul(mh, layer[skin_scroll_layer].pixelheight, -1);
+		if(show,
+			tween(layer[skin_thumbs_container].alpha, 0.0, 0.25, default, set(layer[skin_thumbs_container].visible,false));
+			set(layer[skin_map].visible, true);
+			tween(layer[skin_map].alpha, 1.0, 0.25);
+			set(layer[skin_map].state, 'opened');
+			sub(hh,area.pixelheight,skin_settings.controlbar_offset);
+			sub(hh,layer[skin_control_bar].height);
+			sub(hh,0);
+			add(mh,hh);
+			sub(hh,skin_settings.controlbar_overlap);
+			copy(layer[skin_map].height, hh);
+			tween(layer[skin_scroll_layer].y, get(mh), 0.5, easeOutQuint);
+		  ,
+		  	if(layer[skin_map].state != 'closed',
+				set(layer[skin_map].state, 'closed');
+				add(mh, layer[skin_scroll_layer].y_offset);
+				tween(layer[skin_map].alpha, 0.0, 0.5, easeOutQuint);
+				tween(layer[skin_scroll_layer].y, get(mh), 0.5, easeOutQuint, set(layer[skin_map].visible,false) );
+			  );
+		  );
+	</action>
+
+
+	<!-- webvr button style (adjust to match skin style) -->
+	<style name="webvr_button_style"
+	       border="false"
+	       roundedge="calc:1.0"
+	       backgroundcolor="get:skin_settings.design_bgcolor" backgroundalpha="get:skin_settings.design_bgalpha"
+	       shadow="0.01" shadowrange="10.0" shadowangle="90.0" shadowcolor="0x30261B" shadowalpha="0.50"
+	       css="calc:skin_settings.design_text_css + ' font-size:' + 20*webvr_setup_scale*webvr_button_scale + 'px;'"
+	       />
+
+</krpano>

+ 42 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_flat_light.xml

@@ -0,0 +1,42 @@
+<krpano>
+
+	<!-- Flat Light Design -->
+
+	<!-- modify the <skin_settings> values -->
+	<skin_settings layout_width="100%"
+	               layout_maxwidth="814"
+	               controlbar_width="-24"
+	               controlbar_height="40"
+	               controlbar_offset.normal="20"
+	               controlbar_offset.mobile="20"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap.no-fractionalscaling="10"
+	               controlbar_overlap.fractionalscaling="0"
+	               design_skin_images="vtourskin_light.png"
+	               design_bgcolor="0x2D3E50"
+	               design_bgalpha="0.8"
+	               design_bgborder="0"
+	               design_bgroundedge="1"
+	               design_bgshadow="0 4 10 0x000000 0.3"
+	               design_thumbborder_bgborder="2 0xFFFFFF 0.8"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="0"
+	               design_text_css="color:#FFFFFF; font-family:Arial; font-weight:lighter;"
+	               design_text_shadow="1"
+	               />
+
+	<!-- adjust the design of some skin elements  -->
+	<layer name="skin_layer">
+		<layer name="skin_loadingtext" width="100%" css="calc:skin_settings.design_text_css + ' text-align:center; font-size:20px;'" padding="4 6" textshadow="calc:2.0" textshadowrange="1.0" textshadowangle="90" textshadowcolor="0x2D3E50" textshadowalpha="1.0" />
+	</layer>
+
+	<!-- webvr button style (adjust to match skin style) -->
+	<style name="webvr_button_style"
+	       border="false"
+	       roundedge="calc:1.0"
+	       backgroundcolor="get:skin_settings.design_bgcolor" backgroundalpha="get:skin_settings.design_bgalpha"
+	       shadow="0.01" shadowrange="10.0" shadowangle="90.0" shadowcolor="0x30261B" shadowalpha="0.50"
+	       css="calc:skin_settings.design_text_css + ' font-size:' + 20*webvr_setup_scale*webvr_button_scale + 'px;'"
+	       />
+
+</krpano>

+ 40 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_glass.xml

@@ -0,0 +1,40 @@
+<krpano>
+
+	<!-- Glass Design -->
+
+	<!-- modify the <skin_settings> values -->
+	<skin_settings thumbs_scrollindicator="true"
+	               layout_width="100%"
+	               layout_maxwidth="680"
+	               xcontrolbar_width="-44"
+	               controlbar_width="-20"
+	               controlbar_height="36"
+	               controlbar_offset.normal="40"
+	               controlbar_offset.mobile="12"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap="10"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0xFFFFFF"
+	               design_bgalpha="0.25"
+	               design_bgborder="2 0xFFFFFF 0.1"
+	               design_bgroundedge="13"
+	               design_bgshadow="0"
+	               design_thumbborder_bgborder="3 0xFFFFFF 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="5"
+	               design_text_css="color:#FFFFFF; font-family:Arial; font-weight:bold;"
+	               design_text_shadow="0"
+	               />
+
+	<!-- webvr button style (adjust to match default skin style) -->
+	<style name="webvr_button_style"
+	       border="true" borderwidth="2" bordercolor="0xFFFFFF" borderalpha="0.25"
+	       backgroundcolor="get:skin_settings.design_bgcolor" backgroundalpha="get:skin_settings.design_bgalpha"
+	       shadow="0"
+	       css="calc:skin_settings.design_text_css + ' color:#FFFFFF; font-weight:normal; font-size:' + 20*webvr_setup_scale*webvr_button_scale + 'px;'"
+	       />
+
+	<!-- contextmenu style (adjust to match skin style) -->
+	<contextmenu customstyle="default|default|default|0x77AAAAAA|0xFFFFFF|0xBBBBBB|2|0x7FFFFFFF|13|0|0|0|0xFFFFFF|0|0|4|6|7|0xAAFFFFFF|none|3|0|0|0|3|0xAAFFFFFF|0xAAFFFFFF|0xFFFFFF|12|8" />
+
+</krpano>

+ 58 - 0
packages/mobile/public/lib/krpano/skin/vtourskin_design_ultra_light.xml

@@ -0,0 +1,58 @@
+<krpano>
+
+	<!-- Ultra Light Design -->
+
+	<!-- modify the <skin_settings> values -->
+	<skin_settings title="false"
+	               layout_width="100%"
+	               layout_maxwidth="814"
+	               controlbar_width="-24"
+	               controlbar_height="40"
+	               controlbar_offset.normal="30"
+	               controlbar_offset.mobile="20"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap="0"
+	               design_skin_images="vtourskin_light.png"
+	               design_bgcolor="0x2D3E50"
+	               design_bgalpha="0.0"
+	               design_bgborder="0,0,1,0 0xFFFFFF 1"
+	               design_bgroundedge="0"
+	               design_bgshadow="0"
+	               design_thumbborder_bgborder="1 0xFFFFFF 0.8"
+	               design_thumbborder_padding="0"
+	               design_thumbborder_bgroundedge="0"
+	               design_text_css="color:#FFFFFF; font-family:Arial; font-weight:lighter;"
+	               design_text_shadow="0"
+	               />
+
+	<!-- webvr button style (adjust to match skin style) -->
+	<style name="webvr_button_style"
+	       border="false"
+	       roundedge="calc:1.0"
+	       backgroundcolor="get:skin_settings.design_bgcolor" backgroundalpha="get:skin_settings.design_bgalpha"
+	       shadow="0.01" shadowrange="10.0" shadowangle="90.0" shadowcolor="0x30261B" shadowalpha="0.50"
+	       css="calc:skin_settings.design_text_css + ' font-size:' + 20*webvr_setup_scale*webvr_button_scale + 'px;'"
+	       />
+
+	<!-- adjust video controls to match skin style -->
+	<layer name="skin_layer">
+		<layer name="skin_scroll_window">
+			<layer name="skin_scroll_layer">
+				<layer name="skin_video_controls">
+					<layer name="skin_video_seekbar_container">
+						<layer name="skin_video_seekbar" height="1" y="4">
+							<layer name="skin_video_loadbar" height="1" />
+							<layer name="skin_video_seekpos" bgroundedge="2" width="6" height="6" />
+						</layer>
+					</layer>
+					<layer name="skin_video_time" y="-4" />
+				</layer>
+			</layer>
+		</layer>
+	</layer>
+					
+
+	<!-- contextmenu style (adjust to match skin style) -->
+	<contextmenu customstyle="default|default|default|0x7F000000|0xFFFFFF|0xBBBBBB|0|0x20FFFFFF|1|0|0|0|0xFFFFFF|0|0xFFFFFF|4|6|7|0xAAFFFFFF|none|3|0|0|0|3|0xFFFFFF|0xFFFFFF|0x000000|12|8" />
+
+</krpano>

BIN
packages/mobile/public/lib/krpano/skin/vtourskin_hotspot.png


BIN
packages/mobile/public/lib/krpano/skin/vtourskin_light.png


BIN
packages/mobile/public/lib/krpano/skin/vtourskin_mapspot.png


BIN
packages/mobile/public/lib/krpano/skin/vtourskin_mapspotactive.png


+ 127 - 0
packages/mobile/public/lib/krpano/tour.xml

@@ -0,0 +1,127 @@
+<krpano version="1.19" title="Virtual Tour">
+
+	<include url="%SWFPATH%/skin/vtourskin.xml" />
+
+	<!-- customize skin settings: maps, gyro, webvr, thumbnails, tooltips, layout, design, ... -->
+	<skin_settings maps="false"
+	               maps_type="google"
+	               maps_bing_api_key=""
+	               maps_google_api_key=""
+	               maps_zoombuttons="false"
+	               gyro="true"
+	               webvr="true"
+	               webvr_gyro_keeplookingdirection="false"
+	               webvr_prev_next_hotspots="true"
+	               littleplanetintro="false"
+	               title="true"
+	               thumbs="true"
+	               thumbs_width="120" thumbs_height="80" thumbs_padding="10" thumbs_crop="0|40|240|160"
+	               thumbs_opened="false"
+	               thumbs_text="false"
+	               thumbs_dragging="true"
+	               thumbs_onhoverscrolling="false"
+	               thumbs_scrollbuttons="false"
+	               thumbs_scrollindicator="false"
+	               thumbs_loop="false"
+	               tooltips_buttons="false"
+	               tooltips_thumbs="false"
+	               tooltips_hotspots="false"
+	               tooltips_mapspots="false"
+	               deeplinking="false"
+	               loadscene_flags="MERGE"
+	               loadscene_blend="OPENBLEND(0.5, 0.0, 0.75, 0.05, linear)"
+	               loadscene_blend_prev="SLIDEBLEND(0.5, 180, 0.75, linear)"
+	               loadscene_blend_next="SLIDEBLEND(0.5,   0, 0.75, linear)"
+	               loadingtext="loading..."
+	               layout_width="100%"
+	               layout_maxwidth="814"
+	               controlbar_width="-24"
+	               controlbar_height="40"
+	               controlbar_offset="20"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap.no-fractionalscaling="10"
+	               controlbar_overlap.fractionalscaling="0"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0x2D3E50"
+	               design_bgalpha="0.8"
+	               design_bgborder="0"
+	               design_bgroundedge="1"
+	               design_bgshadow="0 4 10 0x000000 0.3"
+	               design_thumbborder_bgborder="3 0xFFFFFF 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="0"
+	               design_text_css="color:#FFFFFF; font-family:Arial;"
+	               design_text_shadow="1"
+	               />
+
+	<!--
+	    For an alternative skin design either change the <skin_settings> values 
+	    from above or optionally include one of the predefined designs from below.
+	-->
+	<!-- <include url="skin/vtourskin_design_flat_light.xml"  /> -->
+	<!-- <include url="skin/vtourskin_design_glass.xml"       /> -->
+	<!-- <include url="skin/vtourskin_design_ultra_light.xml" /> -->
+	<!-- <include url="skin/vtourskin_design_117.xml"         /> -->
+	<!-- <include url="skin/vtourskin_design_117round.xml"    /> -->
+	<!-- <include url="skin/vtourskin_design_black.xml"       /> -->
+
+
+	<!-- startup action - load the first scene -->
+	<action name="startup" autorun="onstart">
+		if(startscene === null OR !scene[get(startscene)], copy(startscene,scene[0].name); );
+		loadscene(get(startscene), null, MERGE);
+		if(startactions !== null, startactions() );
+	</action>
+
+
+	
+	 
+	
+	<scene name="scene_LC720_0t8dybyQW" title="LC720_0t8dybyQW" onstart="" thumburl="panos/LC720_0t8dybyQW.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="panos/LC720_0t8dybyQW.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="5376" tiledimageheight="5376">
+				<cube url="panos/LC720_0t8dybyQW.tiles/%s/l4/%v/l4_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="2688" tiledimageheight="2688">
+				<cube url="panos/LC720_0t8dybyQW.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="panos/LC720_0t8dybyQW.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="panos/LC720_0t8dybyQW.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+	
+	<scene name="scene_LC720_LPxLKsriO" title="LC720_LPxLKsriO" onstart="" thumburl="panos/LC720_LPxLKsriO.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="panos/LC720_LPxLKsriO.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="2624" tiledimageheight="2624">
+				<cube url="panos/LC720_LPxLKsriO.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="panos/LC720_LPxLKsriO.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="panos/LC720_LPxLKsriO.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+ </krpano>

+ 207 - 0
packages/mobile/public/lib/krpano/tour_test1.xml

@@ -0,0 +1,207 @@
+<krpano version="1.19" debugmode="true" logkey="true" title="Virtual Tour">
+
+	<include url="%SWFPATH%/skin/vtourskin.xml" />
+
+	<!-- customize skin settings: maps, gyro, webvr, thumbnails, tooltips, layout, design, ... -->
+	<skin_settings maps="false"
+	               maps_type="google"
+	               maps_bing_api_key=""
+	               maps_google_api_key=""
+	               maps_zoombuttons="false"
+	               gyro="true"
+	               webvr="true"
+	               webvr_gyro_keeplookingdirection="false"
+	               webvr_prev_next_hotspots="true"
+	               littleplanetintro="false"
+	               title="true"
+	               thumbs="true"
+	               thumbs_width="120" thumbs_height="80" thumbs_padding="10" thumbs_crop="0|40|240|160"
+	               thumbs_opened="false"
+	               thumbs_text="false"
+	               thumbs_dragging="true"
+	               thumbs_onhoverscrolling="false"
+	               thumbs_scrollbuttons="false"
+	               thumbs_scrollindicator="false"
+	               thumbs_loop="false"
+	               tooltips_buttons="false"
+	               tooltips_thumbs="false"
+	               tooltips_hotspots="false"
+	               tooltips_mapspots="false"
+	               deeplinking="false"
+	               loadscene_flags="MERGE"
+	               loadscene_blend="OPENBLEND(0.5, 0.0, 0.75, 0.05, linear)"
+	               loadscene_blend_prev="SLIDEBLEND(0.5, 180, 0.75, linear)"
+	               loadscene_blend_next="SLIDEBLEND(0.5,   0, 0.75, linear)"
+	               loadingtext="loading..."
+	               layout_width="100%"
+	               layout_maxwidth="814"
+	               controlbar_width="-24"
+	               controlbar_height="40"
+	               controlbar_offset="20"
+	               controlbar_offset_closed="-40"
+	               controlbar_overlap.no-fractionalscaling="10"
+	               controlbar_overlap.fractionalscaling="0"
+	               design_skin_images="vtourskin.png"
+	               design_bgcolor="0x2D3E50"
+	               design_bgalpha="0.8"
+	               design_bgborder="0"
+	               design_bgroundedge="1"
+	               design_bgshadow="0 4 10 0x000000 0.3"
+	               design_thumbborder_bgborder="3 0xFFFFFF 1.0"
+	               design_thumbborder_padding="2"
+	               design_thumbborder_bgroundedge="0"
+	               design_text_css="color:#FFFFFF; font-family:Arial;"
+	               design_text_shadow="1"
+	               />
+
+	<!--
+	    For an alternative skin design either change the <skin_settings> values 
+	    from above or optionally include one of the predefined designs from below.
+	-->
+	<!-- <include url="skin/vtourskin_design_flat_light.xml"  /> -->
+	<!-- <include url="skin/vtourskin_design_glass.xml"       /> -->
+	<!-- <include url="skin/vtourskin_design_ultra_light.xml" /> -->
+	<!-- <include url="skin/vtourskin_design_117.xml"         /> -->
+	<!-- <include url="skin/vtourskin_design_117round.xml"    /> -->
+	<!-- <include url="skin/vtourskin_design_black.xml"       /> -->
+
+
+
+	
+	 
+
+
+	
+	<scene name="scene_LC720_0t8dybyQW" title="LC720_0t8dybyQW" onstart="" thumburl="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="5376" tiledimageheight="5376">
+				<cube url="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/%s/l4/%v/l4_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="2688" tiledimageheight="2688">
+				<cube url="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="http://192.168.0.245:8007/pano/LC720_0t8dybyQW/panos/LC720_0t8dybyQW.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+
+
+
+	
+	<scene name="scene_LC720_LPxLKsriO" title="LC720_LPxLKsriO" onstart="" thumburl="http://192.168.0.245:8007/pano/LC720_LPxLKsriO/panos/LC720_LPxLKsriO.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="http://192.168.0.245:8007/pano/LC720_LPxLKsriO/panos/LC720_LPxLKsriO.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="2624" tiledimageheight="2624">
+				<cube url="http://192.168.0.245:8007/pano/LC720_LPxLKsriO/panos/LC720_LPxLKsriO.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="http://192.168.0.245:8007/pano/LC720_LPxLKsriO/panos/LC720_LPxLKsriO.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="http://192.168.0.245:8007/pano/LC720_LPxLKsriO/panos/LC720_LPxLKsriO.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+
+
+
+	
+	<scene name="scene_LC720_E9iaNYFNU" title="LC720_E9iaNYFNU" onstart="" thumburl="http://192.168.0.245:8007/pano/LC720_E9iaNYFNU/panos/LC720_E9iaNYFNU.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="http://192.168.0.245:8007/pano/LC720_E9iaNYFNU/panos/LC720_E9iaNYFNU.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="http://192.168.0.245:8007/pano/LC720_E9iaNYFNU/panos/LC720_E9iaNYFNU.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="http://192.168.0.245:8007/pano/LC720_E9iaNYFNU/panos/LC720_E9iaNYFNU.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+
+
+
+	
+	<scene name="scene_LC720_iFNiGfEaD" title="LC720_iFNiGfEaD" onstart="" thumburl="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="5376" tiledimageheight="5376">
+				<cube url="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/%s/l4/%v/l4_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="2688" tiledimageheight="2688">
+				<cube url="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="http://192.168.0.245:8007/pano/LC720_iFNiGfEaD/panos/LC720_iFNiGfEaD.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+
+
+
+	
+	<scene name="scene_LC720_xBDfIKWZu" title="LC720_xBDfIKWZu" onstart="" thumburl="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/thumb.jpg" lat="" lng="" heading="">
+
+		<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
+
+		<preview url="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/preview.jpg" />
+
+		<image type="CUBE" multires="true" tilesize="512">
+			<level tiledimagewidth="5376" tiledimageheight="5376">
+				<cube url="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/%s/l4/%v/l4_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="2688" tiledimageheight="2688">
+				<cube url="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/%s/l3/%v/l3_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="1280" tiledimageheight="1280">
+				<cube url="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/%s/l2/%v/l2_%s_%v_%h.jpg" />
+			</level>
+			<level tiledimagewidth="640" tiledimageheight="640">
+				<cube url="http://192.168.0.245:8007/pano/LC720_xBDfIKWZu/panos/LC720_xBDfIKWZu.tiles/%s/l1/%v/l1_%s_%v_%h.jpg" />
+			</level>
+		</image>
+
+	</scene>
+
+
+
+ </krpano>

+ 734 - 0
packages/mobile/public/lib/leaflet.css

@@ -0,0 +1,734 @@
+/* required styles */
+
+.leaflet-pane,
+
+#mapDiv { height: 300px; }
+    #mapDiv .point{
+        width: 15px ;
+        height: 15px;
+        background: #3DC5C4;
+        border-radius: 15px;
+        border: 3px solid #FFFFFF;
+        position: relative;
+    }
+    #mapDiv .point .point_bg{
+        width: 100px;
+        height: 100px;
+        position: absolute;
+        top: -42px;
+        left: -42px;
+        z-index: -1;
+        overflow: hidden;
+        border-radius: 50%;
+        display: none;
+    }
+    
+    #mapDiv .point .click_model{
+        width: 40px;
+        height: 40px;
+        position: absolute;
+        top: -12px;
+        left: -12px;
+        z-index: 5;
+        cursor: pointer;
+    }
+    #mapDiv .point .point_bg .rotate{
+        width: 50px;
+        height: 50px;
+        background: rgba(0,0,0,.4);
+        box-sizing: border-box;
+        border-right: 1px solid #ffffff;
+        border-bottom: 1px solid #FFFFFF;
+    }
+    #mapDiv .point.active{
+        background: #ff0000;
+    }
+    #mapDiv .point.active .point_bg{
+        display: block;
+    }
+
+
+
+	#shapan .point{
+        width: 15px ;
+        height: 15px;
+        background: #3DC5C4;
+        border-radius: 15px;
+        border: 3px solid #FFFFFF;
+        position: relative;
+    }
+    #shapan .point .point_bg{
+        width: 100px;
+        height: 100px;
+        position: absolute;
+        top: -42px;
+        left: -42px;
+        z-index: -1;
+        overflow: hidden;
+        border-radius: 50%;
+        display: none;
+    }
+    
+    #shapan .point .click_model{
+        width: 40px;
+        height: 40px;
+        position: absolute;
+        top: -12px;
+        left: -12px;
+        z-index: 5;
+        cursor: pointer;
+    }
+    #shapan .point .point_bg .rotate{
+        width: 50px;
+        height: 50px;
+        background: rgba(0,0,0,.4);
+        box-sizing: border-box;
+        border-right: 1px solid #ffffff;
+        border-bottom: 1px solid #FFFFFF;
+    }
+    #shapan .point.active{
+        background: #ff0000;
+    }
+    #shapan .point.active .point_bg{
+        display: block;
+    }
+
+
+
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-container,
+.leaflet-pane > svg,
+.leaflet-pane > canvas,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+	position: absolute;
+	left: 0;
+	top: 0;
+	}
+.leaflet-container {
+	overflow: hidden;
+	}
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+	-webkit-user-select: none;
+	   -moz-user-select: none;
+	        user-select: none;
+	  -webkit-user-drag: none;
+	}
+/* Prevents IE11 from highlighting tiles in blue */
+.leaflet-tile::selection {
+	background: transparent;
+}
+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
+.leaflet-safari .leaflet-tile {
+	image-rendering: -webkit-optimize-contrast;
+	}
+/* hack that prevents hw layers "stretching" when loading new tiles */
+.leaflet-safari .leaflet-tile-container {
+	width: 1600px;
+	height: 1600px;
+	-webkit-transform-origin: 0 0;
+	}
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+	display: block;
+	}
+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container .leaflet-overlay-pane svg,
+.leaflet-container .leaflet-marker-pane img,
+.leaflet-container .leaflet-shadow-pane img,
+.leaflet-container .leaflet-tile-pane img,
+.leaflet-container img.leaflet-image-layer,
+.leaflet-container .leaflet-tile {
+	max-width: none !important;
+	max-height: none !important;
+	}
+
+.leaflet-container.leaflet-touch-zoom {
+	-ms-touch-action: pan-x pan-y;
+	touch-action: pan-x pan-y;
+	}
+.leaflet-container.leaflet-touch-drag {
+	-ms-touch-action: pinch-zoom;
+	/* Fallback for FF which doesn't support pinch-zoom */
+	touch-action: none;
+	touch-action: pinch-zoom;
+}
+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.leaflet-container {
+	-webkit-tap-highlight-color: transparent;
+}
+.leaflet-container a {
+	-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
+}
+.leaflet-tile {
+	filter: inherit;
+	visibility: hidden;
+	}
+.leaflet-tile-loaded {
+	visibility: inherit;
+	}
+.leaflet-zoom-box {
+	width: 0;
+	height: 0;
+	-moz-box-sizing: border-box;
+	     box-sizing: border-box;
+	z-index: 800;
+	}
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+	-moz-user-select: none;
+	}
+
+.leaflet-pane         { z-index: 400; }
+
+.leaflet-tile-pane    { z-index: 200; }
+.leaflet-overlay-pane { z-index: 400; }
+.leaflet-shadow-pane  { z-index: 500; }
+.leaflet-marker-pane  { z-index: 600; }
+.leaflet-tooltip-pane   { z-index: 650; }
+.leaflet-popup-pane   { z-index: 700; }
+
+.leaflet-map-pane canvas { z-index: 100; }
+.leaflet-map-pane svg    { z-index: 200; }
+
+.leaflet-vml-shape {
+	width: 1px;
+	height: 1px;
+	}
+.lvml {
+	behavior: url(#default#VML);
+	display: inline-block;
+	position: absolute;
+	}
+
+
+/* control positioning */
+
+.leaflet-control {
+	position: relative;
+	z-index: 800;
+	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+	pointer-events: auto;
+	}
+.leaflet-top,
+.leaflet-bottom {
+	position: absolute;
+	z-index: 1000;
+	pointer-events: none;
+	}
+.leaflet-top {
+	top: 0;
+	}
+.leaflet-right {
+	right: 0;
+	}
+.leaflet-bottom {
+	bottom: 0;
+	}
+.leaflet-left {
+	left: 0;
+	}
+.leaflet-control {
+	float: left;
+	clear: both;
+	}
+.leaflet-right .leaflet-control {
+	float: right;
+	}
+.leaflet-top .leaflet-control {
+	margin-top: 10px;
+	}
+.leaflet-bottom .leaflet-control {
+	margin-bottom: 10px;
+	}
+.leaflet-left .leaflet-control {
+	margin-left: 10px;
+	}
+.leaflet-right .leaflet-control {
+	margin-right: 10px;
+	}
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile {
+	will-change: opacity;
+	}
+.leaflet-fade-anim .leaflet-popup {
+	opacity: 0;
+	-webkit-transition: opacity 0.2s linear;
+	   -moz-transition: opacity 0.2s linear;
+	        transition: opacity 0.2s linear;
+	}
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+	opacity: 1;
+	}
+.leaflet-zoom-animated {
+	-webkit-transform-origin: 0 0;
+	    -ms-transform-origin: 0 0;
+	        transform-origin: 0 0;
+	}
+.leaflet-zoom-anim .leaflet-zoom-animated {
+	will-change: transform;
+	}
+.leaflet-zoom-anim .leaflet-zoom-animated {
+	-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+	   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+	        transition:         transform 0.25s cubic-bezier(0,0,0.25,1);
+	}
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile {
+	-webkit-transition: none;
+	   -moz-transition: none;
+	        transition: none;
+	}
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+	visibility: hidden;
+	}
+
+
+/* cursors */
+
+.leaflet-interactive {
+	cursor: pointer;
+	}
+.leaflet-grab {
+	cursor: -webkit-grab;
+	cursor:    -moz-grab;
+	cursor:         grab;
+	}
+.leaflet-crosshair,
+.leaflet-crosshair .leaflet-interactive {
+	cursor: crosshair;
+	}
+.leaflet-popup-pane,
+.leaflet-control {
+	cursor: auto;
+	}
+.leaflet-dragging .leaflet-grab,
+.leaflet-dragging .leaflet-grab .leaflet-interactive,
+.leaflet-dragging .leaflet-marker-draggable {
+	cursor: move;
+	cursor: -webkit-grabbing;
+	cursor:    -moz-grabbing;
+	cursor:         grabbing;
+	}
+
+/* marker & overlays interactivity */
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-image-layer,
+.leaflet-pane > svg path,
+.leaflet-tile-container {
+	pointer-events: none;
+	}
+
+.leaflet-marker-icon.leaflet-interactive,
+.leaflet-image-layer.leaflet-interactive,
+.leaflet-pane > svg path.leaflet-interactive,
+svg.leaflet-image-layer.leaflet-interactive path {
+	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+	pointer-events: auto;
+	}
+
+/* visual tweaks */
+
+.leaflet-container {
+	background: #ddd;
+	outline: 0;
+	}
+.leaflet-container a {
+	color: #0078A8;
+	}
+.leaflet-container a.leaflet-active {
+	outline: 2px solid orange;
+	}
+.leaflet-zoom-box {
+	border: 2px dotted #38f;
+	background: rgba(255,255,255,0.5);
+	}
+
+
+/* general typography */
+.leaflet-container {
+	font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
+	}
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+	box-shadow: 0 1px 5px rgba(0,0,0,0.65);
+	border-radius: 4px;
+	}
+.leaflet-bar a,
+.leaflet-bar a:hover {
+	background-color: #fff;
+	border-bottom: 1px solid #ccc;
+	width: 26px;
+	height: 26px;
+	line-height: 26px;
+	display: block;
+	text-align: center;
+	text-decoration: none;
+	color: black;
+	}
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+	background-position: 50% 50%;
+	background-repeat: no-repeat;
+	display: block;
+	}
+.leaflet-bar a:hover {
+	background-color: #f4f4f4;
+	}
+.leaflet-bar a:first-child {
+	border-top-left-radius: 4px;
+	border-top-right-radius: 4px;
+	}
+.leaflet-bar a:last-child {
+	border-bottom-left-radius: 4px;
+	border-bottom-right-radius: 4px;
+	border-bottom: none;
+	}
+.leaflet-bar a.leaflet-disabled {
+	cursor: default;
+	background-color: #f4f4f4;
+	color: #bbb;
+	}
+
+.leaflet-touch .leaflet-bar a {
+	width: 30px;
+	height: 30px;
+	line-height: 30px;
+	}
+.leaflet-touch .leaflet-bar a:first-child {
+	border-top-left-radius: 2px;
+	border-top-right-radius: 2px;
+	}
+.leaflet-touch .leaflet-bar a:last-child {
+	border-bottom-left-radius: 2px;
+	border-bottom-right-radius: 2px;
+	}
+
+/* zoom control */
+
+.leaflet-control-zoom-in,
+.leaflet-control-zoom-out {
+	font: bold 18px 'Lucida Console', Monaco, monospace;
+	text-indent: 1px;
+	}
+
+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  {
+	font-size: 22px;
+	}
+
+
+/* layers control */
+
+.leaflet-control-layers {
+	box-shadow: 0 1px 5px rgba(0,0,0,0.4);
+	background: #fff;
+	border-radius: 5px;
+	}
+.leaflet-control-layers-toggle {
+	background-image: url(images/layers.png);
+	width: 36px;
+	height: 36px;
+	}
+.leaflet-retina .leaflet-control-layers-toggle {
+	background-image: url(images/layers-2x.png);
+	background-size: 26px 26px;
+	}
+.leaflet-touch .leaflet-control-layers-toggle {
+	width: 44px;
+	height: 44px;
+	}
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+	display: none;
+	}
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+	display: block;
+	position: relative;
+	}
+.leaflet-control-layers-expanded {
+	padding: 6px 10px 6px 6px;
+	color: #333;
+	background: #fff;
+	}
+.leaflet-control-layers-scrollbar {
+	overflow-y: scroll;
+	overflow-x: hidden;
+	padding-right: 5px;
+	}
+.leaflet-control-layers-selector {
+	margin-top: 2px;
+	position: relative;
+	top: 1px;
+	}
+.leaflet-control-layers label {
+	display: block;
+	}
+.leaflet-control-layers-separator {
+	height: 0;
+	border-top: 1px solid #ddd;
+	margin: 5px -10px 5px -6px;
+	}
+
+/* Default icon URLs */
+.leaflet-default-icon-path {
+	background-image: url(images/marker-icon.png);
+	}
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+	background: #fff;
+	background: rgba(255, 255, 255, 0.7);
+	margin: 0;
+	}
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+	padding: 0 5px;
+	color: #333;
+	}
+.leaflet-control-attribution a {
+	text-decoration: none;
+	}
+.leaflet-control-attribution a:hover {
+	text-decoration: underline;
+	}
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+	font-size: 11px;
+	}
+.leaflet-left .leaflet-control-scale {
+	margin-left: 5px;
+	}
+.leaflet-bottom .leaflet-control-scale {
+	margin-bottom: 5px;
+	}
+.leaflet-control-scale-line {
+	border: 2px solid #777;
+	border-top: none;
+	line-height: 1.1;
+	padding: 2px 5px 1px;
+	font-size: 11px;
+	white-space: nowrap;
+	overflow: hidden;
+	-moz-box-sizing: border-box;
+	     box-sizing: border-box;
+
+	background: #fff;
+	background: rgba(255, 255, 255, 0.5);
+	}
+.leaflet-control-scale-line:not(:first-child) {
+	border-top: 2px solid #777;
+	border-bottom: none;
+	margin-top: -2px;
+	}
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+	border-bottom: 2px solid #777;
+	}
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+	box-shadow: none;
+	}
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+	border: 2px solid rgba(0,0,0,0.2);
+	background-clip: padding-box;
+	}
+
+
+/* popup */
+
+.leaflet-popup {
+	position: absolute;
+	text-align: center;
+	margin-bottom: 20px;
+	}
+.leaflet-popup-content-wrapper {
+	padding: 1px;
+	text-align: left;
+	border-radius: 12px;
+	}
+.leaflet-popup-content {
+	margin: 13px 19px;
+	line-height: 1.4;
+	}
+.leaflet-popup-content p {
+	margin: 18px 0;
+	}
+.leaflet-popup-tip-container {
+	width: 40px;
+	height: 20px;
+	position: absolute;
+	left: 50%;
+	margin-left: -20px;
+	overflow: hidden;
+	pointer-events: none;
+	}
+.leaflet-popup-tip {
+	width: 17px;
+	height: 17px;
+	padding: 1px;
+
+	margin: -10px auto 0;
+
+	-webkit-transform: rotate(45deg);
+	   -moz-transform: rotate(45deg);
+	    -ms-transform: rotate(45deg);
+	        transform: rotate(45deg);
+	}
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+	background: white;
+	color: #333;
+	box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+	}
+.leaflet-container a.leaflet-popup-close-button {
+	position: absolute;
+	top: 0;
+	right: 0;
+	padding: 4px 4px 0 0;
+	border: none;
+	text-align: center;
+	width: 18px;
+	height: 14px;
+	font: 16px/14px Tahoma, Verdana, sans-serif;
+	color: #c3c3c3;
+	text-decoration: none;
+	font-weight: bold;
+	background: transparent;
+	}
+.leaflet-container a.leaflet-popup-close-button:hover {
+	color: #999;
+	}
+.leaflet-popup-scrolled {
+	overflow: auto;
+	border-bottom: 1px solid #ddd;
+	border-top: 1px solid #ddd;
+	}
+
+.leaflet-oldie .leaflet-popup-content-wrapper {
+	-ms-zoom: 1;
+	}
+.leaflet-oldie .leaflet-popup-tip {
+	width: 24px;
+	margin: 0 auto;
+
+	-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+	filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+	}
+.leaflet-oldie .leaflet-popup-tip-container {
+	margin-top: -1px;
+	}
+
+.leaflet-oldie .leaflet-control-zoom,
+.leaflet-oldie .leaflet-control-layers,
+.leaflet-oldie .leaflet-popup-content-wrapper,
+.leaflet-oldie .leaflet-popup-tip {
+	border: 1px solid #999;
+	}
+
+
+/* div icon */
+
+.leaflet-div-icon {
+	background: #fff;
+	border: 1px solid #666;
+	}
+
+
+/* Tooltip */
+/* Base styles for the element that has a tooltip */
+.leaflet-tooltip {
+	position: absolute;
+	padding: 6px;
+	background-color: #fff;
+	border: 1px solid #fff;
+	border-radius: 3px;
+	color: #222;
+	white-space: nowrap;
+	-webkit-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+	pointer-events: none;
+	box-shadow: 0 1px 3px rgba(0,0,0,0.4);
+	}
+.leaflet-tooltip.leaflet-clickable {
+	cursor: pointer;
+	pointer-events: auto;
+	}
+.leaflet-tooltip-top:before,
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+	position: absolute;
+	pointer-events: none;
+	border: 6px solid transparent;
+	background: transparent;
+	content: "";
+	}
+
+/* Directions */
+
+.leaflet-tooltip-bottom {
+	margin-top: 6px;
+}
+.leaflet-tooltip-top {
+	margin-top: -6px;
+}
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-top:before {
+	left: 50%;
+	margin-left: -6px;
+	}
+.leaflet-tooltip-top:before {
+	bottom: 0;
+	margin-bottom: -12px;
+	border-top-color: #fff;
+	}
+.leaflet-tooltip-bottom:before {
+	top: 0;
+	margin-top: -12px;
+	margin-left: -6px;
+	border-bottom-color: #fff;
+	}
+.leaflet-tooltip-left {
+	margin-left: -6px;
+}
+.leaflet-tooltip-right {
+	margin-left: 6px;
+}
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+	top: 50%;
+	margin-top: -6px;
+	}
+.leaflet-tooltip-left:before {
+	right: 0;
+	margin-right: -12px;
+	border-left-color: #fff;
+	}
+.leaflet-tooltip-right:before {
+	left: 0;
+	margin-left: -12px;
+	border-right-color: #fff;
+	}

File diff suppressed because it is too large
+ 6 - 0
packages/mobile/public/lib/leaflet.js


+ 48 - 0
packages/mobile/public/model.html

@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <script src="./4dage.js"></script>
+  <title>Document</title>
+  <style>
+    html {
+      overflow: hidden;
+    }
+
+    .bacBox {
+      opacity: 1;
+      pointer-events: auto;
+      position: absolute;
+      z-index: 998;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      transition: all 1s;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="ui"></div>
+  <div class="outerImg">
+    <img src="./img/bg.jpg" alt="">
+  </div>
+  <script>
+    let number = getQueryVariable("m");
+
+    window.autoRotate = true; // 是否自动旋转
+    fdage.embed(number, {
+        width: 800,
+        height: 600,
+        autoStart: true,
+        fullFrame: true,
+        pagePreset: false
+    });
+  </script>
+</body>
+
+</html>

+ 19 - 0
packages/mobile/src/App.vue

@@ -0,0 +1,19 @@
+<script setup>
+import { RouterView } from "vue-router";
+import TopNav from "@/components/TopNav.vue";
+</script>
+
+<template>
+  <TopNav />
+
+  <RouterView />
+</template>
+
+<style>
+:root:root {
+  --van-primary-color: #c50b10;
+  --van-base-font: "SourceHanSerifSC-Regular";
+  --design-width: 750;
+  --design-height: 1424;
+}
+</style>

+ 37 - 0
packages/mobile/src/api/index.js

@@ -0,0 +1,37 @@
+import { requestByGet, requestByPost } from "@dage/service";
+
+export const getListApi = (type, params) => {
+  return requestByPost(`/api/web/${type}/list`, params);
+};
+
+export const getDetailApi = (id) => {
+  return requestByGet(`/api/web/goods/detail/${id}`);
+};
+
+export const getCommentListApi = (params) => {
+  return requestByPost("/api/web/commentList", params);
+};
+
+export const saveCommentApi = (params) => {
+  return requestByPost("/api/web/comment/save", params);
+};
+
+export const saveBarrageApi = (params) => {
+  return requestByPost("/api/web/barrageSave", params);
+};
+
+export const getBarrageListApi = (params) => {
+  return requestByPost("/api/web/barrage/list", params);
+};
+
+export const getStarApi = () => {
+  return requestByGet("/api/web/scene/getStar");
+};
+
+export const starApi = (num) => {
+  return requestByGet(`/api/web/scene/star/${num}`);
+};
+
+export const unStarApi = (num) => {
+  return requestByGet(`api/web/scene/starCancel/${num}`);
+};

BIN
packages/mobile/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF


BIN
packages/mobile/src/assets/fonts/SOURCEHANSERIFCN-REGULAR.OTF


BIN
packages/mobile/src/assets/images/3D.png


BIN
packages/mobile/src/assets/images/active-more.png


BIN
packages/mobile/src/assets/images/back-min.png


BIN
packages/mobile/src/assets/images/bg-min.jpg


BIN
packages/mobile/src/assets/images/button-min.png


BIN
packages/mobile/src/assets/images/close.png


BIN
packages/mobile/src/assets/images/danmu_close.png


BIN
packages/mobile/src/assets/images/danmu_open.png


BIN
packages/mobile/src/assets/images/danmu_top.png


BIN
packages/mobile/src/assets/images/logo-bg-min.png


BIN
packages/mobile/src/assets/images/logo-min.png


BIN
packages/mobile/src/assets/images/menu-min.png


BIN
packages/mobile/src/assets/images/more.png


BIN
packages/mobile/src/assets/images/nodata-min.png


BIN
packages/mobile/src/assets/images/search.png


+ 112 - 0
packages/mobile/src/assets/main.css

@@ -0,0 +1,112 @@
+:root {
+  --z-index-normal: 1;
+  --z-index-top: 1000;
+  --z-index-popper: 2000;
+  --z-hot-popper: 3000;
+}
+
+body,
+ol,
+ul,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+th,
+td,
+dl,
+dd,
+form,
+fieldset,
+legend,
+input,
+textarea,
+select {
+  margin: 0;
+  padding: 0;
+}
+* {
+  box-sizing: border-box;
+  user-select: none;
+}
+body {
+  color: #333333;
+  text-align: justify;
+  font-family: "SourceHanSerifSC-Regular";
+  -webkit-tap-highlight-color: transparent;
+}
+a {
+  color: #fff;
+  cursor: pointer;
+  text-decoration: none;
+}
+em {
+  font-style: normal;
+}
+li {
+  list-style: none;
+}
+img {
+  border: 0;
+  vertical-align: middle;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+p {
+  word-wrap: break-word;
+}
+iframe {
+  border: none;
+}
+
+@font-face {
+  font-family: "SourceHanSerifSC-Bold";
+  src: url("./fonts/SOURCEHANSERIFCN-BOLD.otf");
+}
+@font-face {
+  font-family: "SourceHanSerifSC-Regular";
+  src: url("./fonts/SOURCEHANSERIFCN-REGULAR.otf");
+}
+
+.limit-line {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+.line-2 {
+  -webkit-line-clamp: 2;
+}
+
+.line-3 {
+  -webkit-line-clamp: 3;
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.darkGlass {
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.message-outer {
+  position: absolute;
+  display: table;
+  height: 100%;
+  width: 100%;
+
+  * {
+    transition: all 0.3s;
+  }
+}

+ 7 - 0
packages/mobile/src/assets/utils.scss

@@ -0,0 +1,7 @@
+@function vh-calc($num) {
+  @return calc(100vh * ($num / var(--design-height)));
+}
+
+@function vw-calc($num) {
+  @return calc(100vw * ($num / var(--design-width)));
+}

File diff suppressed because it is too large
+ 80 - 0
packages/mobile/src/components/RemarkPopup.vue


+ 98 - 0
packages/mobile/src/components/Sidebar.vue

@@ -0,0 +1,98 @@
+<template>
+  <VanPopup
+    v-model:show="visible"
+    position="right"
+    class="menu"
+    :overlay="false"
+  >
+    <ul>
+      <li
+        v-for="(item, index) in LIST"
+        :key="item.label"
+        :class="{ active: index === active }"
+        @click="
+          () => {
+            $router.push({ name: item.active[0] });
+            visible = false;
+          }
+        "
+      >
+        <p>{{ item.label }}</p>
+      </li>
+    </ul>
+  </VanPopup>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const props = defineProps(["show"]);
+const emits = defineEmits(["update:show"]);
+const LIST = [
+  {
+    label: "数字史馆",
+    active: ["home"],
+  },
+  {
+    label: "精品典藏",
+    active: ["antiquity"],
+  },
+  {
+    label: "我要留言",
+    active: ["message"],
+  },
+];
+const active = computed(() =>
+  LIST.findIndex((item) => item.active.includes(route.name))
+);
+
+const visible = computed({
+  get() {
+    return props.show;
+  },
+  set(v) {
+    emits("update:show", v);
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.menu {
+  top: calc(50% + 50px);
+  padding-top: 38px;
+  width: 559px;
+  height: calc(100vh - 100px);
+  background: rgba(255, 255, 255, 0.5);
+  backdrop-filter: blur(36px);
+
+  li {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 40px 0 30px;
+    height: 109px;
+    line-height: 109px;
+    color: #999999;
+    font-size: 27px;
+    border-bottom: 1px solid #ebebeb;
+
+    &.active {
+      font-weight: bold;
+      color: var(--van-primary-color);
+
+      &::after {
+        background-image: url("@/assets/images/active-more.png");
+      }
+    }
+    &::after {
+      content: "";
+      display: block;
+      width: 15px;
+      height: 27px;
+      background: url("@/assets/images/more.png") no-repeat center / contain;
+    }
+  }
+}
+</style>

+ 64 - 0
packages/mobile/src/components/TopNav.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="topnav">
+    <i class="topnav-logo" />
+    <h3 class="limit-line">武警北京市总执勤第十三支队数字史馆</h3>
+    <i class="topnav-menu" @click="menuVisible = !menuVisible" />
+  </div>
+
+  <Sidebar v-model:show="menuVisible" />
+</template>
+
+<script setup>
+import { ref } from "vue";
+import Sidebar from "./Sidebar.vue";
+
+const menuVisible = ref(false);
+</script>
+
+<style lang="scss" scoped>
+.topnav {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  padding: 0 80px 0 130px;
+  height: 100px;
+  line-height: 100px;
+  color: white;
+  font-size: 27px;
+  background: #c50b10;
+  box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.15);
+  z-index: var(--z-hot-popper);
+
+  &-logo {
+    position: absolute;
+    top: -8px;
+    left: 15px;
+    display: block;
+    width: 104px;
+    height: 123px;
+    background: url("@/assets/images/logo-bg-min.png") no-repeat center /
+      contain;
+
+    &::before {
+      content: "";
+      position: absolute;
+      top: 8px;
+      left: 50%;
+      width: 90px;
+      height: 88px;
+      background: url("@/assets/images/logo-min.png") no-repeat center / contain;
+      transform: translateX(-50%);
+    }
+  }
+  &-menu {
+    position: absolute;
+    top: 50%;
+    right: 27px;
+    width: 36px;
+    height: 36px;
+    background: url("@/assets/images/menu-min.png") no-repeat center / contain;
+    transform: translateY(-50%);
+  }
+}
+</style>

+ 26 - 0
packages/mobile/src/configure.js

@@ -0,0 +1,26 @@
+import { compose, initial } from "@dage/service";
+import { showNotify } from "vant";
+
+const showMessage = (msg, type = "danger") => {
+  showNotify({ message: msg, type, zIndex: 3100 });
+};
+
+initial({
+  fetch: window.fetch.bind(window),
+  baseURL: import.meta.env.VITE_BASE_URL,
+  interceptor: compose(async (request, next) => {
+    const response = await next();
+    const { showError = true } = request.meta;
+
+    if (response.code !== 0 && request.name.indexOf("someData") < 0) {
+      const message = response.__raw__.data.msg ?? "系统出差中";
+      // 错误信息映射
+      response.errorMessage = message;
+      if (showError) {
+        showMessage(message);
+      }
+    }
+
+    return response;
+  }),
+});

+ 26 - 0
packages/mobile/src/main.js

@@ -0,0 +1,26 @@
+import "./assets/main.css";
+import "./configure";
+import "animate.css";
+
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import { Lazyload } from "vant";
+import VueChatScroll from "vue-chat-scroll";
+import "vant/lib/notify/style/index";
+import "vant/lib/image-preview/style/index";
+
+import App from "./App.vue";
+import router from "./router";
+
+// import { isDevelopment, checkDeviceAndRedirect } from "@nj/base";
+
+// !isDevelopment && checkDeviceAndRedirect();
+
+const app = createApp(App);
+
+app.use(createPinia());
+app.use(router);
+app.use(Lazyload);
+app.use(VueChatScroll);
+
+app.mount("#app");

+ 43 - 0
packages/mobile/src/router/index.js

@@ -0,0 +1,43 @@
+import { createRouter, createWebHashHistory } from "vue-router";
+
+const router = createRouter({
+  history: createWebHashHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: "/",
+      name: "home",
+      component: () => import("../views/Home/index.vue"),
+    },
+    // {
+    //   path: "/exhibition",
+    //   name: "exhibition",
+    //   component: () => import("../views/Exhibition/index.vue"),
+    // },
+    {
+      path: "/antiquity",
+      name: "antiquity",
+      component: () => import("../views/Antiquity/index.vue"),
+    },
+    // {
+    //   path: "/antiquity/detail/:id",
+    //   name: "antiquityDetail",
+    //   meta: {
+    //     // 自定义顶部栏
+    //     customTop: true,
+    //   },
+    //   component: () => import("../views/Antiquity/Detail/index.vue"),
+    // },
+    {
+      path: "/message",
+      name: "message",
+      component: () => import("../views/Message/index.vue"),
+    },
+    {
+      path: "/scene",
+      name: "scene",
+      component: () => import("../views/Scene/index.vue"),
+    },
+  ],
+});
+
+export default router;

+ 12 - 0
packages/mobile/src/stores/counter.js

@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { count, doubleCount, increment }
+})

+ 50 - 0
packages/mobile/src/utils/broswer.js

@@ -0,0 +1,50 @@
+function versions() {
+  var u = window.navigator.userAgent;
+  var uLowCase = u.toLowerCase();
+
+  return {
+    userAgent: u,
+    // IE内核
+    trident: u.indexOf("Trident") > -1,
+    // opera内核
+    presto: u.indexOf("Presto") > -1,
+    // 苹果、谷歌内核
+    webKit: u.indexOf("AppleWebKit") > -1,
+    // 火狐内核
+    gecko: u.indexOf("Gecko") > -1 && u.indexOf("KHTML") === -1,
+    // 是否为移动终端 / Tablets use desktop version
+    mobile:
+      /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent) &&
+      !/Tablet|iPad/i.test(navigator.userAgent),
+    // ios终端
+    ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
+    // android终端或者uc浏览器
+    android: u.indexOf("Android") > -1 || u.indexOf("Linux") > -1,
+    // 是否为iPhone或者安卓QQ浏览器
+    iPhone: u.indexOf("iPhone") > -1 || u.indexOf("Mac") > -1,
+    // 是否为iPad
+    iPad: u.indexOf("iPad") > -1,
+    // 是否为web应用程序,没有头部与底部
+    webApp: u.indexOf("Safari") === -1,
+    // 是否为微信浏览器
+    weixin: ~u.indexOf("MicroMessenger"),
+    // 火狐内核版本
+    firefoxCore: uLowCase.match(/firefox\/([\d.]+)/),
+    // chrome内核版本
+    chromeCore: uLowCase.match(/chrome\/([\d.]+)/),
+    // webkit内核版本
+    webkitCore: uLowCase.match(/applewebkit\/([\d.]+)/),
+    getURLParam: function (key) {
+      let querys = window.location.search.substring(1).split("&");
+      for (let i = 0; i < querys.length; i++) {
+        let keypair = querys[i].split("=");
+        if (keypair.length === 2 && keypair[0] === key) {
+          return keypair[1];
+        }
+      }
+      return "";
+    },
+  };
+}
+
+export default versions();

+ 74 - 0
packages/mobile/src/views/Antiquity/Detail/index.scss

@@ -0,0 +1,74 @@
+.antiquity-detail {
+  padding: 120px 30px 60px;
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background: url("@/assets/images/bg_3.png") no-repeat center top / cover;
+
+  &-head {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 30px 0;
+    height: 120px;
+    z-index: 1;
+  }
+  &__back {
+    position: absolute;
+    top: 50%;
+    left: 30px;
+    width: 60px;
+    height: 60px;
+    transform: translateY(-50%);
+  }
+  &-img {
+    position: relative;
+    margin: 180px 0 160px;
+
+    &__preview {
+      position: absolute;
+      right: 30px;
+      bottom: 30px;
+      width: 60px;
+      height: 60px;
+    }
+  }
+  iframe {
+    height: 40vh;
+  }
+  &-tools {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 57px;
+
+    img {
+      margin: 30px 0;
+      width: 60px;
+      height: 60px;
+    }
+  }
+  &-wrap {
+    flex: 1;
+    padding: 0 40px;
+    height: 0;
+    overflow: auto;
+
+    h3 {
+      margin-bottom: 40px;
+      color: #981b23;
+      text-align: center;
+      font-size: 36px;
+      font-family: "SourceHanSerifSC-Bold";
+    }
+    p {
+      color: #644e36;
+      font-size: 30px;
+      line-height: 50px;
+    }
+  }
+}

+ 119 - 0
packages/mobile/src/views/Antiquity/Detail/index.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="antiquity-detail">
+    <div class="antiquity-detail-head">
+      <img
+        class="antiquity-detail__back"
+        src="@/assets/images/back_2.png"
+        @click="$router.back"
+      />
+    </div>
+
+    <template v-if="!isThree">
+      <div class="antiquity-detail-img">
+        <VanImage
+          fit="contain"
+          width="100%"
+          height="100%"
+          :src="getEnvImagePath(detail?.minImg)"
+        />
+
+        <img
+          class="antiquity-detail-img__preview"
+          src="@/assets/images/1.png"
+          @click="preview = true"
+        />
+      </div>
+    </template>
+    <template v-else>
+      <iframe ref="iframe" :src="iframeUrl" />
+      <div class="antiquity-detail-tools">
+        <img
+          draggable="false"
+          src="@/assets/images/2.png"
+          @click="handleZoom"
+        />
+        <img
+          draggable="false"
+          :src="autoPlay ? PauseImg : PlayImg"
+          @click="handleAutoPlay"
+        />
+        <img
+          draggable="false"
+          src="@/assets/images/4.png"
+          @click="handleResetView"
+        />
+      </div>
+    </template>
+
+    <div class="antiquity-detail-wrap">
+      <h3>{{ detail?.name }}</h3>
+      <div>
+        <!-- <p v-if="detail?.level">文物等级:{{ detail?.level || "--" }}</p> -->
+        <p v-if="detail?.size">尺寸:{{ detail?.size || "--" }}</p>
+        <p v-if="detail?.sd">时代:{{ detail?.sd || "--" }}</p>
+        <p v-if="detail?.zd">质地:{{ detail?.zd || "--" }}</p>
+        <p v-if="detail?.from">来源:{{ detail?.from || "--" }}</p>
+        <p v-if="detail?.type">类别:{{ detail?.type || "--" }}</p>
+        <p v-if="detail?.address">存放位置:{{ detail?.address || "--" }}</p>
+        <p v-html="detail?.content" />
+      </div>
+    </div>
+  </div>
+
+  <van-image-preview v-model:show="preview" :images="imgs" />
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { antiquityData, getEnvImagePath, isDevelopment } from "@nj/base";
+import { useRoute } from "vue-router";
+import PlayImg from "@/assets/images/3.png";
+import PauseImg from "@/assets/images/3-1.png";
+
+const route = useRoute();
+const detail = computed(() =>
+  antiquityData.find((i) => i.id === Number(route.params.id))
+);
+const imgs = computed(() =>
+  detail.value?.img ? detail.value.img.map((i) => getEnvImagePath(i)) : []
+);
+const preview = ref(false);
+// 是否三维文物
+const isThree = computed(() => Boolean(detail.value?.link));
+const iframe = ref(null);
+const iframeUrl = computed(
+  () =>
+    isThree &&
+    `${
+      isDevelopment
+        ? "/model.html"
+        : `${location.origin}${location.pathname.replace(
+            /index\.html$/,
+            "model.html"
+          )}`
+    }?m=${detail.value?.link}`
+);
+// 模型自动旋转
+const autoPlay = ref(true);
+
+const handleZoom = () => {
+  iframe.value.contentWindow.webview.zoomIn(); // 放大
+};
+
+const handleAutoPlay = () => {
+  if (!iframe.value.contentWindow.modelLoding) return;
+
+  iframe.value.contentWindow.webview.stopRotate();
+  autoPlay.value = !autoPlay.value;
+};
+
+const handleResetView = () => {
+  iframe.value.contentWindow.webview.resetView();
+
+  if (!autoPlay.value) handleAutoPlay();
+};
+</script>
+
+<style lang="scss" scoped>
+@use "./index.scss";
+</style>

+ 139 - 0
packages/mobile/src/views/Antiquity/components/DetailPopup.vue

@@ -0,0 +1,139 @@
+<template>
+  <VanOverlay :show="visible" teleport="body" zIndex="3000">
+    <VanLoading
+      v-if="loading"
+      size="50px"
+      color="var(--van-primary-color)"
+      style="
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      "
+    />
+
+    <div v-if="detail" class="detail-popup">
+      <p class="detail-popup__title">{{ detail.entity.name }}</p>
+      <div class="detail-popup-contain">
+        <iframe
+          v-if="detail.entity.type == 'model'"
+          :src="`/model.html?m=${baseUrl}${detail.entity.filePath}`"
+          frameborder="0"
+        ></iframe>
+
+        <VanSwipe
+          v-else-if="detail.entity.type == 'img'"
+          height="100%"
+          style="height: 100%"
+        >
+          <VanSwipeItem v-for="(sub, index) in detail.file" :key="index">
+            <VanImage
+              :src="baseUrl + sub.filePath"
+              width="100%"
+              height="100%"
+              lazy-load
+              fit="contain"
+              @click="
+                showImagePreview({
+                  images: detail.file.map((i) => baseUrl + i.filePath),
+                  startPosition: index,
+                  zIndex: 4000,
+                })
+              "
+            />
+          </VanSwipeItem>
+        </VanSwipe>
+
+        <video
+          v-else
+          x5-playsinline="true"
+          playsinline="true"
+          webkit-playsinline="true"
+          x-webkit-airplay="true"
+          x5-video-player-type="h5-page"
+          controls
+          :src="`${baseUrl}${detail.entity.filePath}`"
+        />
+      </div>
+      <img
+        class="detail-popup__close"
+        src="@/assets/images/close.png"
+        @click="visible = false"
+      />
+    </div>
+  </VanOverlay>
+</template>
+
+<script setup>
+import { computed, ref, watch } from "vue";
+import { getBaseURL } from "@dage/service";
+import { getDetailApi } from "@/api";
+import { showImagePreview } from "vant";
+
+const baseUrl = getBaseURL();
+const props = defineProps(["show", "item"]);
+const emits = defineEmits(["update:show"]);
+const detail = ref(null);
+const loading = ref(false);
+
+const visible = computed({
+  get() {
+    return props.show;
+  },
+  set(v) {
+    emits("update:show", v);
+  },
+});
+
+const getDetail = async () => {
+  try {
+    loading.value = true;
+    const data = await getDetailApi(props.item?.id);
+    detail.value = data;
+  } finally {
+    loading.value = false;
+  }
+};
+
+watch(visible, (v) => {
+  if (!v) {
+    detail.value = null;
+    return;
+  }
+  getDetail();
+});
+</script>
+
+<style lang="scss" scoped>
+.detail-popup {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  gap: 30px;
+  padding: 75px 30px 133px;
+  height: 100%;
+
+  &__title {
+    font-size: 32px;
+    color: white;
+    font-weight: bold;
+    text-align: center;
+  }
+  &__close {
+    width: 52px;
+    height: 52px;
+  }
+  &-contain {
+    flex: 1;
+    width: 100%;
+    height: 0;
+
+    iframe,
+    video {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 151 - 0
packages/mobile/src/views/Antiquity/components/List.vue

@@ -0,0 +1,151 @@
+<template>
+  <VanList
+    class="ant-list"
+    v-model:loading="loading"
+    :finished="noMore"
+    @load="onLoad"
+  >
+    <div
+      v-for="item in list"
+      :key="item.id"
+      class="ant-item"
+      @click="
+        () => {
+          detailVisible = true;
+          checkedItem = item;
+        }
+      "
+    >
+      <div class="ant-item-img">
+        <VanImage lazy-load :src="getEnvImagePath(item.thumb)" fit="contain" />
+      </div>
+      <div class="ant-item-footer">
+        <p class="limit-line">{{ item.name }}</p>
+      </div>
+    </div>
+  </VanList>
+
+  <template v-if="noData">
+    <VanEmpty v-if="!keyword" image="search" description="暂无内容" />
+    <div v-else class="ant-list-nodata">
+      <p>暂时没有数据</p>
+      <p>请试一下其他关键字</p>
+    </div>
+  </template>
+
+  <img
+    v-if="keyword"
+    class="ant-list-back"
+    src="@/assets/images/back-min.png"
+    @click="emits('back')"
+  />
+
+  <DetailPopup v-model:show="detailVisible" :item="checkedItem" />
+</template>
+
+<script setup>
+import { watch, ref } from "vue";
+import { getListApi } from "@/api";
+import DetailPopup from "./DetailPopup.vue";
+import { getEnvImagePath, PaginationType, usePagination } from "@nj/base";
+
+const detailVisible = ref(false);
+const checkedItem = ref(null);
+const props = defineProps(["item", "keyword"]);
+const emits = defineEmits(["back"]);
+const { pageNum, loading, list, noData, noMore, getList, resetParams } =
+  usePagination(
+    async (params) => {
+      return getListApi("goods", {
+        type: !props.keyword ? props.item.type : undefined,
+        searchKey: props.keyword,
+        ...params,
+      });
+    },
+    PaginationType.CONCAT,
+    20
+  );
+
+const onLoad = () => {
+  getList();
+  pageNum.value++;
+};
+
+watch(
+  () => props.keyword,
+  () => {
+    resetParams();
+    getList();
+  }
+);
+</script>
+
+<style lang="scss" scoped>
+.ant-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+
+  &-back {
+    display: block;
+    margin: 50px auto 0;
+    width: 83px;
+    height: 83px;
+  }
+  &-nodata {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    font-size: 29px;
+    color: #c50b10;
+    line-height: 44px;
+
+    &::before {
+      content: "";
+      margin: 200px 0 52px;
+      width: 387px;
+      height: 273px;
+      background: url("@/assets/images/nodata-min.png") no-repeat center /
+        contain;
+    }
+  }
+}
+.ant-item {
+  width: calc(50% - 10px);
+  height: 259px;
+  border-radius: 7px;
+  background: white;
+  overflow: hidden;
+
+  &-img {
+    width: 100%;
+    height: 207px;
+    overflow: hidden;
+    background: linear-gradient(180deg, #d9d9d9 0%, #a2a2a2 100%);
+
+    .van-image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  &-footer {
+    display: flex;
+    align-items: center;
+    padding: 0 14px;
+    height: 52px;
+
+    &::after {
+      content: "";
+      width: 33px;
+      height: 33px;
+      background: url("@/assets/images/3D.png") no-repeat center / contain;
+    }
+  }
+  p {
+    flex: 1;
+    color: #999999;
+    font-size: 24px;
+  }
+}
+</style>

+ 101 - 0
packages/mobile/src/views/Antiquity/index.scss

@@ -0,0 +1,101 @@
+.antiquity {
+  display: flex;
+  flex-direction: column;
+  padding: 100px 27px 0;
+  min-height: 100vh;
+  background: #f9f9f9;
+
+  &-tabs {
+    --van-tabs-line-height: 155px;
+    --van-tab-font-size: 27px;
+    --van-font-bold: 500;
+    --van-padding-xs: 30px;
+    --van-tabs-bottom-bar-width: 147px;
+    --van-tabs-bottom-bar-height: 60px;
+    --van-tab-active-text-color: #f8d163;
+    --van-tabs-nav-background: transparent;
+    --van-tab-text-color: #333;
+
+    flex: 1;
+    height: 0;
+
+    :deep(.van-tab) {
+      position: relative;
+      width: 170px;
+      z-index: 9;
+    }
+    :deep(.van-tabs__nav) {
+      margin: 0 -30px;
+    }
+    :deep(.van-tabs__line) {
+      bottom: 47px;
+    }
+    :deep(.van-tabs__nav--line) {
+      padding-bottom: 0;
+    }
+  }
+  &-search {
+    position: absolute;
+    top: 50%;
+    right: var(--van-padding-xs);
+    display: flex;
+    width: 62px;
+    height: 62px;
+    border-radius: 7px;
+    border: 1px solid #999;
+    transform: translateY(-50%);
+    background: white;
+    overflow: hidden;
+    z-index: 999;
+    transition: width ease-in-out 0.2s;
+
+    &.show {
+      width: calc(100% - var(--van-padding-xs) * 2);
+    }
+    input {
+      flex: 1;
+      padding: 0 16px;
+      height: 100%;
+      border: 0;
+      color: #999999;
+      font-size: var(--van-tab-font-size);
+    }
+    &__icon {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 58px;
+      height: 100%;
+
+      &::after {
+        content: "";
+        width: 37px;
+        height: 37px;
+        background: url("@/assets/images/search.png") no-repeat center / contain;
+      }
+    }
+  }
+  &-result {
+    display: flex;
+    align-items: center;
+    position: absolute;
+    top: 50%;
+    left: var(--van-padding-xs);
+    right: var(--van-padding-xs);
+    height: 60px;
+    white-space: nowrap;
+    background: #f9f9f9;
+    transform: translateY(-50%);
+    font-size: var(--van-tab-font-size);
+    z-index: 999;
+
+    p {
+      padding-left: 30px;
+      color: #999999;
+
+      span {
+        color: #c50b10;
+      }
+    }
+  }
+}

+ 104 - 0
packages/mobile/src/views/Antiquity/index.vue

@@ -0,0 +1,104 @@
+<template>
+  <div class="antiquity">
+    <van-tabs v-model:active="active" animated class="antiquity-tabs" shrink>
+      <van-tab v-for="item in tabs" :key="item.label" :title="item.label">
+        <List
+          :item="item"
+          :keyword="searchKey"
+          @back="
+            () => {
+              searchKey = '';
+              keyword = '';
+              searchVisible = false;
+            }
+          "
+        />
+      </van-tab>
+
+      <template #nav-left>
+        <p
+          v-if="!searchKey"
+          style="
+            line-height: var(--van-tabs-line-height);
+            font-size: var(--van-tab-font-size);
+          "
+        >
+          类型:
+        </p>
+
+        <div v-else class="antiquity-result">
+          搜索结果
+          <p>
+            以下内容包含<span>“{{ searchKey }}”</span>
+          </p>
+        </div>
+      </template>
+
+      <template #nav-right>
+        <div
+          v-if="!searchKey"
+          class="antiquity-search"
+          :class="{ show: searchVisible }"
+        >
+          <input
+            v-if="searchVisible"
+            ref="inputRef"
+            type="search"
+            enterkeyhint="search"
+            v-model="keyword"
+            placeholder="请输入搜索内容"
+            @keydown="handleKeydown"
+          />
+          <i class="antiquity-search__icon" @click="handleSearch" />
+        </div>
+      </template>
+    </van-tabs>
+  </div>
+</template>
+
+<script setup>
+import { ref, nextTick } from "vue";
+import List from "./components/List.vue";
+
+const active = ref(0);
+const keyword = ref("");
+const searchKey = ref("");
+const inputRef = ref();
+const searchVisible = ref(false);
+const tabs = ref([
+  {
+    type: "model",
+    label: "实物模型",
+  },
+  {
+    type: "img",
+    label: "专题图库",
+  },
+  {
+    type: "video",
+    label: "视频档案",
+  },
+]);
+
+const handleSearch = () => {
+  if (searchVisible.value) {
+    searchKey.value = keyword.value;
+    searchVisible.value = false;
+  } else {
+    searchVisible.value = true;
+    nextTick(() => {
+      inputRef.value?.focus();
+    });
+  }
+};
+
+const handleKeydown = (event) => {
+  if (event.key === "Enter") {
+    searchKey.value = event.target.value;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@use "./index.scss";
+</style>

+ 41 - 0
packages/mobile/src/views/Exhibition/index.scss

@@ -0,0 +1,41 @@
+@use "@/assets/utils.scss";
+
+.exhibition {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  justify-content: center;
+  height: 100vh;
+  background: url("@/assets/images/bg_3.png") no-repeat center / cover;
+
+  &__back {
+    position: absolute;
+    top: utils.vh-calc(30);
+    left: utils.vh-calc(30);
+    width: utils.vh-calc(60);
+    height: utils.vh-calc(60);
+  }
+  &__logo {
+    width: utils.vh-calc(341);
+    height: utils.vh-calc(73);
+  }
+  &-card {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: utils.vh-calc(30);
+    width: utils.vh-calc(690);
+    font-size: utils.vh-calc(30);
+    color: #7d7d7d;
+
+    &.first {
+      margin: utils.vh-calc(78) 0 utils.vh-calc(95);
+    }
+    img {
+      width: 100%;
+      height: utils.vh-calc(450);
+      object-fit: cover;
+    }
+  }
+}

+ 33 - 0
packages/mobile/src/views/Exhibition/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="exhibition">
+    <img
+      class="exhibition__back"
+      src="@/assets/images/back_2.png"
+      @click="$router.back"
+    />
+
+    <img class="exhibition__logo" src="@/assets/images/logo.png" />
+
+    <div class="exhibition-card first" @click="goToScene(0)">
+      <img src="@/assets/images/pic_4.png" />
+      <p>贺龙故居</p>
+    </div>
+
+    <div class="exhibition-card" @click="goToScene(1)">
+      <img src="@/assets/images/pic_5.png" />
+      <p>贺龙桥</p>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { SCENES } from "@nj/base";
+
+const goToScene = (index) => {
+  window.location.href = SCENES[index].href;
+};
+</script>
+
+<style lang="scss" scoped>
+@use "./index.scss";
+</style>

+ 18 - 0
packages/mobile/src/views/Home/index.scss

@@ -0,0 +1,18 @@
+.home {
+  height: 100vh;
+  background: url("@/assets/images/bg-min.jpg") no-repeat center / cover;
+
+  &-btn {
+    position: absolute;
+    left: 50%;
+    bottom: 137px;
+    width: 367px;
+    height: 109px;
+    color: #f8d163;
+    font-size: 32px;
+    line-height: 109px;
+    text-align: center;
+    background: url("@/assets/images/button-min.png") no-repeat center/contain;
+    transform: translateX(-50%);
+  }
+}

+ 13 - 0
packages/mobile/src/views/Home/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="home">
+    <div class="home-btn" @click="$router.push({ name: 'scene' })">
+      进入数字史馆
+    </div>
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="scss" scoped>
+@use "./index.scss";
+</style>

BIN
packages/mobile/src/views/Message/images/edit-min.png


BIN
packages/mobile/src/views/Message/images/msg-bg-min.jpg


BIN
packages/mobile/src/views/Message/images/success-min.png


+ 161 - 0
packages/mobile/src/views/Message/index.scss

@@ -0,0 +1,161 @@
+.message {
+  padding: 100px 25px 180px;
+  height: 100vh;
+  overflow: auto;
+  background: url("./images/msg-bg-min.jpg") no-repeat top / cover;
+
+  &-title {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 15px;
+    height: 150px;
+    font-size: 32px;
+    font-weight: bold;
+    color: #c50b10;
+
+    &::before {
+      content: "";
+      width: 35px;
+      height: 35px;
+      background: url("./images/edit-min.png") no-repeat center / contain;
+    }
+  }
+  &-item {
+    &:not(:last-child) {
+      margin-bottom: 40px;
+    }
+    p:first-child {
+      margin-bottom: 17px;
+      padding-bottom: 17px;
+      color: #c50b10;
+      font-size: 27px;
+      border-bottom: 1px solid #c4c4c4;
+
+      span {
+        padding-left: 20px;
+        font-size: 19px;
+      }
+    }
+    p:last-child {
+      color: #333333;
+      font-size: 24px;
+    }
+  }
+  &-footer {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    height: 150px;
+    background: #c50b10;
+    z-index: var(--z-index-top);
+
+    div {
+      display: flex;
+      align-items: center;
+      gap: 11px;
+      padding: 0 20px;
+      width: 697px;
+      height: 71px;
+      line-height: 71px;
+      background: white;
+      font-size: 27px;
+      color: #999999;
+
+      &.success {
+        color: #c50b10;
+        justify-content: center;
+
+        &::before {
+          content: "";
+          width: 33px;
+          height: 33px;
+          background: url("./images/success-min.png") no-repeat center / contain;
+        }
+      }
+    }
+  }
+  &-name {
+    width: 100%;
+    flex-wrap: nowrap;
+    white-space: nowrap;
+    font-size: 27px;
+
+    :deep(.van-radio__icon--checked.van-radio__icon--dot) {
+      background: var(--van-radio-checked-icon-color);
+
+      .van-radio__icon--dot__icon {
+        background: #f8d163;
+      }
+    }
+    .van-radio {
+      &:last-child {
+        flex: 1;
+      }
+      &[aria-checked="true"] span {
+        color: var(--van-radio-checked-icon-color);
+      }
+    }
+    :deep(.van-radio__label) {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      gap: 13px;
+
+      input {
+        padding: 0 10px;
+        flex: 1;
+        width: 0;
+        height: 67px;
+        border: 1px solid #9e9e9e;
+      }
+    }
+  }
+  &-area {
+    display: flex;
+    flex-direction: column;
+    position: relative;
+    margin-top: 33px;
+    padding: 30px 25px 21px;
+    height: 467px;
+    overflow: hidden;
+    border-radius: 3px;
+    border: 1px solid #9e9e9e;
+    background: white;
+
+    span {
+      position: absolute;
+      right: 26px;
+      bottom: 131px;
+      font-size: 19px;
+      color: #aaaaaa;
+    }
+    textarea {
+      flex: 1;
+      width: 100%;
+      height: 0;
+      border: none;
+      font-size: 27px;
+      color: #999999;
+      background: transparent;
+    }
+    &-footer {
+      display: flex;
+      gap: 33px;
+      margin-top: 20px;
+      padding: 23px 25px 0;
+      border-top: 1px solid #d1d1d1;
+
+      .van-button {
+        flex: 1;
+        --van-button-default-color: #999999;
+        --van-button-primary-color: #f8d163;
+        --van-button-default-height: 80px;
+      }
+    }
+  }
+}

+ 132 - 0
packages/mobile/src/views/Message/index.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="message">
+    <div class="message-title">我要留言</div>
+
+    <template v-if="!formVisible">
+      <VanList
+        class="ant-list"
+        v-model:loading="loading"
+        :finished="noMore"
+        @load="onLoad"
+      >
+        <div v-for="item in list" :key="item.id" class="message-item">
+          <p>
+            {{ item.name }}<span>{{ item.createTime }}</span>
+          </p>
+          <p>
+            {{ item.content }}
+          </p>
+        </div>
+      </VanList>
+
+      <VanEmpty v-if="noData" image="search" description="暂无留言" />
+    </template>
+
+    <template v-else>
+      <VanRadioGroup
+        class="message-name"
+        v-model="checked"
+        shape="dot"
+        direction="horizontal"
+      >
+        <VanRadio name="0"><span>匿名发表</span></VanRadio>
+        <VanRadio name="1">
+          <span>您的昵称</span>
+          <input v-model="nickname" placeholder="请输入" />
+        </VanRadio>
+      </VanRadioGroup>
+
+      <div class="message-area">
+        <textarea
+          v-model="message"
+          maxlength="100"
+          placeholder="请写下您的留言"
+        />
+
+        <span>{{ message.length }}/100</span>
+        <div class="message-area-footer">
+          <VanButton
+            :loading="submitLoading"
+            type="primary"
+            @click="handleSubmit"
+            >提交</VanButton
+          >
+          <VanButton @click="message = ''">重置</VanButton>
+        </div>
+      </div>
+    </template>
+  </div>
+
+  <div class="message-footer" @click="formVisible = !formVisible">
+    <div :class="{ success: successVisible }">
+      <p v-if="!successVisible" class="animate__animated animate__fadeInRight">
+        请写下您的留言
+      </p>
+      <p v-else class="animate__animated animate__fadeInLeft">留言成功!</p>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { showNotify } from "vant";
+import { getCommentListApi, saveCommentApi } from "@/api";
+import { PaginationType, usePagination } from "@nj/base";
+
+const formVisible = ref(false);
+const checked = ref("0");
+const nickname = ref("");
+const message = ref("");
+const submitLoading = ref(false);
+const successVisible = ref(false);
+const { pageNum, loading, list, noData, noMore, getList } = usePagination(
+  async (params) => {
+    return getCommentListApi({
+      ...params,
+    });
+  },
+  PaginationType.CONCAT,
+  20
+);
+
+const onLoad = () => {
+  getList();
+  pageNum.value++;
+};
+
+const handleSubmit = async () => {
+  const name = checked.value === "0" ? "匿名" : nickname.value;
+  if (!name) {
+    showNotify({ message: "请输入昵称", type: "danger", zIndex: 3100 });
+    return;
+  } else if (!message.value) {
+    showNotify({ message: "请输入留言", type: "danger", zIndex: 3100 });
+    return;
+  }
+
+  try {
+    submitLoading.value = true;
+    await saveCommentApi({
+      name,
+      content: message.value,
+    });
+    closeForm();
+    successVisible.value = true;
+    setTimeout(() => {
+      successVisible.value = false;
+    }, 2000);
+  } finally {
+    submitLoading.value = false;
+  }
+};
+
+const closeForm = () => {
+  nickname.value = "";
+  message.value = "";
+  formVisible.value = false;
+};
+</script>
+
+<style lang="scss" scoped>
+@use "./index.scss";
+</style>

+ 215 - 0
packages/mobile/src/views/Scene/components/AddressPopup.vue

@@ -0,0 +1,215 @@
+<template>
+  <van-popup v-model:show="show" class="address-popup" @open="handleOpen">
+    <p class="address-popup-header">当前位置:建设成就厅</p>
+    <div id="mapDiv" class="cad-con" />
+    <ul>
+      <li
+        v-for="(item, i) in fixList"
+        :key="i"
+        :class="{ active: isContain(item) }"
+        @click="handleItem(item)"
+      >
+        {{ item.name }}
+      </li>
+    </ul>
+  </van-popup>
+</template>
+
+<script setup>
+import { ref, computed, watch, nextTick } from "vue";
+import { markers } from "../constants";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false,
+  },
+  somedata: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+let leafletMap = null;
+let map_poi = [];
+const current = ref({});
+const view_scale = 4;
+const fixList = ref([]);
+const emits = defineEmits(["update:visible", "loadScene", "changeScene"]);
+const show = computed({
+  get() {
+    return props.visible;
+  },
+  set(v) {
+    emits("update:visible", v);
+  },
+});
+
+const isContain = (item) => {
+  for (let i = 0; i < item.children.length; i++) {
+    const ele = item.children[i];
+    for (let iii = 0; iii < ele.scenes.length; iii++) {
+      const element = ele.scenes[iii];
+      if (element.id == current.value.id) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+};
+
+const handleItem = (item) => {
+  const _item = item.children[0].scenes[0];
+  if (current.value.sceneCode == _item.sceneCode) {
+    return;
+  }
+  emits("loadScene", _item.sceneCode);
+  show.value = false;
+  current.value = _item;
+};
+
+const handleOpen = () => {
+  if (Boolean(leafletMap)) return;
+
+  nextTick(() => {
+    if (Object.keys(props.somedata).length) {
+      const plan = {
+        index: 0,
+        name: "sandpics",
+        pic: "/img/map.png",
+        w: "2048",
+        h: "2048",
+      };
+
+      let img_height = plan.w / 100;
+      let img_width = -plan.h / 100;
+      let url = plan.pic;
+
+      var corner1 = L.latLng(0, 0); //设置左上角经纬度
+      var corner2 = L.latLng(img_width, img_height); //设置右下点经纬度
+      var bounds = L.latLngBounds(corner1, corner2); //构建视图限制范围/
+
+      leafletMap = L.map("mapDiv", {
+        zoomControl: false,
+        attributionControl: false,
+        maxBounds: bounds,
+      }).setView([img_height / 2, img_width / 2], view_scale);
+      leafletMap.options.minZoom = 3.4;
+      leafletMap.options.maxZoom = 7;
+
+      let imageBounds = [
+        [0, 0],
+        [img_width, img_height],
+      ];
+
+      L.imageOverlay(url, imageBounds, { opacity: 1, zIndex: -2 }).addTo(
+        leafletMap
+      );
+
+      for (let i = 0; i < markers.length; i++) {
+        let marker = markers[i];
+        map_poi.push({
+          name: marker.name,
+          view_id: marker.view_id,
+          position: [-marker.y / 100, marker.x / 100],
+          visual: marker.visual,
+        });
+      }
+
+      map_poi.forEach(function (element, i) {
+        let html =
+          '<div class="point " index="' +
+          i +
+          '"><div class="click_model"></div><div class="point_bg"><div class="rotate"></div></div></div>';
+        let myIcon = L.divIcon({ className: "", html: html });
+        // console.log(element.position)
+        L.marker([element.position[0], element.position[1]], {
+          icon: myIcon,
+          zIndexOffset: 1000,
+        }).addTo(leafletMap);
+      });
+      $("#mapDiv").on("click", ".point", function () {
+        let index = $(this).attr("index");
+        let view_id = _map_poi[index].view_id;
+        emits("loadScene", view_id);
+      });
+
+      emits("changeScene");
+    }
+  });
+};
+
+watch(
+  () => props.somedata,
+  (newVal) => {
+    if (newVal.catalogRoot) {
+      let list = newVal.catalogRoot.map((item) => {
+        let ttt = { ...item };
+        ttt.children = item.children.map((ii) => {
+          let tmp = newVal.scenes.filter((iii) => iii.category == ii);
+
+          tmp = tmp.sort((a, b) => a.weight - b.weight);
+
+          let name =
+            newVal.catalogs.find((iii) => {
+              return iii.id == ii;
+            }).name || "";
+
+          tmp.forEach((j) => {
+            j.cname = ttt.name;
+          });
+          return {
+            id: ii,
+            scenes: tmp || [],
+            name,
+            shensuo: false,
+          };
+        });
+
+        ttt.shensuo = true;
+        return ttt;
+      });
+      fixList.value = [...list];
+    }
+  }
+);
+</script>
+
+<style lang="scss" scoped>
+.address-popup {
+  width: 627px;
+  border: 4px solid #f8d163;
+  background: rgba(255, 252, 243, 0.8);
+  backdrop-filter: blur(5px);
+
+  &-header {
+    padding: 0 30px;
+    height: 130px;
+    line-height: 130px;
+    font-size: 32px;
+    color: #f8d163;
+    border-bottom: 4px solid #f8d163;
+    background: #c50b10;
+  }
+  :deep(.leaflet-marker-icon) {
+    display: none !important;
+  }
+  ul {
+    height: 440px;
+    overflow: auto;
+    border-top: 4px solid #f8d163;
+
+    li {
+      padding: 0 90px;
+      height: 68px;
+      line-height: 68px;
+      font-size: 27px;
+
+      &.active {
+        color: #f8d163;
+        background: #c50b10;
+      }
+    }
+  }
+}
+</style>

+ 373 - 0
packages/mobile/src/views/Scene/components/Danmaku.vue

@@ -0,0 +1,373 @@
+<template>
+  <div class="danmaku-area" @keydown.stop>
+    <div v-if="isShowList" class="danmaku-container" ref="chatContainer">
+      <!-- <ul class=""> -->
+      <transition-group appear tag="ul" class="danmaku-list" name="dm">
+        <li
+          class="danmaku-list-item"
+          v-for="(item, index) in showDanmakuData"
+          :key="index"
+        >
+          <span v-html="item"></span>
+        </li>
+      </transition-group>
+    </div>
+    <div class="input-container">
+      <input
+        @click.stop="toggleSelectMenu"
+        v-model="danmu"
+        type="text"
+        placeholder="请选择弹幕发送吧~"
+        class="send-choices"
+      />
+      <div class="send-btn-container">
+        <img
+          @click="hideList"
+          class="show-icon"
+          :src="showIcon || ''"
+          v-if="isShowList"
+        />
+        <img
+          @click="showList"
+          class="close-icon"
+          :src="closeIcon || ''"
+          v-if="!isShowList"
+        />
+        <button class="send-btn primary" @click="leaveMsg">发送</button>
+      </div>
+      <ul class="show-list" v-show="isShowSelectList">
+        <li
+          class="list-item"
+          v-for="(item, key) in quotes"
+          :key="key"
+          @click="sendDanmaku(key)"
+          v-html="item"
+        ></li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+import { saveBarrageApi } from "@/api";
+
+export default {
+  name: "danmaku",
+  props: {
+    quotes: {
+      type: Array,
+      default: function () {
+        return [];
+      },
+    },
+    danmuArr: {
+      type: Array,
+      default: function () {
+        return [];
+      },
+    },
+    showIcon: {
+      type: String,
+      required: true,
+    },
+    closeIcon: {
+      type: String,
+      required: true,
+    },
+    arrowIcon: {
+      type: String,
+      required: true,
+    },
+    limit: {
+      type: Number,
+      required: false,
+      default: 5000,
+    },
+  },
+  watch: {
+    isNotInputAction: function () {},
+    isShowSelectList(newVal) {
+      this.isShowList = !newVal;
+    },
+  },
+
+  data() {
+    return {
+      danmu: "",
+      showDanmakuData: [],
+      isShowList: true,
+      isShowSelectList: false,
+      isNotInputAction: false,
+      timer: null,
+      autoIndex: 0,
+    };
+  },
+  methods: {
+    keydownstop(e) {
+      e.stopPropagation();
+      return;
+    },
+    toggleSelectMenu() {
+      this.isShowSelectList = !this.isShowSelectList;
+    },
+    showList() {
+      this.isShowList = true;
+    },
+    hideList() {
+      this.isShowList = false;
+    },
+    sendDanmaku(index) {
+      const text = this.quotes[index];
+      this.danmu = text;
+      this.leaveMsg();
+    },
+    sendDanmakuSelf(text) {
+      if (this.showDanmakuData.length <= this.limit) {
+        this.showDanmakuData.push(text);
+      }
+      this.isShowSelectList = false;
+      this.isNotInputAction = true;
+      this.danmu = "";
+    },
+    async leaveMsg() {
+      let tmp = this.danmu.trim();
+      if (!tmp) {
+        return alert("弹幕不能为空");
+      }
+      await saveBarrageApi({
+        content: tmp,
+      });
+      this.danmu = "";
+      this.sendDanmakuSelf(tmp);
+    },
+    autoPopAnimation() {
+      if (!this.isNotInputAction) {
+        this.timer = setInterval(() => {
+          if (this.autoIndex < this.danmuArr.length) {
+            if (this.danmuArr[this.autoIndex]) {
+              this.showDanmakuData.push(this.danmuArr[this.autoIndex]);
+              this.autoIndex++;
+            } else {
+              clearInterval(this.timer);
+            }
+          } else {
+            this.autoIndex = 0;
+            this.showDanmakuData.push(this.danmuArr[this.autoIndex]);
+            if (this.showDanmakuData.length > 50) {
+              this.showDanmakuData.splice(0, 50);
+            }
+          }
+
+          this.$nextTick(() => {
+            if (this.$refs.chatContainer) {
+              this.$refs.chatContainer.scrollTo({
+                top: this.$refs.chatContainer.scrollHeight,
+                behavior: "smooth",
+              });
+            }
+          });
+        }, 2000);
+      }
+    },
+  },
+  mounted() {
+    this.autoPopAnimation();
+  },
+  unmounted() {
+    clearInterval(this.timer);
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.danmaku-area {
+  position: fixed;
+  left: 27px;
+  bottom: 59px;
+}
+.danmaku-container {
+  max-width: 487px;
+  max-height: 280px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+
+/* .hidedanmu{
+  visibility: hidden;
+  pointer-events: none;
+} */
+
+.danmaku-list {
+  text-decoration: none;
+  max-width: 487px;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  margin: 0;
+  padding: 0;
+  &::-webkit-scrollbar {
+    width: 2px;
+  }
+  &::-webkit-scrollbar-thumb {
+    outline: none;
+  }
+}
+.danmaku-list li.danmaku-list-item {
+  margin-bottom: 15px;
+  padding: 0;
+  display: block;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  text-align: left;
+}
+.danmaku-list li.danmaku-list-item > span {
+  padding: 0 20px;
+  line-height: 40px;
+  font-size: 24px;
+  display: inline-block;
+  margin: 0;
+  color: #fff;
+  border-radius: 50px;
+  background: rgba(51, 51, 51, 0.39);
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  text-align: left;
+  max-width: 487px;
+}
+.danmaku-list li.danmaku-list-item:nth-last-child(6) {
+  /* visibility: hidden; */
+  animation: fadeout 0.3s linear;
+}
+
+.input-container {
+  background: rgba(51, 51, 51, 0.39);
+  border-radius: 50px;
+  width: 487px;
+  height: 80px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  padding: 18px;
+}
+.input-container {
+  > span,
+  > input {
+    flex: 1;
+    width: 0;
+    cursor: pointer;
+    font-size: 24px;
+    background: none;
+    outline: none;
+    border: none;
+    color: #fff;
+    &::placeholder {
+      color: #ebebeb;
+    }
+  }
+}
+.input-container > * {
+  cursor: pointer;
+}
+.arrow-icon {
+  margin-right: 8px;
+  width: 18px;
+}
+
+.send-btn {
+  width: 93px;
+  height: 53px;
+  line-height: 53px;
+  border: none;
+  text-align: center;
+  background: #c50b10;
+  color: #fff;
+  border-radius: 50px;
+  font-size: 24px;
+  cursor: pointer;
+}
+.send-btn:hover,
+.send-btn:focus {
+  background: rgb(153, 17, 17);
+}
+
+.send-btn-container {
+  display: flex;
+  align-items: center;
+}
+.send-btn-container .show-icon,
+.send-btn-container .close-icon {
+  width: 60px;
+  margin-right: 7px;
+}
+.input-container .show-list {
+  text-decoration: none;
+  position: absolute;
+  left: 0;
+  bottom: 100px;
+  background: rgba(0, 0, 0, 0.4);
+  width: 487px;
+  color: #fff;
+  border-radius: 10px;
+  max-height: 288px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+  margin: 0;
+  padding: 0;
+}
+.input-container .show-list .list-item {
+  text-align: left;
+  font-size: 24px;
+  line-height: 50px;
+  text-decoration: none;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  color: #fff;
+  max-width: 100%;
+  padding: 0 18px;
+  overflow: hidden;
+}
+
+.dm-enter {
+  opacity: 0;
+  transform: translateY(20px);
+}
+
+.dm-leave-to {
+  opacity: 0;
+  transform: translateY(-50px);
+}
+
+.dm-enter-active,
+.dm-leave-active {
+  transition: all 0.6s ease;
+}
+
+.dm-move {
+  transition: all 0.6s ease;
+}
+
+.dm-leave-active {
+  position: absolute;
+}
+
+@keyframes fadeout {
+  0% {
+    opacity: 1;
+    transform: translateY(20px);
+  }
+  50% {
+    opacity: 0.7;
+    transform: translateY(6px);
+  }
+  100% {
+    opacity: 0.2;
+    transform: translateY(0px);
+  }
+}
+</style>

+ 565 - 0
packages/mobile/src/views/Scene/constants.js

@@ -0,0 +1,565 @@
+let markers = [
+  {
+    index: 0,
+    name: "marker0",
+    x: "920",
+    y: "1850",
+    view_id: "LC720_eZhDEstbe",
+    visual: "226",
+  },
+  {
+    index: 1,
+    name: "marker1",
+    x: "750",
+    y: "1800",
+    view_id: "LC720_mw9wpsWTK",
+    visual: "216",
+  },
+  {
+    index: 2,
+    name: "marker2",
+    x: "1050",
+    y: "1800",
+    view_id: "LC720_Dplnb19mZ",
+    visual: "230",
+  },
+  {
+    index: 3,
+    name: "marker3",
+    x: "920",
+    y: "1550",
+    view_id: "LC720_TejndfSL4",
+    visual: "226",
+  },
+  {
+    index: 4,
+    name: "marker4",
+    x: "680",
+    y: "1350",
+    view_id: "LC720_Nm6KFDHYK",
+    visual: "225",
+  },
+  {
+    index: 5,
+    name: "marker5",
+    x: "680",
+    y: "1520",
+    view_id: "LC720_Oipp7VXa9",
+    visual: "225",
+  },
+  {
+    index: 6,
+    name: "marker6",
+    x: "440",
+    y: "1520",
+    view_id: "LC720_fkWCpMyQy",
+    visual: "225",
+  },
+  {
+    index: 7,
+    name: "marker7",
+    x: "310",
+    y: "1600",
+    view_id: "LC720_K37J1GDpT",
+    visual: "235",
+  },
+  {
+    index: 8,
+    name: "marker8",
+    x: "420",
+    y: "1320",
+    view_id: "LC720_r5y180R4M",
+    visual: "235",
+  },
+  {
+    index: 9,
+    name: "marker9",
+    x: "230",
+    y: "1220",
+    view_id: "LC720_k7Pxrby4X",
+    visual: "225",
+  },
+  {
+    index: 10,
+    name: "marker10",
+    x: "230",
+    y: "1050",
+    view_id: "LC720_Omej5TknN",
+    visual: "225",
+  },
+  {
+    index: 11,
+    name: "marker11",
+    x: "350",
+    y: "1050",
+    view_id: "LC720_hDq1alMFN",
+    visual: "225",
+  },
+  {
+    index: 12,
+    name: "marker12",
+    x: "300",
+    y: "800",
+    view_id: "LC720_OTDOCzDCL",
+    visual: "225",
+  },
+  {
+    index: 13,
+    name: "marker13",
+    x: "300",
+    y: "600",
+    view_id: "LC720_YEjYNnoEd",
+    visual: "225",
+  },
+  {
+    index: 14,
+    name: "marker14",
+    x: "300",
+    y: "400",
+    view_id: "LC720_nzVR4b849",
+    visual: "225",
+  },
+  {
+    index: 15,
+    name: "marker15",
+    x: "510",
+    y: "450",
+    view_id: "LC720_HeFPKGner",
+    visual: "225",
+  },
+  {
+    index: 16,
+    name: "marker16",
+    x: "590",
+    y: "340",
+    view_id: "LC720_HMZFJo228",
+    visual: "225",
+  },
+  {
+    index: 17,
+    name: "marker17",
+    x: "710",
+    y: "450",
+    view_id: "LC720_XplUziNn8",
+    visual: "225",
+  },
+  {
+    index: 18,
+    name: "marker18",
+    x: "800",
+    y: "340",
+    view_id: "LC720_BhrNaLk8Y",
+    visual: "225",
+  },
+  {
+    index: 19,
+    name: "marker19",
+    x: "990",
+    y: "340",
+    view_id: "LC720_Z1SIQQ8fu",
+    visual: "225",
+  },
+  {
+    index: 20,
+    name: "marker20",
+    x: "1190",
+    y: "340",
+    view_id: "LC720_BEPETi52W",
+    visual: "225",
+  },
+  {
+    index: 21,
+    name: "marker21",
+    x: "920",
+    y: "450",
+    view_id: "LC720_CL6CdrhmU",
+    visual: "225",
+  },
+  {
+    index: 22,
+    name: "marker22",
+    x: "1150",
+    y: "450",
+    view_id: "LC720_pEqydYHma",
+    visual: "225",
+  },
+  {
+    index: 23,
+    name: "marker23",
+    x: "1280",
+    y: "400",
+    view_id: "LC720_5a0Q48v5c",
+    visual: "225",
+  },
+  {
+    index: 24,
+    name: "marker24",
+    x: "1450",
+    y: "400",
+    view_id: "LC720_O6GTwTCq6",
+    visual: "225",
+  },
+  {
+    index: 25,
+    name: "marker25",
+    x: "1780",
+    y: "400",
+    view_id: "LC720_UtaEbfy25",
+    visual: "225",
+  },
+  {
+    index: 26,
+    name: "marker26",
+    x: "1610",
+    y: "370",
+    view_id: "LC720_2yY3eqGBx",
+    visual: "225",
+  },
+
+  {
+    index: 27,
+    name: "marker27",
+    x: "1610",
+    y: "190",
+    view_id: "LC720_ppfiNQSS0",
+    visual: "225",
+  },
+  {
+    index: 28,
+    name: "marker28",
+    x: "1760",
+    y: "110",
+    view_id: "LC720_RHOlCZWUx",
+    visual: "225",
+  },
+  {
+    index: 29,
+    name: "marker29",
+    x: "1850",
+    y: "190",
+    view_id: "LC720_zfw3KWAir",
+    visual: "225",
+  },
+  {
+    index: 30,
+    name: "marker30",
+    x: "1760",
+    y: "250",
+    view_id: "LC720_d0uoXDcjG",
+    visual: "225",
+  },
+  {
+    index: 31,
+    name: "marker31",
+    x: "1610",
+    y: "140",
+    view_id: "LC720_P2zGXrQHc",
+    visual: "225",
+  },
+  {
+    index: 32,
+    name: "marker32",
+    x: "1430",
+    y: "110",
+    view_id: "LC720_NRoIIldAC",
+    visual: "225",
+  },
+
+  {
+    index: 33,
+    name: "marker33",
+    x: "1380",
+    y: "190",
+    view_id: "LC720_nZzbvHAF3",
+    visual: "225",
+  },
+  {
+    index: 34,
+    name: "marker34",
+    x: "1430",
+    y: "250",
+    view_id: "LC720_wDP3u1VNq",
+    visual: "225",
+  },
+  {
+    index: 35,
+    name: "marker35",
+    x: "1610",
+    y: "430",
+    view_id: "LC720_wudKxT5ap",
+    visual: "225",
+  },
+
+  {
+    index: 36,
+    name: "marker36",
+    x: "1610",
+    y: "680",
+    view_id: "LC720_8JIqiAttw",
+    visual: "225",
+  },
+  {
+    index: 37,
+    name: "marker37",
+    x: "1760",
+    y: "570",
+    view_id: "LC720_ehuM35Egs",
+    visual: "225",
+  },
+  {
+    index: 38,
+    name: "marker38",
+    x: "1850",
+    y: "650",
+    view_id: "LC720_fVXqRrfm3",
+    visual: "225",
+  },
+  {
+    index: 39,
+    name: "marker39",
+    x: "1760",
+    y: "710",
+    view_id: "LC720_35Ho59DCY",
+    visual: "225",
+  },
+  {
+    index: 40,
+    name: "marker40",
+    x: "1610",
+    y: "600",
+    view_id: "LC720_VCKwxQWdn",
+    visual: "225",
+  },
+  {
+    index: 41,
+    name: "marker41",
+    x: "1430",
+    y: "570",
+    view_id: "LC720_FT0JvKic4",
+    visual: "225",
+  },
+  {
+    index: 42,
+    name: "marker42",
+    x: "1380",
+    y: "650",
+    view_id: "LC720_kdOXUW8Vi",
+    visual: "225",
+  },
+  {
+    index: 43,
+    name: "marker43",
+    x: "1430",
+    y: "710",
+    view_id: "LC720_V0YNXIiat",
+    visual: "225",
+  },
+
+  {
+    index: 44,
+    name: "marker44",
+    x: "1610",
+    y: "910",
+    view_id: "LC720_1nRzbqr6c",
+    visual: "225",
+  },
+  {
+    index: 45,
+    name: "marker45",
+    x: "1760",
+    y: "790",
+    view_id: "LC720_wtJer0ZMi",
+    visual: "225",
+  },
+  {
+    index: 46,
+    name: "marker46",
+    x: "1850",
+    y: "880",
+    view_id: "LC720_uAPUfDB9h",
+    visual: "225",
+  },
+  {
+    index: 47,
+    name: "marker47",
+    x: "1760",
+    y: "940",
+    view_id: "LC720_PCb9g8e4F",
+    visual: "225",
+  },
+  {
+    index: 48,
+    name: "marker48",
+    x: "1610",
+    y: "830",
+    view_id: "LC720_pKzOqupQ4",
+    visual: "225",
+  },
+  {
+    index: 49,
+    name: "marker49",
+    x: "1430",
+    y: "800",
+    view_id: "LC720_S0pELKocl",
+    visual: "225",
+  },
+  {
+    index: 50,
+    name: "marker50",
+    x: "1380",
+    y: "880",
+    view_id: "LC720_p5w7BPWhR",
+    visual: "225",
+  },
+  {
+    index: 51,
+    name: "marker51",
+    x: "1430",
+    y: "940",
+    view_id: "LC720_JWOK8T4MS",
+    visual: "225",
+  },
+
+  {
+    index: 52,
+    name: "marker52",
+    x: "1610",
+    y: "1120",
+    view_id: "LC720_y2hYuQsjA",
+    visual: "225",
+  },
+  {
+    index: 53,
+    name: "marker53",
+    x: "1760",
+    y: "1000",
+    view_id: "LC720_5vdrcRUT7",
+    visual: "225",
+  },
+  {
+    index: 54,
+    name: "marker54",
+    x: "1850",
+    y: "1090",
+    view_id: "LC720_v8qej7ttA",
+    visual: "225",
+  },
+  {
+    index: 55,
+    name: "marker55",
+    x: "1760",
+    y: "1150",
+    view_id: "LC720_kKNEytXjD",
+    visual: "225",
+  },
+  {
+    index: 56,
+    name: "marker56",
+    x: "1610",
+    y: "1040",
+    view_id: "LC720_4WfZkgxXF",
+    visual: "225",
+  },
+  {
+    index: 57,
+    name: "marker57",
+    x: "1430",
+    y: "1010",
+    view_id: "LC720_Tty7oVld6",
+    visual: "225",
+  },
+  {
+    index: 58,
+    name: "marker58",
+    x: "1380",
+    y: "1090",
+    view_id: "LC720_GxJo5Z3zQ",
+    visual: "225",
+  },
+  {
+    index: 59,
+    name: "marker59",
+    x: "1430",
+    y: "1150",
+    view_id: "LC720_eL3On0pkS",
+    visual: "225",
+  },
+
+  {
+    index: 60,
+    name: "marker60",
+    x: "1610",
+    y: "1340",
+    view_id: "LC720_mS5mB8SV6",
+    visual: "225",
+  },
+  {
+    index: 61,
+    name: "marker61",
+    x: "1760",
+    y: "1220",
+    view_id: "LC720_uxFCHu9lO",
+    visual: "225",
+  },
+  {
+    index: 62,
+    name: "marker62",
+    x: "1850",
+    y: "1310",
+    view_id: "LC720_UM4wPxhrn",
+    visual: "225",
+  },
+  {
+    index: 63,
+    name: "marker63",
+    x: "1760",
+    y: "1370",
+    view_id: "LC720_WojpJTI5C",
+    visual: "225",
+  },
+  {
+    index: 64,
+    name: "marker64",
+    x: "1610",
+    y: "1260",
+    view_id: "LC720_g045M71XV",
+    visual: "225",
+  },
+  {
+    index: 65,
+    name: "marker65",
+    x: "1430",
+    y: "1230",
+    view_id: "LC720_HVTHvZ04A",
+    visual: "225",
+  },
+  {
+    index: 66,
+    name: "marker66",
+    x: "1380",
+    y: "1310",
+    view_id: "LC720_0GnSiOvjj",
+    visual: "225",
+  },
+  {
+    index: 67,
+    name: "marker67",
+    x: "1430",
+    y: "1370",
+    view_id: "LC720_1BtR3iZLw",
+    visual: "225",
+  },
+
+  {
+    index: 68,
+    name: "marker68",
+    x: "1610",
+    y: "1580",
+    view_id: "LC720_GllVk1HYk",
+    visual: "225",
+  },
+];
+
+// LC720_OT5qC4rar
+
+export { markers };

+ 0 - 0
packages/mobile/src/views/Scene/images/address-1.png


Some files were not shown because too many files changed in this diff