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;
}
/* ── 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) ── */
.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);
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-accent2 { color: #ccff00; }
.card-theme-thug .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6600); }
.card-theme-thug .card-muted { color: rgba(255,255,255,0.45); }
.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-accent { color: #ff0040 !important; }
.card-theme-thug .card-accent2 { color: #ccff00 !important; }
.card-theme-thug .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6600) !important; }
.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.25) !important; border: 1px solid rgba(255,0,64,0.5) !important; }
/* ── Theme: NEON 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);
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-accent2 { color: #ff6600; }
.card-theme-lime .card-stripe { background: linear-gradient(90deg, #ccff00, #88ff00); }
.card-theme-lime .card-muted { color: rgba(255,255,255,0.45); }
.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-accent { color: #ccff00 !important; }
.card-theme-lime .card-accent2 { color: #ff6600 !important; }
.card-theme-lime .card-stripe { background: linear-gradient(90deg, #ccff00, #88ff00) !important; }
.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.2) !important; border: 1px solid rgba(204,255,0,0.5) !important; }
/* ── Theme: CHROME (silver/steel) ── */
.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);
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-accent2 { color: #ff6600; }
.card-theme-chrome .card-stripe { background: linear-gradient(90deg, #888, #ccc, #888); }
.card-theme-chrome .card-muted { color: rgba(232,232,232,0.45); }
.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-accent { color: #c8c8c8 !important; }
.card-theme-chrome .card-accent2 { color: #ff6600 !important; }
.card-theme-chrome .card-stripe { background: linear-gradient(90deg, #888, #ccc, #888) !important; }
.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.15) !important; border: 1px solid rgba(200,200,200,0.3) !important; }
/* ── Theme: WOODGRAIN ── */
.card-theme-wood {
background:
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%);
background: linear-gradient(160deg, #3d2008 0%, #5c3210 30%, #2e1804 60%, #4a2a0e 100%) !important;
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);
color: #f5e6c8;
color: #f5e6c8 !important;
}
.card-theme-wood .card-accent { color: #f5c842; }
.card-theme-wood .card-accent2 { color: #ff6600; }
.card-theme-wood .card-stripe { background: linear-gradient(90deg, #8b5e3c, #d4a05a, #8b5e3c); }
.card-theme-wood .card-muted { color: rgba(245,230,200,0.5); }
.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-accent { color: #f5c842 !important; }
.card-theme-wood .card-accent2 { color: #ff6600 !important; }
.card-theme-wood .card-stripe { background: linear-gradient(90deg, #8b5e3c, #d4a05a, #8b5e3c) !important; }
.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.3) !important; border: 1px solid rgba(160,100,40,0.6) !important; }
/* ── Theme: MIDNIGHT BLUE ── */
.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);
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-accent2 { color: #ccff00; }
.card-theme-midnight .card-stripe { background: linear-gradient(90deg, #3060cc, #64a0ff); }
.card-theme-midnight .card-muted { color: rgba(208,228,255,0.45); }
.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-accent { color: #64a0ff !important; }
.card-theme-midnight .card-accent2 { color: #ccff00 !important; }
.card-theme-midnight .card-stripe { background: linear-gradient(90deg, #3060cc, #64a0ff) !important; }
.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.15) !important; border: 1px solid rgba(100,160,255,0.4) !important; }
/* ── Theme: SUNSET (orange/pink) ── */
.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);
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-accent2 { color: #ff0080; }
.card-theme-sunset .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6020, #ffaa00); }
.card-theme-sunset .card-muted { color: rgba(255,208,160,0.5); }
.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-accent { color: #ff6020 !important; }
.card-theme-sunset .card-accent2 { color: #ff0080 !important; }
.card-theme-sunset .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6020, #ffaa00) !important; }
.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.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 {
display: none;
display: none !important;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
/* ── Print styles ── */
@media print {
/* Hide absolutely everything */
* {
visibility: hidden !important;
/* Step 1: hide the entire page */
html, body {
background: white !important;
}
/* Show only the print-only card and all its children */
.print-only-card,
.print-only-card * {
visibility: visible !important;
body > * {
display: none !important;
}
/* Position the card at top-left of the page */
/* Step 2: show only the print-only-card container */
.print-only-card {
display: block !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 85.6mm !important;
height: 54mm !important;
margin: 0 !important;
padding: 0 !important;
z-index: 99999 !important;
overflow: hidden !important;
}
/* Reset page to card size */
@page {
size: 85.6mm 54mm;
margin: 0;
}
/* Card at exact print dimensions */
/* Step 3: size the card to exact credit-card dimensions */
.print-only-card .rider-card {
width: 85.6mm !important;
height: 54mm !important;
border-radius: 4mm !important;
page-break-inside: avoid;
box-shadow: none !important;
overflow: hidden !important;
}
/* Reset page margins to zero */
@page {
size: 85.6mm 54mm;
margin: 0;
}
/* Kill holographic overlay on print */
.rider-card-holo::after {
display: none !important;
visibility: hidden !important;
}
/* Kill all animations */
/* Kill animations */
*, *::before, *::after {
animation: none !important;
transition: none !important;

View file

@ -875,10 +875,21 @@ export default function RiderCardPage() {
{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">
<Info className="h-4 w-4 text-blue-400 flex-shrink-0 mt-0.5" />
<div>
<span className="font-bold text-blue-400">Park owners only.</span>{' '}
Park Access Cards should only be issued by the park's Nostr key.
For private parks this is enforced on-chain — anyone can verify the issuer matches the park owner.
<div className="space-y-1.5">
<div>
<span className="font-bold text-blue-400">Park owners only.</span>{' '}
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>
)}
@ -1058,11 +1069,21 @@ export default function RiderCardPage() {
placeholder="e.g. 2026-12-31" className="bg-black/50 border-gray-700" />
</div>
<div className="p-3 rounded-xl bg-[#ccff00]/5 border border-[#ccff00]/20 text-xs text-gray-400">
<div className="font-bold text-[#ccff00] mb-1">🔐 Nostr-verified access</div>
This card is published as a signed Nostr event from <em>your</em> key.
Anyone scanning the QR code can verify the card is genuine and was issued by
the park's Nostr identity.
<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]">🔐 How the park verifies it's real</div>
<div>
When you publish this card, it's signed with your Nostr private key and broadcast to relays.
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>
)}