Add whale logo, favicon, apple-touch-icon, and social embeds

Logo: hand-crafted SVG whale silhouette in blue gradient (#3b82f6 to
#1d4ed8), DeepSeek-inspired minimalist style with water spout, eye,
and wave arc. Renders at 28px in the nav header.

Embeds: Open Graph and Twitter Card meta tags on every page. og:image
is a 1200x630 embed card with the whale logo, WhalePrep title, tagline,
and stats. Favicon.png (32x32) and apple-touch-icon.png (180x180)
generated from the same SVG. theme-color #0a0f1c for mobile browsers.
This commit is contained in:
dev 2026-10-16 00:53:46 +05:30
parent 51a01cc2cf
commit b1b6887a26
7 changed files with 156 additions and 5 deletions

View file

@ -88,12 +88,27 @@ pub struct SubjectBlock {
fn head(ctx: &Ctx, title: &str) -> String { fn head(ctx: &Ctx, title: &str) -> String {
format!( format!(
r#"<!doctype html><html lang="en"><head><meta charset="utf-8"> r##"<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>{t} · WhalePrep</title><link rel="stylesheet" href="{b}/static/style.css"> <title>{t} · WhalePrep</title>
<link rel="icon" type="image/png" href="{b}/static/favicon.png">
<link rel="apple-touch-icon" href="{b}/static/apple-touch-icon.png">
<link rel="stylesheet" href="{b}/static/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
<meta property="og:type" content="website">
<meta property="og:site_name" content="WhalePrep">
<meta property="og:title" content="{t} · WhalePrep">
<meta property="og:description" content="Practice JEE Main previous year questions on a timed exam screen. 9,813 PYQs from 2002-2025 across 53 official NTA syllabus units.">
<meta property="og:image" content="{origin}/static/embed.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{t} · WhalePrep">
<meta name="twitter:description" content="Practice JEE Main previous year questions on a timed exam screen. 9,813 PYQs from 2002-2025.">
<meta name="twitter:image" content="{origin}/static/embed.png">
<meta name="theme-color" content="#0a0f1c">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function() {{ <script>document.addEventListener("DOMContentLoaded", function() {{
@ -102,15 +117,23 @@ fn head(ctx: &Ctx, title: &str) -> String {
trust: false, strict: false, throwOnError: false trust: false, strict: false, throwOnError: false
}}); }});
}});</script> }});</script>
</head><body>"#, </head><body>"##,
t = esc(title), t = esc(title),
b = ctx.base b = ctx.base,
origin = public_origin()
) )
} }
/// The absolute origin for og:image URLs — meta tags need a full URL, not a
/// relative path. Derived from the request when behind the tunnel; falls
/// back to the production domain.
fn public_origin() -> String {
std::env::var("PUBLIC_ORIGIN").unwrap_or_else(|_| "https://whaleprep.oiupoyt.space".into())
}
fn nav(ctx: &Ctx, user: Option<&str>) -> String { fn nav(ctx: &Ctx, user: Option<&str>) -> String {
let brand = format!( let brand = format!(
r##"<header class="topbar"><div class="brand-group"><a class="brand" href="{b}/"><svg class="brand-logo" viewBox="0 0 24 24" width="22" height="22"><path fill="#60a5fa" d="M12 2L1 21h22L12 2zm0 4.5l7.5 13H4.5L12 6.5z"/></svg> WhalePrep</a></div><nav class="nav">"##, r##"<header class="topbar"><div class="brand-group"><a class="brand" href="{b}/"><img class="brand-logo" src="{b}/static/logo.svg" alt="WhalePrep logo" width="28" height="28"> WhalePrep</a></div><nav class="nav">"##,
b = ctx.base b = ctx.base
); );
let links = match user { let links = match user {

BIN
static/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
static/embed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

70
static/embed.svg Normal file
View file

@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3b82f6"/>
<stop offset="1" stop-color="#1d4ed8"/>
</linearGradient>
</defs>
<!-- Dark background -->
<rect width="1200" height="630" rx="0" fill="#0a0f1c"/>
<!-- Subtle glow behind whale -->
<ellipse cx="600" cy="300" rx="250" ry="200" fill="#1d4ed8" opacity="0.08"/>
<!-- Whale body -->
<g transform="translate(120, 130) scale(0.72)">
<path fill="url(#g)" d="
M 100 320
C 100 220 190 120 320 120
C 370 120 410 140 435 175
C 448 192 452 210 450 225
C 448 238 438 244 426 240
C 400 230 360 228 320 240
C 270 256 230 290 215 330
L 210 345
C 207 352 200 355 194 351
L 120 310
C 108 303 98 305 94 314
C 91 320 93 327 100 320
Z"/>
<!-- Tail -->
<path fill="url(#g)" d="
M 435 175
L 475 95
C 480 84 494 84 497 96
L 512 135
C 515 145 508 155 497 153
L 448 138
L 485 170
C 492 176 490 188 481 191
L 450 200
Z"/>
<!-- Eye -->
<circle cx="300" cy="190" r="16" fill="#fff"/>
<!-- Spout -->
<circle cx="290" cy="95" r="10" fill="url(#g)" opacity="0.9"/>
<circle cx="315" cy="70" r="7" fill="url(#g)" opacity="0.7"/>
<circle cx="335" cy="50" r="5" fill="url(#g)" opacity="0.5"/>
<!-- Smile -->
<path d="M 270 240 Q 290 255 315 242" stroke="#fff" stroke-width="8" fill="none" stroke-linecap="round" opacity="0.3"/>
<!-- Wave -->
<path d="M 40 430 Q 128 400 216 430 T 392 430" stroke="#3b82f6" stroke-width="16" fill="none" stroke-linecap="round" opacity="0.3"/>
</g>
<!-- WhalePrep text -->
<text x="640" y="280" font-family="system-ui, -apple-system, sans-serif" font-size="72" font-weight="700" fill="#f8fafc" letter-spacing="-2">
WhalePrep
</text>
<text x="642" y="330" font-family="system-ui, -apple-system, sans-serif" font-size="28" font-weight="500" fill="#94a3b8">
JEE Main practice, built like the real exam
</text>
<text x="642" y="375" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="500" fill="#38bdf8">
9,813 PYQs · 2002-2025 · +4/−1 marking
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,005 B

51
static/logo.svg Normal file
View file

@ -0,0 +1,51 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3b82f6"/>
<stop offset="1" stop-color="#1d4ed8"/>
</linearGradient>
</defs>
<!-- Whale body: rounded paisley shape with the tail curving up-right -->
<path fill="url(#g)" d="
M 100 320
C 100 220 190 120 320 120
C 370 120 410 140 435 175
C 448 192 452 210 450 225
C 448 238 438 244 426 240
C 400 230 360 228 320 240
C 270 256 230 290 215 330
L 210 345
C 207 352 200 355 194 351
L 120 310
C 108 303 98 305 94 314
C 91 320 93 327 100 320
Z"/>
<!-- Tail fluke: two pointed triangles -->
<path fill="url(#g)" d="
M 435 175
L 475 95
C 480 84 494 84 497 96
L 512 135
C 515 145 508 155 497 153
L 448 138
L 485 170
C 492 176 490 188 481 191
L 450 200
Z"/>
<!-- Eye -->
<circle cx="300" cy="190" r="16" fill="#fff"/>
<!-- Water spout -->
<circle cx="290" cy="95" r="10" fill="url(#g)" opacity="0.9"/>
<circle cx="315" cy="70" r="7" fill="url(#g)" opacity="0.7"/>
<circle cx="335" cy="50" r="5" fill="url(#g)" opacity="0.5"/>
<!-- Smile curve -->
<path d="M 270 240 Q 290 255 315 242" stroke="#fff" stroke-width="8" fill="none" stroke-linecap="round" opacity="0.3"/>
<!-- Wave -->
<path d="M 40 430 Q 128 400 216 430 T 392 430" stroke="#3b82f6" stroke-width="16" fill="none" stroke-linecap="round" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -2216,3 +2216,10 @@ button.pal-fab {
.zp-subtopic-row:has(input:checked) .zp-subtopic-title { .zp-subtopic-row:has(input:checked) .zp-subtopic-title {
color: #60a5fa; color: #60a5fa;
} }
/* ===== brand logo ===== */
img.brand-logo {
display: inline-block;
vertical-align: middle;
filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.3));
}