Improve onboarding section across home and about
All checks were successful
Deploy Sokko G / deploy (push) Successful in 8s

This commit is contained in:
Shinuwa 2026-08-12 21:45:09 +02:00
parent 53ecde8d2b
commit 2f1526b86b
17 changed files with 440 additions and 187 deletions

View file

@ -81,10 +81,12 @@ export function validateSiteContent(site) {
"home.stats.toolboxPlural",
"home.stats.toolSingular",
"home.stats.toolPlural",
"home.orientation.ariaLabel",
"home.startMap.eyebrow",
"home.startMap.title",
"home.startMap.description",
"home.onboarding.eyebrow",
"home.onboarding.title",
"home.onboarding.description",
"home.onboarding.stepsTitle",
"home.onboarding.sliderLabel",
"home.onboarding.slideButtonLabel",
"home.origin.eyebrow",
"home.origin.title",
"home.origin.visualAlt",
@ -531,27 +533,39 @@ export function validateSiteContent(site) {
assertNonEmptyString({ tool }, "tool.description");
});
const orientationCards = valueAt(site, "home.orientation.cards");
assert.ok(Array.isArray(orientationCards), "home.orientation.cards must be an array");
assert.ok(orientationCards.length >= 2, "home.orientation.cards must contain at least 2 items");
orientationCards.forEach((card, index) => {
assert.equal(typeof card, "object", `home.orientation.cards[${index}] must be an object`);
const onboardingCards = valueAt(site, "home.onboarding.cards");
assert.ok(Array.isArray(onboardingCards), "home.onboarding.cards must be an array");
assert.ok(onboardingCards.length >= 2, "home.onboarding.cards must contain at least 2 items");
onboardingCards.forEach((card, index) => {
assert.equal(typeof card, "object", `home.onboarding.cards[${index}] must be an object`);
assertNonEmptyString({ card }, "card.icon");
assertNonEmptyString({ card }, "card.image");
assertNonEmptyString({ card }, "card.title");
assertNonEmptyString({ card }, "card.text");
assertNonEmptyString({ card }, "card.action");
assertNonEmptyString({ card }, "card.href");
});
const startMapSteps = valueAt(site, "home.startMap.steps");
assert.ok(Array.isArray(startMapSteps), "home.startMap.steps must be an array");
assert.ok(startMapSteps.length >= 1, "home.startMap.steps must contain at least 1 item");
startMapSteps.forEach((step, index) => {
assert.equal(typeof step, "object", `home.startMap.steps[${index}] must be an object`);
const onboardingSteps = valueAt(site, "home.onboarding.steps");
assert.ok(Array.isArray(onboardingSteps), "home.onboarding.steps must be an array");
assert.ok(onboardingSteps.length >= 1, "home.onboarding.steps must contain at least 1 item");
onboardingSteps.forEach((step, index) => {
assert.equal(typeof step, "object", `home.onboarding.steps[${index}] must be an object`);
assertNonEmptyString({ step }, "step.title");
assertNonEmptyString({ step }, "step.text");
});
const onboardingSlides = valueAt(site, "home.onboarding.slides");
assert.ok(Array.isArray(onboardingSlides), "home.onboarding.slides must be an array");
assert.equal(onboardingSlides.length, 4, "home.onboarding.slides must contain 4 items");
onboardingSlides.forEach((slide, index) => {
assert.equal(typeof slide, "object", `home.onboarding.slides[${index}] must be an object`);
assertNonEmptyString({ slide }, "slide.src");
assertNonEmptyString({ slide }, "slide.alt");
assertNonEmptyString({ slide }, "slide.title");
assertNonEmptyString({ slide }, "slide.text");
});
const lines = valueAt(site, "home.origin.lines");
assert.ok(Array.isArray(lines), "home.origin.lines must be an array");
assert.ok(lines.length >= 2, "home.origin.lines must contain at least 2 items");

View file

@ -14,6 +14,7 @@ test("vite entrypoint and app shell are wired", async () => {
const imageViewer = await readFile("website/src/components/ImageViewer.jsx", "utf8");
const richText = await readFile("website/src/components/RichText.jsx", "utf8");
const homePage = await readFile("website/src/pages/HomePage.jsx", "utf8");
const homeOnboarding = await readFile("website/src/pages/HomeOnboardingSection.jsx", "utf8");
const aboutPage = await readFile("website/src/pages/AboutPage.jsx", "utf8");
const libraryPage = await readFile("website/src/pages/LibraryPage.jsx", "utf8");
const hashRouter = await readFile("website/src/router/hashRouter.js", "utf8");
@ -60,8 +61,15 @@ test("vite entrypoint and app shell are wired", async () => {
assert.match(richText, /value\.split\("\\n"\)/);
assert.match(richText, /<br key=/);
assert.match(homePage, /export function HomePage/);
assert.match(homePage, /HomeOnboardingSection/);
assert.match(homePage, /import\.meta\.env\.DEV \|\| toolboxes\.length === 0/);
assert.match(homePage, /dragon\.png/);
assert.match(homeOnboarding, /export function HomeOnboardingSection/);
assert.match(homeOnboarding, /prefers-reduced-motion: reduce/);
assert.match(homeOnboarding, /home-onboarding-dots/);
assert.match(aboutPage, /export function AboutPage/);
assert.match(aboutPage, /HomeOnboardingSection/);
assert.match(aboutPage, /siteContent\.home\.onboarding/);
assert.match(aboutPage, /module-icon-\$\{tool\.icon \|\| "notepad"\}/);
assert.match(libraryPage, /export function LibraryPage/);
assert.match(libraryPage, /LIBRARY_TOOLBOX_ID/);
@ -125,6 +133,12 @@ test("legacy icons and primary images are available", async () => {
await readFile("website/public/static/icons/zoom.svg", "utf8");
await readFile("website/public/favicon.ico");
await readFile("website/public/static/img/dragon.png");
await readFile("website/public/static/img/dragon-gameguide.png");
await readFile("website/public/static/img/dragon-toolbox.png");
await readFile("website/public/static/img/onboarding/toolbox-overview.png");
await readFile("website/public/static/img/onboarding/toolbox-tools.png");
await readFile("website/public/static/img/onboarding/toolbox-save.png");
await readFile("website/public/static/img/onboarding/toolbox-game-link.png");
await readFile("website/public/static/img/toolbox-icons/toolbox.png");
await readFile("website/public/static/img/toolbox-icons/sword.png");
await readFile("website/public/static/img/toolbox-icons/puzzle.png");

View file

@ -37,6 +37,9 @@ test("style entrypoint and theme layers are wired", async () => {
assert.match(styleHome, /font-style: italic/);
assert.match(styleHome, /\.about-page/);
assert.match(styleHome, /\.nebula-panel/);
assert.match(styleHome, /\.home-onboarding-layout/);
assert.match(styleHome, /\.home-onboarding-slider figcaption/);
assert.match(styleHome, /\.home-onboarding-dots/);
assert.match(styleHome, /\.about-story-section/);
assert.match(styleHome, /\.about-tools-list/);
assert.match(styleHome, /\.about-tool-item \.module-icon/);

View file

@ -1182,29 +1182,13 @@
"toolSingular": "outil disponible",
"toolPlural": "outils disponibles"
},
"orientation": {
"ariaLabel": "Accès rapides de la page d'accueil",
"cards": [
{
"icon": "toolbox",
"title": "Toolboxes",
"text": "Votre espace personnel : notes, listes, images, liens et outils pratiques enregistrés dans ce navigateur.",
"action": "Gérer mes toolboxes",
"href": "#/toolboxes"
},
{
"icon": "controller",
"title": "Jeux",
"text": "Les pages de référence disponibles. Vous pouvez ensuite y associer votre toolbox pour garder vos infos à côté.",
"action": "Voir les jeux",
"href": "#/games"
}
]
},
"startMap": {
"eyebrow": "Première toolbox",
"title": "Comment démarrer ?",
"description": "Vous n'avez pas encore de toolbox dans ce navigateur. Créez votre premier espace en quelques étapes, ou récupérez une sauvegarde si quelqu'un vous en a partagé une.",
"onboarding": {
"eyebrow": "Prise en main",
"title": "Prise en main",
"description": "Une toolbox est votre espace personnel pour garder vos notes, listes, images, liens et outils utiles dans ce navigateur.",
"stepsTitle": "Créer votre première toolbox",
"sliderLabel": "Aperçu de la prise en main",
"slideButtonLabel": "Afficher l'aperçu",
"steps": [
{
"title": "Créez une toolbox",
@ -1222,6 +1206,50 @@
"title": "Exportez pour sauvegarder",
"text": "Les données restent dans ce navigateur. Exportez vos toolboxes avant un nettoyage, un changement de machine ou une réinstallation."
}
],
"slides": [
{
"src": "/static/img/onboarding/toolbox-overview.png",
"alt": "Aperçu d'une toolbox Sokko G",
"title": "Un espace unique",
"text": "Regroupez les informations utiles à votre session dans une interface locale."
},
{
"src": "/static/img/onboarding/toolbox-tools.png",
"alt": "Aperçu des outils disponibles dans une toolbox",
"title": "Des outils à choisir",
"text": "Ajoutez seulement les outils dont vous avez besoin pour votre jeu ou votre objectif."
},
{
"src": "/static/img/onboarding/toolbox-save.png",
"alt": "Aperçu d'une sauvegarde de toolbox",
"title": "Des sauvegardes simples",
"text": "Exportez vos toolboxes pour les conserver ou les partager."
},
{
"src": "/static/img/onboarding/toolbox-game-link.png",
"alt": "Aperçu d'une toolbox associée à une page jeu",
"title": "Jeu et notes côte à côte",
"text": "Associez une toolbox à une page jeu pour garder vos repères personnels à proximité."
}
],
"cards": [
{
"icon": "toolbox",
"image": "/static/img/dragon-toolbox.png",
"title": "Toolboxes",
"text": "Votre espace personnel : notes, listes, images, liens et outils pratiques enregistrés dans ce navigateur.",
"action": "Gérer mes toolboxes",
"href": "#/toolboxes"
},
{
"icon": "controller",
"image": "/static/img/dragon-gameguide.png",
"title": "Guides de jeux",
"text": "Les pages de référence disponibles. Vous pouvez ensuite y associer votre toolbox pour garder vos infos à côté.",
"action": "Voir les jeux",
"href": "#/games"
}
]
},
"origin": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 636 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View file

@ -461,8 +461,7 @@ export function TableModule({ toolboxId, moduleId, context, editing }) {
const bounds = getSelectionBounds(selection);
copiedSelectionRef.current = getCellBlock(data.cells, bounds);
const text = getTsv(bounds);
if (!await context.copyText(text)) return;
context.notify?.(textContent.copiedTitle || "Tableau copié");
await context.copyText(text);
}
function pasteBlock(block, rowIndex, columnIndex) {

View file

@ -2,6 +2,7 @@
import React, { useEffect } from "react";
import { Icon } from "../components/Icon.jsx";
import { RichText } from "../components/RichText.jsx";
import { HomeOnboardingSection } from "./HomeOnboardingSection.jsx";
export function AboutPage({ siteContent }) {
const content = siteContent.about;
@ -60,6 +61,7 @@ export function AboutPage({ siteContent }) {
))}
</div>
</section>
<HomeOnboardingSection content={siteContent.home.onboarding} className="about-onboarding-section" />
<section className="about-tools-section nebula-panel">
<div className="page-heading">
<div>

View file

@ -0,0 +1,100 @@
// Rôle : affiche la section de prise en main de la home avec étapes, slider et accès rapides.
import React, { useEffect, useMemo, useState } from "react";
const SLIDE_INTERVAL_MS = 4500;
function prefersReducedMotion() {
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
}
export function HomeOnboardingSection({ content, className = "" }) {
const slides = useMemo(() => content?.slides || [], [content]);
const [activeSlide, setActiveSlide] = useState(0);
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
if (slides.length <= 1 || isPaused || prefersReducedMotion()) return undefined;
const intervalId = window.setInterval(() => {
setActiveSlide((current) => (current + 1) % slides.length);
}, SLIDE_INTERVAL_MS);
return () => window.clearInterval(intervalId);
}, [isPaused, slides.length]);
if (!content) return null;
const currentSlide = slides[activeSlide] || slides[0];
return (
<section className={["home-onboarding nebula-panel", className].filter(Boolean).join(" ")} aria-labelledby="home-onboarding-title">
<div className="home-section-heading home-onboarding-heading">
<p className="eyebrow">{content.eyebrow}</p>
<h2 id="home-onboarding-title">{content.title}</h2>
<p>{content.description}</p>
</div>
<div className="home-onboarding-layout">
<div className="home-onboarding-steps">
<h3>{content.stepsTitle}</h3>
<ol>
{content.steps.map((step, index) => (
<li key={step.title}>
<span>{index + 1}</span>
<div>
<strong>{step.title}</strong>
<p>{step.text}</p>
</div>
</li>
))}
</ol>
</div>
<div
className="home-onboarding-slider"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
onFocus={() => setIsPaused(true)}
onBlur={() => setIsPaused(false)}
>
{currentSlide && (
<figure>
<img src={currentSlide.src} alt={currentSlide.alt} />
<figcaption>
<strong>{currentSlide.title}</strong>
<span>{currentSlide.text}</span>
</figcaption>
</figure>
)}
{slides.length > 1 && (
<div className="home-onboarding-dots" aria-label={content.sliderLabel}>
{slides.map((slide, index) => (
<button
className={index === activeSlide ? "active" : ""}
key={slide.src}
type="button"
onClick={() => setActiveSlide(index)}
aria-label={`${content.slideButtonLabel} ${index + 1}`}
aria-pressed={index === activeSlide}
/>
))}
</div>
)}
</div>
</div>
<div className="home-onboarding-cards">
{content.cards.map((card) => (
<a className="home-onboarding-card" href={card.href} key={card.href}>
<span className="home-onboarding-card-visual">
<img src={card.image} alt="" aria-hidden="true" />
</span>
<span className="home-onboarding-card-body">
<span className="home-onboarding-card-title">
<span className={`module-icon-svg module-icon-${card.icon}`} aria-hidden="true" />
<strong>{card.title}</strong>
</span>
<span className="home-onboarding-card-text">{card.text}</span>
<em>{card.action}</em>
</span>
</a>
))}
</div>
</section>
);
}

View file

@ -2,11 +2,12 @@
import React from "react";
import { TOOLBOX_MODULES } from "../features/toolboxes/modules/index.jsx";
import { RichText } from "../components/RichText.jsx";
import { HomeOnboardingSection } from "./HomeOnboardingSection.jsx";
export function HomePage({ siteContent, toolboxes }) {
const content = siteContent.home;
const toolCount = Object.keys(TOOLBOX_MODULES).length;
const showStartMap = import.meta.env.DEV || toolboxes.length === 0;
const showOnboarding = import.meta.env.DEV || toolboxes.length === 0;
return (
<>
<section className="hero">
@ -24,40 +25,7 @@ export function HomePage({ siteContent, toolboxes }) {
<article><strong>{toolCount}</strong><span>{toolCount > 1 ? content.stats.toolPlural : content.stats.toolSingular}</span></article>
</div>
</section>
<section className="home-orientation nebula-panel" aria-label={content.orientation.ariaLabel}>
{showStartMap && (
<div className="home-start-map" aria-labelledby="home-start-map-title">
<div className="home-section-heading">
<p className="eyebrow">{content.startMap.eyebrow}</p>
<h2 id="home-start-map-title">{content.startMap.title}</h2>
<p>{content.startMap.description}</p>
</div>
<ol className="home-start-steps">
{content.startMap.steps.map((step, index) => (
<li key={step.title}>
<span>{index + 1}</span>
<div>
<strong>{step.title}</strong>
<p>{step.text}</p>
</div>
</li>
))}
</ol>
</div>
)}
<div className={showStartMap ? "home-orientation-main has-start-map" : "home-orientation-main"}>
<div className="home-orientation-grid">
{content.orientation.cards.map((card) => (
<a className="home-orientation-card" href={card.href} key={card.href}>
<span className={`module-icon-svg module-icon-${card.icon}`} aria-hidden="true" />
<strong>{card.title}</strong>
<span>{card.text}</span>
<em>{card.action}</em>
</a>
))}
</div>
</div>
</section>
{showOnboarding && <HomeOnboardingSection content={content.onboarding} />}
<section className="origin-section nebula-panel" aria-labelledby="origin-title">
<div className="origin-copy">
<p className="eyebrow">{content.origin.eyebrow}</p>

View file

@ -199,112 +199,38 @@ span {
max-width: 980px;
}
.home-orientation {
.home-onboarding {
display: grid;
gap: var(--space-5);
gap: var(--space-6);
margin-top: var(--space-6);
padding: var(--space-5);
padding: var(--space-6);
}
.home-orientation-main {
.home-onboarding-heading {
max-width: 980px;
}
.home-onboarding-layout {
display: grid;
grid-template-columns: minmax(280px, 0.85fr) minmax(360px, 1.15fr);
align-items: stretch;
gap: var(--space-5);
}
.home-orientation-main.has-start-map {
padding-top: var(--space-5);
border-top: 1px solid rgba(150, 165, 205, 0.12);
}
.home-orientation-grid {
.home-onboarding-steps {
display: grid;
grid-template-columns: repeat(2, minmax(280px, 440px));
align-content: start;
gap: var(--space-4);
justify-content: center;
}
.home-orientation-card {
position: relative;
isolation: isolate;
display: grid;
min-width: 0;
justify-items: center;
gap: var(--space-3);
overflow: hidden;
min-height: 220px;
padding: var(--space-4);
border: 1px solid transparent;
border-radius: var(--radius-md);
background:
linear-gradient(rgba(10, 13, 27, 0.92), rgba(10, 13, 27, 0.92)) padding-box,
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.2), transparent 28%) border-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.44), rgba(139, 92, 246, 0.22) 66%, rgba(34, 211, 238, 0.16)) border-box;
box-shadow: var(--shadow-sm);
text-decoration: none;
transition:
border-color var(--duration-fast) var(--ease-standard),
background var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
.home-orientation-card > * {
position: relative;
z-index: 1;
}
.home-orientation-card .module-icon-svg {
width: 36px;
height: 36px;
background: var(--color-accent-gold);
}
.home-orientation-card strong {
text-align: center;
.home-onboarding-steps h3 {
margin: 0;
color: var(--color-text-primary);
font-size: var(--font-size-lg);
}
.home-orientation-card span:not(.module-icon-svg) {
max-width: 620px;
text-align: center;
font-size: var(--font-size-sm);
}
.home-orientation-card em {
display: inline-flex;
min-height: 38px;
align-items: center;
justify-content: center;
width: fit-content;
margin-top: auto;
padding: 0 var(--space-3);
border: 1px solid rgba(246, 196, 83, 0.28);
border-radius: var(--radius-md);
background: rgba(246, 196, 83, 0.08);
color: var(--color-accent-gold);
font-style: normal;
font-size: var(--font-size-sm);
font-weight: 800;
}
.home-orientation-card:hover,
.home-orientation-card:focus-visible {
transform: none;
border-color: transparent;
background:
linear-gradient(rgba(18, 23, 44, 0.96), rgba(18, 23, 44, 0.96)) padding-box,
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.26), transparent 28%) border-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.58), rgba(196, 181, 253, 0.34) 62%, rgba(34, 211, 238, 0.2)) border-box;
box-shadow:
var(--shadow-sm),
0 0 18px rgba(139, 92, 246, 0.16);
}
.home-start-map {
display: grid;
gap: var(--space-5);
}
.home-start-steps {
.home-onboarding-steps ol {
display: grid;
gap: 0;
margin: 0;
@ -312,7 +238,7 @@ span {
list-style: none;
}
.home-start-steps li {
.home-onboarding-steps li {
display: flex;
min-width: 0;
gap: var(--space-3);
@ -320,16 +246,16 @@ span {
border-top: 1px solid rgba(150, 165, 205, 0.1);
}
.home-start-steps li:first-child {
.home-onboarding-steps li:first-child {
padding-top: 0;
border-top: 0;
}
.home-start-steps li:last-child {
.home-onboarding-steps li:last-child {
padding-bottom: 0;
}
.home-start-steps li > span {
.home-onboarding-steps li > span {
display: grid;
width: 28px;
min-width: 28px;
@ -343,41 +269,241 @@ span {
font-weight: 900;
}
.home-start-steps div {
.home-onboarding-steps div {
display: grid;
gap: 4px;
min-width: 0;
}
.home-start-steps strong,
.home-start-steps p {
.home-onboarding-steps strong,
.home-onboarding-steps p {
margin: 0;
}
.home-start-steps strong {
.home-onboarding-steps strong {
color: var(--color-text-primary);
}
.home-start-steps p {
.home-onboarding-steps p {
font-size: var(--font-size-sm);
}
.home-onboarding-slider {
position: relative;
display: grid;
min-width: 0;
overflow: hidden;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.38);
box-shadow: var(--shadow-sm);
}
.home-onboarding-slider figure {
position: relative;
display: grid;
min-height: 340px;
margin: 0;
}
.home-onboarding-slider img {
display: block;
width: 100%;
height: 100%;
min-height: 340px;
object-fit: cover;
}
.home-onboarding-slider figcaption {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: grid;
min-height: 4.8em;
align-content: center;
gap: 2px;
padding: var(--space-3) var(--space-4);
background: rgba(0, 0, 0, 0.56);
backdrop-filter: blur(8px);
}
.home-onboarding-slider figcaption strong,
.home-onboarding-slider figcaption span {
margin: 0;
}
.home-onboarding-slider figcaption strong {
color: var(--color-text-primary);
font-size: var(--font-size-md);
}
.home-onboarding-slider figcaption span {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.home-onboarding-dots {
position: absolute;
right: var(--space-3);
bottom: calc(4.8em + var(--space-3));
display: flex;
gap: 7px;
}
.home-onboarding-dots button {
width: 10px;
min-width: 10px;
height: 10px;
min-height: 10px;
padding: 0;
border-color: rgba(246, 196, 83, 0.36);
border-radius: var(--radius-pill);
background: rgba(5, 7, 17, 0.68);
}
.home-onboarding-dots button.active,
.home-onboarding-dots button:hover,
.home-onboarding-dots button:focus-visible {
background: var(--color-accent-gold);
box-shadow: 0 0 10px rgba(246, 196, 83, 0.26);
}
.home-onboarding-cards {
display: grid;
grid-template-columns: repeat(2, minmax(260px, 320px));
gap: var(--space-4);
justify-content: center;
padding-top: var(--space-6);
border-top: 1px solid rgba(150, 165, 205, 0.12);
}
.home-onboarding-card {
position: relative;
isolation: isolate;
display: grid;
min-width: 0;
overflow: hidden;
align-content: start;
padding: 0;
border: 1px solid transparent;
border-radius: var(--radius-md);
background:
linear-gradient(rgba(16, 20, 38, 0.88), rgba(16, 20, 38, 0.88)) padding-box,
radial-gradient(circle at 100% 0%, rgba(34, 211, 238, 0.42), transparent 28%) border-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.62) 0%, rgba(246, 196, 83, 0.42) 72%, rgba(34, 211, 238, 0.24) 100%) border-box;
box-shadow: var(--shadow-sm);
text-decoration: none;
transition:
border-color var(--duration-fast) var(--ease-standard),
background var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
@supports (corner-shape: scoop) {
.home-onboarding-card {
corner-shape: scoop;
}
}
.home-onboarding-card-visual {
position: relative;
display: grid;
aspect-ratio: 3 / 2;
min-height: 0;
place-items: center;
overflow: hidden;
border-bottom: 1px solid rgba(246, 196, 83, 0.42);
background:
radial-gradient(circle at 50% 15%, rgba(246, 196, 83, 0.12), transparent 34%),
rgba(5, 7, 17, 0.28);
}
.home-onboarding-card-visual img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.3));
}
.home-onboarding-card-body {
display: grid;
gap: var(--space-3);
justify-items: center;
padding: var(--space-4);
}
.home-onboarding-card-title {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 9px;
min-width: 0;
color: var(--color-text-primary);
}
.home-onboarding-card-title .module-icon-svg {
width: 20px;
min-width: 20px;
height: 20px;
background: var(--color-accent-gold);
}
.home-onboarding-card strong {
color: var(--color-text-primary);
font-size: var(--font-size-lg);
line-height: 1.2;
}
.home-onboarding-card-text {
max-width: 30ch;
text-align: center;
font-size: var(--font-size-sm);
}
.home-onboarding-card em {
display: inline-flex;
min-height: 38px;
align-items: center;
justify-content: center;
width: fit-content;
padding: 0 var(--space-3);
border: 1px solid rgba(246, 196, 83, 0.28);
border-radius: var(--radius-md);
background: rgba(246, 196, 83, 0.08);
color: var(--color-accent-gold);
font-style: normal;
font-size: var(--font-size-sm);
font-weight: 800;
}
.home-onboarding-card:hover,
.home-onboarding-card:focus-visible {
transform: none;
border-color: transparent;
background:
linear-gradient(rgba(18, 23, 44, 0.96), rgba(18, 23, 44, 0.96)) padding-box,
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.26), transparent 28%) border-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.58), rgba(196, 181, 253, 0.34) 62%, rgba(34, 211, 238, 0.2)) border-box;
box-shadow:
var(--shadow-sm),
0 0 18px rgba(139, 92, 246, 0.16);
}
.home-visual {
display: grid;
align-self: center;
justify-self: stretch;
width: 100%;
height: 100%;
min-height: 320px;
justify-self: center;
width: min(100%, 640px);
place-items: center;
}
.home-visual img {
display: block;
width: 100%;
height: 100%;
height: auto;
max-height: 520px;
object-fit: cover;
border-radius: var(--radius-lg);
filter: drop-shadow(0 22px 36px rgba(0, 0, 0, 0.34));
}
@ -1019,7 +1145,7 @@ span {
.origin-section {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.65fr);
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.78fr);
gap: var(--space-6);
margin-top: var(--space-6);
padding: var(--space-6);
@ -1081,6 +1207,10 @@ span {
gap: var(--space-6);
}
.about-onboarding-section {
margin-top: 0;
}
.about-story-section {
display: grid;
grid-template-columns: minmax(210px, 260px) minmax(0, 1fr);

View file

@ -76,7 +76,8 @@
.game-hero,
.page-hero,
.origin-section,
.home-orientation-grid,
.home-onboarding-layout,
.home-onboarding-cards,
.about-story-section,
.about-contribute-section,
.about-tools-list,
@ -121,13 +122,7 @@
}
.home-visual {
justify-self: center;
width: min(320px, 100%);
min-height: 240px;
}
.home-visual img {
object-fit: contain;
}
.origin-section {