/* 数字一笔 · Web 壳样式。令牌与组件规格见仓库 docs/design.md(改令牌先改文档,再同步 src/theme.ts)。 */ :root { /* ---- 颜色 ---- */ --bg: #ffffff; --bg-app: #f2f2f6; --surface-2: #f3f3f7; --cell: #e6e7ec; --line: #e1e2ea; --ink: #23243a; --ink-2: #6b6d80; --ink-3: #a3a5b5; --primary: #6558f5; --primary-bright: #7d72ff; --primary-soft: #eceafe; --on-primary: #ffffff; --danger: #e5484d; --danger-soft: #fdecec; --hint: #f5a524; --hint-soft: #fef3dd; --clue-ring: #23243a; --overlay: rgba(35, 36, 58, 0.92); --shadow-primary: 0 6px 18px rgba(101, 88, 245, 0.28); --shadow-primary-pressed: 0 3px 10px rgba(101, 88, 245, 0.22); --shadow-frame: 0 20px 60px rgba(20, 20, 40, 0.14); /* ---- 字体 ---- */ --font: ui-rounded, "SF Pro Rounded", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", system-ui, -apple-system, "Segoe UI", sans-serif; --fs-display: 34px; --fs-title: 22px; --fs-heading: 17px; --fs-body: 15px; --fs-label: 14px; --fs-caption: 13px; --fs-micro: 12px; /* ---- 圆角 ---- */ --r-sm: 10px; --r-md: 14px; --r-lg: 18px; --r-xl: 24px; --r-pill: 999px; /* ---- 间距(4pt 网格) ---- */ --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-7: 28px; /* ---- 动效 ---- */ --t-fast: 120ms; --t-base: 200ms; --t-slow: 320ms; --t-hold: 500ms; --ease-out: cubic-bezier(0.22, 1, 0.36, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* ---- 尺寸 ---- */ --tap: 44px; --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); --safe-left: env(safe-area-inset-left, 0px); --safe-right: env(safe-area-inset-right, 0px); } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html, body { margin: 0; height: 100%; background: var(--bg-app); color: var(--ink); font-family: var(--font); font-size: var(--fs-body); line-height: 1.3; -webkit-font-smoothing: antialiased; font-variant-numeric: tabular-nums; user-select: none; -webkit-user-select: none; touch-action: manipulation; overscroll-behavior: none; } #app { position: relative; margin: 0 auto; width: 100%; max-width: 480px; height: 100vh; height: 100dvh; background: var(--bg); overflow: hidden; } .screen { position: absolute; inset: 0; display: flex; flex-direction: column; padding: calc(var(--safe-top) + var(--sp-3)) calc(var(--safe-right) + var(--sp-5)) calc(var(--safe-bottom) + var(--sp-4)) calc(var(--safe-left) + var(--sp-5)); } .screen.hidden { display: none; } .screen.enter { animation: screen-in var(--t-base) var(--ease-out) both; } button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; -webkit-touch-callout: none; transition: transform var(--t-fast) var(--ease-out), background-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out), opacity var(--t-fast) var(--ease-out); } button:active { transform: scale(0.97); } button:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; } /* ---------- 选关 ---------- */ .select-header { padding: var(--sp-7) var(--sp-1) var(--sp-2); } .select-header h1 { margin: 0; font-size: var(--fs-display); font-weight: 800; letter-spacing: 0.02em; line-height: 1.15; } .select-header .sub { margin: var(--sp-2) 0 0; color: var(--ink-2); font-size: var(--fs-body); font-weight: 500; } .overall { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-4); } .overall-bar { flex: 1; height: 6px; border-radius: var(--r-pill); background: var(--cell); overflow: hidden; } .overall-bar i { display: block; height: 100%; width: 0; border-radius: var(--r-pill); background: var(--primary); transition: width var(--t-slow) var(--ease-out); } .overall-text { color: var(--ink-3); font-size: var(--fs-caption); font-weight: 600; min-width: 52px; text-align: right; } .pack-list { flex: 1; overflow-y: auto; margin-top: var(--sp-3); padding-bottom: var(--sp-5); /* 继续按钮固定在底部,包列表自己滚;底部 20px 渐隐,避免被按钮“切断” */ min-height: 0; -webkit-mask-image: linear-gradient(to bottom, transparent, #000 12px, #000 calc(100% - 20px), transparent); mask-image: linear-gradient(to bottom, transparent, #000 12px, #000 calc(100% - 20px), transparent); } .pack { margin-top: var(--sp-7); } .pack-list > .pack:first-child { margin-top: var(--sp-2); } .pack-title { display: flex; align-items: baseline; justify-content: space-between; padding: 0 var(--sp-1) var(--sp-3); font-size: var(--fs-heading); font-weight: 700; } .pack-progress { color: var(--ink-3); font-size: var(--fs-label); font-weight: 600; } .pack.done .pack-progress { color: var(--primary); } .level-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-3); } .level-tile { position: relative; aspect-ratio: 1; border-radius: var(--r-md); background: var(--cell); color: var(--ink); font-size: 20px; font-weight: 700; display: flex; align-items: center; justify-content: center; } .level-tile:active { transform: scale(0.94); } .level-tile.cleared { background: var(--primary); color: var(--on-primary); } /* 已过关:右上角一个小勾(纯 CSS 画,不依赖字体里的 ✓) */ .level-tile.cleared::after { content: ""; position: absolute; right: 8px; top: 6px; width: 4px; height: 8px; border: solid rgba(255, 255, 255, 0.9); border-width: 0 2px 2px 0; transform: rotate(45deg); } .level-tile.current { box-shadow: inset 0 0 0 3px var(--primary); background: var(--primary-soft); color: var(--primary); animation: breathe 2.4s var(--ease-in-out) infinite; } .level-tile.locked { color: var(--ink-3); background: var(--surface-2); } .level-tile.locked::before { content: ""; width: 10px; height: 12px; border: 2px solid var(--ink-3); border-radius: 3px; border-top-width: 3px; } .level-tile.locked span { display: none; } .level-tile.shake { animation: shake var(--t-slow) var(--ease-out); } .primary { margin-top: auto; height: 54px; border-radius: var(--r-lg); background: var(--primary); color: var(--on-primary); font-size: var(--fs-heading); font-weight: 700; letter-spacing: 0.02em; box-shadow: var(--shadow-primary); } .primary:active { transform: scale(0.98); box-shadow: var(--shadow-primary-pressed); } .select-footer { display: flex; justify-content: center; gap: var(--sp-2); padding-top: var(--sp-4); } .chip { height: 34px; padding: 0 var(--sp-4); border-radius: var(--r-pill); background: var(--surface-2); color: var(--ink-2); font-size: var(--fs-caption); font-weight: 600; } .chip.on { background: var(--primary-soft); color: var(--primary); } .chip.danger { color: var(--danger); } /* ---------- 对局 ---------- */ .topbar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 52px; } .topbar .icon { justify-self: start; width: var(--tap); height: var(--tap); display: flex; align-items: center; justify-content: center; color: var(--ink-2); border-radius: var(--r-pill); margin-left: -8px; } .topbar .icon:active { background: var(--surface-2); } .level-info { text-align: center; line-height: 1.1; } .level-title { font-size: var(--fs-title); font-weight: 800; letter-spacing: 0.02em; } .level-sub { margin-top: 3px; color: var(--ink-3); font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.04em; white-space: nowrap; } .remaining { justify-self: end; display: inline-flex; align-items: center; gap: 5px; height: 32px; padding: 0 var(--sp-3) 0 10px; border-radius: var(--r-pill); background: var(--surface-2); color: var(--ink-2); font-size: var(--fs-label); font-weight: 700; min-width: 52px; justify-content: center; transition: background-color var(--t-base) var(--ease-out), color var(--t-base) var(--ease-out); } .remaining-icon { color: var(--ink-3); transition: color var(--t-base) var(--ease-out); } .remaining-icon.check { display: none; } .remaining.bump { animation: bump var(--t-base) var(--ease-spring); } .remaining.done { background: var(--primary-soft); color: var(--primary); } .remaining.done .remaining-icon { color: var(--primary); } .remaining.done .remaining-icon.tile { display: none; } .remaining.done .remaining-icon.check { display: block; } /* 本包进度条:每关一段。灰 = 未到,淡紫 = 已过,实紫 = 当前 */ .level-progress { display: flex; gap: 3px; height: 4px; margin: var(--sp-2) var(--sp-1) 0; } .level-progress i { flex: 1; border-radius: 2px; background: var(--cell); transition: background-color var(--t-slow) var(--ease-out), opacity var(--t-slow) var(--ease-out); } .level-progress i.cleared { background: var(--primary); opacity: 0.4; } .level-progress i.current { background: var(--primary); opacity: 1; } .caption { height: 36px; margin-top: var(--sp-2); display: flex; align-items: center; justify-content: center; color: var(--ink-2); font-size: var(--fs-body); font-weight: 500; text-align: center; transition: opacity var(--t-base) var(--ease-out); } .caption:empty { opacity: 0; } .board-wrap { flex: 1; min-height: 0; position: relative; } #board { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; display: block; } .bottombar { display: flex; justify-content: center; gap: var(--sp-3); padding-top: var(--sp-3); } .gesture-help { margin: 0; padding-top: var(--sp-2); color: var(--ink-2); font-size: var(--fs-caption); text-align: center; } .action { flex: 1; max-width: 170px; height: 56px; border-radius: var(--r-lg); background: var(--surface-2); color: var(--ink); font-size: var(--fs-body); font-weight: 700; display: flex; align-items: center; justify-content: center; gap: var(--sp-1); } .action svg { color: var(--ink-2); } .action:active { background: var(--cell); } .action.pulse { animation: pulse 0.9s var(--ease-in-out) infinite; } .action:disabled { opacity: 0.4; pointer-events: none; } .toast { position: absolute; left: 50%; bottom: calc(var(--safe-bottom) + 128px); transform: translate(-50%, 8px); padding: 10px var(--sp-4); border-radius: var(--r-md); background: var(--overlay); color: #fff; font-size: var(--fs-label); font-weight: 600; width: max-content; max-width: calc(100% - 40px); text-align: center; opacity: 0; pointer-events: none; transition: opacity var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out); } .toast.show { opacity: 1; transform: translate(-50%, 0); } /* ---------- 动效 ---------- */ @keyframes screen-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 50% { transform: translateX(5px); } 75% { transform: translateX(-3px); } } @keyframes pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(245, 165, 36, 0); } 50% { box-shadow: 0 0 0 5px rgba(245, 165, 36, 0.35); } } @keyframes bump { 0% { transform: scale(1); } 40% { transform: scale(1.14); } 100% { transform: scale(1); } } @keyframes breathe { 0%, 100% { box-shadow: inset 0 0 0 3px var(--primary), 0 0 0 0 rgba(101, 88, 245, 0); } 50% { box-shadow: inset 0 0 0 3px var(--primary), 0 0 0 6px rgba(101, 88, 245, 0.16); } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } } @media (min-width: 520px) { body { padding: var(--sp-6) 0; } #app { height: calc(100vh - 48px); height: calc(100dvh - 48px); border-radius: 28px; box-shadow: var(--shadow-frame); } } /* DEMO additions: preserve the original token and component system. */ #constraints{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin:4px 0 0;min-height:0;flex-shrink:0}#constraints .chip{height:28px;min-height:28px;padding:0 10px;font-size:12px}.level-sub{max-width:240px;overflow:hidden;text-overflow:ellipsis}.caption{flex-shrink:0;height:36px;padding:0 4px}.topbar,.level-progress,.bottombar,.gesture-help{flex-shrink:0}#board-wrap{margin-top:8px}.next-overlay{position:absolute;bottom:calc(var(--safe-bottom) + 114px);left:50%;transform:translateX(-50%);padding:0 22px;min-width:160px;height:44px;margin:0;z-index:3}.next-overlay[hidden]{display:none}.next-overlay:active{transform:translateX(-50%) scale(.98)}#status{z-index:4}.rules-button{position:absolute;right:20px;top:calc(var(--safe-top) + 68px);background:none;font-size:12px;color:var(--ink-2);padding:3px}.sheet-backdrop{position:absolute;inset:0;background:rgba(35,36,58,.36);z-index:6;display:flex;align-items:flex-end}.sheet-backdrop[hidden]{display:none}.sheet{width:100%;max-height:75%;overflow:auto;background:var(--bg);border-radius:24px 24px 0 0;padding:24px 20px calc(20px + var(--safe-bottom))}.sheet h2{font-size:22px;font-weight:800;margin:0 0 12px}.sheet p{font-size:15px;line-height:1.7;color:var(--ink-2)}.sheet textarea{width:100%;min-height:100px;padding:12px;border:1px solid var(--line);border-radius:14px;font:inherit;user-select:text}.sheet .sheet-actions{display:flex;gap:8px;margin-top:16px}.sheet .action{height:48px}.sheet .primary{height:48px;padding:0 16px;margin:0}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}.select-footer .chip{min-height:44px;height:44px}.level-tile{min-height:44px}.select-footer a{display:flex;align-items:center;text-decoration:none;color:var(--ink-2)}@media(max-height:660px){.select-header{padding-top:8px}.caption{font-size:13px;height:30px}#constraints .chip{height:24px;min-height:24px}.bottombar{padding-top:8px}.action{height:48px}.gesture-help{font-size:12px}.screen{padding-top:calc(var(--safe-top) + 8px)}} /* Responsive campaign feedback and controls. Original tokens stay intact. */ .feedback-row{display:flex;align-items:center;gap:8px;flex-shrink:0;min-height:58px;padding:6px 0;width:100%} #status{position:static;left:auto;bottom:auto;transform:none;flex:1;min-width:0;width:auto;max-width:none;margin:0;padding:8px 12px;font-size:13px;line-height:1.45;overflow-wrap:anywhere;transition:opacity var(--t-base) var(--ease-out)} #status.show{transform:none} #next{position:static;left:auto;bottom:auto;transform:none;flex:0 0 auto;min-width:108px;height:44px;margin:0;padding:0 14px} #next:active{transform:scale(.98)} .rules-button{position:static;right:auto;top:auto;flex:0 0 44px;width:44px;min-height:44px;border-radius:var(--r-sm);font-size:13px;padding:4px;order:3} .topbar .remaining{order:2}.topbar .level-info{min-width:0}.level-sub{max-width:100%;white-space:nowrap} #constraints .chip{min-height:32px;height:auto;padding:6px 10px;line-height:1.4;max-width:100%;text-align:center} #constraints{gap:4px}.caption{height:auto;min-height:32px;line-height:1.4;margin-top:4px;padding:4px 0} #board-wrap{margin-top:4px}#board{touch-action:none}.sheet textarea{font-size:16px} @media(max-width:430px){.screen{padding-left:calc(var(--safe-left) + 12px);padding-right:calc(var(--safe-right) + 12px)}.topbar{gap:6px}.level-title{font-size:20px}.level-sub{font-size:11px}.gesture-help{font-size:11px;padding-top:2px}.bottombar{gap:8px;padding-top:6px}.caption{font-size:13px}.feedback-row{min-height:54px}.select-header h1{font-size:30px}.sheet{padding-left:16px;padding-right:16px}} @media(max-height:680px){#constraints .chip{min-height:28px;padding:4px 8px}.caption{min-height:28px}.feedback-row{min-height:50px}#status{padding:6px 10px;font-size:12px}.action{height:48px}.screen{padding-top:calc(var(--safe-top) + 8px)}.select-header{padding-top:8px}} @media(orientation:landscape) and (max-height:520px) and (min-width:560px){body{padding:0}#app{max-width:none;height:100dvh;border-radius:0;box-shadow:none}#screen-play:not(.hidden){display:grid;grid-template-columns:minmax(0,1fr) 220px;grid-template-rows:48px 10px auto auto minmax(0,1fr) auto auto;column-gap:16px;row-gap:4px}.topbar{grid-column:1/-1;grid-row:1}.level-progress{grid-column:1/-1;grid-row:2;margin:0}.caption{grid-column:2;grid-row:3}#constraints{grid-column:2;grid-row:4}#board-wrap{grid-column:1;grid-row:3/8;margin:0;min-height:0}.feedback-row{grid-column:2;grid-row:5;flex-direction:column;justify-content:flex-end;align-items:stretch}.gesture-help{grid-column:2;grid-row:6}.bottombar{grid-column:2;grid-row:7}.bottombar .action{height:44px;font-size:12px;gap:2px}.bottombar svg{width:18px;height:18px}} @media(max-height:600px) and (orientation:portrait){.gesture-help{display:none}.level-progress{margin-top:6px;margin-bottom:4px}.caption{min-height:24px;font-size:12px;padding:2px 0}.feedback-row{min-height:48px}.bottombar{padding-top:4px}.action{height:44px}.topbar{min-height:44px}.screen{padding-bottom:calc(var(--safe-bottom) + 8px)}} .sound-button{min-width:44px;min-height:44px;font-size:12px;color:var(--ink-2);border-radius:var(--r-sm);flex:0 0 44px;order:4}.sound-button[aria-pressed="false"]{color:var(--ink-3)} @media(max-width:430px){.topbar{gap:3px}.rules-button,.sound-button{flex-basis:44px}.topbar .level-title{font-size:18px}.topbar .remaining{padding:6px 8px}.level-sub{font-size:10px}}