-
+
{levelIndex + 1}/{worldLevels.length}
{level.title}
@@ -345,19 +349,21 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
className={`gm-btn ${targetPlaying ? 'active' : 'target'}`}
onClick={handlePlayTarget}
>
- {targetPlaying ? 'โน Parar' : '๐ฏ Objetivo'}
+ {targetPlaying ? 'โน' : '๐ฏ'}{!isMobile && {targetPlaying ? ' Parar' : ' Objetivo'}}
-
+ {!isMobile && (
+
+ )}
{adminMode && (
- {/* Left sidebar */}
+ {/* Left sidebar (desktop only โ hidden on mobile via CSS) */}
{/* Description โ always visible */}
@@ -502,6 +508,89 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
+ {/* Mobile bottom sheet with tabs (replaces sidebar) */}
+ {isMobile && (
+
+ {mobileTab === 'mission' && (
+
+
{level.description}
+ {!showHint ? (
+
+ ) : (
+
+
๐ก Pista max โ
โ
+
{level.concept}
+
+ )}
+
+ )}
+
+ {mobileTab === 'objectives' && (
+
+ {level.checks.map((check, i) => {
+ const passed = result?.checks?.[i]?.passed;
+ const cappedByStar = hintUsed && check.star === 3;
+ return (
+
+ {'โ
'.repeat(check.star)}
+
+ {check.desc}
+ {cappedByStar && ' ๐'}
+
+ {passed === true && !cappedByStar && โ}
+ {passed === false && โ}
+
+ );
+ })}
+ {hintUsed && (
+
+ Pista usada โ maximo 2 estrellas (permanente).
+
+ )}
+
+ )}
+
+ {mobileTab === 'modules' && (
+
+ {level.availableModules.length > 0 ? (
+
+ {level.availableModules.map(type => {
+ const def = getModuleDef(type);
+ if (!def) return null;
+ return (
+
handleAddModule(type)}>
+ {def.icon}
+ {def.name}
+
+ );
+ })}
+
+ ) : (
+
No hay modulos extra disponibles para este nivel.
+ )}
+
+
+
+ )}
+
+ )}
+
{/* Level complete overlay */}
{result && result.stars >= 1 && (
= world.unlockStars;
}
+const MOBILE_TABS = [
+ { id: 'game', label: 'JUEGO', icon: '๐ฎ' },
+ { id: 'sandbox', label: 'SANDBOX', icon: '๐' },
+ { id: 'config', label: 'CONFIG', icon: 'โ' },
+];
+
export default function WorldMap({ onSelectLevel, onSandbox, onAdmin }) {
const totalStars = getTotalStars();
const maxStars = getMaxStars();
const [search, setSearch] = useState('');
const searchRef = useRef(null);
+ const isMobile = useIsMobile();
const query = search.trim().toLowerCase();
@@ -209,6 +218,18 @@ export default function WorldMap({ onSelectLevel, onSandbox, onAdmin }) {
);
})
)}
+
+ {/* Mobile tab bar */}
+ {isMobile && (
+ {
+ if (id === 'sandbox') onSandbox?.();
+ if (id === 'config') onAdmin?.();
+ }}
+ />
+ )}
);
}
diff --git a/src/hooks/useIsMobile.js b/src/hooks/useIsMobile.js
new file mode 100644
index 0000000..5f65d8a
--- /dev/null
+++ b/src/hooks/useIsMobile.js
@@ -0,0 +1,14 @@
+import { useState, useEffect } from 'react';
+
+export function useIsMobile(breakpoint = 768) {
+ const [isMobile, setIsMobile] = useState(() => window.innerWidth <= breakpoint);
+
+ useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${breakpoint}px)`);
+ const handler = (e) => setIsMobile(e.matches);
+ mql.addEventListener('change', handler);
+ return () => mql.removeEventListener('change', handler);
+ }, [breakpoint]);
+
+ return isMobile;
+}
diff --git a/src/index.css b/src/index.css
index bfce7b0..917e088 100644
--- a/src/index.css
+++ b/src/index.css
@@ -785,3 +785,236 @@ html, body, #root {
.admin-star-btn.active { background: var(--yellow); color: var(--bg); border-color: var(--yellow); }
.admin-star-btn.zero { color: var(--red); }
.admin-star-btn.zero:hover { border-color: var(--red); color: var(--red); }
+
+/* ============================================
+ MOBILE RESPONSIVE โ max-width: 768px
+ ============================================ */
+
+/* --- Bottom Sheet --- */
+.bottom-sheet {
+ display: none;
+}
+
+/* --- Mobile Tab Bar --- */
+.mobile-tab-bar {
+ display: none;
+}
+
+@media (max-width: 768px) {
+
+ /* --- Sandbox Toolbar --- */
+ .toolbar { height: 44px; padding: 0 12px; gap: 6px; }
+ .toolbar-title { font-size: 13px; letter-spacing: 0.8px; }
+ .toolbar-sep, .toolbar .status-text,
+ .toolbar-btn.save-btn, .toolbar-btn.load-btn,
+ .toolbar-btn.export-btn, .toolbar-btn.import-btn,
+ .toolbar-btn.demo-btn, .toolbar-btn.clear-btn { display: none; }
+ .toolbar-btn.start-btn { padding: 4px 10px; font-size: 11px; }
+
+ /* Hamburger menu button (added via JS) */
+ .mobile-menu-btn {
+ padding: 6px 10px; background: var(--surface); border: 1px solid var(--border);
+ border-radius: 6px; color: var(--text2); cursor: pointer; font-size: 18px;
+ font-weight: 600; line-height: 1;
+ }
+ .mobile-menu-btn:hover { border-color: var(--accent); color: var(--text); }
+
+ .mobile-menu-overlay {
+ position: fixed; inset: 0; background: rgba(0,0,0,0.6);
+ z-index: 200; display: flex; justify-content: flex-end;
+ }
+ .mobile-menu-panel {
+ width: 260px; background: var(--panel); border-left: 1px solid var(--border);
+ padding: 16px; display: flex; flex-direction: column; gap: 6px;
+ animation: slideInRight 0.2s ease-out;
+ }
+ @keyframes slideInRight {
+ from { transform: translateX(100%); }
+ to { transform: translateX(0); }
+ }
+ .mobile-menu-panel .toolbar-btn {
+ display: flex; width: 100%; padding: 10px 14px;
+ font-size: 13px; text-align: left;
+ }
+
+ /* --- Mobile Action Bar (Sandbox) --- */
+ .mobile-action-bar {
+ display: flex; align-items: center; gap: 8px;
+ padding: 0 12px; height: 48px; background: var(--panel);
+ border-top: 1px solid var(--border); flex-shrink: 0;
+ }
+ .mobile-action-bar .start-btn-mobile {
+ flex: 1; padding: 8px 14px; background: var(--accent); color: #000;
+ border: none; border-radius: 6px; font-size: 12px; font-weight: 700;
+ font-family: 'JetBrains Mono', monospace; letter-spacing: 1px; cursor: pointer;
+ text-transform: uppercase;
+ }
+ .mobile-action-bar .start-btn-mobile.active {
+ background: var(--red);
+ }
+ .mobile-action-bar .action-icon-btn {
+ padding: 8px 12px; background: var(--surface); border: 1px solid var(--border);
+ border-radius: 6px; color: var(--text2); cursor: pointer; font-size: 14px;
+ }
+ .mobile-action-bar .action-icon-btn:hover { border-color: var(--accent); }
+
+ /* --- Status Bar --- */
+ .status-bar { display: none; }
+
+ /* --- Module Palette --- */
+ .palette { display: none; }
+
+ /* --- Bottom Sheet (visible on mobile) --- */
+ .bottom-sheet {
+ display: flex; flex-direction: column;
+ background: var(--panel); border-top: 1px solid var(--border);
+ border-radius: 16px 16px 0 0; flex-shrink: 0;
+ max-height: 40vh; transition: max-height 0.3s ease;
+ overflow: hidden;
+ }
+ .bottom-sheet.expanded { max-height: 60vh; }
+
+ .bottom-sheet-handle {
+ display: flex; justify-content: center; padding: 10px 0 6px;
+ cursor: grab;
+ }
+ .bottom-sheet-handle-bar {
+ width: 40px; height: 4px; background: var(--border);
+ border-radius: 2px;
+ }
+
+ .bottom-sheet-tabs {
+ display: flex; padding: 0 16px; gap: 0;
+ border-bottom: 1px solid var(--border);
+ }
+ .bottom-sheet-tab {
+ flex: 1; padding: 8px 0; background: none; border: none;
+ color: var(--text2); font-size: 10px; font-weight: 700;
+ font-family: 'JetBrains Mono', monospace; letter-spacing: 1px;
+ cursor: pointer; text-align: center; position: relative;
+ text-transform: uppercase;
+ }
+ .bottom-sheet-tab.active { color: var(--accent); }
+ .bottom-sheet-tab-line {
+ position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
+ width: 100%; height: 2px; background: var(--accent); border-radius: 1px;
+ }
+
+ .bottom-sheet-content {
+ padding: 12px 16px; overflow-y: auto; flex: 1;
+ }
+
+ /* Module grid tiles (mobile palette) */
+ .mobile-module-grid {
+ display: grid; grid-template-columns: repeat(5, 1fr);
+ gap: 8px;
+ }
+ .mobile-module-tile {
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
+ padding: 10px 4px; background: var(--surface); border: 1px solid var(--border);
+ border-radius: 8px; cursor: pointer; transition: all 0.15s;
+ }
+ .mobile-module-tile:hover, .mobile-module-tile:active {
+ border-color: var(--accent); background: var(--surface2);
+ }
+ .mobile-module-tile .tile-icon { font-size: 20px; }
+ .mobile-module-tile .tile-name {
+ font-size: 9px; font-weight: 600; color: var(--text2);
+ font-family: 'JetBrains Mono', monospace; text-transform: uppercase;
+ }
+
+ /* --- Canvas adjustments --- */
+ .node-canvas { cursor: default; }
+ .zoom-controls { right: 8px; top: 8px; }
+ .zoom-btn { width: 40px; height: 36px; min-height: 44px; }
+ .port-dot { width: 18px; height: 18px; }
+
+ /* --- Mobile Tab Bar (visible on mobile) --- */
+ .mobile-tab-bar {
+ display: flex; align-items: center; height: 56px;
+ background: var(--panel); border-top: 1px solid var(--border);
+ flex-shrink: 0; z-index: 10;
+ }
+ .mobile-tab {
+ flex: 1; display: flex; flex-direction: column; align-items: center;
+ gap: 3px; padding: 6px 0; background: none; border: none;
+ cursor: pointer; color: var(--text2); transition: color 0.15s;
+ }
+ .mobile-tab.active { color: var(--accent); }
+ .mobile-tab-icon { font-size: 18px; }
+ .mobile-tab-label {
+ font-size: 9px; font-weight: 600; letter-spacing: 1px;
+ font-family: 'JetBrains Mono', monospace; text-transform: uppercase;
+ }
+
+ /* --- World Map Mobile --- */
+ .gm-worldmap { padding: 0 12px 80px; }
+ .gm-header { padding: 12px 0; gap: 6px; }
+ .gm-header-top { gap: 8px; }
+ .gm-logo-icon { width: 32px; height: 32px; font-size: 16px; }
+ .gm-title { font-size: 18px; }
+ .gm-tagline { display: none; }
+ .gm-header-actions .gm-btn { display: none; }
+ .gm-search-bar { margin: 0 0 12px; }
+ .gm-level-grid { grid-template-columns: 1fr; }
+ .gm-level-card { padding: 10px 12px; }
+ .gm-world-section { margin-bottom: 16px; }
+
+ /* --- Puzzle View Mobile --- */
+ .gm-puzzle-bar { height: 44px; padding: 0 10px; gap: 6px; }
+ .gm-puzzle-bar .gm-btn { padding: 6px 10px; }
+ .gm-puzzle-bar .gm-btn .btn-label { display: none; }
+ .gm-puzzle-name { font-size: 13px; }
+ .gm-puzzle-num { font-size: 9px; padding: 2px 6px; }
+
+ .gm-puzzle-sidebar { display: none; }
+ .gm-puzzle-canvas-wrap { width: 100%; }
+
+ /* Puzzle bottom sheet specific */
+ .puzzle-mission-text {
+ font-size: 12px; color: var(--text); line-height: 1.5;
+ }
+ .puzzle-hint-btn {
+ display: flex; align-items: center; gap: 8px;
+ padding: 10px 12px; border: 1px dashed var(--yellow);
+ border-radius: 8px; background: rgba(255,204,0,0.04);
+ cursor: pointer; width: 100%; margin-top: 8px;
+ }
+ .puzzle-hint-btn:hover { background: rgba(255,204,0,0.1); border-style: solid; }
+ .puzzle-hint-icon { font-size: 16px; }
+ .puzzle-hint-label { font-size: 12px; font-weight: 600; color: var(--yellow); flex: 1; }
+ .puzzle-hint-penalty {
+ font-size: 9px; font-weight: 700; color: var(--red);
+ background: rgba(255,68,102,0.15); padding: 2px 6px; border-radius: 3px;
+ }
+
+ .puzzle-obj-item {
+ display: flex; align-items: center; gap: 10px;
+ padding: 8px 0; border-bottom: 1px solid var(--border);
+ font-size: 12px;
+ }
+ .puzzle-obj-item:last-child { border-bottom: none; }
+ .puzzle-obj-star { color: var(--yellow); width: 30px; flex-shrink: 0; }
+ .puzzle-obj-desc { color: var(--text2); flex: 1; }
+
+ /* --- Level Complete Modal Mobile --- */
+ .gm-complete-overlay { padding: 0 16px; }
+ .gm-complete-card {
+ min-width: unset; max-width: unset; width: 100%;
+ padding: 24px 20px;
+ }
+ .gm-complete-actions { flex-direction: column; width: 100%; }
+ .gm-complete-actions .gm-btn { width: 100%; justify-content: center; }
+ .gm-complete-actions .gm-btn.primary {
+ order: -1; padding: 14px;
+ font-size: 13px; font-weight: 700;
+ }
+
+ /* --- Preset Modal Mobile --- */
+ .modal { min-width: unset; max-width: unset; width: calc(100% - 32px); }
+
+ /* --- General touch targets --- */
+ .gm-btn { min-height: 44px; display: flex; align-items: center; }
+ .gm-palette-item { padding: 12px 10px; }
+
+}