detail.ejs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <link rel="icon" href="/assets/icon/favicon.ico" type="image/x-icon">
  7. <title><%= title %></title>
  8. <meta name="description" content="<%= description %>">
  9. <meta property="og:title" content="<%= title %>">
  10. <meta property="og:description" content="<%= description %>">
  11. <meta property="og:image" content="<%= detail.poster %>">
  12. <meta property="og:type" content="website">
  13. <!-- MARK: Universal Link / Android App Link 的核心配置 -->
  14. <!-- 这些 meta 标签的值应该是完整的 HTTPS 链接,Facebook 会识别并尝试拉起 App -->
  15. <meta property="og:url" content="<%= applink %>" />
  16. <!-- **Universal Link 路径** -->
  17. <meta property="al:ios:url" content="<%= applink %>" />
  18. <!-- **Universal Link 路径** -->
  19. <meta property="al:ios:app_store_id" content="1575480118" /> <!-- **iOS App Store ID** -->
  20. <meta property="al:ios:app_name" content="Art Color Book" /> <!-- **iOS 应用名称** -->
  21. <meta property="al:android:package" content="com.pcoloring.art.puzzle.color.by.number.debug" /> <!-- **Android 包名** -->
  22. <meta property="al:android:url" content="<%= applink %>" />
  23. <!-- ** Universal Link 路径** -->
  24. <meta property="al:android:app_name" content="Art Color Book" /> <!-- **Android 应用名称** -->
  25. <meta name="apple-itunes-app" content="app-id=1575480118">
  26. <link rel="stylesheet" href="/stylesheets/v2/styles.css">
  27. <style>
  28. h1 {
  29. text-align: start;
  30. }
  31. .buttons {
  32. display: flex;
  33. flex-wrap: wrap;
  34. justify-content: start;
  35. align-items: center;
  36. }
  37. .btn {
  38. display: inline-block;
  39. flex: 1 0 auto;
  40. /* 允许伸缩,不收缩,基础尺寸自适应 */
  41. min-width: max-content;
  42. /* 最小宽度由内容决定 */
  43. white-space: nowrap;
  44. /* 禁止按钮文字换行 */
  45. padding: 10px 20px;
  46. margin-right: 20px;
  47. margin-bottom: 20px;
  48. background-color: var(--primary-color);
  49. color: white;
  50. text-align: center;
  51. text-decoration: none;
  52. border-radius: 5px;
  53. font-weight: bold;
  54. border: none;
  55. cursor: pointer;
  56. transition: transform 0.3s ease;
  57. }
  58. .btn:hover {
  59. transform: translateY(-5px);
  60. opacity: 0.85;
  61. }
  62. .detail-tag {
  63. display: inline-block;
  64. background-color: var(--background-color);
  65. color: var(--light-text);
  66. padding: 5px 10px;
  67. border-radius: 20px;
  68. font-size: 0.9rem;
  69. margin-right: 10px;
  70. margin-bottom: 10px;
  71. transition: background-color 0.3s ease;
  72. }
  73. .detail-tag:hover {
  74. background-color: var(--secondary-color);
  75. }
  76. .poster {
  77. background-color: white;
  78. border-radius: 8px;
  79. padding: 20px;
  80. box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
  81. margin-bottom: 30px;
  82. text-align: center;
  83. }
  84. .poster img {
  85. max-width: 100%;
  86. height: auto;
  87. border-radius: 8px;
  88. display: block;
  89. margin: 0 auto;
  90. }
  91. .creator-info {
  92. display: flex;
  93. align-items: center;
  94. margin-bottom: 20px;
  95. }
  96. .creator-avatar {
  97. width: 40px;
  98. height: 40px;
  99. border-radius: 50%;
  100. /* background-color: var(--secondary-color); */
  101. display: flex;
  102. align-items: center;
  103. justify-content: center;
  104. color: white;
  105. font-weight: 600;
  106. margin-right: 15px;
  107. }
  108. .creator-name {
  109. font-weight: 600;
  110. }
  111. .creator-date {
  112. color: var(--light-text);
  113. font-size: 0.9rem;
  114. }
  115. .collection-grid {
  116. display: grid;
  117. grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  118. gap: 25px;
  119. }
  120. .collection-card {
  121. background-color: white;
  122. border-radius: 10px;
  123. overflow: hidden;
  124. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
  125. transition: transform 0.3s ease;
  126. text-decoration: none;
  127. display: block;
  128. }
  129. .collection-card:hover {
  130. transform: translateY(-8px);
  131. }
  132. .collection-image {
  133. height: 180px;
  134. background-color: #f0f0f0;
  135. overflow: hidden;
  136. }
  137. .collection-image img {
  138. width: 100%;
  139. height: 100%;
  140. object-fit: cover;
  141. transition: transform 0.5s ease;
  142. }
  143. .collection-card:hover .collection-image img {
  144. transform: scale(1.05);
  145. }
  146. .collection-info {
  147. padding: 18px;
  148. }
  149. .collection-title {
  150. font-weight: 700;
  151. font-size: 1.2rem;
  152. margin-bottom: 10px;
  153. color: var(--primary-color);
  154. }
  155. .collection-desc {
  156. color: var(--light-text);
  157. font-size: 0.9rem;
  158. line-height: 1.4;
  159. display: -webkit-box;
  160. -webkit-line-clamp: 3;
  161. -webkit-box-orient: vertical;
  162. overflow: hidden;
  163. }
  164. @media (max-width: 768px) {
  165. h1 {
  166. font-size: 1.8rem;
  167. }
  168. h2 {
  169. font-size: 1.5rem;
  170. }
  171. h3 {
  172. font-size: 1.3rem;
  173. }
  174. section {
  175. padding: 20px;
  176. }
  177. .btn {
  178. width: 90%;
  179. text-align: center;
  180. }
  181. .coloring-grid {
  182. grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  183. gap: 15px;
  184. }
  185. .collection-grid {
  186. grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  187. gap: 20px;
  188. }
  189. }
  190. </style>
  191. <script type='text/javascript'
  192. src='https://platform-api.sharethis.com/js/sharethis.js#property=685036ce6c1ae8001abaded7&product=sop'
  193. async='async'></script>
  194. <script type="application/ld+json">
  195. {
  196. "@context": "https://schema.org",
  197. "@type": "CreativeWork",
  198. "name": "Free Printable Coloring Page: <%= detail.title %>",
  199. "description": "<%= detail.desc %>",
  200. "url": "https://art.pcoloring.com<%= uri %>",
  201. "image": "<%= detail.thumb %>",
  202. "category": "<%= detail.tags[0] %>",
  203. "keywords": "coloring page, <%= detail.title %>, color by number, paint by number, free, printable, <%= detail.tags.join() %>",
  204. "contentRating": "General Audience",
  205. "mainEntityOfPage": "https://art.pcoloring.com<%= uri %>",
  206. "publisher": {
  207. "@type": "Organization",
  208. "name": "Art Color",
  209. "logo": {
  210. "@type": "ImageObject",
  211. "url": "https://art.pcoloring.com/assets/icon/icon.webp",
  212. "width": 180,
  213. "height": 180
  214. }
  215. },
  216. "offers": {
  217. "@type": "Offer",
  218. "priceCurrency": "USD",
  219. "price": "0.00",
  220. "eligibleRegion": {
  221. "@type": "Place",
  222. "name": "Worldwide"
  223. },
  224. "url": "https://art.pcoloring.com<%= uri %>"
  225. },
  226. "author": {
  227. "@type": "Person",
  228. "name": "<%= detail.user.username %>"
  229. },
  230. "datePublished": "<%= detail.publishTime %>"
  231. }
  232. </script>
  233. <!-- <script>
  234. const baseUniversalLink = 'https://art.pcoloring.com';
  235. const currentPageLink = baseUniversalLink + window.location.pathname + window.location.search;
  236. const appDeeplink = baseUniversalLink + window.location.pathname.replace('coloring-page', 'share') + window.location.search;
  237. const linkDownloadCommon = 'https://pcoloring.com/anc/'; // 通用下载页
  238. const linkDownloadAppStore = 'itms-apps://itunes.apple.com/app/id1575480118?utm_source=share'; // ios app下载链接
  239. const linkDownloadPlayMarket = 'https://play.google.com/store/apps/details?id=com.pcoloring.art.puzzle.color.by.number&utm_source=share'; // android app 下载链接
  240. const ua = window.navigator.userAgent.toLowerCase();
  241. // 动态更新 Open Graph 和 App Links 的 URL 为 Universal Link
  242. document.querySelector('meta[property="og:url"]').content = currentPageLink;
  243. document.querySelector('meta[property="al:ios:url"]').content = currentPageLink;
  244. document.querySelector('meta[property="al:android:url"]').content = currentPageLink;
  245. function functionDownload() {
  246. if (ua.search("iphone") > -1 || ua.search("ipad") > -1 || ua.search("ipod") > -1) {
  247. // 对于 iOS 设备,尝试直接打开 Universal Link
  248. // 如果 Universal Link 配置正确,浏览器会尝试拉起 App
  249. // 否则会降级到网页或 App Store
  250. window.location.href = appDeeplink; // 尝试 Universal Link
  251. // 如果 Universal Link 失败,可以设置一个延时跳转到 App Store
  252. setTimeout(function () {
  253. window.location.href = linkDownloadAppStore;
  254. }, 250); // 250ms 延迟,给 Universal Link 尝试拉起 App 的时间
  255. } else if (ua.search("android") > -1) {
  256. // 对于 Android 设备,尝试直接打开 App Link
  257. window.location.href = appDeeplink; // 尝试 App Link
  258. setTimeout(function () {
  259. window.location.href = linkDownloadPlayMarket;
  260. }, 250); // 250ms 延迟
  261. } else {
  262. // 对于其他设备,直接跳转到 Android 下载链接(或通用下载页)
  263. window.location.href = linkDownloadCommon;
  264. }
  265. }
  266. </script> -->
  267. </head>
  268. <!-- Google tag (gtag.js) -->
  269. <script async src="https://www.googletagmanager.com/gtag/js?id=G-JBGGVGLHTP"></script>
  270. <script>
  271. window.dataLayer = window.dataLayer || [];
  272. function gtag() { dataLayer.push(arguments); }
  273. gtag('js', new Date());
  274. gtag('config', 'G-JBGGVGLHTP');
  275. </script>
  276. <body>
  277. <%- include('header') %>
  278. <main class="container">
  279. <section>
  280. <h1>
  281. <%= detail.title %>
  282. </h1>
  283. <p class="text-light">
  284. <% if (detail.totalStartCount> 0) { %> <%=detail.totalStartCount%> people have participated in coloring this
  285. work, and <%=detail.totalDoneCount%> have completed it. Come and take on the challenge! <% } %>
  286. </p>
  287. <h3 id="status" data-content-id="<%= detail._id %>" style="display: none;">You have completed 50%—keep going and
  288. finish it!</h3>
  289. <div class="creator-info">
  290. <div class="creator-avatar">
  291. <a href="/coloring-page-gallery?author=<%= detail.user.username %>"><img src="<%= detail.user.avatar %>"
  292. width="100%" , height="100%" , style="border-radius: 50%;"></a>
  293. </div>
  294. <div>
  295. <div class="creator-name">
  296. <a href="/coloring-page-gallery?author=<%= detail.user.username %>" style="color: var(--secondary-color)">
  297. <%= detail.user.username %>
  298. </a>
  299. </div>
  300. <div class="creator-date">Published on <%= detail.publishTime %>
  301. </div>
  302. </div>
  303. </div>
  304. <div class="tags">
  305. <% detail.tags.forEach(tag=> { %>
  306. <a href="/coloring-page-gallery?category=<%= tag %>"><span class="detail-tag">
  307. <%= tag %>
  308. </span></a>
  309. <% }); %>
  310. </div>
  311. <div id="poster" class="poster" data-content-id="<%= detail._id %>">
  312. <img src="<%= detail.poster %>" alt="<%= detail.title %>">
  313. </div>
  314. <div class="buttons">
  315. <a id="playBtn" href="/play/<%= detail._id %>" class="btn">Paint Now!</a>
  316. <a id="continueBtn" href="/play/<%= detail._id %>" class="btn" style="display: none;">Continue</a>
  317. <a id="repaintBtn" onclick="onRepaint('<%= detail._id %>')" class="btn" style="display: none;">Repaint</a>
  318. <a id="reviewBtn" href="/play/<%= detail._id %>" class="btn"
  319. style="background-color: orange; display: none;">Review</a>
  320. <a id="appBtn" class="btn" href="<%= downlink %>" style="background-color: darkolivegreen;">Paint on APP</a>
  321. <a href="/download/pdf/page/<%= detail._id %>" class="btn"
  322. style="background-color: lightseagreen;">Download</a>
  323. <a id="printBtn" onclick="printImage('<%= detail._id %>')" class="btn"
  324. style="background-color: black;">Print</a>
  325. <a id="deleteBtn" onclick="onDelete('<%= detail._id %>')" class="btn"
  326. style="background-color: grey; display: none;">Delete</a>
  327. </div>
  328. <h2>About This Coloring Page</h2>
  329. <p>
  330. <%= detail.desc %>
  331. </p>
  332. <p>You can <strong>download it in High-definition PDF format, print it</strong>, or choose to <strong>color
  333. online (using our app or directly in a
  334. browser)</strong>—completely free of charge.</p>
  335. <p>Coloring this page can help reduce stress, improve focus, and allow you to express your artistic side.
  336. Whether
  337. you prefer traditional coloring tools or digital methods, this design will look stunning when complete.</p>
  338. <p>After coloring, you can share your creation with our community or use it as decorative art for your home or
  339. workspace. Enjoy the therapeutic benefits of coloring with this beautiful coloring page</p>
  340. </section>
  341. <section>
  342. <h2>You May Also Like</h2>
  343. <div class="coloring-grid">
  344. <% relates.forEach(item=> { %>
  345. <div class="coloring-card">
  346. <div data-content-id="<%= item._id %>" class="coloring-image">
  347. <a href="<%= item.uri %>"><img src="<%= item.thumb %>" loading="lazy" alt="<%= item.title %>"></a>
  348. </div>
  349. <div class="coloring-content">
  350. <div class="coloring-title">
  351. <%= item.title %>
  352. </div>
  353. <div class="coloring-author">by <a href="/coloring-page-gallery?author=<%= item.user.username %>">
  354. <%= item.user.username %>
  355. </a></div>
  356. <div class="coloring-meta">
  357. <div class="date">
  358. <%= item.publishTime %>
  359. </div>
  360. <div class="views">
  361. <%= item.totalStartCount %>
  362. </div>
  363. </div>
  364. <div class="coloring-tags">
  365. <% item.tags.forEach(tag=> { %>
  366. <a href="/coloring-page-gallery?category=<%= tag %>"><span class="tag" data-tag="<%= tag %>">
  367. <%= tag %>
  368. </span></a>
  369. <% }); %>
  370. </div>
  371. </div>
  372. </div>
  373. <% }); %>
  374. </div>
  375. </section>
  376. <section>
  377. <h2>Browse More Coloring Collections</h2>
  378. <div class="collection-grid">
  379. <% collections.forEach(item=> { %>
  380. <a href="<%= item.uri %>" class="collection-card">
  381. <div class="collection-image">
  382. <img src="<%= item.image %>" alt="<%= item.title %>">
  383. </div>
  384. <div class="collection-info">
  385. <div class="collection-title">
  386. <%= item.title %>
  387. </div>
  388. <p class="collection-desc">
  389. <%= item.description %>
  390. </p>
  391. </div>
  392. </a>
  393. <% }); %>
  394. </div>
  395. </section>
  396. <%- include('comment') %>
  397. </main>
  398. <%- include('footer') %>
  399. <script src="/scripts/script.js"></script>
  400. <script src="/scripts/progress2.js"></script>
  401. <script>
  402. // 重新进入或返回,强制刷新
  403. window.addEventListener('pageshow', function (event) {
  404. if (event.persisted) {
  405. window.location.reload();
  406. }
  407. });
  408. function isMobileDevice() {
  409. return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  410. }
  411. window.onload = function () {
  412. if (isMobileDevice()) {
  413. const printBtn = document.getElementById('printBtn');
  414. printBtn.style.display = 'none'; // 移动端隐藏打印按钮
  415. }
  416. };
  417. function jumpToAppDownload() {
  418. const userAgent = navigator.userAgent || navigator.vendor || window.opera;
  419. // Android 检测
  420. if (/android/i.test(userAgent)) {
  421. window.open('https://play.google.com/store/apps/details?id=com.pcoloring.art.puzzle.color.by.number', '_blank');
  422. }
  423. // iOS 检测
  424. else if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
  425. window.open('https://apps.apple.com/gb/app/art-number-coloring-book/id1575480118', '_blank');
  426. }
  427. // 其他操作系统(例如桌面)
  428. else {
  429. // 可以显示一个提示,或者跳转到通用的下载页面
  430. console.log('无法确定操作系统,或者为桌面操作系统');
  431. window.open('https://pcoloring.com/anc/', '_blank');
  432. }
  433. }
  434. // document.getElementById('appBtn').addEventListener('click', function () {
  435. // jumpToAppDownload()
  436. // });
  437. async function printImage(id) {
  438. try {
  439. const response = await fetch(`/download/pdf/page/${id}`);
  440. if (!response.ok) {
  441. throw new Error(`HTTP error! status: ${response.status}`);
  442. }
  443. const pdfBlob = await response.blob();
  444. const pdfUrl = URL.createObjectURL(pdfBlob);
  445. const printWindow = window.open(pdfUrl, '_blank');
  446. printWindow.onload = () => {
  447. printWindow.print();
  448. };
  449. URL.revokeObjectURL(pdfUrl); // 释放 URL 对象
  450. } catch (error) {
  451. console.error('Error printing image:', error);
  452. }
  453. }
  454. function onDelete(id) {
  455. if (confirm('确定要删除游戏进度吗?')) {
  456. // 删除本地存储
  457. localStorage.removeItem(id);
  458. const METADATA_KEY = '__storage_metadata__';
  459. const metadata = JSON.parse(localStorage.getItem(METADATA_KEY) || '{}');
  460. delete metadata[id];
  461. localStorage.setItem(METADATA_KEY, JSON.stringify(metadata));
  462. location.reload(); // 刷新页面恢复初始状态
  463. }
  464. }
  465. function onRepaint(id) {
  466. // 删除本地存储
  467. localStorage.removeItem(id);
  468. const METADATA_KEY = '__storage_metadata__';
  469. const metadata = JSON.parse(localStorage.getItem(METADATA_KEY) || '{}');
  470. delete metadata[id];
  471. localStorage.setItem(METADATA_KEY, JSON.stringify(metadata));
  472. window.open(`/play/${id}`, '_self');
  473. }
  474. // 页面加载完成后执行
  475. document.addEventListener('DOMContentLoaded', () => {
  476. const METADATA_KEY = '__storage_metadata__';
  477. const container = document.getElementById('status');
  478. const contentId = container.dataset.contentId;
  479. // 获取本地存储数据
  480. const metaData = JSON.parse(localStorage.getItem(METADATA_KEY)) || {};
  481. if (metaData[contentId]) {
  482. const progress = Math.round(metaData[contentId].progress);
  483. if (progress < 100) {
  484. container.innerHTML = `You have completed ${progress}%—keep going and finish it!`;
  485. } else {
  486. container.innerHTML = `You have completed 100%, Good Job!`;
  487. container.style = `color: var(--secondary-color)`
  488. }
  489. container.style.display = "block";
  490. // 添加删除按钮
  491. const deleteBtn = document.getElementById('deleteBtn');
  492. deleteBtn.style.display = 'block';
  493. // 开始填色按钮便成继续填色或重新填色
  494. const playBtn = document.getElementById('playBtn');
  495. const continueBtn = document.getElementById('continueBtn');
  496. const repaintBtn = document.getElementById('repaintBtn');
  497. const reviewBtn = document.getElementById('reviewBtn');
  498. playBtn.style.display = 'none';
  499. if (progress < 100) {
  500. continueBtn.style.display = 'block';
  501. } else {
  502. repaintBtn.style.display = 'block';
  503. reviewBtn.style.display = 'block';
  504. const img = document.querySelector('#poster img');
  505. img.src = img.src.replace('/page/', '/work/');
  506. }
  507. }
  508. });
  509. </script>
  510. </body>
  511. </html>