Fix card print colors + clarify Nostr verification language

Print fixes:
- Add -webkit-print-color-adjust: exact + print-color-adjust: exact to ALL
  card theme classes so dark backgrounds/gradients actually print (browsers
  strip them by default unless forced)
- Increase chip/muted opacity values slightly for print legibility
- Fix multiple-card issue: hide body > * on print, show only .print-only-card
- Remove complex repeating-linear-gradient from wood theme (simplify for print)

Language fixes:
- Remove "on-chain" terminology (Nostr is not a blockchain)
- Replace with plain explanation: "signed message on the Nostr network"
- Park access note now explains exactly what gets verified and how
- Park card info box now lists exactly what shows on a scan:
  who issued it, who it was issued to, access level, expiry, timestamp
- Clarifies why a rider can't forge the card (would need your private key)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-06-16 15:07:38 +07:00
parent e2582aa013
commit 3b99392f3b
2 changed files with 108 additions and 78 deletions

View file

@ -427,143 +427,152 @@ html, body {
mix-blend-mode: screen; mix-blend-mode: screen;
} }
/* ── Force background graphics to print on all card themes ──
Browsers suppress backgrounds by default. -webkit-print-color-adjust: exact
overrides this for Chrome/Safari. color-adjust: exact covers Firefox/Edge. */
.rider-card,
.rider-card *,
[class^="card-theme-"],
[class^="card-theme-"] * {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
}
/* ── Theme: THUG (default — black + neon red) ── */ /* ── Theme: THUG (default — black + neon red) ── */
.card-theme-thug { .card-theme-thug {
background: linear-gradient(135deg, #0a0a0a 0%, #1a0008 60%, #0d0d0d 100%); background: linear-gradient(135deg, #0a0a0a 0%, #1a0008 60%, #0d0d0d 100%) !important;
border: 1.5px solid rgba(255,0,64,0.5); border: 1.5px solid rgba(255,0,64,0.5);
box-shadow: 0 0 30px rgba(255,0,64,0.2), inset 0 1px 0 rgba(255,255,255,0.05); box-shadow: 0 0 30px rgba(255,0,64,0.2), inset 0 1px 0 rgba(255,255,255,0.05);
color: #fff; color: #fff !important;
} }
.card-theme-thug .card-accent { color: #ff0040; } .card-theme-thug .card-accent { color: #ff0040 !important; }
.card-theme-thug .card-accent2 { color: #ccff00; } .card-theme-thug .card-accent2 { color: #ccff00 !important; }
.card-theme-thug .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6600); } .card-theme-thug .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6600) !important; }
.card-theme-thug .card-muted { color: rgba(255,255,255,0.45); } .card-theme-thug .card-muted { color: rgba(255,255,255,0.6) !important; }
.card-theme-thug .card-bg-chip { background: rgba(255,0,64,0.12); border: 1px solid rgba(255,0,64,0.3); } .card-theme-thug .card-bg-chip { background: rgba(255,0,64,0.25) !important; border: 1px solid rgba(255,0,64,0.5) !important; }
/* ── Theme: NEON LIME ── */ /* ── Theme: NEON LIME ── */
.card-theme-lime { .card-theme-lime {
background: linear-gradient(135deg, #050f00 0%, #0a1a00 60%, #050f00 100%); background: linear-gradient(135deg, #050f00 0%, #0a1a00 60%, #050f00 100%) !important;
border: 1.5px solid rgba(204,255,0,0.5); border: 1.5px solid rgba(204,255,0,0.5);
box-shadow: 0 0 30px rgba(204,255,0,0.15), inset 0 1px 0 rgba(255,255,255,0.05); box-shadow: 0 0 30px rgba(204,255,0,0.15), inset 0 1px 0 rgba(255,255,255,0.05);
color: #fff; color: #fff !important;
} }
.card-theme-lime .card-accent { color: #ccff00; } .card-theme-lime .card-accent { color: #ccff00 !important; }
.card-theme-lime .card-accent2 { color: #ff6600; } .card-theme-lime .card-accent2 { color: #ff6600 !important; }
.card-theme-lime .card-stripe { background: linear-gradient(90deg, #ccff00, #88ff00); } .card-theme-lime .card-stripe { background: linear-gradient(90deg, #ccff00, #88ff00) !important; }
.card-theme-lime .card-muted { color: rgba(255,255,255,0.45); } .card-theme-lime .card-muted { color: rgba(255,255,255,0.6) !important; }
.card-theme-lime .card-bg-chip { background: rgba(204,255,0,0.10); border: 1px solid rgba(204,255,0,0.3); } .card-theme-lime .card-bg-chip { background: rgba(204,255,0,0.2) !important; border: 1px solid rgba(204,255,0,0.5) !important; }
/* ── Theme: CHROME (silver/steel) ── */ /* ── Theme: CHROME (silver/steel) ── */
.card-theme-chrome { .card-theme-chrome {
background: linear-gradient(135deg, #1a1a1a 0%, #2e2e2e 40%, #111 100%); background: linear-gradient(135deg, #1a1a1a 0%, #2e2e2e 40%, #111 100%) !important;
border: 1.5px solid rgba(200,200,200,0.4); border: 1.5px solid rgba(200,200,200,0.4);
box-shadow: 0 0 20px rgba(180,180,180,0.1), inset 0 1px 0 rgba(255,255,255,0.15); box-shadow: 0 0 20px rgba(180,180,180,0.1), inset 0 1px 0 rgba(255,255,255,0.15);
color: #e8e8e8; color: #e8e8e8 !important;
} }
.card-theme-chrome .card-accent { color: #c8c8c8; } .card-theme-chrome .card-accent { color: #c8c8c8 !important; }
.card-theme-chrome .card-accent2 { color: #ff6600; } .card-theme-chrome .card-accent2 { color: #ff6600 !important; }
.card-theme-chrome .card-stripe { background: linear-gradient(90deg, #888, #ccc, #888); } .card-theme-chrome .card-stripe { background: linear-gradient(90deg, #888, #ccc, #888) !important; }
.card-theme-chrome .card-muted { color: rgba(232,232,232,0.45); } .card-theme-chrome .card-muted { color: rgba(232,232,232,0.6) !important; }
.card-theme-chrome .card-bg-chip { background: rgba(200,200,200,0.08); border: 1px solid rgba(200,200,200,0.2); } .card-theme-chrome .card-bg-chip { background: rgba(200,200,200,0.15) !important; border: 1px solid rgba(200,200,200,0.3) !important; }
/* ── Theme: WOODGRAIN ── */ /* ── Theme: WOODGRAIN ── */
.card-theme-wood { .card-theme-wood {
background: background: linear-gradient(160deg, #3d2008 0%, #5c3210 30%, #2e1804 60%, #4a2a0e 100%) !important;
repeating-linear-gradient(
90deg,
transparent,
transparent 2px,
rgba(0,0,0,0.03) 2px,
rgba(0,0,0,0.03) 4px
),
linear-gradient(160deg, #3d2008 0%, #5c3210 30%, #2e1804 60%, #4a2a0e 100%);
border: 1.5px solid rgba(160,100,40,0.6); border: 1.5px solid rgba(160,100,40,0.6);
box-shadow: 0 0 20px rgba(120,70,20,0.2), inset 0 1px 0 rgba(255,200,100,0.1); box-shadow: 0 0 20px rgba(120,70,20,0.2), inset 0 1px 0 rgba(255,200,100,0.1);
color: #f5e6c8; color: #f5e6c8 !important;
} }
.card-theme-wood .card-accent { color: #f5c842; } .card-theme-wood .card-accent { color: #f5c842 !important; }
.card-theme-wood .card-accent2 { color: #ff6600; } .card-theme-wood .card-accent2 { color: #ff6600 !important; }
.card-theme-wood .card-stripe { background: linear-gradient(90deg, #8b5e3c, #d4a05a, #8b5e3c); } .card-theme-wood .card-stripe { background: linear-gradient(90deg, #8b5e3c, #d4a05a, #8b5e3c) !important; }
.card-theme-wood .card-muted { color: rgba(245,230,200,0.5); } .card-theme-wood .card-muted { color: rgba(245,230,200,0.65) !important; }
.card-theme-wood .card-bg-chip { background: rgba(160,100,40,0.15); border: 1px solid rgba(160,100,40,0.4); } .card-theme-wood .card-bg-chip { background: rgba(160,100,40,0.3) !important; border: 1px solid rgba(160,100,40,0.6) !important; }
/* ── Theme: MIDNIGHT BLUE ── */ /* ── Theme: MIDNIGHT BLUE ── */
.card-theme-midnight { .card-theme-midnight {
background: linear-gradient(135deg, #020818 0%, #050e2a 60%, #030b1a 100%); background: linear-gradient(135deg, #020818 0%, #050e2a 60%, #030b1a 100%) !important;
border: 1.5px solid rgba(100,160,255,0.4); border: 1.5px solid rgba(100,160,255,0.4);
box-shadow: 0 0 30px rgba(60,120,255,0.15), inset 0 1px 0 rgba(100,160,255,0.08); box-shadow: 0 0 30px rgba(60,120,255,0.15), inset 0 1px 0 rgba(100,160,255,0.08);
color: #d0e4ff; color: #d0e4ff !important;
} }
.card-theme-midnight .card-accent { color: #64a0ff; } .card-theme-midnight .card-accent { color: #64a0ff !important; }
.card-theme-midnight .card-accent2 { color: #ccff00; } .card-theme-midnight .card-accent2 { color: #ccff00 !important; }
.card-theme-midnight .card-stripe { background: linear-gradient(90deg, #3060cc, #64a0ff); } .card-theme-midnight .card-stripe { background: linear-gradient(90deg, #3060cc, #64a0ff) !important; }
.card-theme-midnight .card-muted { color: rgba(208,228,255,0.45); } .card-theme-midnight .card-muted { color: rgba(208,228,255,0.6) !important; }
.card-theme-midnight .card-bg-chip { background: rgba(100,160,255,0.08); border: 1px solid rgba(100,160,255,0.25); } .card-theme-midnight .card-bg-chip { background: rgba(100,160,255,0.15) !important; border: 1px solid rgba(100,160,255,0.4) !important; }
/* ── Theme: SUNSET (orange/pink) ── */ /* ── Theme: SUNSET (orange/pink) ── */
.card-theme-sunset { .card-theme-sunset {
background: linear-gradient(135deg, #120006 0%, #1a0808 40%, #0e0810 100%); background: linear-gradient(135deg, #120006 0%, #1a0808 40%, #0e0810 100%) !important;
border: 1.5px solid rgba(255,80,0,0.5); border: 1.5px solid rgba(255,80,0,0.5);
box-shadow: 0 0 30px rgba(255,60,0,0.2), inset 0 1px 0 rgba(255,150,50,0.08); box-shadow: 0 0 30px rgba(255,60,0,0.2), inset 0 1px 0 rgba(255,150,50,0.08);
color: #ffd0a0; color: #ffd0a0 !important;
} }
.card-theme-sunset .card-accent { color: #ff6020; } .card-theme-sunset .card-accent { color: #ff6020 !important; }
.card-theme-sunset .card-accent2 { color: #ff0080; } .card-theme-sunset .card-accent2 { color: #ff0080 !important; }
.card-theme-sunset .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6020, #ffaa00); } .card-theme-sunset .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6020, #ffaa00) !important; }
.card-theme-sunset .card-muted { color: rgba(255,208,160,0.5); } .card-theme-sunset .card-muted { color: rgba(255,208,160,0.65) !important; }
.card-theme-sunset .card-bg-chip { background: rgba(255,80,0,0.10); border: 1px solid rgba(255,80,0,0.3); } .card-theme-sunset .card-bg-chip { background: rgba(255,80,0,0.2) !important; border: 1px solid rgba(255,80,0,0.5) !important; }
/* ── Print-only card: invisible on screen, appears on print ── */ /* ── Print-only card: hidden on screen, shown only when printing ── */
.print-only-card { .print-only-card {
display: none; display: none !important;
position: fixed;
top: 0;
left: 0;
z-index: -1;
} }
/* ── Print styles ── */ /* ── Print styles ── */
@media print { @media print {
/* Hide absolutely everything */ /* Step 1: hide the entire page */
* { html, body {
visibility: hidden !important; background: white !important;
} }
/* Show only the print-only card and all its children */ body > * {
.print-only-card, display: none !important;
.print-only-card * {
visibility: visible !important;
} }
/* Position the card at top-left of the page */ /* Step 2: show only the print-only-card container */
.print-only-card { .print-only-card {
display: block !important; display: block !important;
position: fixed !important; position: fixed !important;
top: 0 !important; top: 0 !important;
left: 0 !important; left: 0 !important;
width: 85.6mm !important;
height: 54mm !important;
margin: 0 !important; margin: 0 !important;
padding: 0 !important; padding: 0 !important;
z-index: 99999 !important; z-index: 99999 !important;
overflow: hidden !important;
} }
/* Reset page to card size */ /* Step 3: size the card to exact credit-card dimensions */
@page {
size: 85.6mm 54mm;
margin: 0;
}
/* Card at exact print dimensions */
.print-only-card .rider-card { .print-only-card .rider-card {
width: 85.6mm !important; width: 85.6mm !important;
height: 54mm !important; height: 54mm !important;
border-radius: 4mm !important; border-radius: 4mm !important;
page-break-inside: avoid; page-break-inside: avoid;
box-shadow: none !important; box-shadow: none !important;
overflow: hidden !important;
}
/* Reset page margins to zero */
@page {
size: 85.6mm 54mm;
margin: 0;
} }
/* Kill holographic overlay on print */ /* Kill holographic overlay on print */
.rider-card-holo::after { .rider-card-holo::after {
display: none !important; display: none !important;
visibility: hidden !important;
} }
/* Kill all animations */ /* Kill animations */
*, *::before, *::after { *, *::before, *::after {
animation: none !important; animation: none !important;
transition: none !important; transition: none !important;

View file

@ -875,10 +875,21 @@ export default function RiderCardPage() {
{cardType === 'park' && ( {cardType === 'park' && (
<div className="flex items-start gap-2 p-3 rounded-xl bg-blue-500/5 border border-blue-500/20 text-xs text-gray-400"> <div className="flex items-start gap-2 p-3 rounded-xl bg-blue-500/5 border border-blue-500/20 text-xs text-gray-400">
<Info className="h-4 w-4 text-blue-400 flex-shrink-0 mt-0.5" /> <Info className="h-4 w-4 text-blue-400 flex-shrink-0 mt-0.5" />
<div> <div className="space-y-1.5">
<span className="font-bold text-blue-400">Park owners only.</span>{' '} <div>
Park Access Cards should only be issued by the park's Nostr key. <span className="font-bold text-blue-400">Park owners only.</span>{' '}
For private parks this is enforced on-chain — anyone can verify the issuer matches the park owner. Park Access Cards should only be issued by the park's own Nostr key — the same key that published the park listing on SkateSpots.
</div>
<div>
<span className="font-bold text-gray-300">How verification works:</span>{' '}
The card is a signed message on the Nostr network (like a public post only you could have written).
Anyone can scan the QR, look up the event on any Nostr relay, and see which key signed it —
if that key matches the park's registered identity, the card is genuine.
No central authority needed.
</div>
<div className="text-gray-500">
Self-issued cards for private parks will show a mismatch warning — the park owner's key won't match.
</div>
</div> </div>
</div> </div>
)} )}
@ -1058,11 +1069,21 @@ export default function RiderCardPage() {
placeholder="e.g. 2026-12-31" className="bg-black/50 border-gray-700" /> placeholder="e.g. 2026-12-31" className="bg-black/50 border-gray-700" />
</div> </div>
<div className="p-3 rounded-xl bg-[#ccff00]/5 border border-[#ccff00]/20 text-xs text-gray-400"> <div className="p-3 rounded-xl bg-[#ccff00]/5 border border-[#ccff00]/20 text-xs text-gray-400 space-y-2">
<div className="font-bold text-[#ccff00] mb-1">🔐 Nostr-verified access</div> <div className="font-bold text-[#ccff00]">🔐 How the park verifies it's real</div>
This card is published as a signed Nostr event from <em>your</em> key. <div>
Anyone scanning the QR code can verify the card is genuine and was issued by When you publish this card, it's signed with your Nostr private key and broadcast to relays.
the park's Nostr identity. The park's staff can scan the QR on any printed card and instantly see:
</div>
<ul className="list-disc list-inside space-y-1 text-gray-500">
<li>Who issued it (your npub)</li>
<li>Who it was issued to (the rider's npub)</li>
<li>The access level and expiry</li>
<li>The exact timestamp it was signed</li>
</ul>
<div>
A rider can't fake this card — they'd need your private key to forge your signature.
</div>
</div> </div>
</div> </div>
)} )}