From 53ecde8d2b69f0a6071977ae108cf862d5736a8c Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Wed, 12 Aug 2026 12:29:54 +0200 Subject: [PATCH] Improve home onboarding and table interactions --- tests/helpers/data-validation.mjs | 25 +++ tests/static-styles.test.mjs | 1 + tests/static-toolboxes.test.mjs | 6 + website/public/data/site.json | 46 ++++- .../toolboxes/modules/TableModule.jsx | 193 +++++++++++++++++- website/src/pages/HomePage.jsx | 35 ++++ website/src/styles/_home.scss | 178 ++++++++++++++++ website/src/styles/_responsive.scss | 1 + website/src/styles/_toolboxes.scss | 8 + 9 files changed, 488 insertions(+), 5 deletions(-) diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index 78d8e80..b55ae53 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -81,6 +81,10 @@ 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.origin.eyebrow", "home.origin.title", "home.origin.visualAlt", @@ -527,6 +531,27 @@ 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`); + assertNonEmptyString({ card }, "card.icon"); + 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`); + assertNonEmptyString({ step }, "step.title"); + assertNonEmptyString({ step }, "step.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"); diff --git a/tests/static-styles.test.mjs b/tests/static-styles.test.mjs index 6d88837..3fcae2b 100644 --- a/tests/static-styles.test.mjs +++ b/tests/static-styles.test.mjs @@ -49,6 +49,7 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleToolboxes, /\.calculator-module/); assert.match(styleToolboxes, /\.table-module/); assert.match(styleToolboxes, /\.table-cell-input\.is-selected/); + assert.match(styleToolboxes, /\.table-cell-input\.is-move-target/); assert.match(styleToolboxes, /\.notepad-toolbar-button\.danger/); assert.match(styleToolboxes, /\.toolbox-icon-picker/); assert.match(styleToolboxes, /\.toolbox-game-icon/); diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs index 80faf06..9ea6059 100644 --- a/tests/static-toolboxes.test.mjs +++ b/tests/static-toolboxes.test.mjs @@ -216,6 +216,12 @@ test("toolbox module registry and modules expose expected behavior", async () => assert.match(tableModule, /notepad-toolbar-button/); assert.match(tableModule, /selection/); assert.match(tableModule, /draggingSelectionRef/); + assert.match(tableModule, /movingSelectionRef/); + assert.match(tableModule, /copiedSelectionRef/); + assert.match(tableModule, /moveSelectionCells/); + assert.match(tableModule, /pasteCellBlock/); + assert.match(tableModule, /parseTsvBlock/); + assert.match(tableModule, /is-move-target/); assert.match(tableModule, /isMultiCellSelection/); assert.match(tableModule, /handleCellKeyDown/); assert.match(tableModule, /clearSelection/); diff --git a/website/public/data/site.json b/website/public/data/site.json index b64ddcf..51c05c5 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -722,11 +722,11 @@ "features": [ { "title": "Grille éditable", - "text": "Ajout et retrait de lignes ou colonnes, édition des en-têtes et sélection de cellules.", + "text": "Ajout et retrait de lignes ou colonnes, édition des en-têtes, sélection, déplacement et copier-coller de cellules.", "details": [ "Les boutons ligne et colonne modifient la taille de la grille sans recréer le tableau.", "Les en-têtes peuvent être renommés pour donner un sens aux données.", - "La cellule active sert de repère pour certaines actions, comme appliquer une formule." + "Une cellule ou une sélection peut être déplacée par glisser-déposer, puis copiée-collée vers une autre zone du tableau." ] }, { @@ -1182,6 +1182,48 @@ "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.", + "steps": [ + { + "title": "Créez une toolbox", + "text": "Donnez-lui un nom simple, par exemple le nom du jeu ou de votre build." + }, + { + "title": "Ajoutez vos outils", + "text": "Choisissez seulement ce dont vous avez besoin : bloc notes, checklist, images, liens, timer ou compteur." + }, + { + "title": "Importez une sauvegarde si vous en avez une", + "text": "Une sauvegarde est un fichier partagé ou exporté depuis Sokko G. Elle permet de récupérer une toolbox déjà prête, mais elle n'est pas obligatoire." + }, + { + "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." + } + ] + }, "origin": { "eyebrow": "Origine du nom", "title": "Pourquoi Sokko G ?", diff --git a/website/src/features/toolboxes/modules/TableModule.jsx b/website/src/features/toolboxes/modules/TableModule.jsx index 7432e65..eac4053 100644 --- a/website/src/features/toolboxes/modules/TableModule.jsx +++ b/website/src/features/toolboxes/modules/TableModule.jsx @@ -72,6 +72,10 @@ function isCellSelected(selection, rowIndex, columnIndex) { return Boolean(bounds && rowIndex >= bounds.startRow && rowIndex <= bounds.endRow && columnIndex >= bounds.startColumn && columnIndex <= bounds.endColumn); } +function isCellInBounds(bounds, rowIndex, columnIndex) { + return Boolean(bounds && rowIndex >= bounds.startRow && rowIndex <= bounds.endRow && columnIndex >= bounds.startColumn && columnIndex <= bounds.endColumn); +} + function isMultiCellSelection(selection) { const bounds = getSelectionBounds(selection); return Boolean(bounds && (bounds.startRow !== bounds.endRow || bounds.startColumn !== bounds.endColumn)); @@ -102,13 +106,116 @@ function getFormulaRangeText(selection) { return addresses.length === 1 ? addresses[0] : `(${addresses.join("+")})`; } +function getCellBlock(cells, bounds) { + if (!bounds) return null; + const values = []; + for (let rowIndex = bounds.startRow; rowIndex <= bounds.endRow; rowIndex += 1) { + const row = []; + for (let columnIndex = bounds.startColumn; columnIndex <= bounds.endColumn; columnIndex += 1) { + row.push(cells[cellAddress(rowIndex, columnIndex)] || ""); + } + values.push(row); + } + return { + height: bounds.endRow - bounds.startRow + 1, + width: bounds.endColumn - bounds.startColumn + 1, + values + }; +} + +function parseTsvBlock(text) { + const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + const lines = normalizedText.endsWith("\n") ? normalizedText.slice(0, -1).split("\n") : normalizedText.split("\n"); + if (!lines.length || (lines.length === 1 && !lines[0])) return null; + const values = lines.map((line) => line.split("\t")); + return { + height: values.length, + width: Math.max(...values.map((row) => row.length)), + values + }; +} + +function getBoundsFromStartAndSize(rowIndex, columnIndex, height, width) { + return { + startRow: rowIndex, + endRow: rowIndex + height - 1, + startColumn: columnIndex, + endColumn: columnIndex + width - 1 + }; +} + +function isSameBounds(firstBounds, secondBounds) { + return Boolean(firstBounds && secondBounds + && firstBounds.startRow === secondBounds.startRow + && firstBounds.endRow === secondBounds.endRow + && firstBounds.startColumn === secondBounds.startColumn + && firstBounds.endColumn === secondBounds.endColumn); +} + +function moveSelectionCells(cells, sourceBounds, targetBounds) { + const nextCells = { ...cells }; + const snapshot = []; + + for (let rowIndex = sourceBounds.startRow; rowIndex <= sourceBounds.endRow; rowIndex += 1) { + for (let columnIndex = sourceBounds.startColumn; columnIndex <= sourceBounds.endColumn; columnIndex += 1) { + const value = cells[cellAddress(rowIndex, columnIndex)] || ""; + snapshot.push({ + rowOffset: rowIndex - sourceBounds.startRow, + columnOffset: columnIndex - sourceBounds.startColumn, + value + }); + delete nextCells[cellAddress(rowIndex, columnIndex)]; + } + } + + for (let rowIndex = targetBounds.startRow; rowIndex <= targetBounds.endRow; rowIndex += 1) { + for (let columnIndex = targetBounds.startColumn; columnIndex <= targetBounds.endColumn; columnIndex += 1) { + delete nextCells[cellAddress(rowIndex, columnIndex)]; + } + } + + snapshot.forEach(({ rowOffset, columnOffset, value }) => { + if (!String(value || "").trim()) return; + nextCells[cellAddress(targetBounds.startRow + rowOffset, targetBounds.startColumn + columnOffset)] = value; + }); + + return nextCells; +} + +function pasteCellBlock(cells, block, targetRow, targetColumn, rows, columns) { + const clippedHeight = Math.min(block?.height || 0, rows - targetRow); + const clippedWidth = Math.min(block?.width || 0, columns - targetColumn); + if (clippedHeight <= 0 || clippedWidth <= 0) return null; + + const nextCells = { ...cells }; + for (let rowOffset = 0; rowOffset < clippedHeight; rowOffset += 1) { + for (let columnOffset = 0; columnOffset < clippedWidth; columnOffset += 1) { + const address = cellAddress(targetRow + rowOffset, targetColumn + columnOffset); + const value = String(block.values[rowOffset]?.[columnOffset] || ""); + if (value.trim()) { + nextCells[address] = value; + } else { + delete nextCells[address]; + } + } + } + + return { + cells: nextCells, + bounds: getBoundsFromStartAndSize(targetRow, targetColumn, clippedHeight, clippedWidth) + }; +} + export function TableModule({ toolboxId, moduleId, context, editing }) { const data = context.normalizeTableData(context.getModuleData(toolboxId, moduleId, { rows: DEFAULT_ROWS, columns: DEFAULT_COLUMNS, cells: {} })); const textContent = context.moduleText?.table || {}; const [editingCell, setEditingCell] = useState(""); const [highlightedCell, setHighlightedCell] = useState(""); const [selection, setSelection] = useState(null); + const [movePreview, setMovePreview] = useState(null); const draggingSelectionRef = useRef(false); + const movingSelectionRef = useRef(null); + const copiedSelectionRef = useRef(null); const formulaSelectionRef = useRef(null); const inputRefs = useRef(new Map()); const highlightTimeoutRef = useRef(0); @@ -118,6 +225,10 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { useEffect(() => { function stopSelectionDrag() { + if (movingSelectionRef.current) { + finishMovingSelection(); + return; + } if (formulaSelectionRef.current) { const formulaSelection = formulaSelectionRef.current; formulaSelectionRef.current = null; @@ -166,6 +277,48 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { window.requestAnimationFrame(() => inputRefs.current.get(focusedAddress)?.focus()); } + function startMovingSelection(rowIndex, columnIndex) { + const sourceBounds = getSelectionBounds(selection); + if (!sourceBounds || !isCellSelected(selection, rowIndex, columnIndex)) return false; + const height = sourceBounds.endRow - sourceBounds.startRow + 1; + const width = sourceBounds.endColumn - sourceBounds.startColumn + 1; + const targetBounds = getBoundsFromStartAndSize(sourceBounds.startRow, sourceBounds.startColumn, height, width); + movingSelectionRef.current = { sourceBounds, targetBounds, height, width }; + setMovePreview(targetBounds); + return true; + } + + function updateMovePreview(rowIndex, columnIndex) { + const movingSelection = movingSelectionRef.current; + if (!movingSelection) return false; + const targetRow = Math.min(data.rows - movingSelection.height, Math.max(0, rowIndex)); + const targetColumn = Math.min(data.columns - movingSelection.width, Math.max(0, columnIndex)); + const targetBounds = getBoundsFromStartAndSize(targetRow, targetColumn, movingSelection.height, movingSelection.width); + movingSelectionRef.current = { ...movingSelection, targetBounds }; + setMovePreview(targetBounds); + return true; + } + + function finishMovingSelection() { + const movingSelection = movingSelectionRef.current; + movingSelectionRef.current = null; + draggingSelectionRef.current = false; + setMovePreview(null); + if (!movingSelection || isSameBounds(movingSelection.sourceBounds, movingSelection.targetBounds)) return; + const nextCells = moveSelectionCells(data.cells, movingSelection.sourceBounds, movingSelection.targetBounds); + save({ ...data, cells: nextCells }); + const anchor = { + rowIndex: movingSelection.targetBounds.startRow, + columnIndex: movingSelection.targetBounds.startColumn + }; + const focus = { + rowIndex: movingSelection.targetBounds.endRow, + columnIndex: movingSelection.targetBounds.endColumn + }; + setSelection({ anchor, focus }); + focusCell(anchor.rowIndex, anchor.columnIndex); + } + function resize(rows, columns) { save({ rows, @@ -295,7 +448,9 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { } async function copyTsv() { - const text = getTsv(isMultiCellSelection(selection) ? getSelectionBounds(selection) : null); + const bounds = isMultiCellSelection(selection) ? getSelectionBounds(selection) : null; + copiedSelectionRef.current = getCellBlock(data.cells, bounds || getBoundsFromStartAndSize(0, 0, data.rows, data.columns)); + const text = getTsv(bounds); if (!await context.copyText(text)) return; context.notify?.(textContent.copiedTitle || "Tableau copié"); } @@ -303,11 +458,34 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { async function copySelection(event) { if (!selection || !(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "c") return; event.preventDefault(); - const text = getTsv(getSelectionBounds(selection)); + 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é"); } + function pasteBlock(block, rowIndex, columnIndex) { + const result = pasteCellBlock(data.cells, block, rowIndex, columnIndex, data.rows, data.columns); + if (!result) return false; + save({ ...data, cells: result.cells }); + setSelection({ + anchor: { rowIndex: result.bounds.startRow, columnIndex: result.bounds.startColumn }, + focus: { rowIndex: result.bounds.endRow, columnIndex: result.bounds.endColumn } + }); + focusCell(result.bounds.startRow, result.bounds.startColumn); + return true; + } + + function pasteSelection(event, rowIndex, columnIndex, isEditing) { + if (isEditing) return; + const text = event.clipboardData?.getData("text/plain") || ""; + const block = parseTsvBlock(text) || copiedSelectionRef.current; + if (!block) return; + event.preventDefault(); + pasteBlock(block, rowIndex, columnIndex); + } + function selectCell(rowIndex, columnIndex, extend = false) { const cell = { rowIndex, columnIndex }; setSelection((currentSelection) => extend && currentSelection?.anchor @@ -517,6 +695,7 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { textContent={textContent} rowLabel={data.rowLabels?.[rowIndex] || ""} selection={selection} + movePreview={movePreview} onRowLabelChange={setRowLabel} onFocus={(cell) => { const parsed = parseCellAddress(cell); @@ -528,8 +707,11 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { onCellChange={setCellValue} onFormulaSelectionStart={startFormulaSelection} onFormulaSelectionExtend={extendFormulaSelection} + onStartMoveSelection={startMovingSelection} + onMoveSelection={updateMovePreview} onSelectCell={selectCell} onExtendSelection={extendSelection} + onPasteSelection={pasteSelection} draggingSelectionRef={draggingSelectionRef} onCellKeyDown={handleCellKeyDown} onStartEditing={startEditing} @@ -542,7 +724,7 @@ export function TableModule({ toolboxId, moduleId, context, editing }) { ); } -function TableRow({ rowIndex, columns, cells, evaluatedCells, editingCell, highlightedCell, textContent, rowLabel, selection, onRowLabelChange, onFocus, onBlur, onCellChange, onFormulaSelectionStart, onFormulaSelectionExtend, onSelectCell, onExtendSelection, draggingSelectionRef, onCellKeyDown, onStartEditing, registerInput }) { +function TableRow({ rowIndex, columns, cells, evaluatedCells, editingCell, highlightedCell, textContent, rowLabel, selection, movePreview, onRowLabelChange, onFocus, onBlur, onCellChange, onFormulaSelectionStart, onFormulaSelectionExtend, onStartMoveSelection, onMoveSelection, onSelectCell, onExtendSelection, onPasteSelection, draggingSelectionRef, onCellKeyDown, onStartEditing, registerInput }) { return ( <> { if (onFormulaSelectionStart(rowIndex, columnIndex)) { event.preventDefault(); + } else if (!event.shiftKey && onStartMoveSelection(rowIndex, columnIndex)) { + event.preventDefault(); } else { draggingSelectionRef.current = true; onSelectCell(rowIndex, columnIndex, event.shiftKey); @@ -586,12 +771,14 @@ function TableRow({ rowIndex, columns, cells, evaluatedCells, editingCell, highl onDoubleClick={() => onStartEditing(address, null, { select: true })} onMouseEnter={() => { if (onFormulaSelectionExtend(rowIndex, columnIndex)) return; + if (onMoveSelection(rowIndex, columnIndex)) return; if (draggingSelectionRef.current) onExtendSelection(rowIndex, columnIndex); }} onFocus={() => onFocus(address)} onBlur={() => onBlur(address)} onChange={(event) => onCellChange(address, event.target.value)} onKeyDown={(event) => onCellKeyDown(event, rowIndex, columnIndex, address, isEditing)} + onPaste={(event) => onPasteSelection(event, rowIndex, columnIndex, isEditing)} aria-label={`${textContent.cellLabel || "Cellule"} ${address}`} title={evaluated?.error && evaluated.error !== "empty" ? textContent.errorTitle || "Formule invalide" : address} inputMode="text" diff --git a/website/src/pages/HomePage.jsx b/website/src/pages/HomePage.jsx index 30864ef..38ec6b3 100644 --- a/website/src/pages/HomePage.jsx +++ b/website/src/pages/HomePage.jsx @@ -6,6 +6,7 @@ import { RichText } from "../components/RichText.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; return ( <>
@@ -23,6 +24,40 @@ export function HomePage({ siteContent, toolboxes }) {
{toolCount}{toolCount > 1 ? content.stats.toolPlural : content.stats.toolSingular}
+
+ {showStartMap && ( +
+
+

{content.startMap.eyebrow}

+

{content.startMap.title}

+

{content.startMap.description}

+
+
    + {content.startMap.steps.map((step, index) => ( +
  1. + {index + 1} +
    + {step.title} +

    {step.text}

    +
    +
  2. + ))} +
+
+ )} +
+
+ {content.orientation.cards.map((card) => ( + + + ))} +
+
+

{content.origin.eyebrow}

diff --git a/website/src/styles/_home.scss b/website/src/styles/_home.scss index b30844f..3560128 100644 --- a/website/src/styles/_home.scss +++ b/website/src/styles/_home.scss @@ -184,6 +184,184 @@ span { line-height: 1; } +.home-section-heading { + display: grid; + gap: var(--space-2); + min-width: 0; +} + +.home-section-heading h2, +.home-section-heading p { + margin: 0; +} + +.home-section-heading p:not(.eyebrow) { + max-width: 980px; +} + +.home-orientation { + display: grid; + gap: var(--space-5); + margin-top: var(--space-6); + padding: var(--space-5); +} + +.home-orientation-main { + display: grid; +} + +.home-orientation-main.has-start-map { + padding-top: var(--space-5); + border-top: 1px solid rgba(150, 165, 205, 0.12); +} + +.home-orientation-grid { + display: grid; + grid-template-columns: repeat(2, minmax(280px, 440px)); + 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; + 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 { + display: grid; + gap: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.home-start-steps li { + display: flex; + min-width: 0; + gap: var(--space-3); + padding: var(--space-3) 0; + border-top: 1px solid rgba(150, 165, 205, 0.1); +} + +.home-start-steps li:first-child { + padding-top: 0; + border-top: 0; +} + +.home-start-steps li:last-child { + padding-bottom: 0; +} + +.home-start-steps li > span { + display: grid; + width: 28px; + min-width: 28px; + height: 28px; + place-items: center; + border: 1px solid rgba(246, 196, 83, 0.32); + border-radius: var(--radius-sm); + background: rgba(246, 196, 83, 0.1); + color: var(--color-accent-gold); + font-size: var(--font-size-sm); + font-weight: 900; +} + +.home-start-steps div { + display: grid; + gap: 4px; + min-width: 0; +} + +.home-start-steps strong, +.home-start-steps p { + margin: 0; +} + +.home-start-steps strong { + color: var(--color-text-primary); +} + +.home-start-steps p { + font-size: var(--font-size-sm); +} + .home-visual { display: grid; align-self: center; diff --git a/website/src/styles/_responsive.scss b/website/src/styles/_responsive.scss index ab5df85..fbeb39a 100644 --- a/website/src/styles/_responsive.scss +++ b/website/src/styles/_responsive.scss @@ -76,6 +76,7 @@ .game-hero, .page-hero, .origin-section, + .home-orientation-grid, .about-story-section, .about-contribute-section, .about-tools-list, diff --git a/website/src/styles/_toolboxes.scss b/website/src/styles/_toolboxes.scss index 82f8ebd..bcc9776 100644 --- a/website/src/styles/_toolboxes.scss +++ b/website/src/styles/_toolboxes.scss @@ -5536,6 +5536,14 @@ button.combo-input-token.combo-value-grab .ui-icon { box-shadow: inset 0 0 0 1px rgba(34, 211, 238, 0.42); } +.table-cell-input.is-move-target { + background: + linear-gradient(135deg, rgba(246, 196, 83, 0.14), rgba(34, 211, 238, 0.08)), + rgba(21, 26, 48, 0.64); + box-shadow: inset 0 0 0 1px rgba(246, 196, 83, 0.66); + cursor: move; +} + .table-cell-input.is-selected:focus { box-shadow: inset 0 0 0 1px rgba(246, 196, 83, 0.6), 0 0 0 2px rgba(34, 211, 238, 0.2); }