detail.ejs 19 KB

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