mobile.html 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>ArozOS</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
  7. <link rel="manifest" href="manifest.webmanifest">
  8. <script src="script/jquery.min.js"></script>
  9. <script src="script/ao_module.js"></script>
  10. <script src="script/applocale.js"></script>
  11. <style>
  12. /* ── Reset ── */
  13. *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  14. html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
  15. body {
  16. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  17. -webkit-font-smoothing: antialiased;
  18. user-select: none;
  19. -webkit-user-select: none;
  20. }
  21. /* ── Wallpaper ── */
  22. #backdrop {
  23. position: fixed; inset: 0; z-index: 0;
  24. background: url('img/desktop/bg/init.jpg') center/cover no-repeat;
  25. transition: background-image 0.5s;
  26. }
  27. #backdrop-dim {
  28. position: fixed; inset: 0; z-index: 1;
  29. background: rgba(0,0,0,0.28);
  30. pointer-events: none;
  31. transition: background 0.3s;
  32. }
  33. body.dark-mode #backdrop-dim { background: rgba(0,0,0,0.52); }
  34. /* ── Launchpad container ── */
  35. #launchpad { position: fixed; inset: 0; z-index: 2; overflow: hidden; }
  36. .lp-section {
  37. position: absolute; width: 100%; height: 100%; top: 0; left: 0;
  38. transition: transform 0.42s cubic-bezier(0.4, 0, 0.2, 1);
  39. will-change: transform;
  40. }
  41. /* ── Section 1: Home ── */
  42. #sec-home {
  43. display: flex; flex-direction: column; align-items: center;
  44. padding-top: max(env(safe-area-inset-top), 20px);
  45. }
  46. #clock-widget {
  47. margin-top: 14vh; text-align: center; color: #fff;
  48. text-shadow: 0 2px 14px rgba(0,0,0,0.45); pointer-events: none;
  49. }
  50. #clock-time { font-size: clamp(60px, 18vw, 92px); font-weight: 100; letter-spacing: -2px; line-height: 1; }
  51. #clock-date { font-size: clamp(14px, 4vw, 18px); font-weight: 300; margin-top: 8px; opacity: 0.88; }
  52. #home-hint {
  53. position: absolute; bottom: calc(max(env(safe-area-inset-bottom), 12px) + 120px);
  54. left: 0; right: 0; text-align: center;
  55. color: rgba(255,255,255,0.5); font-size: 11px; pointer-events: none;
  56. animation: hint 2.2s ease-in-out infinite;
  57. }
  58. @keyframes hint { 0%,100%{opacity:.5;transform:translateY(0)} 50%{opacity:.15;transform:translateY(-5px)} }
  59. #home-hint svg { display: block; margin: 0 auto 2px; }
  60. /* Dock */
  61. #dock {
  62. position: absolute;
  63. bottom: calc(max(env(safe-area-inset-bottom), 10px) + 8px);
  64. left: 14px; right: 14px;
  65. }
  66. #dock-inner {
  67. background: rgba(255,255,255,0.22);
  68. backdrop-filter: blur(22px) saturate(180%);
  69. -webkit-backdrop-filter: blur(22px) saturate(180%);
  70. border-radius: 26px; padding: 10px 6px;
  71. display: grid; grid-template-columns: repeat(4,1fr);
  72. }
  73. body.dark-mode #dock-inner { background: rgba(0,0,0,0.38); }
  74. .dock-item {
  75. display: flex; flex-direction: column; align-items: center; gap: 4px;
  76. cursor: pointer; padding: 7px 4px; border-radius: 16px;
  77. transition: opacity .15s; -webkit-tap-highlight-color: transparent;
  78. }
  79. .dock-item:active { opacity: .55; }
  80. .dock-item img { width: clamp(46px,13vw,60px); height: clamp(46px,13vw,60px); border-radius: 14px; object-fit: cover; box-shadow: 0 2px 10px rgba(0,0,0,.3); }
  81. .dock-label { font-size: 10px; color: rgba(255,255,255,.9); text-shadow: 0 1px 3px rgba(0,0,0,.5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 68px; text-align: center; }
  82. /* ── Section 2: Apps ── */
  83. #sec-apps { display: flex; flex-direction: column; }
  84. #apps-header {
  85. padding: calc(max(env(safe-area-inset-top),16px) + 10px) 16px 8px;
  86. flex-shrink: 0; display: flex; align-items: center; justify-content: space-between;
  87. }
  88. #apps-header h2 { font-size: 26px; font-weight: 700; color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.4); }
  89. #apps-edit-btn {
  90. flex-shrink: 0;
  91. background: rgba(255,255,255,.2); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  92. border: 1px solid rgba(255,255,255,.28); border-radius: 14px;
  93. padding: 5px 16px; color: #fff; font-size: 14px; font-weight: 500;
  94. cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .15s, color .15s;
  95. margin-right: 3em; /* Avoid the control panel button */
  96. }
  97. #apps-edit-btn.on { background: rgba(255,255,255,.9); color: #111; border-color: transparent; }
  98. #apps-edit-btn:active { opacity: .6; }
  99. /* drag states */
  100. .app-item.dragging { opacity: .25; }
  101. .app-item.drag-target .app-icon-wrap { outline: 2px solid rgba(255,255,255,.85); outline-offset: 3px; border-radius: 16px; }
  102. /* removed-app ghost tiles */
  103. .app-item.app-removed { opacity: .35; }
  104. .app-restore {
  105. position:absolute; top:-5px; left:-5px; width:26px; height:26px;
  106. border-radius:50%; background:#107c10;
  107. display:flex; align-items:center; justify-content:center;
  108. z-index:5; cursor:pointer; border:2px solid #fff;
  109. }
  110. .app-restore img { width:13px; height:13px; pointer-events:none; display:block; }
  111. #app-search-wrap {
  112. margin: 6px 14px 4px; flex-shrink: 0;
  113. display: flex; align-items: center; gap: 8px;
  114. background: rgba(255,255,255,.18); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  115. border-radius: 12px; padding: 8px 12px;
  116. border: 1px solid rgba(255,255,255,.22);
  117. }
  118. body.dark-mode #app-search-wrap { background: rgba(0,0,0,.3); border-color: rgba(255,255,255,.12); }
  119. #app-search-input { flex:1; background:transparent; border:none; outline:none; color:#fff; font-size:14px; }
  120. #app-search-input::placeholder { color: rgba(255,255,255,.55); }
  121. #edit-done-btn {
  122. position: absolute; top: calc(max(env(safe-area-inset-top),14px) + 12px); right: 14px;
  123. background: rgba(255,255,255,.92); color: #111; border: none; border-radius: 14px;
  124. padding: 6px 16px; font-size: 14px; font-weight: 600; cursor: pointer;
  125. display: none; z-index: 10; box-shadow: 0 2px 10px rgba(0,0,0,.18);
  126. }
  127. #app-grid-wrap {
  128. flex: 1; overflow-y: auto; overflow-x: hidden;
  129. -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  130. padding: 4px 8px calc(max(env(safe-area-inset-bottom),12px) + 8px);
  131. }
  132. #app-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 2px 0; }
  133. @media(min-width:460px){ #app-grid{grid-template-columns:repeat(5,1fr);} }
  134. @media(min-width:700px){ #app-grid{grid-template-columns:repeat(6,1fr);} }
  135. .app-item {
  136. display: flex; flex-direction: column; align-items: center; gap: 5px;
  137. padding: 9px 4px; cursor: pointer; border-radius: 18px;
  138. transition: background .15s; position: relative;
  139. -webkit-tap-highlight-color: transparent;
  140. }
  141. .app-item:active:not(.jiggling) { background: rgba(255,255,255,.12); }
  142. .app-icon-wrap { position: relative; width: clamp(52px,14vw,68px); height: clamp(52px,14vw,68px); }
  143. .app-icon-wrap img {
  144. width: 100%; height: 100%; border-radius: 16px; object-fit: cover;
  145. box-shadow: 0 2px 8px rgba(0,0,0,.3); pointer-events: none;
  146. display: block;
  147. }
  148. .app-name { font-size: clamp(9px,2.4vw,11px); color: rgba(255,255,255,.94); text-shadow: 0 1px 3px rgba(0,0,0,.5); text-align: center; max-width: 76px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; }
  149. /* Jiggle */
  150. @keyframes jiggle { 0%{transform:rotate(-1.8deg) scale(1.02)} 50%{transform:rotate(1.8deg) scale(1.02)} 100%{transform:rotate(-1.8deg) scale(1.02)} }
  151. .jiggling .app-icon-wrap { animation: jiggle .32s infinite ease-in-out; }
  152. /* Delete badge */
  153. .app-del { position:absolute; top:-5px; left:-5px; width:26px; height:26px; border-radius:50%; background:#c42b1c; display:none; z-index:5; cursor:pointer; border:2px solid #fff; align-items:center; justify-content:center; }
  154. .editing .app-del { display: flex; }
  155. .app-del img { width:13px; height:13px; pointer-events:none; display:block; }
  156. /* Drag ghost */
  157. #drag-ghost { position:fixed; pointer-events:none; z-index:999; width:clamp(52px,14vw,68px); height:clamp(52px,14vw,68px); border-radius:16px; overflow:hidden; opacity:.82; box-shadow:0 10px 28px rgba(0,0,0,.45); transform:scale(1.12); display:none; }
  158. #drag-ghost img { width:100%; height:100%; object-fit:cover; }
  159. /* ── Section 3: Files ── */
  160. #sec-files { display: flex; flex-direction: column; }
  161. #files-header {
  162. padding: calc(max(env(safe-area-inset-top),16px) + 10px) 16px 8px;
  163. flex-shrink: 0;
  164. }
  165. #files-header h2 { font-size: 26px; font-weight: 700; color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.4); }
  166. #files-header p { font-size: 12px; color: rgba(255,255,255,.6); margin-top: 3px; }
  167. #files-grid-wrap {
  168. flex:1; overflow-y:auto; overflow-x:hidden;
  169. -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
  170. padding: 10px 12px calc(max(env(safe-area-inset-bottom),12px)+8px);
  171. }
  172. #files-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
  173. @media(min-width:460px){#files-grid{grid-template-columns:repeat(4,1fr);}}
  174. .folder-item {
  175. display:flex; flex-direction:column; align-items:center; gap:6px;
  176. padding:14px 6px; border-radius:16px; cursor:pointer;
  177. background:rgba(255,255,255,.13); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  178. border:1px solid rgba(255,255,255,.18);
  179. transition:background .15s; -webkit-tap-highlight-color:transparent;
  180. }
  181. body.dark-mode .folder-item{background:rgba(0,0,0,.28);border-color:rgba(255,255,255,.1);}
  182. .folder-item:active{background:rgba(255,255,255,.24);}
  183. .folder-emoji{width:40px;height:40px;display:block;object-fit:contain;}
  184. .shortcut-icon{width:40px;height:40px;border-radius:10px;object-fit:cover;display:block;background:rgba(255,255,255,.25);}
  185. .folder-name{font-size:11px;color:rgba(255,255,255,.92);text-shadow:0 1px 3px rgba(0,0,0,.4);text-align:center;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  186. #files-empty{text-align:center;color:rgba(255,255,255,.5);margin-top:64px;font-size:14px;display:none;}
  187. /* ── Page dots ── */
  188. #page-dots {
  189. position:fixed; bottom:36px;
  190. left:50%; transform:translateX(-50%); display:flex; gap:7px; z-index:50; pointer-events:none;
  191. }
  192. .pd{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.35);transition:background .2s,transform .2s;}
  193. .pd.on{background:rgba(255,255,255,.9);transform:scale(1.35);}
  194. /* ── Control Panel ── */
  195. #cp-btn {
  196. position:fixed; top:max(env(safe-area-inset-top),10px); right:10px; z-index:82;
  197. width:36px; height:36px; border-radius:50%;
  198. background:rgba(0,0,0,.32); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  199. border:1px solid rgba(255,255,255,.22);
  200. display:flex; align-items:center; justify-content:center;
  201. cursor:pointer; -webkit-tap-highlight-color:transparent;
  202. }
  203. #cp-btn:active{opacity:.6;}
  204. #cp-backdrop{position:fixed;inset:0;z-index:80;display:none;}
  205. #cp-backdrop.on{display:block;}
  206. #cp-panel {
  207. position:fixed; top:0; right:0; z-index:81;
  208. width:min(310px,90vw); max-height:86vh; overflow-y:auto;
  209. background:rgba(235,235,235,.9); backdrop-filter:blur(40px) saturate(200%); -webkit-backdrop-filter:blur(40px) saturate(200%);
  210. border-radius:0 0 0 22px; box-shadow:0 8px 36px rgba(0,0,0,.28);
  211. padding-top:max(env(safe-area-inset-top),20px);
  212. transform:translateY(-110%); transition:transform .34s cubic-bezier(.4,0,.2,1);
  213. }
  214. body.dark-mode #cp-panel{background:rgba(28,28,28,.93);}
  215. #cp-panel.on{transform:translateY(0);}
  216. .cp-user{display:flex;align-items:center;gap:12px;padding:12px 16px 10px;}
  217. .cp-av{width:44px;height:44px;border-radius:50%;object-fit:cover;flex-shrink:0;}
  218. .cp-name{font-size:14px;font-weight:600;color:#111;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  219. body.dark-mode .cp-name{color:#f0f0f0;}
  220. .cp-grp{font-size:11px;color:#666;margin-top:1px;}
  221. body.dark-mode .cp-grp{color:#aaa;}
  222. .cp-sep{height:1px;background:rgba(0,0,0,.08);margin:0 16px;}
  223. body.dark-mode .cp-sep{background:rgba(255,255,255,.1);}
  224. .cp-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px;padding:10px 14px;}
  225. .cp-tile{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:12px 8px;background:rgba(0,0,0,.06);border-radius:14px;border:none;cursor:pointer;font-size:11px;font-weight:500;color:#111;-webkit-tap-highlight-color:transparent;transition:opacity .15s;}
  226. body.dark-mode .cp-tile{background:rgba(255,255,255,.1);color:#f0f0f0;}
  227. .cp-tile:active{opacity:.55;}
  228. .cp-tile.on{background:#111;color:#fff;}
  229. body.dark-mode .cp-tile.on{background:#e8e8e8;color:#111;}
  230. .cp-tile-icon{display:flex;align-items:center;justify-content:center;}
  231. .cp-tile-icon img{width:26px;height:26px;display:block;}
  232. /* Light/dark icon pair switching */
  233. img.cp-ic-w{display:none !important;}
  234. body.dark-mode img.cp-ic-b{display:none !important;}
  235. body.dark-mode img.cp-ic-w{display:block !important;}
  236. .cp-list-lbl{font-size:10px;font-weight:700;color:#888;text-transform:uppercase;letter-spacing:.4px;padding:6px 16px 2px;}
  237. .cp-acc-item{display:flex;align-items:center;gap:10px;padding:8px 16px;cursor:pointer;-webkit-tap-highlight-color:transparent;}
  238. .cp-acc-item:active{background:rgba(0,0,0,.05);}
  239. body.dark-mode .cp-acc-item:active{background:rgba(255,255,255,.05);}
  240. .cp-acc-item img{width:32px;height:32px;border-radius:50%;}
  241. .cp-acc-name{font-size:13px;color:#111;}
  242. body.dark-mode .cp-acc-name{color:#f0f0f0;}
  243. .cp-btns{padding:4px 14px 16px;}
  244. .cp-btn{width:100%;padding:10px 14px;margin-top:7px;background:rgba(0,0,0,.06);border:none;border-radius:12px;font-size:13px;font-weight:500;color:#111;cursor:pointer;display:flex;align-items:center;gap:10px;-webkit-tap-highlight-color:transparent;}
  245. body.dark-mode .cp-btn{background:rgba(255,255,255,.1);color:#f0f0f0;}
  246. .cp-btn:active{opacity:.55;}
  247. .cp-btn.red{color:#c42b1c;}
  248. body.dark-mode .cp-btn.red{color:#ff6b6b;}
  249. .cp-btn-ic{width:22px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
  250. .cp-btn-ic img{width:18px;height:18px;display:block;}
  251. /* ── Float Window Layer ── */
  252. #fw-layer{position:fixed;inset:0;z-index:100;display:none;flex-direction:column;background:#000;}
  253. #fw-layer.on{display:flex;}
  254. #fw-bar {
  255. background:rgba(18,18,18,.96); backdrop-filter:blur(10px);
  256. padding:calc(max(env(safe-area-inset-top),10px)+4px) 10px 8px;
  257. display:flex; align-items:center; gap:7px; flex-shrink:0;
  258. overflow-x:auto; overflow-y:hidden; scrollbar-width:none;
  259. }
  260. #fw-bar::-webkit-scrollbar{display:none;}
  261. #fw-back{flex-shrink:0;background:rgba(255,255,255,.16);border:none;border-radius:8px;padding:5px 11px;color:#fff;font-size:13px;cursor:pointer;display:flex;align-items:center;gap:4px;-webkit-tap-highlight-color:transparent;}
  262. #fw-back:active{opacity:.55;}
  263. #fw-tabs{display:flex;gap:6px;flex-shrink:0;}
  264. .fw-tab{background:rgba(255,255,255,.12);border:none;border-radius:8px;padding:4px 10px;color:rgba(255,255,255,.65);font-size:12px;cursor:pointer;display:flex;align-items:center;gap:5px;min-width:60px;max-width:160px;-webkit-tap-highlight-color:transparent;transition:background .15s;}
  265. .fw-tab.on{background:rgba(255,255,255,.26);color:#fff;}
  266. .fw-tab img{width:16px;height:16px;border-radius:3px;flex-shrink:0;}
  267. .fw-tab-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  268. .fw-tab-x{width:14px;height:14px;border-radius:50%;background:rgba(255,255,255,.2);display:inline-flex;align-items:center;justify-content:center;font-size:9px;flex-shrink:0;cursor:pointer;}
  269. .fw-tab-x:active{background:#c42b1c;}
  270. #fw-body{flex:1;position:relative;overflow:hidden;}
  271. .floatWindowWrapper{position:absolute;inset:0;display:none;}
  272. .floatWindowWrapper.on{display:block;}
  273. .floatWindow{width:100%;height:100%;}
  274. .fwtab{width:100%;height:100%;}
  275. .fwtab iframe{width:100%;height:100%;border:none;}
  276. /* ── Connection lost ── */
  277. #conndrop{position:fixed;top:max(env(safe-area-inset-top),8px);left:50%;transform:translateX(-50%);z-index:200;background:rgba(196,43,28,.9);backdrop-filter:blur(8px);border-radius:20px;padding:5px 13px;display:none;align-items:center;gap:7px;font-size:12px;color:#fff;}
  278. #conndrop img{width:14px;height:14px;}
  279. /* ── FW Tab Context Menu ── */
  280. #fw-ctx-overlay{position:fixed;inset:0;z-index:210;display:none;}
  281. #fw-ctx-overlay.on{display:block;}
  282. #fw-ctx-menu{
  283. position:fixed;z-index:211;display:none;
  284. background:rgba(235,235,235,.94);backdrop-filter:blur(40px) saturate(200%);-webkit-backdrop-filter:blur(40px) saturate(200%);
  285. border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.32);
  286. min-width:220px;overflow:hidden;
  287. transform-origin:top left;
  288. animation:fw-ctx-in .14s ease;
  289. }
  290. body.dark-mode #fw-ctx-menu{background:rgba(30,30,30,.96);}
  291. @keyframes fw-ctx-in{from{opacity:0;transform:scale(.88);}to{opacity:1;transform:scale(1);}}
  292. .fw-ctx-item{
  293. display:flex;align-items:center;gap:12px;
  294. padding:13px 16px;font-size:14px;color:#111;cursor:pointer;
  295. border:none;background:none;width:100%;text-align:left;
  296. -webkit-tap-highlight-color:transparent;transition:background .1s;
  297. }
  298. body.dark-mode .fw-ctx-item{color:#f0f0f0;}
  299. .fw-ctx-item:active{background:rgba(0,0,0,.07);}
  300. body.dark-mode .fw-ctx-item:active{background:rgba(255,255,255,.09);}
  301. .fw-ctx-item+.fw-ctx-item{border-top:1px solid rgba(0,0,0,.06);}
  302. body.dark-mode .fw-ctx-item+.fw-ctx-item{border-top-color:rgba(255,255,255,.08);}
  303. .fw-ctx-item.red{color:#c42b1c;}
  304. body.dark-mode .fw-ctx-item.red{color:#ff6b6b;}
  305. .fw-ctx-item svg{flex-shrink:0;opacity:.75;}
  306. </style>
  307. </head>
  308. <body>
  309. <div id="backdrop"></div>
  310. <div id="backdrop-dim"></div>
  311. <!-- Page indicator -->
  312. <div id="page-dots">
  313. <div class="pd on" data-s="0"></div>
  314. <div class="pd" data-s="1"></div>
  315. <div class="pd" data-s="2"></div>
  316. </div>
  317. <!-- Control panel trigger -->
  318. <div id="cp-btn" onclick="cpToggle()">
  319. <svg width="17" height="17" viewBox="0 0 17 17" fill="none">
  320. <circle cx="4.5" cy="4.5" r="1.8" fill="white"/>
  321. <circle cx="12.5" cy="4.5" r="1.8" fill="white"/>
  322. <circle cx="4.5" cy="12.5" r="1.8" fill="white"/>
  323. <circle cx="12.5" cy="12.5" r="1.8" fill="white"/>
  324. </svg>
  325. </div>
  326. <div id="cp-backdrop" onclick="cpClose()"></div>
  327. <div id="cp-panel">
  328. <div class="cp-user">
  329. <img class="cp-av" id="cp-av" src="img/desktop/system_icon/user.svg">
  330. <div style="flex:1;min-width:0;">
  331. <div class="cp-name" id="cp-name">User</div>
  332. <div class="cp-grp" id="cp-grp">@user</div>
  333. </div>
  334. </div>
  335. <div class="cp-sep"></div>
  336. <div class="cp-grid">
  337. <button class="cp-tile" id="cp-dark-btn" onclick="cpToggleDark()">
  338. <span class="cp-tile-icon">
  339. <img id="cp-dark-ic" src="SystemAO/desktop/img/icons/lightmode_black.svg">
  340. </span>
  341. <span id="cp-dark-lbl">Dark Mode</span>
  342. </button>
  343. <button class="cp-tile" onclick="cpFullscreen()">
  344. <span class="cp-tile-icon">
  345. <img class="cp-ic-b" src="SystemAO/desktop/img/icons/fullscreen_black.svg">
  346. <img class="cp-ic-w" src="SystemAO/desktop/img/icons/fullscreen_white.svg">
  347. </span>
  348. <span>Fullscreen</span>
  349. </button>
  350. <button class="cp-tile" onclick="cpWallpaper()">
  351. <span class="cp-tile-icon">
  352. <img class="cp-ic-b" src="SystemAO/desktop/img/icons/wallpaper_black.svg">
  353. <img class="cp-ic-w" src="SystemAO/desktop/img/icons/wallpaper_white.svg">
  354. </span>
  355. <span>Wallpaper</span>
  356. </button>
  357. <button class="cp-tile" onclick="cpSettings()">
  358. <span class="cp-tile-icon">
  359. <img class="cp-ic-b" src="SystemAO/desktop/img/icons/settings_black.svg">
  360. <img class="cp-ic-w" src="SystemAO/desktop/img/icons/settings_white.svg">
  361. </span>
  362. <span>Settings</span>
  363. </button>
  364. </div>
  365. <div class="cp-sep"></div>
  366. <div class="cp-list-lbl">Other Accounts</div>
  367. <div id="cp-acc-list"></div>
  368. <div class="cp-btns">
  369. <button class="cp-btn" onclick="cpAddAccount()">
  370. <span class="cp-btn-ic">
  371. <img class="cp-ic-b" src="SystemAO/desktop/img/icons/switch_user_black.svg">
  372. <img class="cp-ic-w" src="SystemAO/desktop/img/icons/switch_user_white.svg">
  373. </span>
  374. Add / Switch Account
  375. </button>
  376. <button class="cp-btn red" onclick="cpLogout()">
  377. <span class="cp-btn-ic">
  378. <img class="cp-ic-b" src="SystemAO/desktop/img/icons/signout_black.svg">
  379. <img class="cp-ic-w" src="SystemAO/desktop/img/icons/signout_white.svg">
  380. </span>
  381. Sign Out
  382. </button>
  383. </div>
  384. </div>
  385. <!-- Launchpad -->
  386. <div id="launchpad">
  387. <!-- Section 1: Home -->
  388. <div class="lp-section" id="sec-home">
  389. <div id="clock-widget">
  390. <div id="clock-time">00:00</div>
  391. <div id="clock-date">Saturday, January 1</div>
  392. </div>
  393. <div id="home-hint">
  394. <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
  395. <path d="M9 13V5M5 9L9 5L13 9" stroke="rgba(255,255,255,0.55)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  396. </svg>
  397. Swipe up for apps
  398. </div>
  399. <div id="dock">
  400. <div id="dock-inner">
  401. <div class="dock-item" onclick="launchShortcut(1)"><img id="d1" src="img/mobile/pending.svg"><span class="dock-label" id="dl1"></span></div>
  402. <div class="dock-item" onclick="launchShortcut(2)"><img id="d2" src="img/mobile/pending.svg"><span class="dock-label" id="dl2"></span></div>
  403. <div class="dock-item" onclick="launchShortcut(3)"><img id="d3" src="img/mobile/pending.svg"><span class="dock-label" id="dl3"></span></div>
  404. <div class="dock-item" onclick="launchShortcut(4)"><img id="d4" src="img/mobile/pending.svg"><span class="dock-label" id="dl4"></span></div>
  405. </div>
  406. </div>
  407. </div>
  408. <!-- Section 2: Apps -->
  409. <div class="lp-section" id="sec-apps">
  410. <div id="apps-header">
  411. <h2>Apps</h2>
  412. <button id="apps-edit-btn" onclick="toggleEdit()">Edit</button>
  413. </div>
  414. <div id="app-search-wrap">
  415. <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
  416. <circle cx="6.5" cy="6.5" r="4.5" stroke="rgba(255,255,255,0.65)" stroke-width="1.4"/>
  417. <path d="M10 10L14 14" stroke="rgba(255,255,255,0.65)" stroke-width="1.4" stroke-linecap="round"/>
  418. </svg>
  419. <input id="app-search-input" type="text" placeholder="Search apps…" oninput="filterApps(this.value)" autocomplete="off">
  420. </div>
  421. <div id="app-grid-wrap"><div id="app-grid"></div></div>
  422. </div>
  423. <!-- Section 3: Files -->
  424. <div class="lp-section" id="sec-files">
  425. <div id="files-header"><h2>Desktop</h2><p>Folders &amp; Shortcuts</p></div>
  426. <div id="files-grid-wrap">
  427. <div id="files-grid"></div>
  428. <div id="files-empty">No folders or shortcuts on desktop</div>
  429. </div>
  430. </div>
  431. </div>
  432. <!-- Drag ghost -->
  433. <div id="drag-ghost"><img id="drag-ghost-img" src=""></div>
  434. <!-- Float window layer -->
  435. <div id="fw-layer">
  436. <div id="fw-bar">
  437. <button id="fw-back" onclick="fwBack()">
  438. <svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8L10 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
  439. Back
  440. </button>
  441. <div id="fw-tabs"></div>
  442. </div>
  443. <div id="fw-body"></div>
  444. </div>
  445. <!-- FW tab context menu -->
  446. <div id="fw-ctx-overlay" onclick="fwCtxClose()"></div>
  447. <div id="fw-ctx-menu">
  448. <button class="fw-ctx-item" onclick="fwCtxNewTab()">
  449. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  450. <path d="M6 3H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
  451. <path d="M9 2h5v5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
  452. <path d="M14 2L8 8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
  453. </svg>
  454. Open in New Tab
  455. </button>
  456. <button class="fw-ctx-item" onclick="fwCtxCopy()">
  457. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  458. <rect x="5" y="5" width="9" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
  459. <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-7A1.5 1.5 0 0 0 1 3.5v7A1.5 1.5 0 0 0 2.5 12H4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
  460. </svg>
  461. Copy URL
  462. </button>
  463. <button class="fw-ctx-item red" onclick="fwCtxCloseWindow()">
  464. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  465. <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.4"/>
  466. <path d="M5.5 5.5l5 5M10.5 5.5l-5 5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
  467. </svg>
  468. Close Window
  469. </button>
  470. </div>
  471. <!-- Connection indicator -->
  472. <div id="conndrop">
  473. <img src="SystemAO/desktop/icons/connlost.svg">
  474. No connection
  475. </div>
  476. <script>
  477. /* ============================================================
  478. ArozOS Mobile Interface — Launchpad Design
  479. Maintains full float-window API compatibility with desktop.html
  480. ============================================================ */
  481. /* ── Virtual desktop flag (read by ao_module.js in loaded iframes) ── */
  482. window.isDesktopMode = true;
  483. window.ime = null;
  484. /* ── State ── */
  485. var moduleInstalled = [];
  486. var desktopThemeList = [];
  487. var userInfo = {};
  488. var shortcuts = {1:null, 2:null, 3:null, 4:null};
  489. var currentSec = 0;
  490. var isDark = false;
  491. var isEditMode = false;
  492. var appOrder = []; // ordered array of active module names
  493. var removedApps = []; // module names the user has hidden
  494. var dragSrcName = null;
  495. var dragTargetName = null;
  496. var dragGhostOff = {x:0, y:0};
  497. var isDragging = false;
  498. var _autoScrollRaf = null;
  499. var _autoScrollDir = 0;
  500. /* ── Locale ── */
  501. var mLoc = (typeof NewAppLocale === "function") ? NewAppLocale() : {getString:function(k,d){return d;},init:function(f,cb){if(cb)cb();},translate:function(){}};
  502. mLoc.init("SystemAO/locale/desktop.json", function(){ mLoc.translate(); });
  503. /* ── Clock data (must be defined before clockLoop() is called below) ── */
  504. var _mon=["January","February","March","April","May","June","July","August","September","October","November","December"];
  505. var _day=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
  506. /* ── Boot ── */
  507. initTheme();
  508. initBackdrop();
  509. initAccentColor();
  510. initModules();
  511. initShortcuts();
  512. initUserInfo();
  513. initFiles();
  514. initStartupAudio();
  515. clockLoop();
  516. connCheck();
  517. goSec(0); // initialise section positions so they don't all stack at translateY(0)
  518. $.get("system/desktop/host", function(d){ if(d.Hostname) document.title = d.Hostname; });
  519. /* ─────────────────────────────────────────────
  520. THEME
  521. ───────────────────────────────────────────── */
  522. function initTheme(){
  523. $.get("system/file_system/preference?key=file_explorer/theme", function(d){
  524. setDarkMode(d === "darkTheme");
  525. });
  526. }
  527. function setDarkMode(dark){
  528. isDark = dark;
  529. $("body").toggleClass("dark-mode", dark);
  530. if(dark){
  531. // Panel is dark → use white icon; button shows "Light Mode" (click to leave dark)
  532. $("#cp-dark-ic").attr("src","SystemAO/desktop/img/icons/darkmode_white.svg");
  533. $("#cp-dark-lbl").text("Light Mode");
  534. $("#cp-dark-btn").addClass("on");
  535. }else{
  536. // Panel is light → use black icon; button shows "Dark Mode" (click to enter dark)
  537. $("#cp-dark-ic").attr("src","SystemAO/desktop/img/icons/lightmode_black.svg");
  538. $("#cp-dark-lbl").text("Dark Mode");
  539. $("#cp-dark-btn").removeClass("on");
  540. }
  541. }
  542. /* Called by parent.desktopThemeChanged — not applicable here (standalone page),
  543. but defined for forward-compat if ever embedded */
  544. window.desktopThemeChanged = function(theme){ setDarkMode(theme === "dark"); };
  545. /* ─────────────────────────────────────────────
  546. WALLPAPER / ACCENT
  547. ───────────────────────────────────────────── */
  548. function initBackdrop(){
  549. $.get("system/desktop/theme", function(list){
  550. desktopThemeList = list;
  551. $.get("system/desktop/theme?get=true", function(t){ changeDesktopTheme(t); });
  552. });
  553. }
  554. function changeDesktopTheme(t){
  555. if(!t.includes(":/")){
  556. for(var i=0;i<desktopThemeList.length;i++){
  557. if(desktopThemeList[i].Theme===t){
  558. var bg = desktopThemeList[i].Bglist[0];
  559. $("#backdrop").css("background-image","url(img/desktop/bg/"+t+"/"+bg+")");
  560. return;
  561. }
  562. }
  563. }else{
  564. $.get("system/desktop/theme?load="+t,function(d){
  565. if(d.error){changeDesktopTheme("default");return;}
  566. $("#backdrop").css("background-image","url(media/?file="+d[0]+")");
  567. });
  568. }
  569. }
  570. function initAccentColor(){
  571. pref("themecolor",function(c){ if(c&&!c.error) setThemeColor(c); });
  572. }
  573. function setThemeColor(c){
  574. /* Backward-compat: modules call parent.setThemeColor() */
  575. document.documentElement.style.setProperty("--accent",c);
  576. }
  577. /* ─────────────────────────────────────────────
  578. MODULE LIST + APP GRID
  579. ───────────────────────────────────────────── */
  580. function initModules(){
  581. $.ajax({url:"system/modules/list",success:function(data){
  582. moduleInstalled = data;
  583. var saved = localStorage.getItem("mobile/appOrder");
  584. if(saved){try{appOrder=JSON.parse(saved);}catch(e){appOrder=[];}}
  585. var savedRm = localStorage.getItem("mobile/removedApps");
  586. if(savedRm){try{removedApps=JSON.parse(savedRm);}catch(e){removedApps=[];}}
  587. renderGrid("");
  588. }});
  589. }
  590. function getOrderedModules(){
  591. var result = [];
  592. appOrder.forEach(function(n){
  593. var m=moduleInstalled.find(function(x){return x.Name===n;});
  594. if(m) result.push(m);
  595. });
  596. // Auto-include newly installed modules (not in appOrder and not explicitly removed)
  597. moduleInstalled.forEach(function(m){
  598. if(m.StartDir
  599. && !result.find(function(x){return x.Name===m.Name;})
  600. && removedApps.indexOf(m.Name)===-1){
  601. result.push(m);
  602. }
  603. });
  604. return result;
  605. }
  606. function renderGrid(filter){
  607. var grid = $("#app-grid"); grid.empty();
  608. // Rebuild appOrder from active (non-removed) modules when not filtering
  609. if(!filter){
  610. appOrder = getOrderedModules()
  611. .filter(function(m){return !!m.StartDir;})
  612. .map(function(m){return m.Name;});
  613. }
  614. var idx=0;
  615. getOrderedModules().forEach(function(m){
  616. if(!m.StartDir) return;
  617. if(filter && !m.Name.toLowerCase().includes(filter.toLowerCase())) return;
  618. var ic = m.IconPath||"img/system/service.png";
  619. var name = escH(m.Name);
  620. var attr = escA(m.Name);
  621. grid.append('<div class="app-item" data-mod="'+attr+'" data-i="'+idx+'">'
  622. +'<div class="app-icon-wrap">'
  623. +'<img src="'+escA(ic)+'" onerror="this.src=\'img/system/service.png\'">'
  624. +'<div class="app-del" onclick="appDelTap(event,\''+attr+'\')">'
  625. +'<img src="SystemAO/desktop/img/icons/close_white.svg">'
  626. +'</div>'
  627. +'</div>'
  628. +'<span class="app-name">'+name+'</span>'
  629. +'</div>');
  630. idx++;
  631. });
  632. // In edit mode (no filter active) show removed apps as ghost tiles at the bottom
  633. if(isEditMode && !filter){
  634. removedApps.forEach(function(name){
  635. var m=moduleInstalled.find(function(x){return x.Name===name;});
  636. if(!m||!m.StartDir) return;
  637. var ic = m.IconPath||"img/system/service.png";
  638. var attr = escA(m.Name);
  639. grid.append('<div class="app-item app-removed" data-mod="'+attr+'">'
  640. +'<div class="app-icon-wrap">'
  641. +'<img src="'+escA(ic)+'" onerror="this.src=\'img/system/service.png\'">'
  642. +'<div class="app-restore" onclick="appRestoreTap(event,\''+attr+'\')">'
  643. +'<img src="SystemAO/desktop/img/icons/add_white.svg">'
  644. +'</div>'
  645. +'</div>'
  646. +'<span class="app-name">'+escH(m.Name)+'</span>'
  647. +'</div>');
  648. });
  649. }
  650. bindAppTouch();
  651. }
  652. function filterApps(q){
  653. if(isEditMode) exitEdit();
  654. renderGrid(q||"");
  655. }
  656. function bindAppTouch(){
  657. $("#app-grid .app-item").off("touchstart pointerdown").on("touchstart pointerdown",function(e){
  658. if(!isEditMode) return;
  659. // Don't start drag when the touch lands on a badge button
  660. if($(e.target).closest(".app-del,.app-restore").length) return;
  661. startDrag(e,this);
  662. });
  663. $("#app-grid .app-item").off("click").on("click",function(){
  664. if(isEditMode)return;
  665. openModule($(this).data("mod"));
  666. });
  667. }
  668. /* ── Edit mode ── */
  669. function toggleEdit(){ isEditMode ? exitEdit() : enterEdit(); }
  670. function enterEdit(){
  671. isEditMode=true;
  672. $("#apps-edit-btn").text("Done").addClass("on");
  673. renderGrid(""); // rebuild DOM so ghost tiles appear
  674. // Apply edit-mode classes to the freshly created active items
  675. $("#app-grid .app-item:not(.app-removed)").addClass("jiggling editing");
  676. }
  677. function exitEdit(){
  678. isEditMode=false;
  679. $("#apps-edit-btn").text("Edit").removeClass("on");
  680. localStorage.setItem("mobile/appOrder",JSON.stringify(appOrder));
  681. renderGrid(""); // rebuild DOM to remove ghost tiles
  682. }
  683. function appDelTap(e,name){
  684. e.stopPropagation();
  685. var i=appOrder.indexOf(name);
  686. if(i>-1) appOrder.splice(i,1);
  687. if(removedApps.indexOf(name)===-1) removedApps.push(name);
  688. localStorage.setItem("mobile/appOrder",JSON.stringify(appOrder));
  689. localStorage.setItem("mobile/removedApps",JSON.stringify(removedApps));
  690. enterEdit(); // enterEdit renders + applies edit classes
  691. }
  692. function appRestoreTap(e,name){
  693. e.stopPropagation();
  694. var i=removedApps.indexOf(name);
  695. if(i>-1) removedApps.splice(i,1);
  696. if(appOrder.indexOf(name)===-1) appOrder.push(name);
  697. localStorage.setItem("mobile/appOrder",JSON.stringify(appOrder));
  698. localStorage.setItem("mobile/removedApps",JSON.stringify(removedApps));
  699. enterEdit(); // enterEdit renders + applies edit classes
  700. }
  701. /* ── Drag-to-reorder ── */
  702. function startDrag(e,el){
  703. e.preventDefault();
  704. isDragging=true;
  705. dragSrcName=$(el).data("mod");
  706. dragTargetName=null;
  707. var t=e.touches?e.touches[0]:e;
  708. var r=el.getBoundingClientRect();
  709. dragGhostOff={x:t.clientX-r.left,y:t.clientY-r.top};
  710. $("#drag-ghost-img").attr("src",$(el).find("img").first().attr("src"));
  711. $("#drag-ghost").css({display:"block",left:t.clientX-dragGhostOff.x,top:t.clientY-dragGhostOff.y});
  712. $(el).addClass("dragging");
  713. $(document).on("touchmove.dg pointermove.dg",onDragMove);
  714. $(document).on("touchend.dg pointerup.dg",onDragEnd);
  715. }
  716. function onDragMove(e){
  717. if(!isDragging)return;
  718. var t=e.touches?e.touches[0]:e;
  719. var gx=t.clientX-dragGhostOff.x, gy=t.clientY-dragGhostOff.y;
  720. $("#drag-ghost").css({left:gx,top:gy});
  721. /* ── auto-scroll when ghost nears the top/bottom edge of the list ── */
  722. var wrap=document.getElementById("app-grid-wrap");
  723. var wr=wrap.getBoundingClientRect();
  724. var threshold=60;
  725. var prev=_autoScrollDir;
  726. if(gy<wr.top+threshold) _autoScrollDir=-1;
  727. else if(gy+60>wr.bottom-threshold) _autoScrollDir=1;
  728. else _autoScrollDir=0;
  729. if(_autoScrollDir!==0&&prev===0) _runAutoScroll();
  730. /* ── highlight drop target (no re-render during drag) ── */
  731. var found=null;
  732. $("#app-grid .app-item").each(function(){
  733. var r=this.getBoundingClientRect();
  734. if(t.clientX>=r.left&&t.clientX<=r.right&&t.clientY>=r.top&&t.clientY<=r.bottom){
  735. var n=$(this).data("mod");
  736. if(n!==dragSrcName) found=n;
  737. }
  738. });
  739. if(found!==dragTargetName){
  740. $(".app-item").removeClass("drag-target");
  741. dragTargetName=found;
  742. if(found) $(".app-item[data-mod='"+found+"']").addClass("drag-target");
  743. }
  744. }
  745. function _runAutoScroll(){
  746. if(!isDragging||_autoScrollDir===0){_autoScrollRaf=null;return;}
  747. document.getElementById("app-grid-wrap").scrollTop+=_autoScrollDir*5;
  748. _autoScrollRaf=requestAnimationFrame(_runAutoScroll);
  749. }
  750. function onDragEnd(){
  751. isDragging=false;
  752. _autoScrollDir=0;
  753. if(_autoScrollRaf){cancelAnimationFrame(_autoScrollRaf);_autoScrollRaf=null;}
  754. $("#drag-ghost").hide();
  755. $(document).off("touchmove.dg pointermove.dg touchend.dg pointerup.dg");
  756. /* commit reorder on drop */
  757. if(dragTargetName){
  758. var a=appOrder.indexOf(dragSrcName), b=appOrder.indexOf(dragTargetName);
  759. if(a>-1&&b>-1){appOrder.splice(a,1);appOrder.splice(b,0,dragSrcName);}
  760. }
  761. localStorage.setItem("mobile/appOrder",JSON.stringify(appOrder));
  762. renderGrid(""); enterEdit();
  763. }
  764. /* ─────────────────────────────────────────────
  765. DESKTOP FILES (Section 3)
  766. ───────────────────────────────────────────── */
  767. function initFiles(){
  768. $.get("system/desktop/listDesktop",function(data){
  769. if(!data||data.error){$("#files-empty").show();return;}
  770. // URL shortcuts shown first
  771. var urlShortcuts=data.filter(function(f){return f.IsShortcut&&f.ShortcutType==="url";});
  772. // Folders shown after
  773. var folders=data.filter(function(f){return f.IsDir&&!f.IsShortcut;});
  774. if(!urlShortcuts.length&&!folders.length){$("#files-empty").show();return;}
  775. urlShortcuts.forEach(function(f){
  776. var icon=f.ShortcutImage||"";
  777. var name=escH(f.ShortcutName||f.Filename);
  778. var url=escA(f.ShortcutPath);
  779. $("#files-grid").append(
  780. '<div class="folder-item" onclick="openUrlShortcut(\''+url+'\')">'
  781. +'<img class="shortcut-icon" src="'+escA(icon)+'" onerror="this.src=\'img/system/favicon.png\'">'
  782. +'<span class="folder-name">'+name+'</span>'
  783. +'</div>'
  784. );
  785. });
  786. folders.forEach(function(f){
  787. $("#files-grid").append(
  788. '<div class="folder-item" onclick="openFolder(\''+escA(f.Filepath)+'\')">'
  789. +'<img class="folder-emoji" src="SystemAO/desktop/img/icons/folder_blue.svg">'
  790. +'<span class="folder-name">'+escH(f.Filename)+'</span>'
  791. +'</div>'
  792. );
  793. });
  794. });
  795. }
  796. function openFolder(fp){
  797. window.open("SystemAO/file_system/file_explorer.html#"+encodeURIComponent(fp),"_blank");
  798. }
  799. function openUrlShortcut(url){
  800. window.open(url,"_blank");
  801. }
  802. /* ─────────────────────────────────────────────
  803. DOCK SHORTCUTS
  804. ───────────────────────────────────────────── */
  805. function initShortcuts(){[1,2,3,4].forEach(function(i){initShortcut(i);});}
  806. function initShortcut(id){
  807. pref("ao/mobile/shorcut/"+id,function(name){
  808. if(!name){$("#d"+id).attr("src","img/mobile/pending.svg");return;}
  809. $.get("system/modules/getLaunchPara?module="+encodeURIComponent(name),function(p){
  810. if(p.error)return;
  811. shortcuts[id]=p;
  812. $("#d"+id).attr("src",p.IconPath||"img/system/service.png");
  813. $("#dl"+id).text(p.Name);
  814. });
  815. });
  816. }
  817. function launchShortcut(id){if(shortcuts[id])openModule(shortcuts[id].Name);}
  818. /* ─────────────────────────────────────────────
  819. SECTION NAVIGATION
  820. ───────────────────────────────────────────── */
  821. function goSec(n){
  822. if(n<0||n>2)return;
  823. if(n!==1&&isEditMode)exitEdit();
  824. currentSec=n;
  825. ["#sec-home","#sec-apps","#sec-files"].forEach(function(s,i){
  826. $(s).css("transform","translateY("+(i-n)*100+"%)");
  827. });
  828. $(".pd").removeClass("on");
  829. $(".pd[data-s='"+n+"']").addClass("on");
  830. }
  831. /* Swipe handling per-section */
  832. var _sy=0;
  833. function _trackStart(e){_sy=e.touches?e.touches[0].clientY:e.clientY;}
  834. function _swipeDelta(e){var y=e.changedTouches?e.changedTouches[0].clientY:e.clientY;return _sy-y;}
  835. // Section 1: anywhere swipe up → section 2
  836. $("#sec-home").on("touchstart",function(e){_sy=e.touches[0].clientY;})
  837. .on("touchend",function(e){if(_swipeDelta(e)>55)goSec(1);});
  838. // Section 2 header: swipe down → sec 1, swipe up → sec 3
  839. $("#apps-header,#app-search-wrap").on("touchstart",function(e){_sy=e.touches[0].clientY;})
  840. .on("touchend",function(e){var d=_swipeDelta(e);if(d<-55)goSec(0);else if(d>55)goSec(2);});
  841. // App grid edge-overscroll
  842. var _agw=document.getElementById("app-grid-wrap");
  843. _agw.addEventListener("touchstart",function(e){_sy=e.touches[0].clientY;},{passive:true});
  844. _agw.addEventListener("touchend",function(e){
  845. var d=_sy-e.changedTouches[0].clientY;
  846. if(_agw.scrollTop<=0&&d<-70)goSec(0);
  847. if(_agw.scrollTop+_agw.clientHeight>=_agw.scrollHeight-2&&d>70)goSec(2);
  848. },{passive:true});
  849. // Section 3 header: swipe down → sec 2
  850. $("#files-header").on("touchstart",function(e){_sy=e.touches[0].clientY;})
  851. .on("touchend",function(e){if(_swipeDelta(e)<-55)goSec(1);});
  852. var _fgw=document.getElementById("files-grid-wrap");
  853. _fgw.addEventListener("touchstart",function(e){_sy=e.touches[0].clientY;},{passive:true});
  854. _fgw.addEventListener("touchend",function(e){
  855. if(_fgw.scrollTop<=0&&(_sy-e.changedTouches[0].clientY)<-70)goSec(1);
  856. },{passive:true});
  857. /* ─────────────────────────────────────────────
  858. CLOCK
  859. ───────────────────────────────────────────── */
  860. var _mon=["January","February","March","April","May","June","July","August","September","October","November","December"];
  861. var _day=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
  862. function clockLoop(){updateClock();setInterval(updateClock,15000);}
  863. function updateClock(){
  864. var d=new Date();
  865. $("#clock-time").text(pad2(d.getHours())+":"+pad2(d.getMinutes()));
  866. $("#clock-date").text(_day[d.getDay()]+", "+_mon[d.getMonth()]+" "+d.getDate());
  867. }
  868. function pad2(n){return n<10?"0"+n:""+n;}
  869. /* ─────────────────────────────────────────────
  870. USER INFO
  871. ───────────────────────────────────────────── */
  872. function initUserInfo(){
  873. $.get("system/desktop/user",function(d){
  874. if(d.error)return;
  875. userInfo=d;
  876. $("#cp-name").text(d.Username);
  877. $("#cp-grp").text("@"+(d.UserGroups||[]).join("/"));
  878. if(d.UserIcon)$("#cp-av").attr("src",d.UserIcon);
  879. loadCpAccounts();
  880. });
  881. }
  882. function loadCpAccounts(){
  883. $("#cp-acc-list").empty();
  884. $.get("system/auth/u/list",function(data){
  885. if(!data||data.error)return;
  886. data.forEach(function(ac){
  887. if(ac.Username===userInfo.Username)return;
  888. $.get("system/desktop/user?target="+ac.Username,function(ud){
  889. var ic=ud.UserIcon||"img/desktop/system_icon/user.svg";
  890. $("#cp-acc-list").append(
  891. '<div class="cp-acc-item" onclick="cpSwitch(\''+escA(ac.Username)+'\')">'
  892. +'<img src="'+escA(ic)+'">'
  893. +'<span class="cp-acc-name">'+escH(ac.Username)+(ac.IsExpired?" (expired)":"")+"</span>"
  894. +'</div>'
  895. );
  896. });
  897. });
  898. });
  899. }
  900. /* ─────────────────────────────────────────────
  901. CONTROL PANEL
  902. ───────────────────────────────────────────── */
  903. function cpToggle(){cpOpen?cpClose():cpShow();}
  904. var cpOpen=false;
  905. function cpShow(){cpOpen=true;$("#cp-panel,#cp-backdrop").addClass("on");}
  906. function cpClose(){cpOpen=false;$("#cp-panel,#cp-backdrop").removeClass("on");}
  907. function cpToggleDark(){
  908. setDarkMode(!isDark);
  909. $.get("system/file_system/preference?key=file_explorer/theme&value="+(isDark?"darkTheme":"whiteTheme"));
  910. }
  911. function cpFullscreen(){
  912. cpClose();
  913. var el=document.documentElement;
  914. if(!document.fullscreenElement){(el.requestFullscreen||el.webkitRequestFullscreen||el.mozRequestFullScreen).call(el);}
  915. else{(document.exitFullscreen||document.webkitExitFullscreen||document.mozCancelFullScreen).call(document);}
  916. }
  917. function cpWallpaper(){
  918. cpClose();
  919. newFloatWindow({
  920. url:"SystemAO/system_setting/index.html#"+encodeURIComponent(JSON.stringify({group:"Desktop",name:"Wallpaper"})),
  921. appicon:"SystemAO/desktop/img/personalization.png",
  922. title:"System Settings"
  923. });
  924. }
  925. function cpSettings(){cpClose();openModule("System Setting");}
  926. function cpLogout(){
  927. cpClose();
  928. if(confirm(mLoc.getString("quickAccess/logout/confirm","Sign out of ArozOS?"))){
  929. $.get("system/auth/logout",function(){window.location.href="/";});
  930. }
  931. }
  932. function cpAddAccount(){
  933. cpClose();
  934. newFloatWindow({url:"SystemAO/advance/switchAccount.html",appicon:"SystemAO/desktop/img/account-switch.png",title:"Switch Account"});
  935. }
  936. function cpSwitch(username){
  937. cpClose();
  938. $.ajax({url:"system/auth/u/switch",data:{username:username},success:function(d){
  939. if(d.error)alert(d.error);else window.location.reload();
  940. }});
  941. }
  942. /* ─────────────────────────────────────────────
  943. MODULE OPENING
  944. ───────────────────────────────────────────── */
  945. function openModule(name){
  946. $.get("system/modules/getLaunchPara?module="+encodeURIComponent(name),function(d){
  947. if(d.error){if(d.error==="Not logged in.")window.location.href="login.html";return;}
  948. var url=d.StartDir, ic=d.IconPath||"img/system/favicon.png";
  949. if(d.SupportFW&&d.LaunchFWDir)url=d.LaunchFWDir;
  950. newFloatWindow({url:url,appicon:ic,title:d.Name});
  951. });
  952. }
  953. function openFileWithModule(info,files){
  954. var url=info.StartDir, ic=info.IconPath||"img/system/favicon.png";
  955. if(info.SupportFW&&info.LaunchFWDir)url=info.LaunchFWDir;
  956. if(info.SupportEmb&&info.LaunchEmb)url=info.LaunchEmb;
  957. newFloatWindow({url:url+"#"+encodeURIComponent(JSON.stringify(files)),appicon:ic,title:info.Name});
  958. }
  959. /* ─────────────────────────────────────────────
  960. FLOAT WINDOW LAYER (full API compatibility)
  961. ───────────────────────────────────────────── */
  962. function newFloatWindow(opts){
  963. var wid = Date.now()+"";
  964. var par = opts.parent||"", cb = opts.callback||"";
  965. var url = opts.url||"", title = opts.title||"Window", ic = opts.appicon||"img/system/favicon.png";
  966. // Structure: .floatWindowWrapper > .floatWindow[windowId,parent,callback] > .fwtab > iframe
  967. var wrap=$('<div class="floatWindowWrapper" windowId="'+wid+'">'
  968. +'<div class="floatWindow" windowId="'+wid+'" parent="'+escA(par)+'" callback="'+escA(cb)+'">'
  969. +'<div class="fwtab">'
  970. +'<iframe src="'+escA(url)+'" allow="fullscreen"></iframe>'
  971. +'</div></div></div>');
  972. $("#fw-body").append(wrap);
  973. // Hide others, show this
  974. $(".floatWindowWrapper").removeClass("on").hide();
  975. wrap.addClass("on").show();
  976. // Tab button
  977. var tab=$('<div class="fw-tab on" data-wid="'+wid+'">'
  978. +'<img src="'+escA(ic)+'" onerror="this.src=\'img/system/favicon.png\'">'
  979. +'<span class="fw-tab-title">'+escH(title)+'</span>'
  980. +'<span class="fw-tab-x" onclick="fwTabClose(event,\''+wid+'\')">×</span>'
  981. +'</div>');
  982. tab.on("click",function(e){if(!$(e.target).hasClass("fw-tab-x"))fwFocus(wid);});
  983. $("#fw-tabs .fw-tab").removeClass("on");
  984. $("#fw-tabs").append(tab);
  985. $("#fw-layer").addClass("on");
  986. return wid;
  987. }
  988. function fwBack(){
  989. // Close all windows and return to launchpad
  990. $(".floatWindowWrapper").each(function(){closeFwProcess($(this).attr("windowId"));});
  991. $("#fw-layer").removeClass("on");
  992. }
  993. function fwFocus(wid){
  994. $(".floatWindowWrapper").removeClass("on").hide();
  995. $(".fw-tab").removeClass("on");
  996. var fw=getFloatWindowByID(wid);
  997. if(fw)fw.addClass("on").show();
  998. $(".fw-tab[data-wid='"+wid+"']").addClass("on");
  999. }
  1000. function fwTabClose(e,wid){e.stopPropagation();closeFloatWindow(wid);}
  1001. function closeFwProcess(wid){
  1002. var prev=getPreviousWindowObject(wid);
  1003. $(".floatWindowWrapper[windowId='"+wid+"']").remove();
  1004. $(".fw-tab[data-wid='"+wid+"']").remove();
  1005. if(!$(".floatWindowWrapper").length){
  1006. $("#fw-layer").removeClass("on");
  1007. }else if(prev){
  1008. fwFocus(prev);
  1009. }else{
  1010. fwFocus($(".floatWindowWrapper").last().attr("windowId"));
  1011. }
  1012. }
  1013. function closeFloatWindow(wid){
  1014. var fw=getFloatWindowByID(wid);
  1015. if(!fw)return;
  1016. try{
  1017. var cw=fw.find("iframe")[0].contentWindow;
  1018. if(cw&&cw.ao_module_close){cw.ao_module_close();return;}
  1019. }catch(e){}
  1020. closeFwProcess(wid);
  1021. }
  1022. function getFloatWindowByID(id){
  1023. var f=null;
  1024. $(".floatWindowWrapper").each(function(){if($(this).attr("windowId")===""+id)f=$(this);});
  1025. return f;
  1026. }
  1027. function MoveFloatWindowToTop(fw){
  1028. var id=$(fw).attr("windowId")||$(fw).find(".floatWindow").attr("windowId");
  1029. if(id)fwFocus(id);
  1030. }
  1031. function setFloatWindowTitle(id,t){$(".fw-tab[data-wid='"+id+"'] .fw-tab-title").text(t);}
  1032. function setFloatWindowResizePolicy(){}
  1033. function setFloatWindowSize(){}
  1034. function setFloatWindowMinimizePolicy(){}
  1035. function PinFloatWindowToTopMostMode(){}
  1036. function UnpinFloatWindowFromTopMostMode(){}
  1037. function bindObjectToIMEEvents(){}
  1038. function getPreviousWindowObject(wid){
  1039. var prev=null,res=null;
  1040. $(".fw-tab").each(function(){
  1041. var w=$(this).data("wid");
  1042. if(w===wid)res=prev;else prev=w;
  1043. });
  1044. return res;
  1045. }
  1046. /* Legacy taskbar stubs (no sidebar in new design) */
  1047. function showTaskBar(){}
  1048. function hideTaskBar(){}
  1049. function showShortcuts(){}
  1050. function hideShortcuts(){}
  1051. function showDesktop(){$("#fw-layer").removeClass("on");}
  1052. function openDesktopCustomization(){cpWallpaper();}
  1053. function openDesktopAsFolder(){
  1054. window.open("SystemAO/file_system/file_explorer.html#"+encodeURIComponent("user:/Desktop/"),"_blank");
  1055. }
  1056. function openSystemSettings(){cpSettings();}
  1057. function initDesktop(){window.location.reload();}
  1058. function fullscreen(){cpFullscreen();}
  1059. function logout(){cpLogout();}
  1060. function toggleProfileInfo(){}
  1061. /* ─────────────────────────────────────────────
  1062. STARTUP AUDIO
  1063. ───────────────────────────────────────────── */
  1064. function initStartupAudio(){
  1065. pref("startup-audio",function(d){
  1066. if(!d)return;
  1067. var v=parseFloat(localStorage.getItem("global_volume"))||0.7;
  1068. var a=new Audio("media?file="+d);
  1069. a.volume=v; a.play().catch(function(){});
  1070. });
  1071. }
  1072. /* ─────────────────────────────────────────────
  1073. STORAGE HELPERS
  1074. ───────────────────────────────────────────── */
  1075. function pref(key,cb){
  1076. $.ajax({url:"system/desktop/preference",method:"POST",data:{preference:key},success:cb});
  1077. }
  1078. function setPref(key,val,cb){
  1079. $.ajax({url:"system/desktop/preference",method:"POST",data:{preference:key,value:val},
  1080. success:function(d){if(!d.error&&cb)cb();}});
  1081. }
  1082. function getStorage(key,cb){pref(key,cb);}
  1083. function setStorage(key,val,cb){setPref(key,val,cb);}
  1084. /* ─────────────────────────────────────────────
  1085. CONNECTION CHECK
  1086. ───────────────────────────────────────────── */
  1087. function connCheck(){
  1088. setInterval(function(){
  1089. $.ajax({url:"system/auth/checkLogin",timeout:15000,
  1090. success:function(d){if(!d)window.location.href="index.html";$("#conndrop").hide();},
  1091. error:function(){$("#conndrop").css("display","flex");}
  1092. });
  1093. },15000);
  1094. }
  1095. /* ─────────────────────────────────────────────
  1096. UTILITY
  1097. ───────────────────────────────────────────── */
  1098. function escH(s){return $("<span>").text(s||"").html();}
  1099. function escA(s){return(s||"").replace(/"/g,"&quot;").replace(/'/g,"&#39;");}
  1100. /* ─────────────────────────────────────────────
  1101. FW TAB LONG-PRESS CONTEXT MENU
  1102. ───────────────────────────────────────────── */
  1103. var _fwCtxWid = null;
  1104. var _fwLongPressTimer = null;
  1105. var _fwLongPressFired = false;
  1106. (function(){
  1107. var fwTabs = document.getElementById("fw-tabs");
  1108. var startX, startY;
  1109. fwTabs.addEventListener("touchstart", function(e){
  1110. var tab = e.target.closest(".fw-tab");
  1111. if(!tab || e.target.closest(".fw-tab-x")) return;
  1112. var wid = tab.getAttribute("data-wid");
  1113. var touch = e.touches[0];
  1114. startX = touch.clientX; startY = touch.clientY;
  1115. _fwLongPressFired = false;
  1116. _fwLongPressTimer = setTimeout(function(){
  1117. _fwLongPressFired = true;
  1118. fwCtxShow(wid, startX, startY);
  1119. if(navigator.vibrate) navigator.vibrate(35);
  1120. }, 500);
  1121. }, {passive: true});
  1122. fwTabs.addEventListener("touchmove", function(e){
  1123. if(!_fwLongPressTimer) return;
  1124. var t = e.touches[0];
  1125. if(Math.abs(t.clientX - startX) > 8 || Math.abs(t.clientY - startY) > 8){
  1126. clearTimeout(_fwLongPressTimer);
  1127. _fwLongPressTimer = null;
  1128. }
  1129. }, {passive: true});
  1130. fwTabs.addEventListener("touchend", function(){
  1131. clearTimeout(_fwLongPressTimer);
  1132. _fwLongPressTimer = null;
  1133. }, {passive: true});
  1134. // Suppress the click that follows a long-press
  1135. fwTabs.addEventListener("click", function(e){
  1136. if(_fwLongPressFired){
  1137. _fwLongPressFired = false;
  1138. e.stopPropagation();
  1139. e.preventDefault();
  1140. }
  1141. }, true);
  1142. })();
  1143. function fwCtxShow(wid, x, y){
  1144. _fwCtxWid = wid;
  1145. var menu = document.getElementById("fw-ctx-menu");
  1146. var menuW = 224, menuH = 150;
  1147. var left = Math.min(x, window.innerWidth - menuW - 10);
  1148. var top = Math.min(y, window.innerHeight - menuH - 10);
  1149. left = Math.max(10, left);
  1150. top = Math.max(10, top);
  1151. menu.style.left = left + "px";
  1152. menu.style.top = top + "px";
  1153. document.getElementById("fw-ctx-overlay").classList.add("on");
  1154. menu.style.display = "block";
  1155. // re-trigger animation
  1156. menu.style.animation = "none";
  1157. menu.offsetHeight; // reflow
  1158. menu.style.animation = "";
  1159. }
  1160. function fwCtxClose(){
  1161. _fwCtxWid = null;
  1162. document.getElementById("fw-ctx-overlay").classList.remove("on");
  1163. document.getElementById("fw-ctx-menu").style.display = "none";
  1164. }
  1165. function fwCtxGetUrl(){
  1166. if(!_fwCtxWid) return null;
  1167. var fw = getFloatWindowByID(_fwCtxWid);
  1168. if(!fw) return null;
  1169. try{ return fw.find("iframe")[0].src || null; }catch(e){ return null; }
  1170. }
  1171. function fwCtxNewTab(){
  1172. var url = fwCtxGetUrl();
  1173. fwCtxClose();
  1174. if(url) window.open(url, "_blank");
  1175. }
  1176. function fwCtxCopy(){
  1177. var url = fwCtxGetUrl();
  1178. fwCtxClose();
  1179. if(!url) return;
  1180. if(navigator.clipboard){
  1181. navigator.clipboard.writeText(url).catch(function(){});
  1182. } else {
  1183. var ta = document.createElement("textarea");
  1184. ta.value = url; ta.style.cssText = "position:fixed;opacity:0";
  1185. document.body.appendChild(ta); ta.select();
  1186. document.execCommand("copy"); document.body.removeChild(ta);
  1187. }
  1188. }
  1189. function fwCtxCloseWindow(){
  1190. var wid = _fwCtxWid;
  1191. fwCtxClose();
  1192. if(wid) closeFloatWindow(wid);
  1193. }
  1194. </script>
  1195. </body>
  1196. </html>