Add public DS-2 welcome page on root for unauthenticated visitors

Second-brain scheme (logo + six cards with rays) reworked from the old
platform's infographic. Root stays role-redirecting for signed-in users;
middleware opens "/" as an exact match only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 09:32:49 +05:00
parent 1133557a62
commit fa7c25465c
5 changed files with 322 additions and 2 deletions
+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.9.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 283.5 283.5" style="enable-background:new 0 0 283.5 283.5;" xml:space="preserve" fill="#323232">
<g>
<g>
<path d="M141.7,283.5C63.6,283.5,0,219.9,0,141.7C0,63.6,63.6,0,141.7,0s141.7,63.6,141.7,141.7S219.9,283.5,141.7,283.5z
M141.7,2.7c-76.7,0-139,62.4-139,139c0,76.7,62.4,139,139,139s139-62.4,139-139C280.8,65.1,218.4,2.7,141.7,2.7z"/>
</g>
<g>
<path d="M239.1,154.4c2.9-6.2,3.9-12.6,3.1-19c-1.2-8.9-5.8-15.8-10-20.4c0.5-5.1-0.3-10.2-2.4-15.3c-2.8-6.7-7-11.8-12.5-15.2
c-1.6-13.1-9.3-23.2-21.1-27.5c-4.1-9-12.3-15.4-22.2-17.5c-16.1-3.4-27.3,5.5-33.6,14.4c-5.6-8.6-16.1-17.7-31.4-14.5
c-9.7,2-17.3,8.5-20.8,17.5c-14.6,4.4-20.4,16.7-21.1,27.8c-5.9,3-10.3,8-13.1,14.8c-2.2,5.3-2.9,10.6-2,15.8
c-3.9,3.9-9.4,11-10.7,20.4c-0.9,6.6,0.4,13.2,3.9,19.5c-7.4,10.1-7.3,23.6,0.5,33.9c2.8,3.7,6.2,6.6,10.2,8.7
c-0.9,13.3,3.7,29.1,19.9,32.7c4.3,1,8.5,1.2,12.7,0.6c5.3,9.2,15.2,15.1,26,15.1c11,0,20.6-6,25.8-14.8
c5.9,8.9,16,14.8,27.4,14.8c10.8,0,21-5.4,27.1-14.2c3.6,0.3,7.2,0,11-0.9c16.9-3.8,22.7-19.1,22.5-33.1c3.4-2.1,6.4-4.9,8.8-8.2
C245.1,179.2,245.7,165.3,239.1,154.4z M134.9,146.4c0,5.5-5.6-8.7-9.8-12.9c-1.4-1.4-2.8-1.8-4.5-1.9c-1.1-3-2.8-7.5-5.2-9.8
c-3.4-3.2-1.5-7.8-1.4-10.2c0.1-2.4-3.5,1.8-3,5c0.5,3.2,0.6,4.7-0.6,9.7c-0.7,2.6,1.2,4.8,3.2,6.4c-1.9,0.4-4,0.7-6.2,0.7
c-8.1,0-9.1,14.9-7.8,17.5c1.3,2.6,5.8,8.1,5.8-0.6c0-8.7,12-12,17.5-7.9c5.5,4.1,4.9,15,5.2,22.4c0.3,7.4,6.9,1.6,6.9,11.3
c0,9.7-2,12.3-4,16.5c-1.9,4.2-17.8,7.1-14.6,9.4c3.2,2.3,18.5-1,18.5-1v12.2v0.1c0,18.1-21.1,21.7-28.9,20.7
c-7.8-1-11.6-10-9.4-14.9c2.3-4.9,10-2.9,11.6-5.5c1.6-2.6-5.5-0.3-11-5.8c-5.5-5.5-14.6-5.8-10.3-2.3c4.2,3.6,2.6,10.7-1.9,13.6
c-4.5,2.9-18.8,1.6-20.1-9.4c-1.3-11,4.2-14.6,6.1-18.1c1.9-3.6-2.3-14.2-2.3-14.2s-2.3,11-5.8,11.3c-3.6,0.3-11.6-5.5-11.9-12.9
c-0.2-7.4,3.8-10.3,4.7-13.3c1-2.9-1.9-4.9-4.5-20.1c-2.6-15.3,18.4-19.3,20.4-17.4c1.9,1.9,12.6,6.5,10.3,3.2
c-2.3-3.2-2.9-11-4.2-18.1c-1.3-7.1-7.4-4.2-8.7-0.3c-1.3,3.9-6.8,18.1-8.1,4.5c-1.3-13.6,8.4-23,12-25.6c3.6-2.6,3.2-8.1,6.5-15
c3.2-7,11.6-9.5,16.8-8.6c5.2,1,13.3-1.6,13.3-1.6c-1.9-3.2-10-4.5-11.3-4.9c-1.3-0.3,3.6-11,17.5-11.3
c13.9-0.3,18.1,11.6,19.1,18.8c1,7.1-6.5,10.3-6.5,10.3c5.5,3.9,4.5,23.6,4.5,23.6c0.4,12.3-11,14.9-11,14.9s13,5.2,13,9.7V146.4z
M196.7,100.9c3,0,5.5,2.5,5.5,5.5c0,3-2.5,5.5-5.5,5.5c-3,0-5.5-2.5-5.5-5.5C191.2,103.4,193.7,100.9,196.7,100.9z M169.2,75.6
c0,3-2.5,5.5-5.5,5.5c-3,0-5.5-2.5-5.5-5.5c0-3,2.5-5.5,5.5-5.5C166.8,70.1,169.2,72.6,169.2,75.6z M188.2,199.8
c-3,0-5.5-2.5-5.5-5.5c0-3,2.5-5.5,5.5-5.5c3,0,5.5,2.5,5.5,5.5C193.6,197.3,191.2,199.8,188.2,199.8z M230.4,184.7
c-2.3,3-5.1,5.5-8.4,7.1l-2.4,1.3l0.2,2.7c0.4,5.4,0.3,23.4-15.8,27c-3.7,0.8-7.2,1-10.7,0.5l-1.8-0.3v-17
c5.1-1.5,8.8-6.2,8.8-11.7c0-6.7-5.5-12.2-12.2-12.2s-12.2,5.5-12.2,12.2c0,5.6,3.7,10.3,8.8,11.7v24.8c-4.5,4.4-10.6,6.9-17,6.9
c-7.9,0-15-3.8-19.4-9.6l22.2-22.2v-26.8c5.1-1.5,8.8-6.2,8.8-11.7c0-6.7-5.5-12.2-12.2-12.2c-6.7,0-12.2,5.5-12.2,12.2
c0,5.6,3.7,10.3,8.8,11.7v24L144.9,222c-0.3-0.7-0.5-1.3-0.7-2c0.2-1.2,0.3-2.5,0.3-3.8v-69.4l15.3-15.3c4.6,2.6,10.6,1.9,14.5-2
c4.8-4.8,4.8-12.5,0-17.3c-4.8-4.8-12.5-4.8-17.3,0c-3.9,3.9-4.6,9.9-2,14.5l-10.5,10.5v-16.5v-0.3V63.3c2.7-5.5,8-12.4,15.9-15
l0,15.6c-5.1,1.5-8.8,6.2-8.8,11.7c0,6.7,5.5,12.2,12.2,12.2c6.7,0,12.2-5.5,12.2-12.2c0-5.6-3.7-10.3-8.8-11.7l0-16.7
c1.6,0,3.3,0.1,5.1,0.5c7.9,1.7,14.1,6.9,16.9,14.2l0.7,1.9l2,0.6c0.5,0.2,1,0.4,1.5,0.5l0,29.8c-5.1,1.5-8.8,6.2-8.8,11.7
c0,6.7,5.5,12.2,12.2,12.2c6.7,0,12.2-5.5,12.2-12.2c0-5.6-3.7-10.3-8.8-11.7l0-26.1c7.3,5.8,8.8,14.5,9,18.7l0.1,2.4l2.1,1.1
c4.7,2.4,8.3,6.5,10.6,12.1c1.8,4.3,2.3,8.6,1.6,12.8l-0.4,2.1l1.5,1.5c3.5,3.6,8,9.5,9.1,17.2c0,0.3,0.1,0.6,0.1,0.9h-28.6
c-1.5-5.1-6.2-8.8-11.7-8.8c-6.7,0-12.2,5.5-12.2,12.2c0,6.7,5.5,12.2,12.2,12.2c5.6,0,10.3-3.7,11.7-8.8h28.3
c-0.5,2.9-1.5,5.7-3.1,8.5l-1.3,2.3l1.6,2.2c2.2,3,3.6,6.4,4.1,9.8h-14.4c-1.5-5.1-6.2-8.8-11.7-8.8c-6.7,0-12.2,5.5-12.2,12.2
c0,6.7,5.5,12.2,12.2,12.2c5.6,0,10.3-3.7,11.7-8.8h14.4C234.5,177.5,233,181.3,230.4,184.7z M167.2,172.9c-3,0-5.5-2.5-5.5-5.5
c0-3,2.5-5.5,5.5-5.5c3,0,5.5,2.5,5.5,5.5C172.6,170.4,170.2,172.9,167.2,172.9z M161.8,117c2.1-2.1,5.6-2.1,7.7,0
c2.1,2.1,2.1,5.6,0,7.7c-2.1,2.1-5.6,2.1-7.7,0C159.7,122.6,159.7,119.1,161.8,117z M199.1,140.8c0,3-2.5,5.5-5.5,5.5
c-3,0-5.5-2.5-5.5-5.5c0-3,2.5-5.5,5.5-5.5C196.7,135.3,199.1,137.8,199.1,140.8z M214.4,170.4c0,3-2.5,5.5-5.5,5.5
c-3,0-5.5-2.5-5.5-5.5c0-3,2.5-5.5,5.5-5.5C211.9,164.9,214.4,167.4,214.4,170.4z"/>
<path d="M125.6,90.7c-0.8-4.9,0.5-16.5-3.9-15.5c-4.4,1-1.6,6.4-2.6,7.8c-2.4,3.2-0.5,3.7,2.7,7.1c3.2,3.4,3.1,10.5-1,12.8
c-4,2.3-3.8,7.6-1.8,6.8c2.1-0.8,5-3.7,6.4-7.4C127,98.5,126.4,95.6,125.6,90.7z"/>
<path d="M71.9,169.5c-1.8-2.9-3.3,3.7-6.7-0.2c-3.4-3.9-4.4-4.7-6-3.2c-3.9,3.5-1.9,8,1.5,10.9c3.4,2.9,2.9,0,2.7-3.1
c-0.2-3.1,1.8-1.8,5.2-0.6C72,174.3,73.8,172.4,71.9,169.5z"/>
<path d="M114.2,186.3c-3.4,2.6-5,6.1-1.5,6c3.6-0.2,3.2-5.3,6.1-4.7c2.9,0.6,1.7,5.7,4.7,3.6c3-2.1,5.4-11.1,2.8-14.2
c-2.6-3.1-4.8,3.2-5.7,3.4C116.7,181.2,117.6,183.7,114.2,186.3z"/>
<path d="M111,222.4c1.2,1.7,3.4,3.6,9.5,1.2c6.1-2.4,2.3-8.5-1.5-6.5C114.2,219.6,109.8,220.7,111,222.4z"/>
<path d="M116.6,98.2c-1.9-0.7-8.7-7.8-8.7-12.4c0-4.6-5.8,5.8-6.1,8.7c-0.2,2.9-5.3,5.8-9.6,7.3c-4.3,1.5-6.6,5.3-6.4,5.8
c0.2,0.5,3.4-0.2,6.4,0c3,0.2,2.6,6.5,2.4,13.8c-0.2,7.3-3.2,18.7-14.6,17.2c-11.4-1.5-11.4,7.8-13.3,15.8c-1.9,8,9,10.4,11.2,6.8
c2.2-3.6,6.5-7.3,11.2-4c0,0,3.4-5.7,0-7.6c-3.4-1.9-13.1,5.8-13.6,1c-0.5-4.9,4.6-3.4,14.1-8.2c9.5-4.8,9.2-19.2,10.5-22.6
c1.3-3.4-1.1-10.9,0-14.3c1.1-3.4,4.7-6.1,8.6-5.1C112.5,101.3,118.5,98.9,116.6,98.2z"/>
<path d="M122.4,59.4c-4.2-5.4-4.1,5.8-1.2,9C124.1,71.5,125.8,63.7,122.4,59.4z"/>
<path d="M86.3,95c3.2-3.2,1.3-3.4,6.7-4.6c5.4-1.2,4.4-4.9,7-7.8c2.6-2.9,8.6-9.2,3.5-10.2c-5.1-1-12.6-4.6-13.1,0
c-0.5,4.6,5.6,1.9,7.5,3.4c1.9,1.5-3.3,7.1-4.9,8.8c-1.6,1.6-6.7,3.3-6.7,3.3C82.4,91.9,83.1,98.2,86.3,95z"/>
<path d="M83.4,171.2c-0.5,4.9,8.8,9,8.8,9s0.3,0.2,0.7,0.6c-2.7,0.8-5.8,2.5-6.2,3.7c-0.6,1.9-9.1,10.2-12.5,12.2
c-3.4,2-2.7,7.7,1.1,12.2c3.9,4.5,7.6,5,8.4,3.6c0.8-1.5-5.6-4.2-6.7-8.9c-1.1-4.7,5.2-8.2,8.1-9.9c2.9-1.6,6.5-10.7,13.4-8
c2.4,1.9,4.1,9.5,4.1,9.5c2.2,3.2,2.7,1.2,1.9-4.1c-0.7-5.3,0-8.7,3.9-11.2c3.9-2.4,6.1-7,7-13.1c1-6.1,2.2-8.5,0-9.5
c-2.2-1.1-2.4,8.8-4.6,12.6c-2.1,3.8-6.8,5.4-14.1,6.1C89.4,176.8,83.8,166.3,83.4,171.2z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

+2 -1
View File
@@ -1,12 +1,13 @@
import { redirect } from "next/navigation";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { WelcomePage } from "@/components/welcome/welcome-page";
export default async function HomePage() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
redirect("/login");
return <WelcomePage />;
}
const role = session.user.role;
@@ -0,0 +1,56 @@
"use client";
import { useEffect, useRef } from "react";
// Draws 2px rays from the edge of every [data-scheme-card] to the central
// [data-scheme-logo] circle. Redraws on container resize and after fonts load.
export function SchemeConnectors() {
const ref = useRef<SVGSVGElement>(null);
useEffect(() => {
const svg = ref.current;
const scheme = svg?.parentElement;
if (!svg || !scheme) return;
function draw() {
if (!svg || !scheme) return;
const logo = scheme.querySelector<HTMLElement>("[data-scheme-logo]");
if (!logo) return;
const s = scheme.getBoundingClientRect();
const l = logo.getBoundingClientRect();
const cx = l.left + l.width / 2 - s.left;
const cy = l.top + l.height / 2 - s.top;
const r = l.width / 2 + 4;
svg.setAttribute("viewBox", `0 0 ${s.width} ${s.height}`);
let lines = "";
scheme.querySelectorAll<HTMLElement>("[data-scheme-card]").forEach((card) => {
const c = card.getBoundingClientRect();
const isLeft = card.dataset.schemeCard === "left";
const x1 = (isLeft ? c.right : c.left) - s.left;
const y1 = c.top + c.height / 2 - s.top;
const dx = cx - x1;
const dy = cy - y1;
const d = Math.hypot(dx, dy) || 1;
const x2 = cx - (dx / d) * r;
const y2 = cy - (dy / d) * r;
lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />`;
});
svg.innerHTML = lines;
}
draw();
const ro = new ResizeObserver(draw);
ro.observe(scheme);
document.fonts?.ready.then(draw).catch(() => {});
return () => ro.disconnect();
}, []);
return (
<svg
ref={ref}
aria-hidden="true"
className="hidden md:block absolute inset-0 w-full h-full pointer-events-none"
style={{ stroke: "var(--border)", strokeWidth: 2 }}
/>
);
}
+195
View File
@@ -0,0 +1,195 @@
// Public landing shown on "/" for unauthenticated visitors.
// DS-2 rework of the old platform's "second brain" infographic.
import Link from "next/link";
import {
StickyNote,
BookOpen,
Lightbulb,
Database,
GraduationCap,
Wrench,
type LucideIcon,
} from "lucide-react";
import { SchemeConnectors } from "./scheme-connectors";
interface SchemeBlock {
icon: LucideIcon;
title: string;
items: string[];
}
const BLOCKS_LEFT: SchemeBlock[] = [
{
icon: StickyNote,
title: "Заметки",
items: [
"разбираемся, как создавать качественные заметки, а не просто писать всё подряд",
"изучаем самые эффективные подходы в заметкоделии и заметковедении",
],
},
{
icon: BookOpen,
title: "Знания",
items: [
"превращаем сырые данные и разрозненную информацию в устойчивые знания, готовые к повторному применению",
],
},
{
icon: Lightbulb,
title: "Идеи",
items: [
"фиксируем, развиваем и воплощаем гениальные идеи в жизнь",
"и не даём им сгинуть в процессе",
],
},
];
const BLOCKS_RIGHT: SchemeBlock[] = [
{
icon: Database,
title: "Базы знаний",
items: [
"создаём картотеку, строим структуру, наполняем её качественными заметками",
"связываем отдельные элементы в единую сеть идей и размышлений",
],
},
{
icon: GraduationCap,
title: "Обучение",
items: [
"учимся правильно учиться, осваиваем эффективные стратегии",
"выстраиваем свой индивидуальный трек развития",
],
},
{
icon: Wrench,
title: "Инструменты",
items: [
"разбираемся с особенностями инструментов и выбираем самые подходящие",
],
},
];
const ROW_START = ["md:row-start-1", "md:row-start-2", "md:row-start-3"];
function SchemeCard({ block, side, row }: { block: SchemeBlock; side: "left" | "right"; row: number }) {
const Icon = block.icon;
const colClass = side === "left" ? "md:col-start-1" : "md:col-start-3";
return (
<div
data-scheme-card={side}
className={`card-aubade relative z-[1] p-5 ${colClass} ${ROW_START[row]}`}
>
<div className="flex items-center gap-3 mb-3">
<span
className="inline-flex items-center justify-center w-10 h-10 flex-shrink-0"
style={{ backgroundColor: "var(--accent)", border: "2px solid var(--foreground)" }}
>
<Icon size={22} style={{ color: "var(--foreground)" }} />
</span>
<span className="font-bold uppercase" style={{ fontSize: "16px", letterSpacing: "0.08em" }}>
{block.title}
</span>
</div>
<ul className="space-y-2">
{block.items.map((item) => (
<li
key={item}
className="relative pl-5 leading-normal"
style={{ fontSize: "14.5px" }}
>
<span className="absolute left-0" style={{ color: "var(--muted-foreground)" }}>
</span>
{item}
</li>
))}
</ul>
</div>
);
}
export function WelcomePage() {
return (
<div className="min-h-screen flex flex-col">
<header
className="flex items-center justify-between gap-3 px-4 sm:px-8 py-3.5"
style={{ borderBottom: "2px solid var(--border)" }}
>
<span className="font-bold text-lg">Second Brain</span>
<nav className="flex gap-3">
<Link href="/login" className="btn-aubade text-sm">
Войти
</Link>
<Link href="/register" className="btn-aubade btn-aubade-accent text-sm">
Зарегистрироваться
</Link>
</nav>
</header>
<main className="flex-1 w-full max-w-[1240px] mx-auto px-4 sm:px-8 pt-10 md:pt-14 pb-16">
<p
className="text-center text-xs font-bold uppercase mb-3.5"
style={{ color: "var(--muted-foreground)", letterSpacing: "0.18em" }}
>
Образовательная платформа Second Brain
</p>
<h1 className="text-center font-bold leading-tight text-[26px] md:text-[40px] mb-2.5">
Создаём и развиваем
<br />
второй мозг
</h1>
<p
className="text-center mb-8 md:mb-14"
style={{ color: "var(--muted-foreground)", fontSize: "17px" }}
>
Заметки, знания и идеи в одной системе, которая работает на вас
</p>
<div className="relative grid grid-cols-1 md:grid-cols-[1fr_320px_1fr] gap-y-5 md:gap-y-7 gap-x-11 items-center">
<SchemeConnectors />
<div className="md:col-start-2 md:row-start-1 md:row-span-3 flex flex-col items-center gap-4 mb-4 md:mb-0">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/logo.svg"
alt="Second Brain"
data-scheme-logo
className="w-[150px] h-[150px] md:w-[280px] md:h-[280px]"
/>
<span
className="text-xs font-bold uppercase"
style={{ color: "var(--muted-foreground)", letterSpacing: "0.18em" }}
>
Second Brain
</span>
</div>
{BLOCKS_LEFT.map((block, i) => (
<SchemeCard key={block.title} block={block} side="left" row={i} />
))}
{BLOCKS_RIGHT.map((block, i) => (
<SchemeCard key={block.title} block={block} side="right" row={i} />
))}
</div>
<div className="flex flex-col sm:flex-row justify-center gap-4 mt-10 md:mt-14">
<Link href="/register" className="btn-aubade btn-aubade-accent px-6 py-2.5">
Начать учиться
</Link>
<Link href="/login" className="btn-aubade px-6 py-2.5">
У меня уже есть доступ
</Link>
</div>
</main>
<footer
className="flex flex-col sm:flex-row justify-between gap-1.5 px-4 sm:px-8 py-4 text-sm"
style={{ borderTop: "2px solid var(--border)", color: "var(--muted-foreground)" }}
>
<span>ИП Second Brain Production</span>
<a href="https://second-brain.ru" className="underline" style={{ color: "var(--muted-foreground)" }}>
second-brain.ru все курсы и материалы
</a>
</footer>
</div>
);
}
+3 -1
View File
@@ -14,9 +14,11 @@ export function middleware(request: NextRequest) {
}
if (
pathname === "/" || // public welcome page; "/" must stay an EXACT match (startsWith would open everything)
PUBLIC_ROUTES.some((route) => pathname.startsWith(route)) ||
pathname.startsWith("/_next") ||
pathname.startsWith("/favicon")
pathname.startsWith("/favicon") ||
pathname === "/logo.svg"
) {
return NextResponse.next();
}