rogasper commited on
Commit
c665186
·
1 Parent(s): 81d2e69

feat: update Open Graph and Twitter Card metadata in index.html, introduce new image assets for social previews, and enhance image compression script for better compatibility. Maintain legacy image path for backward compatibility.

Browse files
apps/web/index.html CHANGED
@@ -8,9 +8,11 @@
8
  <meta property="og:title" content="Labas — AI-Powered Exam Practice Platform" />
9
  <meta property="og:description" content="Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." />
10
  <meta property="og:url" content="https://labas.rogasper.com/" />
 
 
11
  <meta property="og:type" content="website" />
12
- <meta property="og:image" content="https://labas.rogasper.com/og_image.png" />
13
- <meta property="og:image:secure_url" content="https://labas.rogasper.com/og_image.png" />
14
  <meta property="og:image:type" content="image/png" />
15
  <meta property="og:image:width" content="1200" />
16
  <meta property="og:image:height" content="630" />
@@ -18,8 +20,11 @@
18
  <meta name="twitter:card" content="summary_large_image" />
19
  <meta name="twitter:title" content="Labas — AI-Powered Exam Practice Platform" />
20
  <meta name="twitter:description" content="Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." />
21
- <meta name="twitter:image" content="https://labas.rogasper.com/og_image.png" />
22
  <meta name="twitter:image:alt" content="Labas — platform latihan ujian bahasa dengan AI Generator dan simulasi tes" />
 
 
 
23
  <meta name="robots" content="index, follow" />
24
  <link rel="canonical" href="https://labas.rogasper.com/" />
25
  <title>Labas — AI-Powered Exam Practice Platform</title>
 
8
  <meta property="og:title" content="Labas — AI-Powered Exam Practice Platform" />
9
  <meta property="og:description" content="Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." />
10
  <meta property="og:url" content="https://labas.rogasper.com/" />
11
+ <meta property="og:site_name" content="Labas" />
12
+ <meta property="og:locale" content="id_ID" />
13
  <meta property="og:type" content="website" />
14
+ <meta property="og:image" content="https://labas.rogasper.com/opengraph-image.png" />
15
+ <meta property="og:image:secure_url" content="https://labas.rogasper.com/opengraph-image.png" />
16
  <meta property="og:image:type" content="image/png" />
17
  <meta property="og:image:width" content="1200" />
18
  <meta property="og:image:height" content="630" />
 
20
  <meta name="twitter:card" content="summary_large_image" />
21
  <meta name="twitter:title" content="Labas — AI-Powered Exam Practice Platform" />
22
  <meta name="twitter:description" content="Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." />
23
+ <meta name="twitter:image" content="https://labas.rogasper.com/twitter-image.jpg" />
24
  <meta name="twitter:image:alt" content="Labas — platform latihan ujian bahasa dengan AI Generator dan simulasi tes" />
25
+ <meta name="twitter:image:type" content="image/jpeg" />
26
+ <meta name="twitter:image:width" content="1200" />
27
+ <meta name="twitter:image:height" content="630" />
28
  <meta name="robots" content="index, follow" />
29
  <link rel="canonical" href="https://labas.rogasper.com/" />
30
  <title>Labas — AI-Powered Exam Practice Platform</title>
apps/web/public/og_image.png CHANGED

Git LFS Details

  • SHA256: 485a28961923c2e52527589ae19876071c1ed6da648b919f7af0d9a9fd212720
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB

Git LFS Details

  • SHA256: 3b1a32858ed61cf3b80c66327de4a28cd8b651af5ca9b86fce2aa03ee45b22b5
  • Pointer size: 131 Bytes
  • Size of remote file: 470 kB
apps/web/public/opengraph-image.png ADDED

Git LFS Details

  • SHA256: 3b1a32858ed61cf3b80c66327de4a28cd8b651af5ca9b86fce2aa03ee45b22b5
  • Pointer size: 131 Bytes
  • Size of remote file: 470 kB
apps/web/public/twitter-image.jpg ADDED

Git LFS Details

  • SHA256: 245e2c9fe2776bc7119abca1673353f726d2b4f6aaff8eba6c0cc3c6d63ebc3f
  • Pointer size: 131 Bytes
  • Size of remote file: 145 kB
apps/web/src/lib/site-seo.ts CHANGED
@@ -1,7 +1,14 @@
1
  export const SITE_URL = "https://labas.rogasper.com";
 
 
 
 
 
 
 
2
 
3
- export const OG_IMAGE_PATH = "/og_image.png";
4
  export const OG_IMAGE_URL = `${SITE_URL}${OG_IMAGE_PATH}`;
 
5
  export const OG_IMAGE_WIDTH = 1200;
6
  export const OG_IMAGE_HEIGHT = 630;
7
  export const OG_IMAGE_ALT =
@@ -28,6 +35,8 @@ export function buildSocialMeta(input: SiteHeadInput = {}) {
28
  { property: "og:title", content: title },
29
  { property: "og:description", content: description },
30
  { property: "og:url", content: url },
 
 
31
  { property: "og:type", content: "website" },
32
  { property: "og:image", content: OG_IMAGE_URL },
33
  { property: "og:image:secure_url", content: OG_IMAGE_URL },
@@ -38,7 +47,10 @@ export function buildSocialMeta(input: SiteHeadInput = {}) {
38
  { name: "twitter:card", content: "summary_large_image" },
39
  { name: "twitter:title", content: title },
40
  { name: "twitter:description", content: description },
41
- { name: "twitter:image", content: OG_IMAGE_URL },
42
  { name: "twitter:image:alt", content: OG_IMAGE_ALT },
 
 
 
43
  ];
44
  }
 
1
  export const SITE_URL = "https://labas.rogasper.com";
2
+ export const SITE_NAME = "Labas";
3
+
4
+ /** Match rogasper.com: separate OG (PNG) + Twitter (JPEG) assets for crawler compatibility. */
5
+ export const OG_IMAGE_PATH = "/opengraph-image.png";
6
+ export const TWITTER_IMAGE_PATH = "/twitter-image.jpg";
7
+ /** @deprecated Use OG_IMAGE_PATH — kept for old links */
8
+ export const LEGACY_OG_IMAGE_PATH = "/og_image.png";
9
 
 
10
  export const OG_IMAGE_URL = `${SITE_URL}${OG_IMAGE_PATH}`;
11
+ export const TWITTER_IMAGE_URL = `${SITE_URL}${TWITTER_IMAGE_PATH}`;
12
  export const OG_IMAGE_WIDTH = 1200;
13
  export const OG_IMAGE_HEIGHT = 630;
14
  export const OG_IMAGE_ALT =
 
35
  { property: "og:title", content: title },
36
  { property: "og:description", content: description },
37
  { property: "og:url", content: url },
38
+ { property: "og:site_name", content: SITE_NAME },
39
+ { property: "og:locale", content: "id_ID" },
40
  { property: "og:type", content: "website" },
41
  { property: "og:image", content: OG_IMAGE_URL },
42
  { property: "og:image:secure_url", content: OG_IMAGE_URL },
 
47
  { name: "twitter:card", content: "summary_large_image" },
48
  { name: "twitter:title", content: title },
49
  { name: "twitter:description", content: description },
50
+ { name: "twitter:image", content: TWITTER_IMAGE_URL },
51
  { name: "twitter:image:alt", content: OG_IMAGE_ALT },
52
+ { name: "twitter:image:type", content: "image/jpeg" },
53
+ { name: "twitter:image:width", content: String(OG_IMAGE_WIDTH) },
54
+ { name: "twitter:image:height", content: String(OG_IMAGE_HEIGHT) },
55
  ];
56
  }
scripts/compress-og-image.ts CHANGED
@@ -1,19 +1,24 @@
1
  /**
2
- * Compress apps/web/public/og_image.png for social previews.
3
  * Requires Bun >= 1.3.14 (Bun.Image API).
4
  *
 
 
5
  * Usage: bun scripts/compress-og-image.ts
6
  */
7
 
8
  import { join } from "node:path";
9
 
10
  const ROOT = join(import.meta.dir, "..");
11
- const TARGET = join(ROOT, "apps/web/public/og_image.png");
 
 
 
12
  const WIDTH = 1200;
13
  const HEIGHT = 630;
14
 
15
- function requireBunImage() {
16
- const file = Bun.file(TARGET);
17
  if (typeof file.image !== "function") {
18
  console.error(
19
  "Bun.Image is not available. Upgrade Bun to >= 1.3.14:\n" +
@@ -29,27 +34,37 @@ function formatKb(bytes: number) {
29
  return `${(bytes / 1024).toFixed(1)} KB`;
30
  }
31
 
 
 
 
 
 
 
32
  async function main() {
33
- const before = (await Bun.file(TARGET).arrayBuffer()).byteLength;
34
- console.log(`Source: ${TARGET}`);
35
- console.log(`Before: ${formatKb(before)} (${WIDTH}×${HEIGHT} target)`);
36
-
37
- const pipeline = requireBunImage()
38
- .resize(WIDTH, HEIGHT, { fit: "fill", filter: "lanczos3" })
39
- .png({
40
- compressionLevel: 9,
41
- palette: true,
42
- colors: 256,
43
- dither: true,
44
- });
45
-
46
- await pipeline.write(TARGET);
47
-
48
- const after = (await Bun.file(TARGET).arrayBuffer()).byteLength;
49
- const meta = await Bun.file(TARGET).image().metadata();
50
-
51
- console.log(`After: ${formatKb(after)} (${meta.width}×${meta.height}, ${meta.format})`);
52
- console.log(`Saved: ${formatKb(before - after)} (${(((before - after) / before) * 100).toFixed(1)}%)`);
 
 
 
 
53
  }
54
 
55
  main().catch((err) => {
 
1
  /**
2
+ * Build social preview images for Labas (Open Graph + Twitter).
3
  * Requires Bun >= 1.3.14 (Bun.Image API).
4
  *
5
+ * Outputs truecolor PNG + JPEG — palette PNG breaks some Twitter/Threads crawlers.
6
+ *
7
  * Usage: bun scripts/compress-og-image.ts
8
  */
9
 
10
  import { join } from "node:path";
11
 
12
  const ROOT = join(import.meta.dir, "..");
13
+ const PUBLIC = join(ROOT, "apps/web/public");
14
+ const SOURCE = join(PUBLIC, "og_image.png");
15
+ const OPENGRAPH = join(PUBLIC, "opengraph-image.png");
16
+ const TWITTER = join(PUBLIC, "twitter-image.jpg");
17
  const WIDTH = 1200;
18
  const HEIGHT = 630;
19
 
20
+ function requireBunImage(path: string) {
21
+ const file = Bun.file(path);
22
  if (typeof file.image !== "function") {
23
  console.error(
24
  "Bun.Image is not available. Upgrade Bun to >= 1.3.14:\n" +
 
34
  return `${(bytes / 1024).toFixed(1)} KB`;
35
  }
36
 
37
+ async function writeMeta(label: string, path: string) {
38
+ const bytes = (await Bun.file(path).arrayBuffer()).byteLength;
39
+ const meta = await Bun.file(path).image().metadata();
40
+ console.log(` ${label}: ${formatKb(bytes)} (${meta.width}×${meta.height}, ${meta.format})`);
41
+ }
42
+
43
  async function main() {
44
+ const before = (await Bun.file(SOURCE).arrayBuffer()).byteLength;
45
+ console.log(`Source: ${SOURCE}`);
46
+ console.log(`Before: ${formatKb(before)}`);
47
+
48
+ const base = requireBunImage(SOURCE).resize(WIDTH, HEIGHT, {
49
+ fit: "fill",
50
+ filter: "lanczos3",
51
+ });
52
+
53
+ // Truecolor PNG — compatible with Meta/Twitter crawlers (no 8-bit palette).
54
+ await base.png({ compressionLevel: 9, palette: false }).write(OPENGRAPH);
55
+
56
+ // JPEG for twitter:image — matches rogasper.com pattern, very reliable on X/Threads.
57
+ await requireBunImage(OPENGRAPH)
58
+ .jpeg({ quality: 88, mozjpeg: true })
59
+ .write(TWITTER);
60
+
61
+ // Keep legacy filename in sync with the Open Graph asset.
62
+ await Bun.write(SOURCE, Bun.file(OPENGRAPH));
63
+
64
+ console.log("Wrote:");
65
+ await writeMeta("opengraph-image.png", OPENGRAPH);
66
+ await writeMeta("twitter-image.jpg", TWITTER);
67
+ await writeMeta("og_image.png (legacy)", SOURCE);
68
  }
69
 
70
  main().catch((err) => {