moracarta 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/moracarta)](https://www.npmjs.com/package/moracarta)
6
6
  [![npm downloads](https://img.shields.io/npm/dt/moracarta)](https://www.npmjs.com/package/moracarta)
7
7
 
8
- 🌐 **[Live Demo](https://moracarta.pages.dev/)**
8
+ 🌐 **[Live Demo - Latest Stable Branch](https://moracarta.pages.dev/)** | 🛠️ **[Live Demo - Development Branch](https://development.moracarta.pages.dev/)**
9
9
 
10
10
  ## Features
11
11
 
@@ -20,12 +20,17 @@
20
20
  **And it's as simple as:**
21
21
 
22
22
  ```bash
23
- npm init -y
24
23
  npm i moracarta
25
24
  npx moracarta setup
26
25
  npx moracarta build
27
26
  npx moracarta deploy
28
27
  ```
28
+ ### What each command does
29
+
30
+ - `npm i moracarta` installs Moracarta as a dependency in your project.
31
+ - `npx moracarta setup` sets up Moracarta and lets you configure the website.
32
+ - `npx moracarta build` builds your website using your configured letters and settings.
33
+ - `npx moracarta deploy` deploys the finished website for free.
29
34
 
30
35
  ### App Demo
31
36
  <p align="center">
@@ -36,7 +41,7 @@ npx moracarta deploy
36
41
  ### Setup Demo
37
42
 
38
43
  <img src="public/assets/images/setup-demo.png" width="100%">
39
- <br><br>
44
+ <br>
40
45
 
41
46
  ## That's it.
42
47
 
package/index.html CHANGED
@@ -1,31 +1,35 @@
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, maximum-scale=1.0">
6
- <meta name="robots" content="noindex, nofollow, noarchive">
7
- <title data-variable="TITLE"></title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300..700;1,300..700&family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Lora:ital,wght@0,400;1,400&family=Montserrat:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
11
- <link rel="stylesheet" href="src/styles/main.css">
12
- </head>
13
- <body>
14
- <div class="header">
15
- <div class="header-ornament">❧ ❦ ☙</div>
16
- <h1 data-variable="TITLE"></h1>
17
- <div class="header-line"></div>
18
- <p class="header-sub love-note" data-variable="TEXT_TOP"></p>
19
- </div>
20
-
21
- <div class="letter-list" id="letter-list"></div>
22
-
23
- <div class="footer">
24
- <div class="footer-line"></div>
25
- <p data-variable="TEXT_BOTTOM"></p>
26
- <p data-variable="YOUR_NAME"></p>
27
- </div>
28
-
29
- <script type="module" src="src/scripts/main.script.js"></script>
30
- <script type="module" src="src/scripts/data.variable.js"></script>
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, maximum-scale=1.0">
6
+ <meta name="robots" content="noindex, nofollow, noarchive">
7
+ <title data-variable="TITLE"></title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300..700;1,300..700&family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Lora:ital,wght@0,400;1,400&family=Montserrat:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="src/styles/main.css">
12
+ </head>
13
+ <body>
14
+ <div class="header">
15
+ <div class="header-ornament">❧ ❦ ☙</div>
16
+ <h1 data-variable="TITLE"></h1>
17
+ <div class="header-line"></div>
18
+ <p class="header-sub love-note" data-variable="TEXT_TOP"></p>
19
+ </div>
20
+
21
+ <div class="letter-list" id="letter-list"></div>
22
+
23
+ <div class="bottom-text">
24
+ <div class="bottom-text-line"></div>
25
+ <p data-variable="TEXT_BOTTOM"></p>
26
+ <p data-variable="YOUR_NAME"></p>
27
+ </div>
28
+
29
+ <footer class="footer">
30
+ <a id="footer-link" class="footer-link" href="https://github.com/wesleyhanauer/moracarta"></a>
31
+ </footer>
32
+
33
+ <script type="module" src="src/scripts/main.js"></script>
34
+ <script type="module" src="src/scripts/templateBinding.js"></script>
31
35
  </html>
package/package.json CHANGED
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "name": "moracarta",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "license": "MIT",
7
7
  "main": "index.js",
8
8
  "keywords": [
9
- "cli",
10
- "scaffold",
11
- "static-site",
12
- "cloudflare",
13
- "wrangler",
14
- "gift",
15
- "envelope",
16
- "letters",
9
+ "love letter",
10
+ "digital love letter",
11
+ "romantic website",
12
+ "love website",
17
13
  "romantic",
18
- "html-css-js"
14
+ "love",
15
+ "letter",
16
+ "valentines",
17
+ "valentines day",
18
+ "anniversary",
19
+ "romantic website template",
20
+ "website template",
21
+ "static website",
22
+ "npm",
23
+ "open source"
19
24
  ],
20
25
  "files": [
21
26
  "index.html",
@@ -23,17 +28,16 @@
23
28
  "public",
24
29
  "src/commands",
25
30
  "src/config/fonts.js",
26
- "src/data/letters.example.js",
27
- "src/data/lettersLoader.js",
28
- "src/i18n",
31
+ "src/content/letters.example.js",
32
+ "src/loaders/lettersLoader.js",
29
33
  "src/scripts",
30
34
  "src/services",
31
35
  "src/styles",
32
36
  "src/views"
33
37
  ],
34
- "postinstall": "node src/commands/postinstall.js",
38
+ "postinstall": "node src/commands/postInstall.js",
35
39
  "bin": {
36
- "moracarta": "src/commands/cliConfig.js"
40
+ "moracarta": "src/config/cliConfig.js"
37
41
  },
38
42
  "scripts": {
39
43
  "setup": "node src/commands/setup.js",
@@ -47,11 +51,11 @@
47
51
  "type": "git",
48
52
  "url": "git+https://github.com/WesleyHanauer/moracarta.git"
49
53
  },
50
- "author": "",
54
+ "author": "Wesley Hanauer",
51
55
  "bugs": {
52
56
  "url": "https://github.com/WesleyHanauer/moracarta/issues"
53
57
  },
54
- "homepage": "https://github.com/WesleyHanauer/moracarta#readme",
58
+ "homepage": "https://moracarta.pages.dev",
55
59
  "dependencies": {
56
60
  "@inquirer/prompts": "^8.6.0",
57
61
  "vite": "^8.2.2",
@@ -11,7 +11,7 @@ import { getDocumentText } from "../services/googleDocs.js";
11
11
  const DATA_DIRECTORY = resolve(
12
12
  process.cwd(),
13
13
  "src",
14
- "data"
14
+ "content"
15
15
  );
16
16
 
17
17
  const LETTERS_FILE = resolve(
@@ -76,7 +76,7 @@ ${letterObject}
76
76
 
77
77
  if (closingIndex === -1) {
78
78
  throw new Error(
79
- "Could not find the end of the letters array in src/data/letters.js."
79
+ "Could not find the end of the letters array in src/content/letters.js."
80
80
  );
81
81
  }
82
82
 
@@ -4,12 +4,12 @@ import { execSync } from "node:child_process";
4
4
  import { resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
- const configPath = resolve(process.cwd(), "src", "config", "globalVariables.js");
7
+ const dataPath = resolve(process.cwd(), "src", "config", "globalVariables.js");
8
8
 
9
9
  let globalVariables;
10
10
 
11
11
  try {
12
- ({ default: globalVariables } = await import(pathToFileURL(configPath).href));
12
+ ({ default: globalVariables } = await import(pathToFileURL(dataPath).href));
13
13
  } catch (error) {
14
14
  if (error.code === "ERR_MODULE_NOT_FOUND") {
15
15
  console.error("\n✗ No configuration found. Run moracarta setup first.");
@@ -7,7 +7,7 @@ import { resolve } from "node:path";
7
7
  const LETTERS_FILE = resolve(
8
8
  process.cwd(),
9
9
  "src",
10
- "data",
10
+ "content",
11
11
  "letters.js"
12
12
  );
13
13
 
@@ -32,7 +32,7 @@ function extractLetters(lettersFile) {
32
32
 
33
33
  if (!match) {
34
34
  throw new Error(
35
- "Could not find the letters array in src/data/letters.js."
35
+ "Could not find the letters array in src/content/letters.js."
36
36
  );
37
37
  }
38
38
 
@@ -1,8 +1,4 @@
1
- import {
2
- select,
3
- input,
4
- confirm
5
- } from "@inquirer/prompts";
1
+ import { select, input, confirm } from "@inquirer/prompts";
6
2
  import { writeFile, mkdir, cp, readFile } from "node:fs/promises";
7
3
  import { resolve, dirname } from "node:path";
8
4
  import { fileURLToPath } from "node:url";
@@ -11,29 +7,18 @@ const __filename = fileURLToPath(import.meta.url);
11
7
  const __dirname = dirname(__filename);
12
8
  const PACKAGE_ROOT = resolve(__dirname, "..", "..");
13
9
 
14
- /** TODO
15
- const LANGUAGE = await select({
16
- message: "What is the main language of the application?",
17
- choices: [
18
- { name: "English", value: "en" },
19
- { name: "Português (Brasil)", value: "pt-BR" }
20
- ]
21
- });
22
- */
23
- const LANGUAGE = "en";
24
-
25
10
  const PROJECT_NAME = await input({
26
11
  message: "What should your project be called? (Cloudflare project name, do not use 'moracarta')",
27
12
  default: "romantic-gift",
28
13
  });
29
14
 
30
15
  const TITLE = await input({
31
- message: "Enter the title of the application:",
16
+ message: "Enter the title of the application: ",
32
17
  default: "Letters to my love"
33
18
  });
34
19
 
35
20
  const FONT = await select({
36
- message: "What font would you like to use?",
21
+ message: "What font style would you like to use?",
37
22
  choices: [
38
23
  { name: "Handwritten", value: "handwritten" },
39
24
  { name: "Classic", value: "classic" },
@@ -113,9 +98,15 @@ const DEFAULT_CLOSED_LETTER_TEXT_TOP_LINE =
113
98
  const DEFAULT_CLOSED_LETTER_TEXT_BOTTOM_LINE =
114
99
  "&#10084;&#65039; I love you &#10084;&#65039;";
115
100
 
116
- const EDIT_ENVELOPE_TEXT = await confirm({
117
- message: "Would you like to customize the envelope text?",
118
- default: false
101
+ const EDIT_ENVELOPE_TEXT = await select({
102
+ message: "Would you like to customize the envelope text? ",
103
+ choices: [{
104
+ name: "yes",
105
+ value: true
106
+ },{
107
+ name: "no",
108
+ value: false
109
+ }],
119
110
  });
120
111
 
121
112
  let CLOSED_LETTER_TEXT_TOP_LINE = DEFAULT_CLOSED_LETTER_TEXT_TOP_LINE;
@@ -133,12 +124,21 @@ if (EDIT_ENVELOPE_TEXT) {
133
124
  });
134
125
  }
135
126
 
127
+ const SHOW_BRANDING = await select({
128
+ message: "Would you like to display 'Made with 💌 Moracarta' branding in the footer? ",
129
+ choices: [{
130
+ name: "yes",
131
+ value: true
132
+ },{
133
+ name: "no",
134
+ value: false
135
+ }],
136
+ });
137
+
136
138
  const config = `/**
137
139
  * This file was generated automatically by Moracarta setup.
138
140
  */
139
141
 
140
- const LANGUAGE = ${JSON.stringify(LANGUAGE)};
141
-
142
142
  const PROJECT_NAME = ${JSON.stringify(PROJECT_NAME)};
143
143
  const TITLE = ${JSON.stringify(TITLE)};
144
144
 
@@ -154,8 +154,9 @@ const CLOSED_LETTER_TEXT_TOP_LINE =
154
154
  const CLOSED_LETTER_TEXT_BOTTOM_LINE =
155
155
  ${JSON.stringify(CLOSED_LETTER_TEXT_BOTTOM_LINE)};
156
156
 
157
+ const SHOW_BRANDING = ${SHOW_BRANDING};
158
+
157
159
  const globalVariables = {
158
- LANGUAGE,
159
160
  PROJECT_NAME,
160
161
  TITLE,
161
162
  YOUR_NAME,
@@ -163,21 +164,22 @@ const globalVariables = {
163
164
  TEXT_TOP,
164
165
  TEXT_BOTTOM,
165
166
  CLOSED_LETTER_TEXT_TOP_LINE,
166
- CLOSED_LETTER_TEXT_BOTTOM_LINE
167
+ CLOSED_LETTER_TEXT_BOTTOM_LINE,
168
+ SHOW_BRANDING
167
169
  };
168
170
 
169
171
  export default globalVariables;
170
172
  `;
171
173
 
172
- const configPath = resolve(
174
+ const dataPath = resolve(
173
175
  process.cwd(),
174
176
  "src",
175
177
  "config",
176
178
  "globalVariables.js"
177
179
  );
178
180
 
179
- await mkdir(dirname(configPath), { recursive: true });
180
- await writeFile(configPath, config, "utf8");
181
+ await mkdir(dirname(dataPath), { recursive: true });
182
+ await writeFile(dataPath, config, "utf8");
181
183
 
182
184
  /**
183
185
  * Files that make up the actual site. These get copied out of the
@@ -189,9 +191,8 @@ const TEMPLATE_ENTRIES = [
189
191
  "vite.config.mjs",
190
192
  "public",
191
193
  "src/config/fonts.js",
192
- "src/data/letters.example.js",
193
- "src/data/lettersLoader.js",
194
- "src/i18n",
194
+ "src/content/letters.example.js",
195
+ "src/loaders/lettersLoader.js",
195
196
  "src/scripts",
196
197
  "src/services",
197
198
  "src/styles",
@@ -253,5 +254,5 @@ await copyTemplateFiles();
253
254
  await ensureModuleType();
254
255
 
255
256
  console.log("\n❤️ Moracarta setup complete!");
256
- console.log(`Configuration saved to: ${configPath}`);
257
+ console.log(`Configuration saved to: ${dataPath}`);
257
258
  console.log("Run moracarta dev to test the application");
@@ -0,0 +1,57 @@
1
+ // This file is the placeholder for the letters, this one is used until the user
2
+ // runs 'npx moracarta add' for the first time, then letters.js is created.
3
+ // This file is also usefull in the live showcase since it's built together
4
+ // with the rest of the app.
5
+
6
+ // Each different letter is a different object inside const letters.
7
+ // Color palettes are generated from the id, date and title.
8
+ // Since they are generated from a hashed string letters with
9
+ // the same information will always generate the same colors.
10
+ export const letters = [
11
+ {
12
+ id: 1,
13
+ date: 'Aug 28, 2026',
14
+ title: 'Custom Letter Title 1',
15
+ content: `My Dearest Mary Jane,
16
+ As I sit down to write these words, I am thinking of you and how grateful I am for everything we experience together. Life moves quickly, but whenever I pause to reflect on what brings me the most joy, you are always at the top of my mind.
17
+ You bring so much inspiration and comfort into my life. Whether we are sharing long conversations or simply enjoying quiet moments together, being with you feels natural and right. Your support and kindness mean more to me than I can easily put into words.
18
+ I wanted to remind you today of how deeply appreciated and loved you are. No matter where life takes us, know that you hold a very special place in my heart.
19
+
20
+ Yours always,
21
+ Peter Parker`
22
+ },
23
+ {
24
+ id: 2,
25
+ date: 'Aug 28, 2026',
26
+ title: 'Custom Letter Title 2',
27
+ content: `Dearest Mary Jane,
28
+
29
+ I hope this letter finds you well. I wanted to take a quiet moment today to write to you and express just how much you mean to me. Even in the midst of everyday life, my thoughts frequently return to you and the wonderful moments we share.
30
+ Your presence brings warmth, kindness, and light into my world. Every memory with you is something I truly cherish, and I am constantly reminded of how fortunate I am to have you in my life. You have a way of making ordinary days feel special just by being yourself.
31
+ Thank you for your affection, your understanding, and all the happiness you bring into my life. I look forward to creating many more beautiful memories together in the time ahead.
32
+
33
+
34
+ With all my love,
35
+ Peter Parker`
36
+ },
37
+ {
38
+ id: 3,
39
+ date: 'Aug 31, 2026',
40
+ title: 'Custom Letter Title 3',
41
+ content: `Dear Mary Jane,
42
+ Sometimes I wonder how I got so lucky to have you in my life. Between all the chaos, the late nights, and everything I have to keep hidden, you're always the person who makes me feel like I can just be Peter. You make even the hardest days feel a little lighter, and I hope you know how much that means to me.
43
+ I know I don't always have the time or the words to explain how I feel, but I want you to know that you're always on my mind. No matter how far I swing, no matter how crazy things get, there's always a part of me that wants to come home to you. You're not just someone I love, MJ. You're my favorite part of being Peter Parker.
44
+ Love,
45
+ Peter Parker`
46
+ },
47
+ {
48
+ id: 4,
49
+ date: 'Aug 31, 2026',
50
+ title: 'Custom Letter Title 4',
51
+ content: `Dear MJ,
52
+ There are so many things I wish I could tell you, but somehow every time I try, the words never seem to be enough. You're the person who sees me when I don't even know how to see myself. When everything feels overwhelming, just hearing your voice reminds me that maybe things aren't as hopeless as they seem.
53
+ I know I've made mistakes, and I know I've kept things from you that I probably shouldn't have. But through all of it, you've stayed by my side. You make me want to be better—not because you expect me to be perfect, but because being with you makes me want to become someone worthy of the way you believe in me.
54
+ Love,
55
+ Peter`
56
+ }
57
+ ];
@@ -0,0 +1,13 @@
1
+ // This file checks wheter or not src/content/letters.js exists, if not
2
+ // it loads letters.example.js as the letter provider file.
3
+ // It is done this way so that the user doesn't have to manually remove the
4
+ // placeholder example letters before adding his own letters.
5
+
6
+ // Uses meta.glob to check files with given name.
7
+ const modules = import.meta.glob("../content/letters*.js", {
8
+ eager: true,
9
+ import: "letters",
10
+ });
11
+
12
+ export const letters =
13
+ modules["../content/letters.js"] ?? modules["../content/letters.example.js"];
@@ -1,8 +1,7 @@
1
- import { getTranslation } from './i18n.js';
2
- import { letters } from '../data/lettersLoader.js';
3
-
4
- const listContainer = document.getElementById('letter-list');
5
-
1
+ // A set of base color families used to generate a unique
2
+ // envelope palette for each letter based on its id, title, and date.
3
+ // Same code as lettersPage.js so that the same color is applied on both
4
+ // main page and the envelope opening animation.
6
5
  const paletteFamilies = [
7
6
  { hue: 18, sat: 54, light: 88, sealHue: 345, sealSat: 76, sealLight: 42 },
8
7
  { hue: 26, sat: 48, light: 86, sealHue: 12, sealSat: 72, sealLight: 38 },
@@ -16,6 +15,7 @@ const paletteFamilies = [
16
15
  { hue: 14, sat: 44, light: 88, sealHue: 8, sealSat: 80, sealLight: 36 }
17
16
  ];
18
17
 
18
+ // Simple hash function to determine which color palette to use on the envelope.
19
19
  function hashString(value) {
20
20
  let hash = 0;
21
21
  for (let index = 0; index < value.length; index += 1) {
@@ -25,18 +25,21 @@ function hashString(value) {
25
25
  return Math.abs(hash);
26
26
  }
27
27
 
28
+ // Keeps color value within a set range.
28
29
  function clamp(value, min, max) {
29
30
  return Math.min(Math.max(value, min), max);
30
31
  }
31
32
 
32
- function generatePalette(letter) {
33
+ // Build a deterministic palette based on the letter's identity.
34
+ // The base hash selects one of the palette families, while the variant
35
+ // arrays add subtle shifts to keep similar letters visually distinct.
36
+ export default function generatePalette(letter) {
33
37
  const baseHash = hashString(`${letter.id}|${letter.title}|${letter.date}`);
34
38
  const family = paletteFamilies[baseHash % paletteFamilies.length];
35
39
  const variant = Math.floor(baseHash / paletteFamilies.length) % 7;
36
40
  const hueShift = [0, 14, -12, 20, -18, 26, -22][variant];
37
41
  const satShift = [0, 8, -6, 12, -10, 14, -8][variant];
38
42
  const lightShift = [0, -8, 8, -5, 6, -10, 10][variant];
39
-
40
43
  const envelope = `hsl(${clamp(family.hue + hueShift, 0, 360)}, ${clamp(family.sat + satShift, 42, 72)}%, ${clamp(family.light + lightShift, 78, 92)}%)`;
41
44
  const flap = `hsl(${clamp(family.hue + hueShift + 2, 0, 360)}, ${clamp(family.sat + satShift + 8, 50, 82)}%, ${clamp(family.light - 18 + lightShift, 60, 76)}%)`;
42
45
  const shadow = `hsl(${clamp(family.hue + hueShift + 8, 0, 360)}, ${clamp(family.sat + satShift - 12, 30, 58)}%, ${clamp(family.light - 36 + lightShift, 42, 58)}%)`;
@@ -45,57 +48,4 @@ function generatePalette(letter) {
45
48
  const text = `hsl(${family.hue}, 22%, 18%)`;
46
49
 
47
50
  return { envelope, flap, shadow, base, seal, text };
48
- }
49
-
50
- function renderLetters() {
51
- if (!listContainer) {
52
- return;
53
- }
54
-
55
- const tapToOpenText = getTranslation('TAP_TO_OPEN');
56
- const orderedLetters = [...letters].reverse();
57
-
58
- orderedLetters.forEach((letter) => {
59
- const palette = generatePalette(letter);
60
- const link = document.createElement('a');
61
- link.href = `./src/views/letters.view.html?id=${letter.id}`;
62
- link.className = 'envelope-link';
63
-
64
- link.innerHTML = `
65
- <div class="envelope-wrap">
66
- <div class="envelope-sombra"></div>
67
- <div class="envelope" style="background:${palette.envelope}; --envelope-sombra:${palette.shadow};">
68
- <div class="env-corpo-svg">
69
- <svg viewBox="0 0 100 63" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
70
- <polygon points="0,0 0,63 50,31" fill="${palette.shadow}" />
71
- <polygon points="100,0 100,63 50,31" fill="${palette.shadow}" />
72
- <polygon points="0,63 100,63 50,31" fill="${palette.base}" />
73
- </svg>
74
- </div>
75
-
76
- <div class="env-aba" style="border-top-color:${palette.flap};">
77
- <svg viewBox="0 0 100 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
78
- <polygon points="0,0 100,0 50,44" fill="${palette.flap}" />
79
- </svg>
80
- <div class="env-lacre" style="background:${palette.seal};">♡</div>
81
- </div>
82
-
83
- <div class="envelope-centro">
84
- <span class="envelope-data" style="color:${palette.text};">${letter.date}</span>
85
- <span class="envelope-titulo" style="color:${palette.text};">${letter.title}</span>
86
- <span class="envelope-hint" style="color:${palette.text};">${tapToOpenText}</span>
87
- </div>
88
- <div class="envelope-canto tl"></div>
89
- <div class="envelope-canto tr"></div>
90
- <div class="envelope-canto bl"></div>
91
- <div class="envelope-canto br"></div>
92
- </div>
93
- </div>
94
- `;
95
-
96
- listContainer.appendChild(link);
97
- });
98
- }
99
-
100
- renderLetters();
101
-
51
+ }
@@ -1,73 +1,22 @@
1
- import { getTranslation } from './i18n.js';
2
- import { letters } from '../data/lettersLoader.js';
3
-
4
- console.log('letters.script loaded');
1
+ import { letters } from '../loaders/lettersLoader.js';
2
+ import generatePalette from './generatePalette.js';
5
3
 
4
+ // Checks the URL to see what ID is select.
5
+ // Then loads letter with that same ID.
6
6
  const params = new URLSearchParams(window.location.search);
7
7
  const requestedId = parseInt(params.get('id'), 10);
8
8
  const letter = letters.find((entry) => entry.id === requestedId);
9
9
 
10
- // A set of base color families used to generate a unique yet consistent
11
- // envelope palette for each letter based on its id, title, and date.
12
- const paletteFamilies = [
13
- { hue: 18, sat: 54, light: 88, sealHue: 345, sealSat: 76, sealLight: 42 },
14
- { hue: 26, sat: 48, light: 86, sealHue: 12, sealSat: 72, sealLight: 38 },
15
- { hue: 4, sat: 50, light: 84, sealHue: 5, sealSat: 78, sealLight: 34 },
16
- { hue: 38, sat: 42, light: 90, sealHue: 24, sealSat: 66, sealLight: 36 },
17
- { hue: 8, sat: 58, light: 86, sealHue: 6, sealSat: 82, sealLight: 30 },
18
- { hue: 12, sat: 50, light: 88, sealHue: 11, sealSat: 74, sealLight: 33 },
19
- { hue: 22, sat: 46, light: 92, sealHue: 20, sealSat: 68, sealLight: 40 },
20
- { hue: 16, sat: 60, light: 84, sealHue: 5, sealSat: 86, sealLight: 32 },
21
- { hue: 28, sat: 52, light: 90, sealHue: 15, sealSat: 70, sealLight: 38 },
22
- { hue: 14, sat: 44, light: 88, sealHue: 8, sealSat: 80, sealLight: 36 }
23
- ];
24
-
25
- function hashString(value) {
26
- // Simple deterministic string hash used to select a palette and variant.
27
- // This keeps each letter appearance stable across page loads.
28
- let hash = 0;
29
- for (let index = 0; index < value.length; index += 1) {
30
- hash = ((hash << 5) - hash) + value.charCodeAt(index);
31
- hash |= 0;
32
- }
33
- return Math.abs(hash);
34
- }
35
-
36
- function clamp(value, min, max) {
37
- return Math.min(Math.max(value, min), max);
38
- }
39
-
40
- function generatePalette(letterItem) {
41
- // Build a deterministic palette based on the letter's identity.
42
- // The base hash selects one of the palette families, while the variant
43
- // arrays add subtle shifts to keep similar letters visually distinct.
44
- const baseHash = hashString(`${letterItem.id}|${letterItem.title}|${letterItem.date}`);
45
- const family = paletteFamilies[baseHash % paletteFamilies.length];
46
- const variant = Math.floor(baseHash / paletteFamilies.length) % 7;
47
- const hueShift = [0, 14, -12, 20, -18, 26, -22][variant];
48
- const satShift = [0, 8, -6, 12, -10, 14, -8][variant];
49
- const lightShift = [0, -8, 8, -5, 6, -10, 10][variant];
50
-
51
- const envelope = `hsl(${clamp(family.hue + hueShift, 0, 360)}, ${clamp(family.sat + satShift, 42, 72)}%, ${clamp(family.light + lightShift, 78, 92)}%)`;
52
- const flap = `hsl(${clamp(family.hue + hueShift + 2, 0, 360)}, ${clamp(family.sat + satShift + 8, 50, 82)}%, ${clamp(family.light - 18 + lightShift, 60, 76)}%)`;
53
- const shadow = `hsl(${clamp(family.hue + hueShift + 8, 0, 360)}, ${clamp(family.sat + satShift - 12, 30, 58)}%, ${clamp(family.light - 36 + lightShift, 42, 58)}%)`;
54
- const base = `hsl(${clamp(family.hue + hueShift + 8, 0, 360)}, ${clamp(family.sat + satShift - 10, 30, 58)}%, ${clamp(family.light - 30 + lightShift, 44, 62)}%)`;
55
- const seal = `hsl(${clamp(family.sealHue + hueShift, 0, 360)}, ${clamp(family.sealSat + satShift, 68, 92)}%, ${clamp(family.sealLight + lightShift * 0.8, 28, 46)}%)`;
56
- const text = `hsl(${family.hue}, 22%, 18%)`;
57
-
58
- return { envelope, flap, shadow, base, seal, text };
59
- }
60
-
61
10
  function applyEnvelopePalette(letterItem) {
62
11
  const palette = generatePalette(letterItem);
63
- document.querySelector('.env-fundo').style.background = palette.envelope;
12
+ document.querySelector('.env-background').style.background = palette.envelope;
64
13
 
65
- const flapPolygon = document.querySelector('.env-aba svg polygon');
14
+ const flapPolygon = document.querySelector('.env-flap svg polygon');
66
15
  if (flapPolygon) {
67
16
  flapPolygon.setAttribute('fill', palette.flap);
68
17
  }
69
18
 
70
- const sidePolygons = document.querySelectorAll('.env-corpo-svg svg polygon');
19
+ const sidePolygons = document.querySelectorAll('.env-body-svg svg polygon');
71
20
  if (sidePolygons.length === 3) {
72
21
  sidePolygons[0].setAttribute('fill', palette.shadow);
73
22
  sidePolygons[1].setAttribute('fill', palette.shadow);
@@ -99,17 +48,17 @@ if (!letter) {
99
48
  const previousLetter = currentIndex > 0 ? sortedLetters[currentIndex - 1] : null;
100
49
  const nextLetter = currentIndex < sortedLetters.length - 1 ? sortedLetters[currentIndex + 1] : null;
101
50
 
102
- document.getElementById('letter-counter').textContent = `${currentIndex + 1} ${getTranslation('OF')} ${sortedLetters.length}`;
51
+ document.getElementById('letter-counter').textContent = `${currentIndex + 1} Of ${sortedLetters.length}`;
103
52
 
104
53
  if (previousLetter) {
105
54
  const previousButton = document.getElementById('nav-previous');
106
- previousButton.href = `./letters.view.html?id=${previousLetter.id}`;
55
+ previousButton.href = `./letters.html?id=${previousLetter.id}`;
107
56
  previousButton.classList.remove('invisible');
108
57
  }
109
58
 
110
59
  if (nextLetter) {
111
60
  const nextButton = document.getElementById('nav-next');
112
- nextButton.href = `./letters.view.html?id=${nextLetter.id}`;
61
+ nextButton.href = `./letters.html?id=${nextLetter.id}`;
113
62
  nextButton.classList.remove('invisible');
114
63
  }
115
64
 
@@ -137,10 +86,10 @@ if (!letter) {
137
86
  }, 2100);
138
87
  }
139
88
 
140
- function startHeartAnimation() {
141
89
  // Floating heart particle animation for the letter view background.
142
90
  // Particles are born after a delay, float upward, fade at the top, and die
143
91
  // when they leave the canvas to keep the animation visually light.
92
+ function startHeartAnimation() {
144
93
  const canvas = document.getElementById('canvas-hearts');
145
94
  const context = canvas.getContext('2d');
146
95
  const devicePixelRatio = window.devicePixelRatio || 1;
@@ -252,6 +201,5 @@ function startHeartAnimation() {
252
201
  context.clearRect(0, 0, width, height);
253
202
  }
254
203
  }
255
-
256
204
  window.requestAnimationFrame(animate);
257
205
  }
@@ -0,0 +1,62 @@
1
+ import { letters } from '../loaders/lettersLoader.js';
2
+ import globalVariables from '../config/globalVariables.js';
3
+ import generatePalette from './generatePalette.js';
4
+
5
+ const listContainer = document.getElementById('letter-list');
6
+
7
+ function renderLetters() {
8
+ if (!listContainer) {
9
+ return;
10
+ }
11
+
12
+ const orderedLetters = [...letters].reverse();
13
+
14
+ orderedLetters.forEach((letter) => {
15
+ const palette = generatePalette(letter);
16
+ const link = document.createElement('a');
17
+ link.href = `./src/views/letters.html?id=${letter.id}`;
18
+ link.className = 'envelope-link';
19
+
20
+ // Creates a new envelope object for each loaded letter.
21
+ link.innerHTML = `
22
+ <div class="envelope-wrap">
23
+ <div class="envelope-shadow"></div>
24
+ <div class="envelope" style="background:${palette.envelope}; --envelope-shadow:${palette.shadow};">
25
+ <div class="env-body-svg">
26
+ <svg viewBox="0 0 100 63" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
27
+ <polygon points="0,0 0,63 50,31" fill="${palette.shadow}" />
28
+ <polygon points="100,0 100,63 50,31" fill="${palette.shadow}" />
29
+ <polygon points="0,63 100,63 50,31" fill="${palette.base}" />
30
+ </svg>
31
+ </div>
32
+
33
+ <div class="env-flap" style="border-top-color:${palette.flap};">
34
+ <svg viewBox="0 0 100 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
35
+ <polygon points="0,0 100,0 50,44" fill="${palette.flap}" />
36
+ </svg>
37
+ <div class="env-seal" style="background:${palette.seal};">♡</div>
38
+ </div>
39
+
40
+ <div class="envelope-center">
41
+ <span class="envelope-date" style="color:${palette.text};">${letter.date}</span>
42
+ <span class="envelope-title" style="color:${palette.text};">${letter.title}</span>
43
+ <span class="envelope-hint" style="color:${palette.text};">Tap to open</span>
44
+ </div>
45
+ <div class="envelope-corner tl"></div>
46
+ <div class="envelope-corner tr"></div>
47
+ <div class="envelope-corner bl"></div>
48
+ <div class="envelope-corner br"></div>
49
+ </div>
50
+ </div>
51
+ `;
52
+
53
+ listContainer.appendChild(link);
54
+ });
55
+ }
56
+
57
+ renderLetters();
58
+
59
+ if(globalVariables.SHOW_BRANDING){
60
+ let footer = document.getElementById('footer-link');
61
+ footer.textContent = "Made with 💌 Moracarta";
62
+ }
@@ -1,24 +1,21 @@
1
1
  import globalVariables from '../config/globalVariables.js';
2
2
  import fonts from '../config/fonts.js';
3
3
 
4
+ // Sets the main font for the application
4
5
  document.documentElement.style.setProperty(
5
6
  '--main-font',
6
7
  fonts[globalVariables.FONT] || fonts.handwritten
7
8
  );
8
9
 
9
- // Data variables are injected from `src/config/globalVariables.js` into
10
+ // Template variables are injected from `src/config/globalVariables.js` into
10
11
  // HTML elements using the `data-variable` attribute.
11
- // HTML entities are preserved when the value contains markup characters.
12
12
  document.querySelectorAll("[data-variable]").forEach(element => {
13
13
  const value = globalVariables[element.dataset.variable];
14
14
 
15
+ //Checks if the value is to be interpreted as literal HTML or pure text
15
16
  if (typeof value === 'string' && (value.includes('&') || value.includes('<') || value.includes('>'))) {
16
17
  element.innerHTML = value;
17
18
  } else {
18
19
  element.textContent = value;
19
20
  }
20
- });
21
-
22
- document.querySelectorAll("[data-variable-src]").forEach(element => {
23
- element.src = globalVariables[element.dataset.variableSrc];
24
21
  });
@@ -74,7 +74,7 @@ body {
74
74
  opacity: 1;
75
75
  }
76
76
 
77
- .env-fundo {
77
+ .env-background {
78
78
  position: absolute;
79
79
  inset: 0;
80
80
  background: #e8d5b0;
@@ -82,7 +82,7 @@ body {
82
82
  z-index: 1;
83
83
  }
84
84
 
85
- .env-papel {
85
+ .env-paper {
86
86
  position: absolute;
87
87
  left: 11%;
88
88
  right: 11%;
@@ -97,7 +97,7 @@ body {
97
97
  overflow: hidden;
98
98
  }
99
99
 
100
- .env-papel.leaving {
100
+ .env-paper.leaving {
101
101
  transform: translateY(-120%);
102
102
  }
103
103
 
@@ -111,19 +111,19 @@ body {
111
111
  font-style: italic;
112
112
  }
113
113
 
114
- .env-corpo-svg {
114
+ .env-body-svg {
115
115
  position: absolute;
116
116
  inset: 0;
117
117
  z-index: 3;
118
118
  pointer-events: none;
119
119
  }
120
120
 
121
- .env-corpo-svg svg {
121
+ .env-body-svg svg {
122
122
  width: 100%;
123
123
  height: 100%;
124
124
  }
125
125
 
126
- .env-aba {
126
+ .env-flap {
127
127
  position: absolute;
128
128
  top: 0;
129
129
  left: 0;
@@ -136,20 +136,20 @@ body {
136
136
  overflow: visible;
137
137
  }
138
138
 
139
- .env-aba.open {
139
+ .env-flap.open {
140
140
  transform: rotateX(180deg);
141
141
  }
142
142
 
143
- .env-aba.behind {
143
+ .env-flap.behind {
144
144
  z-index: 0;
145
145
  }
146
146
 
147
- .env-aba svg {
147
+ .env-flap svg {
148
148
  width: 100%;
149
149
  height: 100%;
150
150
  }
151
151
 
152
- .env-lacre {
152
+ .env-seal {
153
153
  position: absolute;
154
154
  z-index: 5;
155
155
  width: 50px;
@@ -176,7 +176,7 @@ body {
176
176
  will-change: transform;
177
177
  }
178
178
 
179
- .env-lacre.fading {
179
+ .env-seal.fading {
180
180
  opacity: 0;
181
181
  transform: translate(-50%, -120%) scale(0.5);
182
182
  }
@@ -143,7 +143,7 @@ body {
143
143
  transform: scale(0.97) rotate(0deg);
144
144
  }
145
145
 
146
- .envelope-sombra {
146
+ .envelope-shadow {
147
147
  position: absolute;
148
148
  bottom: -6px;
149
149
  left: 6px;
@@ -180,20 +180,20 @@ body {
180
180
  background-repeat: no-repeat;
181
181
  }
182
182
 
183
- .env-corpo-svg {
183
+ .env-body-svg {
184
184
  position: absolute;
185
185
  inset: 0;
186
186
  z-index: 1;
187
187
  pointer-events: none;
188
188
  }
189
189
 
190
- .env-corpo-svg svg {
190
+ .env-body-svg svg {
191
191
  width: 100%;
192
192
  height: 100%;
193
193
  display: block;
194
194
  }
195
195
 
196
- .env-aba {
196
+ .env-flap {
197
197
  position: relative;
198
198
  z-index: 4;
199
199
  pointer-events: none;
@@ -202,13 +202,13 @@ body {
202
202
  width: 100%;
203
203
  }
204
204
 
205
- .env-aba svg {
205
+ .env-flap svg {
206
206
  width: 100%;
207
207
  height: 100%;
208
208
  display: block;
209
209
  }
210
210
 
211
- .env-lacre {
211
+ .env-seal {
212
212
  position: absolute;
213
213
  z-index: 5;
214
214
  width: 48px;
@@ -226,7 +226,7 @@ body {
226
226
  bottom: -24px;
227
227
  }
228
228
 
229
- .envelope-centro {
229
+ .envelope-center {
230
230
  position: relative;
231
231
  z-index: 2;
232
232
  padding: 12px 16px 16px;
@@ -235,7 +235,7 @@ body {
235
235
  gap: 2px;
236
236
  }
237
237
 
238
- .envelope-data {
238
+ .envelope-date {
239
239
  font-size: 10px;
240
240
  letter-spacing: 0.5px;
241
241
  text-transform: uppercase;
@@ -244,7 +244,7 @@ body {
244
244
  font-style: italic;
245
245
  }
246
246
 
247
- .envelope-titulo {
247
+ .envelope-title {
248
248
  font-family: 'Playfair Display', Georgia, serif;
249
249
  font-size: 16px;
250
250
  font-weight: 600;
@@ -267,7 +267,7 @@ body {
267
267
  color: var(--red);
268
268
  }
269
269
 
270
- .envelope-canto {
270
+ .envelope-corner {
271
271
  position: absolute;
272
272
  width: 20px;
273
273
  height: 20px;
@@ -276,25 +276,25 @@ body {
276
276
  opacity: 0.5;
277
277
  }
278
278
 
279
- .envelope-canto.tl {
279
+ .envelope-corner.tl {
280
280
  top: 6px;
281
281
  left: 6px;
282
282
  border-width: 1px 0 0 1px;
283
283
  }
284
284
 
285
- .envelope-canto.tr {
285
+ .envelope-corner.tr {
286
286
  top: 6px;
287
287
  right: 6px;
288
288
  border-width: 1px 1px 0 0;
289
289
  }
290
290
 
291
- .envelope-canto.bl {
291
+ .envelope-corner.bl {
292
292
  bottom: 6px;
293
293
  left: 6px;
294
294
  border-width: 0 0 1px 1px;
295
295
  }
296
296
 
297
- .envelope-canto.br {
297
+ .envelope-corner.br {
298
298
  bottom: 6px;
299
299
  right: 6px;
300
300
  border-width: 0 1px 1px 0;
@@ -310,7 +310,7 @@ body {
310
310
  }
311
311
  }
312
312
 
313
- .footer {
313
+ .bottom-text {
314
314
  text-align: center;
315
315
  margin-top: 40px;
316
316
  padding: 0 20px;
@@ -319,14 +319,14 @@ body {
319
319
  margin-right: auto;
320
320
  }
321
321
 
322
- .footer-line {
322
+ .bottom-text-line {
323
323
  width: 60px;
324
324
  height: 1px;
325
325
  background: linear-gradient(to right, transparent, #6b4c2a, transparent);
326
326
  margin: 0 auto 16px;
327
327
  }
328
328
 
329
- .footer p {
329
+ .bottom-text p {
330
330
  font-size: 12px;
331
331
  color: #6b4c2a;
332
332
  font-style: italic;
@@ -335,4 +335,13 @@ body {
335
335
  text-align: justify;
336
336
  }
337
337
 
338
-
338
+ .footer{
339
+ padding-top: 11vh;
340
+ display: flex;
341
+ justify-content: center;
342
+ }
343
+
344
+ .footer-link{
345
+ text-decoration: none;
346
+ color: rgba(176, 53, 16, 0.571);
347
+ }
@@ -15,24 +15,24 @@
15
15
  <div class="back-link-wrap">
16
16
  <a href="../../index.html" class="back-link">
17
17
  <span class="back-link-arrow">&#8592;</span>
18
- <span data-i18n="RETURN"></span>
18
+ <span>Return</span>
19
19
  </a>
20
20
  </div>
21
21
 
22
- <div id="error-message" class="error" style="display:none"><p data-i18n="LETTER_NOT_FOUND"></p></div>
22
+ <div id="error-message" class="error" style="display:none"><p>Letter not found</p></div>
23
23
 
24
24
  <div class="envelope-animation-wrap" id="envelope-wrap">
25
25
  <div class="envelope-stage">
26
- <div class="env-fundo"></div>
26
+ <div class="env-background"></div>
27
27
 
28
- <div class="env-papel" id="paper">
28
+ <div class="env-paper" id="paper">
29
29
  <div class="paper-text">
30
30
  <p data-variable="CLOSED_LETTER_TEXT_TOP_LINE"></p><br>
31
31
  <p data-variable="CLOSED_LETTER_TEXT_BOTTOM_LINE"></p>
32
32
  </div>
33
33
  </div>
34
34
 
35
- <div class="env-corpo-svg">
35
+ <div class="env-body-svg">
36
36
  <svg viewBox="0 0 100 63" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
37
37
  <polygon points="0,0 0,63 47,42" fill="#c9a87c" />
38
38
  <polygon points="100,0 100,63 53,42" fill="#c4a07a" />
@@ -40,13 +40,13 @@
40
40
  </svg>
41
41
  </div>
42
42
 
43
- <div class="env-aba" id="flap">
43
+ <div class="env-flap" id="flap">
44
44
  <svg viewBox="0 0 100 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
45
45
  <polygon points="0,0 100,0 50,44" fill="#d4b896" />
46
46
  </svg>
47
47
  </div>
48
48
 
49
- <div class="env-lacre" id="seal"><span class="seal-heart" aria-hidden="true">&#9825;</span></div>
49
+ <div class="env-seal" id="seal"><span class="seal-heart" aria-hidden="true">&#9825;</span></div>
50
50
  </div>
51
51
  </div>
52
52
 
@@ -72,12 +72,11 @@
72
72
  </div>
73
73
 
74
74
  <nav class="letter-nav" id="letter-nav">
75
- <a id="nav-previous" class="nav-button invisible" href="#">&#8592; <p data-i18n="PREVIOUS"></p></a>
75
+ <a id="nav-previous" class="nav-button invisible" href="#">&#8592; <p>Previous</p></a>
76
76
  <span class="nav-counter" id="letter-counter"></span>
77
- <a id="nav-next" class="nav-button invisible" href="#"><p data-i18n="NEXT"></p> &#8594;</a>
77
+ <a id="nav-next" class="nav-button invisible" href="#"><p>Next</p> &#8594;</a>
78
78
  </nav>
79
-
80
- <script type="module" src="../scripts/letters.script.js"></script>
81
- <script type="module" src="../scripts/i18n.js"></script>
82
- <script type="module" src="../scripts/data.variable.js"></script>
79
+
80
+ <script type="module" src="../scripts/lettersPage.js"></script>
81
+ <script type="module" src="../scripts/templateBinding.js"></script>
83
82
  </html>
package/vite.config.mjs CHANGED
@@ -9,7 +9,7 @@ export default defineConfig({
9
9
  rollupOptions: {
10
10
  input: {
11
11
  main: resolve(import.meta.dirname, 'index.html'),
12
- letter: resolve(import.meta.dirname, 'src/views/letters.view.html')
12
+ letter: resolve(import.meta.dirname, 'src/views/letters.html')
13
13
  }
14
14
  }
15
15
  }
@@ -1,28 +0,0 @@
1
- export const letters = [
2
- {
3
- id: 1,
4
- date: 'Aug 28, 2026',
5
- title: 'Custom Letter Title 1',
6
- content: `My Dearest Mary Jane,
7
- As I sit down to write these words, I am thinking of you and how grateful I am for everything we experience together. Life moves quickly, but whenever I pause to reflect on what brings me the most joy, you are always at the top of my mind.
8
- You bring so much inspiration and comfort into my life. Whether we are sharing long conversations or simply enjoying quiet moments together, being with you feels natural and right. Your support and kindness mean more to me than I can easily put into words.
9
- I wanted to remind you today of how deeply appreciated and loved you are. No matter where life takes us, know that you hold a very special place in my heart.
10
-
11
- Yours always,
12
- Peter Parker`
13
- },
14
- {
15
- id: 2,
16
- date: 'Aug 28, 2026',
17
- title: 'Custom Letter Title 2',
18
- content: `Dearest Mary Jane,
19
-
20
- I hope this letter finds you well. I wanted to take a quiet moment today to write to you and express just how much you mean to me. Even in the midst of everyday life, my thoughts frequently return to you and the wonderful moments we share.
21
- Your presence brings warmth, kindness, and light into my world. Every memory with you is something I truly cherish, and I am constantly reminded of how fortunate I am to have you in my life. You have a way of making ordinary days feel special just by being yourself.
22
- Thank you for your affection, your understanding, and all the happiness you bring into my life. I look forward to creating many more beautiful memories together in the time ahead.
23
-
24
-
25
- With all my love,
26
- Peter Parker`
27
- }
28
- ];
@@ -1,7 +0,0 @@
1
- const modules = import.meta.glob("./letters*.js", {
2
- eager: true,
3
- import: "letters",
4
- });
5
-
6
- export const letters =
7
- modules["./letters.js"] ?? modules["./letters.example.js"];
package/src/i18n/de.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "Zum Öffnen tippen",
3
- RETURN: "Zurück",
4
- PREVIOUS: "Vorherige",
5
- NEXT: "Nächste",
6
- OF: "von",
7
- LETTER_NOT_FOUND: "Brief nicht gefunden."
8
- }
package/src/i18n/en.js DELETED
@@ -1,8 +0,0 @@
1
- export default{
2
- TAP_TO_OPEN: "Tap to open",
3
- RETURN: "Return",
4
- PREVIOUS: "Previous",
5
- NEXT: "Next",
6
- OF: "of",
7
- LETTER_NOT_FOUND: "Letter not found."
8
- }
package/src/i18n/es.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "Toca para abrir",
3
- RETURN: "Volver",
4
- PREVIOUS: "Anterior",
5
- NEXT: "Siguiente",
6
- OF: "de",
7
- LETTER_NOT_FOUND: "Carta no encontrada."
8
- }
package/src/i18n/fr.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "Touchez pour ouvrir",
3
- RETURN: "Retour",
4
- PREVIOUS: "Précédente",
5
- NEXT: "Suivante",
6
- OF: "sur",
7
- LETTER_NOT_FOUND: "Lettre introuvable."
8
- }
package/src/i18n/it.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "Tocca per aprire",
3
- RETURN: "Indietro",
4
- PREVIOUS: "Precedente",
5
- NEXT: "Successiva",
6
- OF: "di",
7
- LETTER_NOT_FOUND: "Lettera non trovata."
8
- }
package/src/i18n/ja.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "タップして開く",
3
- RETURN: "戻る",
4
- PREVIOUS: "前へ",
5
- NEXT: "次へ",
6
- OF: "の",
7
- LETTER_NOT_FOUND: "手紙が見つかりません。"
8
- }
package/src/i18n/ko.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "탭하여 열기",
3
- RETURN: "돌아가기",
4
- PREVIOUS: "이전",
5
- NEXT: "다음",
6
- OF: "중",
7
- LETTER_NOT_FOUND: "편지를 찾을 수 없습니다."
8
- }
package/src/i18n/nl.js DELETED
File without changes
package/src/i18n/pr.js DELETED
File without changes
package/src/i18n/pt-BR.js DELETED
@@ -1,8 +0,0 @@
1
- export default{
2
- TAP_TO_OPEN: "Toque para abrir",
3
- RETURN: "Voltar",
4
- PREVIOUS: "Anterior",
5
- NEXT: "Próxima",
6
- OF: "de",
7
- LETTER_NOT_FOUND: "Carta não encontrada."
8
- }
package/src/i18n/ru.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "Нажмите, чтобы открыть",
3
- RETURN: "Назад",
4
- PREVIOUS: "Предыдущая",
5
- NEXT: "Следующая",
6
- OF: "из",
7
- LETTER_NOT_FOUND: "Письмо не найдено."
8
- }
package/src/i18n/tl.js DELETED
File without changes
package/src/i18n/zh.js DELETED
@@ -1,8 +0,0 @@
1
- export default {
2
- TAP_TO_OPEN: "点击打开",
3
- RETURN: "返回",
4
- PREVIOUS: "上一封",
5
- NEXT: "下一封",
6
- OF: "共",
7
- LETTER_NOT_FOUND: "未找到信件。"
8
- }
@@ -1,67 +0,0 @@
1
- import globalVariables from "../config/globalVariables.js";
2
-
3
- import en from "../i18n/en.js";
4
- import ptbr from "../i18n/pt-BR.js";
5
- import es from "../i18n/es.js";
6
- import fr from "../i18n/fr.js";
7
- import de from "../i18n/de.js";
8
- import it from "../i18n/it.js";
9
- import ja from "../i18n/ja.js";
10
- import ko from "../i18n/ko.js";
11
- import zh from "../i18n/zh.js";
12
- import ru from "../i18n/ru.js";
13
-
14
- // Language files are selected by the configured LANGUAGE value.
15
- // Missing translation keys fall back to English, then to the key name.
16
- const translations = {
17
- en,
18
- "pt-BR": ptbr,
19
- es,
20
- fr,
21
- de,
22
- it,
23
- ja,
24
- ko,
25
- zh,
26
- ru
27
- };
28
-
29
- const getCurrentLanguage = () => globalVariables?.LANGUAGE || "en";
30
-
31
- export const getTranslation = (key, ...args) => {
32
- const language = translations[getCurrentLanguage()] || translations.en;
33
-
34
- const translation =
35
- language?.[key] ??
36
- translations.en?.[key] ??
37
- key;
38
-
39
- // Support translated functions such as PROGRESS(current, total)
40
- return typeof translation === "function"
41
- ? translation(...args)
42
- : translation;
43
- };
44
-
45
- export const translateElement = (element) => {
46
- const key = element.dataset.i18n;
47
-
48
- if (key) {
49
- element.textContent = getTranslation(key);
50
- }
51
- };
52
-
53
- document
54
- .querySelectorAll("[data-i18n]")
55
- .forEach(translateElement);
56
-
57
- export const refreshTranslations = () => {
58
- document
59
- .querySelectorAll("[data-i18n]")
60
- .forEach(translateElement);
61
- };
62
-
63
- window.i18n = {
64
- getTranslation,
65
- translateElement,
66
- refreshTranslations
67
- };
File without changes