init
This commit is contained in:
BIN
app/favicon.ico
BIN
app/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -20,6 +20,7 @@
|
||||
--color-sand: var(--color-sand);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-display: var(--font-space-grotesk);
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -27,3 +28,87 @@ body {
|
||||
color: var(--color-fg);
|
||||
font-family: var(--font-sans), Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Ambient accent glow behind everything — modern dark-UI depth. */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(60rem 40rem at 15% -10%, color-mix(in oklab, var(--color-accent) 14%, transparent), transparent 70%),
|
||||
radial-gradient(50rem 40rem at 100% 0%, color-mix(in oklab, var(--color-secondary) 45%, transparent), transparent 65%);
|
||||
}
|
||||
|
||||
/* Display font for all headings. */
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: var(--font-display), var(--font-sans), sans-serif;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
/* Tabular figures for stats so numbers line up. */
|
||||
.tabular-nums {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Confine Leaflet's internal panes (z-index 400–1000) to their own stacking
|
||||
context so the map never overlaps the sticky navbar. */
|
||||
.leaflet-container {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Unified button system. In @layer components so Tailwind utility overrides
|
||||
(e.g. px-8, text-xs, w-full) still win over these base styles. */
|
||||
@layer components {
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: transform 0.12s ease, background-color 0.15s ease,
|
||||
border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease,
|
||||
filter 0.15s ease;
|
||||
}
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 18px -6px color-mix(in oklab, var(--color-accent) 70%, transparent);
|
||||
}
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
filter: brightness(1.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.btn-primary:active:not(:disabled) {
|
||||
filter: brightness(0.96);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: color-mix(in oklab, var(--color-surface) 55%, transparent);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: color-mix(in oklab, var(--color-fg) 88%, transparent);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
border-color: color-mix(in oklab, var(--color-accent) 55%, transparent);
|
||||
color: var(--color-accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 450 B |
@@ -1,36 +1,31 @@
|
||||
import { signIn } from "@/auth";
|
||||
import { Wordmark } from "@/components/wordmark";
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center gap-10">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="KNUR"
|
||||
width={160}
|
||||
height={160}
|
||||
className="rounded-3xl shadow-2xl ring-2 ring-surface"
|
||||
/>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-fg">KNUR</h1>
|
||||
<p className="text-sm text-fg/50">
|
||||
Książka Notowań Udźwigów i Rezultatów
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex min-h-screen flex-col items-center justify-center px-4">
|
||||
<div className="flex w-full max-w-sm flex-col items-center gap-7 rounded-2xl border border-white/10 bg-surface/55 p-8 shadow-lg shadow-black/10 backdrop-blur-md">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/logo-bars.svg" alt="" width={51} height={48} className="h-12 w-auto" />
|
||||
<Wordmark className="text-3xl" />
|
||||
<p className="text-center text-sm text-fg/50">
|
||||
Książka Notowań Udźwigów i Rezultatów
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form
|
||||
action={async () => {
|
||||
"use server";
|
||||
await signIn("keycloak", { redirectTo: "/" });
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-lg bg-accent px-8 py-3 text-sm font-semibold text-white transition-opacity hover:opacity-90"
|
||||
<form
|
||||
className="w-full"
|
||||
action={async () => {
|
||||
"use server";
|
||||
await signIn("keycloak", { redirectTo: "/" });
|
||||
}}
|
||||
>
|
||||
Zaloguj
|
||||
</button>
|
||||
</form>
|
||||
<button type="submit" className="btn btn-primary w-full px-8 py-3 text-base">
|
||||
Zaloguj
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ function buildGrid(maxDistKm: number): number[] {
|
||||
function RouteMap({ activity }: { activity: RunningActivity }) {
|
||||
const routePoints = activity.routePoints;
|
||||
return (
|
||||
<div className="col-span-2 row-span-3 min-h-[240px] overflow-hidden rounded-lg border border-muted/40">
|
||||
<div className="col-span-2 row-span-3 min-h-[240px] overflow-hidden rounded-2xl border border-white/10">
|
||||
{routePoints && routePoints.length > 0 ? (
|
||||
<RouteMapSection points={routePoints} />
|
||||
) : (
|
||||
|
||||
@@ -34,7 +34,7 @@ export default async function RunningPage() {
|
||||
<li key={activity._id.toString()}>
|
||||
<Link
|
||||
href={`/running/${activity._id.toString()}`}
|
||||
className="flex items-center justify-between rounded-lg border border-muted/40 bg-surface p-4 transition-colors hover:border-accent/60"
|
||||
className="flex items-center justify-between rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4 transition-colors hover:border-accent/60"
|
||||
>
|
||||
<div>
|
||||
<div className="font-semibold text-fg">{activity.name}</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function GarminCredentialsForm({ savedEmail }: { savedEmail: string | nul
|
||||
defaultValue={savedEmail ?? ""}
|
||||
placeholder="twoj@email.com"
|
||||
required
|
||||
className="rounded-md border border-muted/40 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent/60 focus:outline-none"
|
||||
className="rounded-md border border-white/10 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent/60 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
@@ -35,7 +35,7 @@ export function GarminCredentialsForm({ savedEmail }: { savedEmail: string | nul
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
required
|
||||
className="rounded-md border border-muted/40 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent/60 focus:outline-none"
|
||||
className="rounded-md border border-white/10 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent/60 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export function GarminCredentialsForm({ savedEmail }: { savedEmail: string | nul
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="self-start rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary self-start"
|
||||
>
|
||||
{pending ? "Zapisywanie…" : "Zapisz"}
|
||||
</button>
|
||||
|
||||
@@ -15,7 +15,7 @@ function StatusRow({ label, ok, okLabel = "Skonfigurowano", failLabel = "Brak ko
|
||||
failLabel?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="flex items-center justify-between rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<span className="text-fg">{label}</span>
|
||||
{ok ? (
|
||||
<span className="flex items-center gap-1.5 text-sm text-fg/70">
|
||||
@@ -67,14 +67,14 @@ export default async function SettingsPage() {
|
||||
|
||||
<section className="flex flex-col gap-3">
|
||||
<h2 className="text-lg font-semibold text-fg">Konto Garmin Connect</h2>
|
||||
<div className="rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<GarminCredentialsForm savedEmail={garminCreds?.email ?? null} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-3">
|
||||
<h2 className="text-lg font-semibold text-fg">Synchronizacja z Garmin</h2>
|
||||
<div className="flex items-center justify-between rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="flex items-center justify-between rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<span className="text-sm text-fg/70">
|
||||
{lastSyncAt
|
||||
? `Ostatnia synchronizacja: ${formatDate(lastSyncAt)}`
|
||||
|
||||
@@ -55,7 +55,7 @@ export default async function StrengthWorkoutPage({
|
||||
{exercisesWithHistory.map(({ exercise }, index) => {
|
||||
const e1rm = exerciseE1rm(exercise);
|
||||
return (
|
||||
<div key={index} className="flex flex-col gap-2 rounded-lg border border-muted/40 bg-surface px-3 py-2.5">
|
||||
<div key={index} className="flex flex-col gap-2 rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md px-3 py-2.5">
|
||||
<p className="text-xs font-semibold text-fg">{exercise.name}</p>
|
||||
{exercise.notes ? <p className="text-xs text-fg/50 italic">{exercise.notes}</p> : null}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
|
||||
@@ -18,12 +18,12 @@ export function ImportForm() {
|
||||
rows={16}
|
||||
required
|
||||
placeholder={"Trening A\nWednesday, 10 June 2026 at 06:40\n\nDeadlift (Barbell)\nSet 1: 80 kg × 8\n..."}
|
||||
className="w-full rounded-md border border-muted/40 bg-surface p-3 font-mono text-sm text-fg placeholder:text-fg/30 focus:border-accent focus:outline-none"
|
||||
className="w-full rounded-md border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-3 font-mono text-sm text-fg placeholder:text-fg/30 focus:border-accent focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="self-start rounded-md bg-accent px-4 py-2 text-sm font-semibold text-fg transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary self-start"
|
||||
>
|
||||
{pending ? "Importowanie..." : "Importuj"}
|
||||
</button>
|
||||
|
||||
@@ -31,7 +31,7 @@ export default async function StrengthPage() {
|
||||
</div>
|
||||
<Link
|
||||
href="/strength/import"
|
||||
className="flex items-center gap-1.5 rounded-md bg-accent px-3 py-2 text-sm font-semibold text-fg transition-opacity hover:opacity-90"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Importuj
|
||||
@@ -52,7 +52,7 @@ export default async function StrengthPage() {
|
||||
<li key={workout._id.toString()}>
|
||||
<Link
|
||||
href={`/strength/${workout._id.toString()}`}
|
||||
className="flex items-center justify-between rounded-lg border border-muted/40 bg-surface p-4 transition-colors hover:border-accent/60"
|
||||
className="flex items-center justify-between rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4 transition-colors hover:border-accent/60"
|
||||
>
|
||||
<div>
|
||||
<div className="font-semibold text-fg">{workout.name}</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function AiAnalysisCard({ targetType, targetId, analysis }: AiAnalysisCar
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-3 rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<section className="flex flex-col gap-3 rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="flex items-center gap-1.5 text-lg font-semibold text-fg">
|
||||
<Sparkles size={18} className="text-accent" />
|
||||
@@ -29,7 +29,7 @@ export function AiAnalysisCard({ targetType, targetId, analysis }: AiAnalysisCar
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="rounded-md bg-accent px-3 py-1.5 text-sm font-semibold text-fg transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{pending ? "Generowanie..." : analysis ? "Wygeneruj ponownie" : "Generuj analizę"}
|
||||
</button>
|
||||
|
||||
@@ -98,7 +98,7 @@ export function DashboardAnalysisCard({ analyses, runs, workouts }: Props) {
|
||||
const dayWorkouts = workouts.filter((w) => isSameDay(new Date(w.date), selectedDate));
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-4 rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<section className="flex flex-col gap-4 rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="flex items-center gap-1.5 text-lg font-semibold text-fg">
|
||||
<Sparkles size={18} className="text-accent" />
|
||||
@@ -109,7 +109,7 @@ export function DashboardAnalysisCard({ analyses, runs, workouts }: Props) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="rounded-md bg-accent px-3 py-1.5 text-sm font-semibold text-fg transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{pending ? "Analizuję..." : selectedAnalysis ? "Odśwież analizę" : "Generuj analizę"}
|
||||
</button>
|
||||
@@ -216,7 +216,7 @@ export function DashboardAnalysisCard({ analyses, runs, workouts }: Props) {
|
||||
<li key={run.id}>
|
||||
<Link
|
||||
href={`/running/${run.id}`}
|
||||
className="flex items-center gap-2 rounded-md border border-muted/40 bg-bg/40 px-2.5 py-1.5 transition-colors hover:border-accent/60"
|
||||
className="flex items-center gap-2 rounded-md border border-white/10 bg-bg/40 px-2.5 py-1.5 transition-colors hover:border-accent/60"
|
||||
>
|
||||
<Activity size={14} className="shrink-0 text-accent" />
|
||||
<span className="min-w-0 flex-1">
|
||||
@@ -235,7 +235,7 @@ export function DashboardAnalysisCard({ analyses, runs, workouts }: Props) {
|
||||
<li key={workout.id}>
|
||||
<Link
|
||||
href={`/strength/${workout.id}`}
|
||||
className="flex items-center gap-2 rounded-md border border-muted/40 bg-bg/40 px-2.5 py-1.5 transition-colors hover:border-accent/60"
|
||||
className="flex items-center gap-2 rounded-md border border-white/10 bg-bg/40 px-2.5 py-1.5 transition-colors hover:border-accent/60"
|
||||
>
|
||||
<Dumbbell size={14} className="shrink-0 text-accent" />
|
||||
<span className="min-w-0 flex-1">
|
||||
|
||||
@@ -31,7 +31,7 @@ export function ElevationChart({ data, syncId }: Props) {
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
if (!mounted) {
|
||||
return <div className="h-[140px] animate-pulse rounded-lg border border-muted/40 bg-surface" />;
|
||||
return <div className="h-[140px] animate-pulse rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md" />;
|
||||
}
|
||||
|
||||
const altitudes = data.map((p) => p.altM);
|
||||
@@ -46,15 +46,15 @@ export function ElevationChart({ data, syncId }: Props) {
|
||||
const pacePad = Math.max(5, Math.round((maxPace - minPace) * 0.15));
|
||||
|
||||
const tooltipStyle = {
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-muted/40 bg-surface p-4" style={{ minWidth: 0 }}>
|
||||
<div className="rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4" style={{ minWidth: 0 }}>
|
||||
<div className="mb-2 flex items-center gap-4 text-sm text-fg/60">
|
||||
<span>Profil wysokości</span>
|
||||
{hasPace && (
|
||||
@@ -73,7 +73,7 @@ export function ElevationChart({ data, syncId }: Props) {
|
||||
<stop offset="95%" stopColor="var(--color-accent)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid stroke="var(--color-muted)" opacity={0.3} vertical={false} />
|
||||
<CartesianGrid stroke="rgba(255,255,255,0.08)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="distanceKm"
|
||||
stroke="var(--color-fg)"
|
||||
|
||||
@@ -10,14 +10,14 @@ type EmptyStateProps = {
|
||||
|
||||
export function EmptyState({ title, description, action, icon }: EmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-muted/40 px-6 py-10 text-center">
|
||||
<div className="flex flex-col items-center justify-center gap-2 rounded-2xl border border-dashed border-white/10 px-6 py-10 text-center">
|
||||
{icon ? <div className="text-fg/40">{icon}</div> : null}
|
||||
<div className="text-base font-semibold text-fg">{title}</div>
|
||||
{description ? <p className="max-w-sm text-sm text-fg/60">{description}</p> : null}
|
||||
{action ? (
|
||||
<Link
|
||||
href={action.href}
|
||||
className="mt-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-fg transition-opacity hover:opacity-90"
|
||||
className="btn btn-primary mt-2"
|
||||
>
|
||||
{action.label}
|
||||
</Link>
|
||||
|
||||
@@ -34,22 +34,22 @@ export function ExerciseProgressChart({ name, data }: ExerciseProgressChartProps
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="mb-2 h-4 w-32 animate-pulse rounded bg-muted/30" />
|
||||
<div className="h-[150px] animate-pulse rounded bg-muted/20" />
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 h-4 w-32 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-[150px] animate-pulse rounded bg-white/5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 flex items-baseline justify-between gap-2">
|
||||
<span className="text-sm text-fg/60">{name}</span>
|
||||
<E1rmDelta data={data} />
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={150}>
|
||||
<LineChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
|
||||
<CartesianGrid stroke="var(--color-muted)" opacity={0.3} vertical={false} />
|
||||
<CartesianGrid stroke="rgba(255,255,255,0.08)" vertical={false} />
|
||||
<XAxis dataKey="label" stroke="var(--color-fg)" opacity={0.5} fontSize={12} />
|
||||
<YAxis yAxisId="volume" stroke="var(--color-accent)" opacity={0.7} fontSize={12} width={48} />
|
||||
<YAxis
|
||||
@@ -62,9 +62,9 @@ export function ExerciseProgressChart({ name, data }: ExerciseProgressChartProps
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
@@ -25,11 +25,11 @@ export function GcbChart({ data, syncId }: Props) {
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
if (!mounted) {
|
||||
return <div className="h-[160px] animate-pulse rounded-lg border border-muted/40 bg-surface" />;
|
||||
return <div className="h-[160px] animate-pulse rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 flex items-center gap-4 text-sm text-fg/60">
|
||||
<span>Balans kontaktu</span>
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -53,7 +53,7 @@ export function GcbChart({ data, syncId }: Props) {
|
||||
<stop offset="95%" stopColor="var(--color-sand)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid stroke="var(--color-muted)" opacity={0.3} vertical={false} />
|
||||
<CartesianGrid stroke="rgba(255,255,255,0.08)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="distanceKm"
|
||||
stroke="var(--color-fg)"
|
||||
@@ -73,9 +73,9 @@ export function GcbChart({ data, syncId }: Props) {
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,7 @@ export function InfoTooltip({ text }: { text: string }) {
|
||||
return (
|
||||
<span className="group relative inline-flex items-center">
|
||||
<Info size={13} className="cursor-default text-fg/40 transition-colors group-hover:text-fg/70" />
|
||||
<span className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 w-56 -translate-x-1/2 rounded-md border border-muted/40 bg-bg px-3 py-2 text-xs text-fg/70 opacity-0 shadow-lg transition-opacity group-hover:opacity-100">
|
||||
<span className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 w-56 -translate-x-1/2 rounded-md border border-white/10 bg-bg px-3 py-2 text-xs text-fg/70 opacity-0 shadow-lg transition-opacity group-hover:opacity-100">
|
||||
{text}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -16,7 +16,7 @@ export function LoadRouteButton({ activityId }: { activityId: string }) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="flex items-center gap-1.5 rounded-md border border-muted/40 bg-surface px-3 py-2 text-sm font-medium text-fg/80 transition-colors hover:border-accent/60 hover:text-accent disabled:opacity-50"
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
<Map size={15} className={pending ? "animate-pulse" : ""} />
|
||||
{pending ? "Pobieranie mapy..." : "Załaduj mapę trasy"}
|
||||
|
||||
43
components/nav-links.tsx
Normal file
43
components/nav-links.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Activity, Dumbbell, LayoutDashboard, Settings } from "lucide-react";
|
||||
|
||||
const links = [
|
||||
{ href: "/", label: "Panel", icon: LayoutDashboard },
|
||||
{ href: "/running", label: "Bieganie", icon: Activity },
|
||||
{ href: "/strength", label: "Siłownia", icon: Dumbbell },
|
||||
{ href: "/settings", label: "Ustawienia", icon: Settings },
|
||||
];
|
||||
|
||||
function isActive(pathname: string, href: string) {
|
||||
return href === "/" ? pathname === "/" : pathname.startsWith(href);
|
||||
}
|
||||
|
||||
export function NavLinks() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<>
|
||||
{links.map(({ href, label, icon: Icon }) => {
|
||||
const active = isActive(pathname, href);
|
||||
return (
|
||||
<Link
|
||||
key={href}
|
||||
href={href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={`flex items-center gap-1.5 rounded-full px-2.5 py-1.5 text-sm font-medium transition-colors sm:px-3.5 ${
|
||||
active
|
||||
? "bg-accent/15 text-accent"
|
||||
: "text-fg/70 hover:bg-white/5 hover:text-fg"
|
||||
}`}
|
||||
>
|
||||
<Icon size={16} />
|
||||
<span className="hidden sm:inline">{label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import { Activity, Dumbbell, LayoutDashboard, Settings } from "lucide-react";
|
||||
import { auth, signOut } from "@/auth";
|
||||
import { NavLinks } from "./nav-links";
|
||||
import { SignOutButton } from "./sign-out-button";
|
||||
|
||||
const links = [
|
||||
{ href: "/", label: "Panel", icon: LayoutDashboard },
|
||||
{ href: "/running", label: "Bieganie", icon: Activity },
|
||||
{ href: "/strength", label: "Siłownia", icon: Dumbbell },
|
||||
{ href: "/settings", label: "Ustawienia", icon: Settings },
|
||||
];
|
||||
import { Wordmark } from "./wordmark";
|
||||
|
||||
export async function Nav() {
|
||||
const session = await auth();
|
||||
@@ -19,33 +13,21 @@ export async function Nav() {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b border-muted/40 bg-surface">
|
||||
<header className="sticky top-0 z-50 border-b border-white/10 bg-surface/70 backdrop-blur-xl">
|
||||
<div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3 sm:px-6">
|
||||
<Link href="/" className="flex items-center gap-3 text-lg font-bold text-fg">
|
||||
<Link href="/" className="flex items-center gap-2.5 text-lg font-bold text-fg">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/logo-icon.svg"
|
||||
alt=""
|
||||
width={72}
|
||||
height={72}
|
||||
className="-mt-1 -mb-6 h-[72px] w-[72px] rounded-2xl shadow-lg ring-2 ring-surface"
|
||||
src="/logo-bars.svg"
|
||||
alt="KNUR.APP"
|
||||
width={51}
|
||||
height={48}
|
||||
className="h-7 w-auto sm:h-8"
|
||||
/>
|
||||
<span className="hidden text-2xl tracking-tight text-white [font-family:var(--font-space-grotesk)] sm:block">
|
||||
<span className="font-bold [-webkit-text-stroke:0.7px_white]">KNUR</span>
|
||||
<span className="font-medium text-accent">.APP</span>
|
||||
</span>
|
||||
<Wordmark className="hidden text-2xl sm:block" />
|
||||
</Link>
|
||||
<nav className="flex items-center gap-1 sm:gap-2">
|
||||
{links.map(({ href, label, icon: Icon }) => (
|
||||
<Link
|
||||
key={href}
|
||||
href={href}
|
||||
className="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm font-medium text-fg/80 transition-colors hover:bg-bg hover:text-accent sm:px-3"
|
||||
>
|
||||
<Icon size={16} />
|
||||
<span className="hidden sm:inline">{label}</span>
|
||||
</Link>
|
||||
))}
|
||||
<NavLinks />
|
||||
{session?.user?.name && (
|
||||
<span className="hidden px-2 text-xs text-fg/40 sm:inline">
|
||||
{session.user.name}
|
||||
|
||||
@@ -25,22 +25,21 @@ export function NotesEditor({ initialNotes, onSave }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="flex flex-col gap-2 rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<label className="text-sm font-medium text-fg/60">Notatki</label>
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => { setValue(e.target.value); setSaved(false); }}
|
||||
placeholder="Dodaj notatki do tego treningu…"
|
||||
rows={3}
|
||||
className="w-full resize-none rounded border border-muted/40 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent focus:outline-none"
|
||||
className="w-full resize-none rounded border border-white/10 bg-bg px-3 py-2 text-sm text-fg placeholder:text-fg/30 focus:border-accent focus:outline-none"
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-3">
|
||||
{saved && <span className="text-xs text-fg/40">Zapisano</span>}
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={!dirty || isPending}
|
||||
className="rounded px-3 py-1.5 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
style={{ background: "var(--color-accent)", color: "#fff" }}
|
||||
className="btn btn-primary px-3 py-1.5 text-xs"
|
||||
>
|
||||
{isPending ? "Zapisuję…" : "Zapisz"}
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { RoutePoint } from "@/lib/models/running";
|
||||
|
||||
const RouteMap = nextDynamic(() => import("@/components/route-map").then((m) => m.RouteMap), {
|
||||
ssr: false,
|
||||
loading: () => <div className="h-full w-full animate-pulse rounded-lg bg-surface" />,
|
||||
loading: () => <div className="h-full w-full animate-pulse rounded-2xl bg-surface" />,
|
||||
});
|
||||
|
||||
export function RouteMapSection({ points }: { points: RoutePoint[] }) {
|
||||
|
||||
@@ -42,7 +42,7 @@ export function RunMetricChart({
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
if (!mounted) {
|
||||
return <div className="h-[160px] animate-pulse rounded-lg border border-muted/40 bg-surface" />;
|
||||
return <div className="h-[160px] animate-pulse rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md" />;
|
||||
}
|
||||
|
||||
const values = data.map((p) => p.value);
|
||||
@@ -59,7 +59,7 @@ export function RunMetricChart({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 text-sm text-fg/60">{label}</div>
|
||||
<ResponsiveContainer width="100%" height={120}>
|
||||
<AreaChart syncId={syncId} data={data} margin={{ top: 4, right: 8, left: 0, bottom: 0 }}>
|
||||
@@ -69,7 +69,7 @@ export function RunMetricChart({
|
||||
<stop offset="95%" stopColor={color} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid stroke="var(--color-muted)" opacity={0.3} vertical={false} />
|
||||
<CartesianGrid stroke="rgba(255,255,255,0.08)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="distanceKm"
|
||||
stroke="var(--color-fg)"
|
||||
@@ -89,9 +89,9 @@ export function RunMetricChart({
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
@@ -25,15 +25,15 @@ export function RunningStatsChart({ data }: Props) {
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-muted/30" />
|
||||
<div className="h-[220px] animate-pulse rounded bg-muted/20" />
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-[220px] animate-pulse rounded bg-white/5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 text-sm text-fg/60">Tygodniowy dystans biegowy (od poniedziałku)</div>
|
||||
<ResponsiveContainer width="100%" height={220}>
|
||||
<AreaChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
|
||||
@@ -48,9 +48,9 @@ export function RunningStatsChart({ data }: Props) {
|
||||
<Tooltip
|
||||
cursor={{ stroke: "var(--color-accent)", strokeWidth: 1 }}
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ export function SignOutButton({ action }: { action: () => Promise<void> }) {
|
||||
<form action={action}>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm font-medium text-fg/80 transition-colors hover:bg-bg hover:text-accent sm:px-3"
|
||||
className="flex items-center gap-1.5 rounded-full px-2.5 py-1.5 text-sm font-medium text-fg/70 transition-colors hover:bg-white/5 hover:text-fg sm:px-3.5"
|
||||
title="Wyloguj się"
|
||||
>
|
||||
<LogOut size={16} />
|
||||
|
||||
@@ -10,16 +10,25 @@ type StatCardProps = {
|
||||
export function StatCard({ label, value, hint, highlight }: StatCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
className={`group relative overflow-hidden rounded-2xl border bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4 transition-all duration-200 hover:-translate-y-0.5 ${
|
||||
highlight
|
||||
? "rounded-lg border border-l-2 border-muted/30 border-l-accent/70 bg-surface p-4"
|
||||
: "rounded-lg border border-muted/40 bg-surface px-3 py-2.5"
|
||||
}
|
||||
? "border-accent/30 shadow-accent/5"
|
||||
: "border-white/10 hover:border-white/20"
|
||||
}`}
|
||||
>
|
||||
<div className={highlight ? "text-xs font-medium uppercase tracking-widest text-fg/50" : "text-xs text-fg/55"}>
|
||||
{highlight && (
|
||||
<div className="pointer-events-none absolute -right-8 -top-10 h-24 w-24 rounded-full bg-accent/20 blur-2xl" />
|
||||
)}
|
||||
<div className="text-xs font-medium uppercase tracking-widest text-fg/50">
|
||||
{label}
|
||||
</div>
|
||||
<div className={highlight ? "mt-1.5 text-2xl font-bold text-fg" : "mt-0.5 text-base font-semibold text-fg"}>{value}</div>
|
||||
<div
|
||||
className={`mt-1.5 font-display font-bold tabular-nums text-fg ${
|
||||
highlight ? "text-2xl" : "text-xl"
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
{hint ? <div className="mt-1 text-xs text-fg/50">{hint}</div> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export function SyncButton() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="flex items-center gap-1.5 rounded-md bg-accent px-3 py-2 text-sm font-semibold text-fg transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
<RefreshCw size={16} className={pending ? "animate-spin" : ""} />
|
||||
{pending ? "Synchronizowanie..." : "Synchronizuj z Garmin"}
|
||||
@@ -36,12 +36,12 @@ export function SyncButton() {
|
||||
maxLength={6}
|
||||
placeholder="Kod z e-maila"
|
||||
required
|
||||
className="w-32 rounded-md border border-muted/40 bg-bg px-2 py-1.5 text-sm text-fg"
|
||||
className="w-32 rounded-md border border-white/10 bg-bg px-2 py-1.5 text-sm text-fg"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={mfaPending}
|
||||
className="rounded-md bg-accent px-3 py-1.5 text-sm font-semibold text-fg transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{mfaPending ? "Weryfikacja..." : "Zatwierdź kod"}
|
||||
</button>
|
||||
|
||||
@@ -13,15 +13,15 @@ export function VolumeChart({ data }: VolumeChartProps) {
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-muted/30" />
|
||||
<div className="h-[180px] animate-pulse rounded bg-muted/20" />
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-[180px] animate-pulse rounded bg-white/5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 text-sm text-fg/60">Wolumen treningowy (ciężar × powtórzenia)</div>
|
||||
<ResponsiveContainer width="100%" height={180}>
|
||||
<BarChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
|
||||
@@ -30,9 +30,9 @@ export function VolumeChart({ data }: VolumeChartProps) {
|
||||
<Tooltip
|
||||
cursor={{ fill: "var(--color-bg)" }}
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
@@ -38,9 +38,9 @@ export function VolumeTrendChart({ data }: Props) {
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-muted/30" />
|
||||
<div className="h-[220px] animate-pulse rounded bg-muted/20" />
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 h-4 w-56 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-[220px] animate-pulse rounded bg-white/5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export function VolumeTrendChart({ data }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-muted/40 bg-surface p-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-surface/55 shadow-lg shadow-black/10 backdrop-blur-md p-4">
|
||||
<div className="mb-2 text-sm text-fg/60">
|
||||
Trend wolumenu treningowego wg typu
|
||||
</div>
|
||||
@@ -75,9 +75,9 @@ export function VolumeTrendChart({ data }: Props) {
|
||||
<Tooltip
|
||||
cursor={{ fill: "var(--color-bg)" }}
|
||||
contentStyle={{
|
||||
background: "var(--color-bg)",
|
||||
border: "1px solid var(--color-muted)",
|
||||
borderRadius: 8,
|
||||
background: "color-mix(in oklab, var(--color-surface) 92%, transparent)",
|
||||
border: "1px solid rgba(255,255,255,0.12)",
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
color: "var(--color-fg)",
|
||||
}}
|
||||
|
||||
10
components/wordmark.tsx
Normal file
10
components/wordmark.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export function Wordmark({ className }: { className?: string }) {
|
||||
return (
|
||||
<span
|
||||
className={`tracking-tight text-white [font-family:var(--font-space-grotesk)] ${className ?? ""}`}
|
||||
>
|
||||
<span className="font-bold [-webkit-text-stroke:0.7px_white]">KNUR</span>
|
||||
<span className="font-medium text-accent">.APP</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,6 @@ export default auth((req) => {
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
"/((?!api/auth|_next/static|_next/image|favicon.ico|icon.svg|logo.svg|public/).*)",
|
||||
"/((?!api/auth|_next/static|_next/image|.*\\.(?:svg|ico|png|jpg|jpeg|webp|gif)$).*)",
|
||||
],
|
||||
};
|
||||
|
||||
1
public/logo-bars.svg
Normal file
1
public/logo-bars.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?><svg xmlns="http://www.w3.org/2000/svg" width="51" height="48" viewBox="0 0 51 48"><rect x="0" y="30" width="9" height="18" rx="3" fill="#f7f3e9"/><rect x="14" y="20" width="9" height="28" rx="3" fill="#f7f3e9"/><rect x="28" y="10" width="9" height="38" rx="3" fill="#f7f3e9"/><rect x="42" y="0" width="9" height="48" rx="3" fill="#fb4617"/></svg>
|
||||
|
After Width: | Height: | Size: 387 B |
1
public/logo-mark.svg
Normal file
1
public/logo-mark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="738" height="444" viewBox="144 173 738 444"><path fill="#FB4617" d="M589.071 183.464C588.989 200.986 587.545 218.73 584.386 235.974C581.445 252.03 573.953 266.541 566.054 280.654C578.512 268.568 585.684 260.918 593.143 244.721C605.705 249.265 617.021 253.152 629.062 258.986C659.52 274.056 686.77 294.881 709.308 320.313C712.759 324.175 717.78 329.744 720.783 333.87C705.571 328.15 692.428 322.932 676.623 318.914C664.264 315.773 653.535 314.191 640.993 312.06C694.298 344.092 730.223 388.478 752.218 446.707C754.895 453.794 757.871 461.496 759.981 468.74C744.97 462.908 732.874 457.141 716.92 452.462C666.376 437.132 612.003 440.5 563.738 461.951C545.769 469.881 533.769 477.827 517.007 487.548C516.492 476.189 515.701 463.88 515.466 452.584C519.489 449.076 539.421 433.622 540.968 431.308C548.098 420.647 552.047 386.799 552.894 374.485C552.995 373.012 553.063 371.538 553.099 370.062C552.431 369.296 551.94 368.713 551.534 367.778L551.245 367.138C545.705 381.527 536.061 406.355 526.555 418.298C524.188 421.272 499.614 441.531 495.652 444.455C469.031 431.736 442.395 428.96 414.2 438.853C403.784 442.509 394.849 447.798 385.04 452.675C395.157 439.475 404.789 426.436 415.147 413.388C407.454 405.28 398.662 393.894 391.503 384.985C371.281 395.789 362.475 398.797 339.005 391.662C336.708 390.948 334.421 390.201 332.146 389.423C338.733 384.047 345.998 378.953 352.794 373.761C370.36 360.281 387.995 346.892 405.7 333.594C413.181 327.916 422.823 320.998 429.782 315.125C431.767 294.387 431.691 291.982 447.552 278.715C468.828 260.918 488.288 250.944 513.807 241.011C514.264 248.793 515.056 256.551 516.18 264.264C519.882 252.327 522.577 238.619 525.272 226.302C532.058 220.996 543.089 214.204 550.498 209.268C563.301 200.585 576.159 191.984 589.071 183.464Z"/><path fill="#2E162E" d="M553.336 360.501C553.556 363.982 553.677 366.593 553.099 370.062C552.431 369.296 551.94 368.713 551.534 367.778L551.245 367.138C551.868 365.025 552.647 362.601 553.336 360.501Z"/><path fill="#2B2D42" d="M570.935 212.987L571.307 213.432C571.912 217.117 571.411 221.206 571.342 224.941C570.998 243.577 561.401 269.281 542.992 277.612L542.385 276.866C533.473 239.579 543.969 235.242 570.935 212.987Z"/><path fill="#2B2D42" d="M471.391 297.628C475.757 297.206 480.158 297.302 484.502 297.915C486.611 298.21 488.709 298.58 490.791 299.023C479.083 306.75 467.269 312.814 455.468 320.065C451.181 322.6 447.58 325.094 443.074 327.371C449.046 313.381 454.987 300.83 471.391 297.628Z"/><path fill="#F7F3E9" d="M267.117 499.251L776.99 499.438L777.019 539.616L756.867 539.62L247.708 539.582C247.643 526.181 247.67 512.78 247.788 499.379C253.977 499.169 260.874 499.28 267.117 499.251Z"/><path fill="#F7F3E9" d="M785.179 425.193C799.766 424.912 815.041 425.141 829.68 425.145C830.294 452.897 829.793 482.691 829.706 510.519L829.969 605.863C828.359 606.902 790.399 606.472 785.239 606.47L785.179 425.193Z"/><path fill="#F7F3E9" d="M194.401 425.072C209.055 425.056 223.709 425.118 238.363 425.258L238.253 606.168C223.581 606.317 208.908 606.356 194.236 606.285C193.972 545.881 194.027 485.475 194.401 425.072Z"/><path fill="#F7F3E9" d="M287.314 332.065C287.556 331.302 287.585 330.915 288.092 330.379L288.208 330.366C288.681 330.813 288.499 333.528 288.495 334.087C288.83 342.987 292.198 352.474 295.94 360.434C306.213 382.29 326.212 397.322 350.151 400.935C364.862 403.156 374.434 400.556 387.564 394.073C391.551 400.051 400.91 410.545 405.812 416.649C400.346 420.821 395.704 424.163 389.838 427.831C376.36 436.258 354.31 435.91 339.387 431.892C319.937 426.65 303.397 413.836 293.462 396.312C282.286 376.63 281.364 353.542 287.314 332.065Z"/><path fill="#D4CBBB" d="M287.314 332.065C287.556 331.302 287.585 330.915 288.092 330.379L288.208 330.366C288.681 330.813 288.499 333.528 288.495 334.087L287.314 332.065Z"/><path fill="#F7F3E9" d="M154.418 461.74L188.238 461.567C188.333 470.275 188.612 568.781 187.857 570.438L153.317 570.429C152.984 542.388 153.316 514.27 153.281 486.222C153.272 478.985 152.763 469.256 153.482 462.279L154.418 461.74Z"/><path fill="#F7F3E9" d="M836.544 461.666L871.531 461.719C871.513 497.627 872.037 534.539 871.57 570.312C859.879 570.388 848.187 570.328 836.498 570.133L836.544 461.666Z"/><path fill="#FB4617" d="M315.795 429C318.261 429.843 321.362 431.778 323.908 432.934C337.347 439.033 349.753 442.893 364.656 442.573C362.576 444.323 360.586 446.234 358.574 447.893C356.092 450.049 352.715 452.791 350.04 454.766C347.289 453.311 342.152 450.745 339.749 449.249C334.614 446.054 318.477 433.622 315.795 429Z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
Reference in New Issue
Block a user