refactor(water-log): tone down report preview modal to production-appropriate GlassModal
Deploy to route.crispygoat.com / deploy (push) Successful in 3m55s

Drops the overdesigned 'Field Bulletin' treatment (perforated tear-strip,
crop marks, rubber stamp, paper grain, side stats, signature) in favor of
the shared GlassModal chrome with a clean data table, meta line, and a
plain-text copy action. Matches the rest of the admin UI.
This commit is contained in:
Tyler
2026-07-02 12:16:04 -06:00
parent 060520b7be
commit 137b5e7ffe
+138 -643
View File
@@ -22,7 +22,7 @@
* `src/actions/water-log/admin.ts`.
*/
import { useEffect, useMemo, useReducer, useRef, useState } from "react";
import { useEffect, useMemo, useReducer, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
@@ -49,6 +49,7 @@ import {
} from "@/lib/water-log-reporting";
import { WaterGauge, SeasonMark } from "@/components/water/icons";
import AdminButton from "./design-system/AdminButton";
import GlassModal from "@/components/admin/GlassModal";
import { formatDate, formatDateTime } from "@/lib/format-date";
const DEFAULT_SEASON: IrrigationSeasonSettings = {
@@ -76,7 +77,6 @@ type ReportPreviewData = {
dateIso: string;
recipientName?: string;
inSeason: boolean;
bulletinNumber: number;
};
type State = {
@@ -493,13 +493,6 @@ export default function WaterLogAdminPanel({
const unit =
rows.find((r) => r.unit)?.unit ?? state.headgates[0]?.unit ?? "CFS";
const now = new Date();
// Deterministic-ish bulletin number from today's date + entry count so
// it changes per-day but feels like a real reference code.
const dayKey =
state.today.replace(/-/g, "") ||
`${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}`;
const bulletinNumber =
(parseInt(dayKey, 10) % 1000) + (state.entries.length % 7) * 13 + 1;
if (rows.length === 0) {
notify(
@@ -518,7 +511,6 @@ export default function WaterLogAdminPanel({
dateIso: state.today || now.toISOString().slice(0, 10),
recipientName: state.recipientName.trim() || undefined,
inSeason,
bulletinNumber,
},
});
}
@@ -1832,12 +1824,10 @@ function DayInput({
// ─── Report Preview Modal ─────────────────────────────────────────────────
//
// A "Field Bulletin" — modeled after USDA Soil Conservation Service / USGS
// survey bulletins. Cream paper, deep forest ink, monospaced data with
// hairline rules, perforated tear-strip on the left, and a rotated
// PREVIEW rubber stamp to keep it clearly distinct from the actual
// deliverable. The composition is a single tactile sheet that opens with
// a soft paper-curl motion and staggers each entry row in.
// Simple, production-appropriate preview of the daily water report that
// would be sent by the cron. Built on the shared GlassModal so ESC, focus
// trapping, scroll-lock, and the standard admin header/close chrome all
// match the rest of /admin.
function ReportPreviewModal({
data,
@@ -1848,30 +1838,11 @@ function ReportPreviewModal({
onClose: () => void;
notify: (msg: string, kind?: "ok" | "err") => void;
}) {
const dialogRef = useRef<HTMLDivElement>(null);
const [copied, setCopied] = useState(false);
// ESC closes; lock body scroll while open.
useEffect(() => {
if (!data) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", onKey);
const prevOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
// Move focus into the dialog for keyboard users.
const t = setTimeout(() => dialogRef.current?.focus(), 30);
return () => {
document.removeEventListener("keydown", onKey);
document.body.style.overflow = prevOverflow;
clearTimeout(t);
};
}, [data, onClose]);
if (!data) return null;
const { rows, total, unit, dateLabel, dateIso, recipientName, inSeason, bulletinNumber } = data;
const { rows, total, unit, dateLabel, dateIso, recipientName, inSeason } = data;
const totalUnit = rows[0]?.unit ?? unit ?? "CFS";
async function handleCopy() {
@@ -1890,560 +1861,159 @@ function ReportPreviewModal({
}
}
// Earliest entry time → "shift open", latest → "shift close" — a nice
// touch pulled straight from irrigation logbook conventions.
const times = rows
.map((r) => new Date(r.logged_at))
.filter((d) => !Number.isNaN(d.getTime()));
const firstTime = times.length
? times.reduce((a, b) => (a < b ? a : b))
: null;
const lastTime = times.length
? times.reduce((a, b) => (a > b ? a : b))
: null;
const fmtTime = (d: Date | null) =>
d
? d.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
})
: "—";
// Most active headgate (max sum of measurement) — useful color piece.
const byHeadgate = new Map<string, number>();
for (const r of rows) {
byHeadgate.set(r.headgate_name, (byHeadgate.get(r.headgate_name) ?? 0) + r.measurement);
}
const topHeadgate = Array.from(byHeadgate.entries()).sort((a, b) => b[1] - a[1])[0];
const subtitle =
`${rows.length} ${rows.length === 1 ? "entry" : "entries"} · ` +
`${total.toFixed(2)} ${totalUnit} total · ` +
(inSeason ? "in season" : "out of season");
return (
<div
role="dialog"
aria-modal="true"
aria-label="Daily water report preview"
className="wl-modal-backdrop fixed inset-0 z-50 flex items-start justify-center overflow-y-auto px-4 py-8 sm:py-14"
onMouseDown={(e) => {
// Click on the backdrop (not the paper) closes.
if (e.target === e.currentTarget) onClose();
}}
<GlassModal
title="Daily water report — preview"
subtitle={subtitle}
onClose={onClose}
maxWidth="max-w-2xl"
>
{/* Backdrop wash — deep forest tint with subtle radial vignette. */}
<div
aria-hidden
className="wl-modal-backdrop__wash fixed inset-0"
style={{
background:
"radial-gradient(120% 80% at 50% 30%, rgba(26,77,46,0.42) 0%, rgba(15,42,28,0.72) 60%, rgba(8,28,18,0.82) 100%)",
backdropFilter: "blur(3px) saturate(0.9)",
WebkitBackdropFilter: "blur(3px) saturate(0.9)",
}}
/>
{/* The paper itself */}
<div
ref={dialogRef}
tabIndex={-1}
className="wl-modal-paper relative z-10 w-full max-w-2xl outline-none"
style={{
// soft drop shadow + warm paper gradient
background:
"linear-gradient(180deg, #fdfaf2 0%, #f7f1de 100%)",
boxShadow:
"0 1px 0 rgba(255,255,255,0.6) inset, 0 30px 60px -20px rgba(15,42,28,0.45), 0 18px 32px -22px rgba(15,42,28,0.6)",
border: "1px solid #1a4d2e",
}}
>
{/* Paper grain overlay — extremely subtle */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 opacity-[0.10] mix-blend-multiply"
style={{
backgroundImage:
"radial-gradient(circle at 25% 15%, rgba(122,90,38,0.22) 0, transparent 35%), radial-gradient(circle at 80% 80%, rgba(26,77,46,0.18) 0, transparent 40%)",
}}
/>
{/* Perforated left tear-strip — like a printed form */}
<div
aria-hidden
className="wl-modal-perf absolute left-0 top-0 h-full w-3"
style={{
backgroundImage:
"radial-gradient(circle, #fdfaf2 1.2px, transparent 1.6px)",
backgroundSize: "8px 12px",
backgroundPosition: "2px 6px",
opacity: 0.55,
maskImage:
"linear-gradient(180deg, transparent 0, black 6%, black 94%, transparent 100%)",
WebkitMaskImage:
"linear-gradient(180deg, transparent 0, black 6%, black 94%, transparent 100%)",
}}
/>
{/* Crop marks at the four corners — printer's registration marks */}
<CropMark className="absolute left-3 top-3" rotate={0} />
<CropMark className="absolute right-3 top-3" rotate={90} />
<CropMark className="absolute right-3 bottom-3" rotate={180} />
<CropMark className="absolute left-3 bottom-3" rotate={270} />
{/* Header band — forest ink strip with cream mono caps */}
<div
className="relative flex items-stretch border-b border-[#1a4d2e]"
style={{ background: "#1a4d2e", color: "#fdfaf2" }}
>
<div className="flex flex-1 flex-col justify-center gap-0.5 px-6 py-3">
<div
className="text-[10px] uppercase"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.32em",
}}
>
Tuxedo Ditch Co. · Water Log
</div>
<div
className="text-[11px] uppercase opacity-80"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.24em",
}}
>
Daily Field Bulletin
</div>
</div>
<div
className="flex flex-col items-end justify-center border-l border-[#fdfaf2]/30 px-5 py-3 text-right"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.18em",
}}
>
<div className="text-[10px] uppercase opacity-80">Bulletin </div>
<div className="text-[15px] font-medium tracking-[0.18em]">
{String(bulletinNumber).padStart(4, "0")}
</div>
</div>
</div>
{/* Title row + PREVIEW stamp */}
<div className="relative px-6 pb-2 pt-5 sm:px-8 sm:pt-6">
<h2
className="leading-[0.95] tracking-tight text-[#1a4d2e]"
style={{
fontFamily: "var(--font-display, Georgia, serif)",
fontWeight: 600,
fontSize: "clamp(34px, 5.4vw, 48px)",
letterSpacing: "-0.02em",
}}
>
Daily Water Report
</h2>
<p
className="mt-1 text-[12px] uppercase tracking-[0.24em] text-[#57694e]"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
>
<div className="space-y-4">
{/* Meta line */}
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[var(--admin-text-muted)]">
<span>
<span className="font-semibold text-[var(--admin-text-secondary)]">Date:</span>{" "}
{dateLabel}
<span className="mx-2 text-[#d4d9d3]">/</span>
<span className="text-[#1a4d2e]">
{dateIso.replace(/-/g, " · ")}
</span>
</p>
{/* Rotated rubber stamp — PREVIEW */}
<div
aria-hidden
className="wl-modal-stamp pointer-events-none absolute right-4 top-4 select-none sm:right-8 sm:top-5"
style={{
transform: "rotate(-9deg)",
color: "#b1452a",
fontFamily: "var(--font-display, Georgia, serif)",
fontWeight: 800,
letterSpacing: "0.18em",
border: "2.5px double currentColor",
padding: "4px 12px 5px",
borderRadius: "3px",
opacity: 0.78,
boxShadow: "0 0 0 2px rgba(177,69,42,0.05)",
fontSize: "16px",
}}
>
PREVIEW
<span
className="ml-2 text-[10px] tracking-[0.3em]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
fontWeight: 500,
opacity: 0.85,
}}
>
ONLY
</span>
</div>
</div>
{/* Meta strip — date / season / total / count */}
<div className="relative grid grid-cols-2 gap-px border-y border-[#1a4d2e] bg-[#1a4d2e] sm:grid-cols-4">
<MetaCell label="Date" value={dateLabel} mono />
<MetaCell
label="Season"
value={inSeason ? "In season" : "Out of season"}
accent={inSeason ? "green" : "amber"}
/>
<MetaCell
label="Total volume"
value={`${total.toFixed(2)} ${totalUnit}`}
mono
emphasis
/>
<MetaCell
label="Entries"
value={`${rows.length} ${rows.length === 1 ? "log" : "logs"}`}
mono
/>
</div>
{/* Salutation */}
<div className="relative px-6 pt-5 sm:px-8">
</span>
<span className="hidden sm:inline text-[var(--admin-border)]">·</span>
<span className="font-mono text-[var(--admin-text-secondary)]">
{dateIso}
</span>
{recipientName ? (
<p
className="text-[#1a4d2e]"
style={{
fontFamily: "var(--font-display, Georgia, serif)",
fontStyle: "italic",
fontSize: "20px",
lineHeight: 1.25,
}}
>
{recipientName},
</p>
) : (
<p
className="text-[#57694e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
fontSize: "11px",
letterSpacing: "0.22em",
textTransform: "uppercase",
}}
>
To the irrigator
</p>
)}
<p
className="mt-2 text-[13px] leading-relaxed text-[#3a4a36]"
style={{ fontFamily: "var(--font-manrope, system-ui, sans-serif)" }}
>
Below is the day&apos;s record of measured flow at every headgate
in service. Times are local; volumes are reported in the unit
declared for each gate.{" "}
<span className="text-[#a16207]">
This bulletin is a preview no message has been sent.
</span>
</p>
<>
<span className="hidden sm:inline text-[var(--admin-border)]">·</span>
<span>
<span className="font-semibold text-[var(--admin-text-secondary)]">
Recipient:
</span>{" "}
{recipientName}
</span>
</>
) : null}
</div>
{/* Preview banner — keep it clear this hasn't been sent */}
<div
className="rounded-lg px-3 py-2 text-xs"
style={{
backgroundColor: "var(--admin-warning-bg, #fef9c3)",
color: "var(--admin-warning-text, #a16207)",
}}
>
Preview only. No message has been sent. Daily-report delivery is
handled by the scheduled{" "}
<code className="font-mono">api/cron/water-report</code> endpoint.
</div>
{/* Data table */}
<div className="relative mt-5 px-6 sm:px-8">
<div className="overflow-hidden rounded-sm border border-[#1a4d2e]/40 bg-[#fffaf0]/60">
<table className="w-full border-collapse">
<thead>
<tr
className="border-b border-[#1a4d2e]/50"
style={{
background:
"linear-gradient(180deg, rgba(26,77,46,0.08) 0%, rgba(26,77,46,0.02) 100%)",
}}
<div className="overflow-hidden rounded-lg border border-[var(--admin-border)]">
<table className="w-full border-collapse text-sm">
<thead>
<tr style={{ backgroundColor: "var(--admin-bg-subtle)" }}>
<Th className="w-[72px]">Time</Th>
<Th>Headgate</Th>
<Th>Irrigator</Th>
<Th className="w-[88px] text-right">Volume</Th>
<Th>Note</Th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => {
const time = new Date(row.logged_at).toLocaleTimeString(
"en-US",
{ hour: "numeric", minute: "2-digit" },
);
return (
<tr
key={i}
className="border-t border-[var(--admin-border-light)]"
>
<Td className="font-mono text-[var(--admin-text-secondary)]">
{time}
</Td>
<Td className="font-medium text-[var(--admin-text-primary)]">
{row.headgate_name}
</Td>
<Td className="text-[var(--admin-text-secondary)]">
{row.user_name}
</Td>
<Td className="text-right">
<span className="font-mono font-semibold text-[var(--admin-text-primary)]">
{row.measurement.toFixed(2)}
</span>{" "}
<span className="text-xs text-[var(--admin-text-muted)]">
{row.unit}
</span>
</Td>
<Td className="text-[var(--admin-text-muted)]">
{row.notes ?? <span className="opacity-40"></span>}
</Td>
</tr>
);
})}
</tbody>
<tfoot>
<tr
className="border-t-2"
style={{ borderColor: "var(--admin-border)" }}
>
<Td
className="!border-0 pt-2 text-xs font-semibold uppercase tracking-wide text-[var(--admin-text-secondary)]"
colSpan={3}
>
<Th className="w-[68px]">Time</Th>
<Th>Headgate</Th>
<Th>Irrigator</Th>
<Th className="w-[86px] text-right">Volume</Th>
<Th className="w-[28%]">Note</Th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => {
const time = new Date(row.logged_at).toLocaleTimeString(
"en-US",
{ hour: "numeric", minute: "2-digit" },
);
const isTop =
topHeadgate &&
row.headgate_name === topHeadgate[0] &&
row.measurement === topHeadgate[1];
return (
<tr
key={i}
className="wl-modal-row border-b border-[#1a4d2e]/15 transition-colors last:border-b-0 hover:bg-[#fdfaf2]"
style={{
animationDelay: `${120 + i * 35}ms`,
}}
>
<Td className="text-[#3a4a36]">{time}</Td>
<Td>
<span
className="inline-flex items-center gap-1.5 text-[#1a4d2e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
}}
>
<span
aria-hidden
className="inline-block h-1.5 w-1.5 rounded-full"
style={{
background: isTop ? "#ca8a04" : "#1a4d2e",
opacity: isTop ? 1 : 0.55,
}}
/>
{row.headgate_name}
</span>
</Td>
<Td className="text-[#3a4a36]">{row.user_name}</Td>
<Td className="text-right">
<span
className="text-[#1a4d2e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
}}
>
{row.measurement.toFixed(2)}
</span>
<span
className="ml-1 text-[10px] text-[#57694e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.08em",
}}
>
{row.unit}
</span>
</Td>
<Td className="text-[#57694e]">
{row.notes ? (
<span
className="italic"
style={{
fontFamily:
"var(--font-display, Georgia, serif)",
}}
>
&ldquo;{row.notes}&rdquo;
</span>
) : (
<span className="text-[#d4d9d3]">·</span>
)}
</Td>
</tr>
);
})}
</tbody>
<tfoot>
<tr className="border-t-2 border-[#1a4d2e]">
<Td className="!border-0 pt-2 text-[11px] uppercase tracking-[0.22em] text-[#57694e]">
Day total
</Td>
<Td className="!border-0 pt-2">
<span className="text-[#d4d9d3]">·</span>
</Td>
<Td className="!border-0 pt-2 text-right text-[11px] uppercase tracking-[0.22em] text-[#57694e]">
Σ
</Td>
<Td className="!border-0 pt-2 text-right">
<span
className="text-[#1a4d2e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
fontSize: "16px",
fontWeight: 600,
letterSpacing: "0.02em",
}}
>
{total.toFixed(2)}
</span>
<span
className="ml-1 text-[11px] text-[#57694e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.08em",
}}
>
{totalUnit}
</span>
</Td>
<Td className="!border-0 pt-2 text-right text-[11px] text-[#57694e]">
{rows.length} logs
</Td>
</tr>
</tfoot>
</table>
</div>
Total
</Td>
<Td className="!border-0 pt-2 text-right">
<span className="font-mono font-bold text-[var(--admin-text-primary)]">
{total.toFixed(2)}
</span>{" "}
<span className="text-xs text-[var(--admin-text-muted)]">
{totalUnit}
</span>
</Td>
<Td className="!border-0 pt-2">
<span className="opacity-40"></span>
</Td>
</tr>
</tfoot>
</table>
</div>
{/* Side stats — first/last entry, top headgate */}
<div className="relative grid grid-cols-3 gap-px border-y border-[#1a4d2e]/40 bg-[#1a4d2e]/10 px-6 sm:px-8 mt-5">
<SideStat label="First entry" value={fmtTime(firstTime)} />
<SideStat label="Last entry" value={fmtTime(lastTime)} />
<SideStat
label="Top gate"
value={topHeadgate ? topHeadgate[0] : "—"}
sub={
topHeadgate ? `${topHeadgate[1].toFixed(2)} ${totalUnit}` : ""
}
/>
</div>
{/* Footer signature */}
<div className="relative px-6 pb-6 pt-4 text-center sm:px-8">
<div className="mx-auto mb-3 h-px w-24 bg-[#1a4d2e]/40" />
<p
className="text-[10px] uppercase tracking-[0.36em] text-[#57694e]"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
>
End of transmission
</p>
<p
className="mt-2 text-[11px] text-[#57694e]"
style={{
fontFamily: "var(--font-display, Georgia, serif)",
fontStyle: "italic",
}}
>
Filed by Route Commerce Water Log
</p>
</div>
{/* Action bar — sticky to the bottom of the paper */}
<div
className="relative flex flex-col-reverse items-stretch gap-2 border-t border-[#1a4d2e] px-5 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-6"
style={{ background: "rgba(26,77,46,0.04)" }}
>
<button
type="button"
{/* Actions */}
<div className="flex flex-col-reverse items-stretch gap-2 pt-2 sm:flex-row sm:items-center sm:justify-end">
<button type="button"
onClick={onClose}
className="rounded-sm border border-[#1a4d2e]/40 px-4 py-2 text-[11px] uppercase tracking-[0.24em] text-[#57694e] transition-colors hover:border-[#1a4d2e] hover:text-[#1a4d2e]"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
className="rounded-lg border border-[var(--admin-border)] px-4 py-2 text-sm font-semibold text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)] transition-colors"
>
Close · Esc
Close
</button>
<button
type="button"
<button type="button"
onClick={handleCopy}
className="group inline-flex items-center justify-center gap-2 rounded-sm border border-[#1a4d2e] bg-[#1a4d2e] px-4 py-2 text-[11px] uppercase tracking-[0.24em] text-[#fdfaf2] transition-all hover:bg-[#163f25] active:translate-y-px"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
className="rounded-lg bg-[var(--admin-accent)] px-4 py-2 text-sm font-bold text-white hover:bg-[var(--admin-accent-hover)] transition-colors"
>
{copied ? (
<>
<CheckMark /> Copied
</>
) : (
<>
<CopyMark /> Copy plain text
</>
)}
{copied ? "Copied" : "Copy plain text"}
</button>
</div>
</div>
{/* Component-local animations */}
<style>{`
@keyframes wl-modal-pop {
0% { opacity: 0; transform: translateY(14px) scale(0.97); }
60% { opacity: 1; transform: translateY(-2px) scale(1.005); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes wl-modal-fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes wl-modal-row {
0% { opacity: 0; transform: translateX(-6px); }
100% { opacity: 1; transform: translateX(0); }
}
@keyframes wl-modal-stamp {
0% { opacity: 0; transform: rotate(-9deg) scale(1.4); }
60% { opacity: 0.78; transform: rotate(-9deg) scale(0.96); }
100% { opacity: 0.78; transform: rotate(-9deg) scale(1); }
}
.wl-modal-backdrop { animation: wl-modal-fade 200ms ease-out both; }
.wl-modal-paper {
animation: wl-modal-pop 320ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.wl-modal-row {
animation: wl-modal-row 360ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
opacity: 0;
}
.wl-modal-stamp { animation: wl-modal-stamp 480ms ease-out 220ms both; }
@media (prefers-reduced-motion: reduce) {
.wl-modal-backdrop,
.wl-modal-paper,
.wl-modal-row,
.wl-modal-stamp {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
`}</style>
</div>
</GlassModal>
);
}
function MetaCell({
label,
value,
mono = false,
emphasis = false,
accent,
function Th({
children,
className = "",
}: {
label: string;
value: string;
mono?: boolean;
emphasis?: boolean;
accent?: "green" | "amber";
children: React.ReactNode;
className?: string;
}) {
const accentColor =
accent === "green"
? "#1a4d2e"
: accent === "amber"
? "#a16207"
: undefined;
return (
<div className="flex flex-col gap-0.5 bg-[#fdfaf2] px-4 py-2.5">
<span
className="text-[9px] uppercase tracking-[0.28em] text-[#57694e]"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
>
{label}
</span>
<span
className={
emphasis
? "text-[16px] text-[#1a4d2e]"
: mono
? "text-[13px] text-[#1a4d2e]"
: "text-[13px] text-[#1a4d2e]"
}
style={{
fontFamily: mono
? "var(--font-fragment-mono, monospace)"
: "var(--font-manrope, system-ui, sans-serif)",
fontWeight: emphasis ? 600 : 500,
letterSpacing: mono ? "0.02em" : undefined,
color: accentColor,
}}
>
{value}
</span>
</div>
);
}
function Th({ children, className = "" }: { children: React.ReactNode; className?: string }) {
return (
<th
className={`px-3 py-2 text-left text-[10px] uppercase tracking-[0.22em] text-[#1a4d2e] ${className}`}
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
className={`px-3 py-2 text-left text-[11px] font-semibold uppercase tracking-wide text-[var(--admin-text-secondary)] ${className}`}
>
{children}
</th>
@@ -2453,96 +2023,21 @@ function Th({ children, className = "" }: { children: React.ReactNode; className
function Td({
children,
className = "",
colSpan,
}: {
children: React.ReactNode;
className?: string;
colSpan?: number;
}) {
return (
<td
className={`px-3 py-2 text-[13px] text-[#1d1d1f] ${className}`}
style={{
fontFamily: "var(--font-manrope, system-ui, sans-serif)",
verticalAlign: "top",
}}
colSpan={colSpan}
className={`px-3 py-2 text-sm text-[var(--admin-text-primary)] ${className}`}
>
{children}
</td>
);
}
function SideStat({
label,
value,
sub,
}: {
label: string;
value: string;
sub?: string;
}) {
return (
<div className="flex flex-col gap-0.5 bg-[#fdfaf2] px-4 py-2.5">
<span
className="text-[9px] uppercase tracking-[0.28em] text-[#57694e]"
style={{ fontFamily: "var(--font-fragment-mono, monospace)" }}
>
{label}
</span>
<span
className="text-[14px] text-[#1a4d2e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.02em",
}}
>
{value}
</span>
{sub ? (
<span
className="text-[10px] text-[#57694e]"
style={{
fontFamily: "var(--font-fragment-mono, monospace)",
letterSpacing: "0.06em",
}}
>
{sub}
</span>
) : null}
</div>
);
}
function CropMark({ className = "", rotate = 0 }: { className?: string; rotate?: number }) {
return (
<svg
aria-hidden
width="10"
height="10"
viewBox="0 0 10 10"
className={className}
style={{ transform: `rotate(${rotate}deg)`, opacity: 0.5 }}
>
<path d="M0 5 L10 5 M5 0 L5 10" stroke="#1a4d2e" strokeWidth="0.6" />
</svg>
);
}
function CopyMark() {
return (
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4">
<rect x="5" y="5" width="9" height="9" rx="1" />
<path d="M11 5 V3 a1 1 0 0 0 -1 -1 H3 a1 1 0 0 0 -1 1 V10 a1 1 0 0 0 1 1 H5" />
</svg>
);
}
function CheckMark() {
return (
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6">
<path d="M3 8 L7 12 L13 4" />
</svg>
);
}
// ─── Icons (1620px, currentColor) ──────────────────────────────────────
function QrIcon() {