:root { --bg: #0a0a0f; --bg-panel: #131320; --bg-panel-glass: rgba(19, 19, 32, 0.82); --bg-input: #1a1a2a; --border: #2b2b3d; --border-bright: #3d3d55; --text: #e2ddd0; --text-dim: #918c7d; --accent: #d4a94e; --accent-bright: #e8c476; --accent-dim: #96773a; --accent-glow: rgba(212, 169, 78, 0.25); --danger: #d06565; --player: #9fc7d1; --font-display: 'Cinzel', Georgia, serif; --font-story: 'Crimson Pro', Georgia, 'Times New Roman', serif; --font-ui: 'Inter', 'Segoe UI', system-ui, sans-serif; font-family: var(--font-ui); color-scheme: dark; } * { box-sizing: border-box; } body { margin: 0; background: radial-gradient(1200px 700px at 15% -10%, rgba(212, 169, 78, 0.06), transparent 60%), radial-gradient(1000px 600px at 90% 110%, rgba(88, 76, 140, 0.08), transparent 55%), var(--bg); background-attachment: fixed; color: var(--text); min-height: 100vh; } #root { min-height: 100vh; display: flex; flex-direction: column; } h1, h2, h3 { font-weight: 600; letter-spacing: 0.03em; } a { color: var(--accent); text-decoration: none; transition: color 0.15s; } a:hover { color: var(--accent-bright); } ::selection { background: var(--accent-dim); color: #0f0d08; } /* ---------- Scrollbars ---------- */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 5px; border: 2px solid var(--bg); } ::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); } /* ---------- Top navigation ---------- */ .topnav { display: flex; align-items: center; gap: 26px; padding: 0 28px; height: 56px; background: var(--bg-panel-glass); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); position: sticky; top: 0; z-index: 10; } .topnav .brand { font-family: var(--font-display); font-size: 1.2rem; font-weight: 700; color: var(--accent); letter-spacing: 0.14em; text-shadow: 0 0 18px var(--accent-glow); margin-right: 6px; } .topnav a.navlink { color: var(--text-dim); padding: 17px 2px 15px; font-size: 0.9rem; font-weight: 500; letter-spacing: 0.04em; border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.2s; } .topnav a.navlink.active { color: var(--accent-bright); border-bottom-color: var(--accent); text-shadow: 0 0 14px var(--accent-glow); } .topnav a.navlink:hover { color: var(--text); } /* On desktop the wrapper is transparent — its children (nav links + account) participate directly in the .topnav flex row, exactly as before. The mobile query below turns it into a dropdown. */ .nav-links { display: contents; } .nav-hamburger { display: none; background: none; border: none; color: var(--text); font-size: 1.2rem; padding: 6px 8px; margin-left: auto; line-height: 1; } /* Phase 8: account area (hosted multi-user mode only) */ .nav-account { margin-left: auto; display: flex; align-items: center; gap: 10px; min-width: 0; } .nav-account button { padding: 5px 12px; font-size: 0.82rem; } .guest-nudge { color: var(--text-dim); font-size: 0.78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .account-email { color: var(--text-dim); font-size: 0.82rem; max-width: 220px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } button.linklike { background: none; border: none; padding: 0; color: var(--accent); font-size: 0.82rem; cursor: pointer; } button.linklike:hover { text-decoration: underline; } @media (max-width: 900px) { .guest-nudge { display: none; } } /* Phase 8: demo-key banner (Settings) */ .demo-banner { border: 1px solid var(--border-bright); border-left: 3px solid var(--accent); border-radius: 8px; padding: 12px 16px; margin-bottom: 18px; color: var(--text-dim); font-size: 0.88rem; line-height: 1.5; } .demo-banner strong { color: var(--accent-bright); } .page { flex: 1; width: 100%; max-width: 960px; margin: 0 auto; padding: 28px 24px; animation: page-in 0.35s ease both; } @keyframes page-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } /* ---------- Forms ---------- */ label.field { display: block; margin-bottom: 14px; } label.field .label { display: block; font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-dim); margin-bottom: 5px; } input[type="text"], input[type="email"], input[type="password"], input[type="number"], textarea, select { width: 100%; background: var(--bg-input); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; font-size: 0.95rem; font-family: inherit; transition: border-color 0.15s, box-shadow 0.15s; } textarea { resize: vertical; min-height: 70px; line-height: 1.5; } input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent-dim); box-shadow: 0 0 0 3px rgba(212, 169, 78, 0.12); } input::placeholder, textarea::placeholder { color: #5c584d; } button { background: var(--bg-input); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 7px 16px; font-size: 0.88rem; font-weight: 500; cursor: pointer; font-family: inherit; transition: border-color 0.15s, background 0.15s, box-shadow 0.15s, transform 0.1s; } button:hover { border-color: var(--accent-dim); box-shadow: 0 0 12px rgba(212, 169, 78, 0.1); } button:active { transform: translateY(1px); } button.primary { background: linear-gradient(160deg, var(--accent-bright), var(--accent) 55%, #b78d3e); border-color: var(--accent); color: #191307; font-weight: 600; letter-spacing: 0.02em; box-shadow: 0 2px 10px var(--accent-glow); } button.primary:hover { box-shadow: 0 2px 18px rgba(212, 169, 78, 0.45); filter: brightness(1.08); } button.danger { color: var(--danger); } button.danger:hover { border-color: var(--danger); box-shadow: 0 0 12px rgba(208, 101, 101, 0.15); } button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: none; } /* ---------- Cards & lists ---------- */ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; } .card { position: relative; background: linear-gradient(170deg, #17172400, #17172400 60%, rgba(212, 169, 78, 0.03)), var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; padding: 18px; cursor: pointer; transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s; overflow: hidden; } .card::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 2px; background: linear-gradient(90deg, transparent, var(--accent-dim), transparent); opacity: 0; transition: opacity 0.25s; } .card:hover { border-color: var(--border-bright); transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45), 0 0 20px rgba(212, 169, 78, 0.06); } .card:hover::before { opacity: 1; } .card h3 { margin: 0 0 6px; font-family: var(--font-display); font-size: 1.02rem; color: var(--accent); } .card p { margin: 0; color: var(--text-dim); font-size: 0.88rem; line-height: 1.5; } .card .meta { margin-top: 12px; font-size: 0.75rem; color: var(--text-dim); } .page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; } .page-header h1 { margin: 0; font-family: var(--font-display); font-size: 1.55rem; color: var(--accent-bright); text-shadow: 0 0 24px var(--accent-glow); } .empty { color: var(--text-dim); text-align: center; padding: 60px 0; font-style: italic; font-family: var(--font-story); font-size: 1.05rem; } /* ---------- Story card editor ---------- */ .storycard { background: var(--bg-input); border: 1px solid var(--border); border-left: 3px solid var(--accent-dim); border-radius: 8px; padding: 12px; margin-bottom: 10px; transition: border-color 0.15s; } .storycard:focus-within { border-color: var(--accent-dim); } .storycard .row { display: flex; gap: 10px; margin-bottom: 8px; } .storycard .row > * { flex: 1; } /* ---------- Play page ---------- */ .play-page { display: flex; flex-direction: column; min-height: calc(100dvh - 56px); /* Room to scroll the last beat clear of a composer lifted by the keyboard (see --kb-inset in keyboard.js); 0 on desktop and on browsers that shrink the layout viewport themselves. Margin, not padding — .page already sets padding and a longhand here would drop its bottom gutter. */ margin-bottom: var(--kb-inset, 0px); } /* Keep the Plot/Memory/Scripts/Insights toggles reachable while the story scrolls — pin the header just below the 56px top nav. */ .play-page .page-header { position: sticky; top: 56px; z-index: 5; background: var(--bg); margin-bottom: 12px; padding: 12px 0; border-bottom: 1px solid var(--border); } .story { flex: 1; font-family: var(--font-story); font-size: 1.14rem; line-height: 1.7; white-space: pre-wrap; padding-bottom: 16px; } /* The head of a windowed transcript: how much story is still above, and the way back to it. Deliberately quiet — it is a seam in the page, not a feature — and it sits inside .story so it inherits the story face. */ .story-earlier { text-align: center; margin: 4px 0 22px; font-size: 0.9rem; /* No action-in animation here: this appears above content the reader is already looking at, and a fade would read as the story moving. */ } .story-earlier .dim { color: var(--text-dim); font-style: italic; } .story-earlier button { background: none; border: none; border-bottom: 1px solid rgba(159, 199, 209, 0.3); color: var(--text-dim); font: inherit; font-style: italic; cursor: pointer; padding: 2px 4px; } .story-earlier button:hover { color: var(--accent-bright); border-bottom-color: var(--accent-bright); } .story .action { position: relative; margin-bottom: 15px; padding-right: 56px; animation: action-in 0.4s ease both; } @keyframes action-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } } .story .action.player { color: var(--player); border-left: 2px solid rgba(159, 199, 209, 0.35); padding-left: 14px; font-style: italic; } .story .action.streaming { color: var(--text); } .story .cursor { color: var(--accent-bright); text-shadow: 0 0 8px var(--accent-glow); animation: blink 1s steps(1) infinite; } @keyframes blink { 50% { opacity: 0; } } /* Reasoning-model thinking, collapsed above the AI's response */ .story .reasoning { margin: 0 0 8px; font-size: 0.82rem; line-height: 1.5; color: var(--text-dim); font-style: normal; } .story .reasoning summary { cursor: pointer; user-select: none; color: var(--text-dim); opacity: 0.8; } .story .reasoning summary:hover { opacity: 1; } .story .reasoning .reasoning-text { margin-top: 6px; padding: 8px 12px; border-left: 2px solid var(--border); opacity: 0.85; max-height: 260px; overflow-y: auto; } .story .action-tools { position: absolute; top: 0; right: 0; display: inline-flex; gap: 4px; opacity: 0; pointer-events: none; transition: opacity 0.15s; } .story .action:hover .action-tools { opacity: 1; pointer-events: auto; } .story .action-tools button { padding: 1px 8px; font-size: 0.72rem; font-family: var(--font-ui); color: var(--text-dim); background: var(--bg-panel-glass); backdrop-filter: blur(6px); } .story .action-tools button:hover { color: var(--text); } /* The attempts at one AI beat. Stays quiet until hovered — it's a footnote on the message, not part of the prose. */ .attempts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 6px; font-family: var(--font-ui); opacity: 0.45; transition: opacity 0.15s; } .story .action:hover .attempts, .attempts:focus-within { opacity: 1; } .attempt-chip { padding: 3px 10px; font-size: 0.73rem; line-height: 1.5; border-radius: 999px; color: var(--text-dim); background: transparent; border: 1px solid var(--border); } .attempt-chip:hover:not(:disabled) { color: var(--text); border-color: var(--border-bright); } .attempt-chip[aria-pressed="true"] { color: var(--bg); background: var(--accent); border-color: var(--accent); font-weight: 600; } .attempt-chip:disabled { opacity: 0.35; cursor: default; } /* Dashed until hovered: taking a path the story moved past creates a branch, so it should not look like the same weight of click as browsing one. */ .take-path { padding: 3px 10px; font-size: 0.73rem; line-height: 1.5; color: var(--accent); background: transparent; border: 1px dashed var(--accent-dim); } .take-path:hover:not(:disabled) { color: var(--accent-bright); border-style: solid; } .take-path:disabled { opacity: 0.35; cursor: default; } .attempt-note { margin-left: 2px; font-size: 0.72rem; font-style: italic; color: var(--accent-dim); } /* ---------- Branches panel (Phase 14, SP7) ---------- */ .panel-empty { padding: 18px 0; color: var(--text-dim); font-size: 0.85rem; } .branch-panel { display: flex; flex-direction: column; gap: 14px; } .branch-intro { margin: 0; color: var(--text-dim); font-size: 0.82rem; line-height: 1.55; } .branch-list { display: flex; flex-direction: column; gap: 8px; } .branch-row { display: flex; flex-direction: column; gap: 5px; padding: 9px 11px; border: 1px solid var(--border); border-radius: 5px; background: var(--bg-input); } .branch-row.here { border-color: var(--accent-dim); background: rgba(212, 169, 78, 0.07); } .branch-head { display: flex; align-items: center; gap: 7px; } .branch-glyph { color: var(--border-bright); font-size: 0.85rem; } .branch-row.here .branch-glyph { color: var(--accent); } .branch-name { font-family: var(--font-story); font-size: 0.98rem; color: var(--text); } .branch-row.here .branch-name { color: var(--accent-bright); } .branch-here { margin-left: auto; font-size: 0.64rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-dim); } .branch-rename { flex: 1; min-width: 0; padding: 3px 7px; font-family: var(--font-story); font-size: 0.95rem; } .branch-meta { font-size: 0.72rem; color: var(--text-dim); font-variant-numeric: tabular-nums; } .branch-tools, .branch-confirm { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; } .branch-tools button, .branch-confirm button { padding: 3px 9px; font-size: 0.72rem; color: var(--text-dim); background: transparent; border: 1px solid var(--border); } .branch-tools button:hover:not(:disabled), .branch-confirm button:hover:not(:disabled) { color: var(--accent-bright); border-color: var(--border-bright); } .branch-tools button.danger:hover:not(:disabled), .branch-confirm button.danger:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); } .branch-tools button:disabled, .branch-confirm button:disabled { opacity: 0.4; cursor: default; } .branch-confirm span { font-size: 0.74rem; color: var(--danger); flex-basis: 100%; } .action-edit { margin-bottom: 14px; } /* Sized by AutoTextarea to fit the text being edited — an AI beat is usually several paragraphs, and the old fixed 110px turned that into a keyhole. min-height keeps a short player action from collapsing; max-height stops a very long beat from pushing Save/Cancel off the screen (it scrolls instead). resize stays on, so the height is a starting point, not a cage. */ .action-edit textarea { min-height: 110px; max-height: 65vh; overflow-y: auto; font-family: inherit; font-size: inherit; } .action-edit .edit-buttons { display: flex; gap: 8px; margin-top: 6px; } .play-controls { position: sticky; /* Sits on the keyboard's top edge when one is open, on the viewport bottom otherwise — --kb-inset is 0 unless a virtual keyboard is covering us. */ bottom: var(--kb-inset, 0px); background: linear-gradient(to top, var(--bg) 75%, transparent); padding: 18px 0 16px; border-top: 1px solid var(--border); } .turn-buttons { display: flex; gap: 8px; margin-bottom: 10px; } .turn-buttons button { font-size: 0.78rem; padding: 4px 12px; color: var(--text-dim); } .turn-buttons button:hover:not(:disabled) { color: var(--text); } .input-bar { display: flex; gap: 8px; align-items: flex-end; } .input-bar input { flex: 1; } /* Auto-growing action box: starts at one line, grows to ~4, then scrolls. */ .input-bar textarea { flex: 1; resize: none; min-height: 0; max-height: calc(6em + 20px); /* ~4 lines (1.5em each) + padding/border */ overflow-y: auto; line-height: 1.5; } .mode-select { display: flex; } .mode-select button { border-radius: 0; border-right-width: 0; padding: 7px 14px; color: var(--text-dim); text-transform: capitalize; } .mode-select button:first-child { border-radius: 8px 0 0 8px; } .mode-select button:last-child { border-radius: 0 8px 8px 0; border-right-width: 1px; } .mode-select button.active { color: var(--accent-bright); background: var(--bg-panel); border-color: var(--accent-dim); border-right-width: 1px; box-shadow: inset 0 0 12px rgba(212, 169, 78, 0.08); } .mode-select button.active + button { border-left-width: 0; } /* ---------- Side panel (Plot / Insights) ---------- */ .play-layout { flex: 1; display: flex; min-height: calc(100dvh - 56px); } .play-layout .play-page { max-width: 960px; } .play-layout.with-panel .play-page { max-width: none; } .panel-toggles { display: flex; gap: 8px; } .panel-toggles button.active { color: var(--accent-bright); border-color: var(--accent-dim); box-shadow: inset 0 0 12px rgba(212, 169, 78, 0.08); } .side-panel { width: 420px; flex-shrink: 0; border-left: 1px solid var(--border); background: var(--bg-panel); padding: 20px; overflow-y: auto; height: calc(100dvh - 56px); position: sticky; top: 56px; animation: panel-in 0.25s ease both; } /* ---------- In-play script viewer ---------- */ .adv-script { border-bottom: 1px solid var(--border); padding: 10px 0; } .adv-script-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .adv-script-desc { font-size: 0.82rem; margin-top: 4px; } .linklike { border: none; background: none; padding: 0; color: var(--accent-bright); font-size: 0.8rem; cursor: pointer; flex-shrink: 0; } .linklike:hover { text-decoration: underline; } .adv-script-code { margin-top: 8px; } .adv-script-hook { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin: 8px 0 2px; } .adv-script-code pre { margin: 0; padding: 10px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px; overflow-x: auto; font-family: var(--font-mono, monospace); font-size: 0.76rem; line-height: 1.5; white-space: pre; } /* ---------- Left status drawer (script state) ---------- */ .status-drawer { flex-shrink: 0; position: sticky; top: 56px; height: calc(100dvh - 56px); display: flex; align-items: flex-start; border-right: 1px solid var(--border); background: var(--bg-panel); } .status-drawer.open { width: 300px; } .status-toggle { flex-shrink: 0; height: 100%; width: 30px; padding: 12px 0; border: none; border-radius: 0; background: transparent; color: var(--text-dim); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 8px; } .status-toggle:hover { color: var(--text); background: var(--bg-input); } .status-toggle-label { writing-mode: vertical-rl; text-orientation: mixed; letter-spacing: 0.12em; font-size: 0.72rem; text-transform: uppercase; } .status-body { flex: 1; min-width: 0; height: 100%; overflow-y: auto; padding: 20px 16px; animation: panel-in 0.2s ease both; } .status-vars { list-style: none; margin: 0; padding: 0; } .status-vars li { display: flex; flex-direction: column; gap: 2px; padding: 8px 0; border-bottom: 1px solid var(--border); } .status-key { font-family: var(--font-mono, monospace); font-size: 0.82rem; color: var(--accent-bright); word-break: break-word; } .status-val { font-family: var(--font-mono, monospace); font-size: 0.82rem; color: var(--text); white-space: pre-wrap; word-break: break-word; } /* Recursive script-state value tree */ .jv-string { color: var(--text); } .jv-number { color: var(--player); } .jv-bool { color: var(--accent-bright); } .jv-null { color: var(--text-dim); font-style: italic; } .jv-empty { color: var(--text-dim); } .jv-tree { display: inline-block; vertical-align: top; max-width: 100%; } .jv-toggle { background: none; border: none; padding: 0; cursor: pointer; font-family: inherit; font-size: 0.72rem; color: var(--text-dim); } .jv-toggle:hover { color: var(--accent-bright); } .jv-children { list-style: none; margin: 2px 0 2px 0; padding-left: 12px; border-left: 1px solid var(--border); } .jv-children li { padding: 2px 0; word-break: break-word; } .jv-key { color: var(--accent); margin-right: 6px; } .jv-key::after { content: ':'; color: var(--text-dim); } @keyframes panel-in { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } } /* ---------- World State drawer (Phase 12 RPG) ---------- */ .ws-group { margin-bottom: 18px; } .ws-group-title { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); margin: 0 0 8px; } .ws-stat { padding: 5px 0; } .ws-stat-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 0.85rem; } .ws-stat-name { color: var(--text); text-transform: capitalize; } .ws-stat-val { color: var(--text-dim); font-variant-numeric: tabular-nums; font-size: 0.8rem; text-align: right; } .ws-band { color: var(--accent-bright); } .ws-bar { margin-top: 4px; height: 5px; border-radius: 3px; background: var(--bg-input); overflow: hidden; } .ws-bar-fill { height: 100%; border-radius: 3px; background: var(--accent); transition: width 0.35s ease; } /* Cast — each NPC is a small plate inside the one "Cast" group, so a roster of five doesn't read as five more unrelated sections in the rail. */ .ws-npc { padding: 7px 9px; margin-bottom: 7px; border: 1px solid var(--border); border-left: 2px solid var(--accent-dim); border-radius: 7px; background: rgba(255, 255, 255, 0.022); } .ws-npc:last-child { margin-bottom: 0; } .ws-npc-head { display: flex; align-items: center; gap: 7px; margin-bottom: 3px; } .ws-npc-avatar { flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; font-family: var(--font-display); font-size: 0.6rem; color: #14100a; background: linear-gradient(150deg, var(--accent-bright), var(--accent-dim)); } .ws-npc-name { font-family: var(--font-display); font-size: 0.85rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ws-none { font-size: 0.78rem; color: var(--text-dim); font-style: italic; } .ws-milestones { list-style: none; margin: 0; padding: 0; } .ws-milestones li { display: flex; gap: 7px; align-items: flex-start; padding: 4px 0; font-size: 0.85rem; color: var(--text); } .ws-milestones li.done { color: var(--text-dim); } .ws-check { flex-shrink: 0; color: var(--accent-bright); } .ws-milestones li.done .ws-check { color: var(--player); } .ws-flags { list-style: none; margin: 0; padding: 0; } .ws-flags li { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 0.85rem; color: var(--text); } .ws-flag-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; background: var(--bg-input); border: 1px solid var(--border); } .ws-flag-dot.on { background: var(--accent); border-color: var(--accent); } .ws-flag-name { flex: 1; } .ws-flag-val { color: var(--text-dim); font-size: 0.8rem; } .ws-flag-edit { display: flex; align-items: center; gap: 8px; cursor: pointer; width: 100%; } .ws-edit-actions { display: flex; gap: 6px; } .ws-edit-input { width: 100%; margin-top: 3px; padding: 4px 7px; font-size: 0.82rem; border-radius: 6px; } .schema-editor { width: 100%; font-family: var(--font-mono, monospace); font-size: 0.82rem; line-height: 1.5; tab-size: 2; white-space: pre; overflow-x: auto; } .schema-error { margin-top: 6px; color: var(--danger, #e5484d); font-size: 0.82rem; } .schema-json-actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; } .schema-json-actions .schema-error { margin-top: 0; } .schema-preview { margin-top: 14px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-panel); } .schema-preview-title { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-dim); margin-bottom: 10px; } .schema-group { margin-bottom: 16px; } .schema-group:last-child { margin-bottom: 0; } .schema-group-title { font-family: Georgia, serif; font-size: 0.98rem; color: var(--accent-bright); margin-bottom: 6px; } .schema-npc-id { font-family: var(--font-mono, monospace); font-size: 0.72rem; color: var(--text-dim); margin-left: 6px; } .schema-stat { padding: 4px 0; border-bottom: 1px solid var(--border); } .schema-stat:last-child { border-bottom: none; } .schema-stat-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; } .schema-stat-name { color: var(--text); text-transform: capitalize; } .schema-stat-meta { color: var(--text-dim); font-size: 0.8rem; font-variant-numeric: tabular-nums; text-align: right; } .schema-desc { color: var(--text-dim); font-size: 0.8rem; margin-top: 2px; } .schema-bands { color: var(--text-dim); font-size: 0.76rem; margin-top: 2px; font-style: italic; } /* ---- Form-based schema editor ---- One shape for everything: a section is a panel, every entry inside it is a `.se-row` tile, and every control wears a `.se-field` caption. Adding a new kind of entry means reusing those three, not inventing a fourth box. */ .se-section { margin-bottom: 16px; padding: 13px 15px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-panel); } .se-section-nested { margin: 10px 0 0; padding: 11px 12px; background: rgba(0, 0, 0, 0.22); border-radius: 8px; } .se-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; } .se-section-title { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent); } .se-hint { margin: -2px 0 10px; font-size: 0.78rem; line-height: 1.45; color: var(--text-dim); } .se-empty { color: var(--text-dim); font-size: 0.82rem; padding: 4px 0; } .se-add, .se-add-sm { font-size: 0.78rem; padding: 3px 10px; white-space: nowrap; } .se-add-sm { margin-top: 6px; } .se-remove { background: transparent; border: none; color: var(--text-dim); cursor: pointer; padding: 2px 6px; font-size: 0.9rem; line-height: 1; flex-shrink: 0; } .se-remove:hover { color: var(--danger, #e5484d); } /* Every control in the editor, one look. The global field rule keys off `input[type="…"]`, so the plain ``s here (keys, descriptions) never matched it and fell back to browser defaults — that mismatch is most of why this panel used to read as a pile of unrelated boxes. Everything below is qualified with `.se` so it outranks that global rule (0,1,1) too. */ /* `:where()` so the exclusion costs no specificity — a plain `:not([type="checkbox"])` would rank this above the `.se .se-*` overrides below and swallow every one of them. */ .se input:not(:where([type="checkbox"])), .se textarea, .se select { width: 100%; background: var(--bg-input); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 5px 8px; font-size: 0.84rem; font-family: inherit; transition: border-color 0.15s, box-shadow 0.15s; } /* One entry — a stat, a flag, a milestone. */ .se-row { padding: 10px 12px; margin-bottom: 8px; border: 1px solid var(--border); border-radius: 8px; background: rgba(255, 255, 255, 0.018); transition: border-color 0.15s; } .se-row:last-child { margin-bottom: 0; } .se-row:focus-within { border-color: var(--border-bright); } /* Wraps because a phone-width row can't fit key + toggle + ✕ side by side, and an input's default `min-width: auto` would push the ✕ off-screen instead. */ .se-row-top { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 8px; } .se .se-key { font-family: var(--font-mono, monospace); font-size: 0.82rem; min-width: 0; max-width: 220px; } /* Only in the row context — `.se-field` is a *column* flex, where a basis of 140px would set the input's height instead of its width. */ .se .se-row-top .se-key { flex: 1 1 140px; } .se-field { display: flex; flex-direction: column; gap: 3px; } .se-field > span { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-dim); } .se-fields { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: flex-end; margin-bottom: 8px; } .se .se-num input, .se .se-num select { width: 72px; padding: 4px 6px; } .se .se-kind select { width: 132px; } .se .se-initial-text input { width: 220px; max-width: 100%; } .se-check { display: flex; align-items: center; gap: 5px; font-size: 0.8rem; color: var(--text-dim); white-space: nowrap; } .se-check input { width: auto; } /* The global textarea rule also forces min-height:70px, which a 2-row inline field doesn't want. */ .se .se-area { min-height: 0; line-height: 1.45; resize: vertical; } .se-sub-head { font-size: 0.7rem; color: var(--text-dim); margin: 4px 0 4px; } .se-band { display: flex; gap: 6px; align-items: center; margin-bottom: 4px; } /* Qualified so it outweighs the global input[type="number"] rule (0,1,1) — as a bare class it lost, and the band bounds rendered full-width. */ .se .se-band-n { width: 64px; padding: 4px 6px; } .se .se-band-l { flex: 1 1 60px; width: auto; min-width: 0; } .se-bands { margin-top: 8px; } /* ---- NPC roster (its own page section) ---- */ .se-npc-grid { display: grid; /* min() so a 320px phone gets one column instead of a 340px overflow */ grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 14px; align-items: start; } .se-npc { border: 1px solid var(--border); border-radius: 10px; background: var(--bg-panel); overflow: hidden; } .se-npc:focus-within { border-color: var(--border-bright); } .se-npc-head { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--border); background: linear-gradient(180deg, rgba(212, 169, 78, 0.07), transparent); } .se-avatar { flex-shrink: 0; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; font-family: var(--font-display); font-size: 0.8rem; letter-spacing: 0.02em; color: #14100a; background: linear-gradient(150deg, var(--accent-bright), var(--accent-dim)); } .se-npc-ident { flex: 1; min-width: 0; } .se .se-npc-name { padding: 2px 6px; font-family: var(--font-display); font-size: 0.95rem; background: transparent; border-color: transparent; } .se .se-npc-name:focus { background: var(--bg-input); border-color: var(--border-bright); } .se-npc-addr { display: block; margin: 1px 0 0 6px; font-family: var(--font-mono, monospace); font-size: 0.7rem; color: var(--text-dim); } .se-npc-count { flex-shrink: 0; font-size: 0.7rem; color: var(--text-dim); white-space: nowrap; } .se-npc-body { padding: 12px; display: flex; flex-direction: column; gap: 10px; } .se-npc-idfield .se-key { max-width: 100%; } .ws-report { list-style: none; margin: 4px 0 0; padding: 0; font-size: 0.82rem; } .ws-report li { padding: 2px 0; color: var(--text); } /* Inline per-turn change chips under an AI message */ .turn-changes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .chg { font-size: 0.74rem; padding: 2px 8px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-input); color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; } .chg-up { color: var(--player, #4caf82); border-color: color-mix(in srgb, var(--player, #4caf82) 40%, var(--border)); } .chg-down { color: var(--danger, #e5484d); border-color: color-mix(in srgb, var(--danger, #e5484d) 40%, var(--border)); } .chg-flag { color: var(--accent-bright); } .chg-ms { color: var(--accent-bright); border-color: color-mix(in srgb, var(--accent-bright) 40%, var(--border)); } .ws-report code { color: var(--accent-bright); } .ws-delta { color: var(--player); font-variant-numeric: tabular-nums; } .ws-flag { color: var(--text-dim); font-style: italic; } .ws-rejected { color: var(--text-dim); } .side-panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; } .side-panel-header h2 { margin: 0; font-family: var(--font-display); font-size: 1.05rem; color: var(--accent); } /* ---------- Insights ---------- */ .insights-meta { margin-bottom: 16px; font-size: 0.85rem; } .insights-mode { color: var(--text-dim); margin-bottom: 8px; } .insights-mode button { padding: 2px 8px; font-size: 0.75rem; } .token-total { font-size: 1rem; color: var(--text); margin-bottom: 4px; } .token-total.over { color: var(--danger); } /* Stacked share-of-context bar. Segment colours come from SECTION_COLORS in Play.jsx so the bar, its legend and each section header stay in sync. */ .token-split { display: flex; height: 14px; background: var(--bg-input); border-radius: 4px; overflow: hidden; margin-bottom: 8px; } .token-split.over { box-shadow: 0 0 0 1px var(--danger); } .token-seg { min-width: 2px; /* a 0.2% section is still worth seeing */ border-right: 1px solid var(--bg-panel); transition: opacity 0.15s ease; } .token-seg:last-child { border-right: none; } .token-seg-free { background: transparent; min-width: 0; border-right: none; } .token-seg.faded, .token-legend-item.faded { opacity: 0.25; } .token-legend { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1px 12px; margin-bottom: 10px; } .token-legend-item { display: flex; align-items: center; gap: 7px; background: none; border: none; border-radius: 4px; padding: 2px 5px; font-size: 0.78rem; color: var(--text-dim); text-align: left; transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease; } .token-legend-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); } .token-swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; } .token-legend-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .token-legend-pct { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; } .token-legend-tok { width: 4.5em; text-align: right; opacity: 0.65; font-variant-numeric: tabular-nums; } .token-legend-more { justify-self: start; background: none; border: none; padding: 2px 5px; font-size: 0.75rem; color: var(--text-dim); } .token-legend-more:hover { color: var(--accent); background: none; } .insights-history { color: var(--text-dim); margin-bottom: 8px; } .insights-cards div { margin-bottom: 3px; } .insights-cards .dropped { color: var(--danger); } .ctx-section { margin-bottom: 12px; border-left: 3px solid var(--border); background: rgba(255, 255, 255, 0.015); border-radius: 0 6px 6px 0; } .ctx-section .ctx-header { display: flex; justify-content: space-between; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); padding: 5px 10px 3px; } .ctx-section pre { margin: 0; padding: 6px 10px 8px; white-space: pre-wrap; word-break: break-word; font-family: Consolas, monospace; font-size: 0.8rem; line-height: 1.5; color: var(--text); max-height: 260px; overflow-y: auto; } /* Per-section border/header colours are applied inline from SECTION_COLORS (Play.jsx) — one palette drives the bar, the legend and these headers. */ /* ---------- Scripts ---------- */ .slot-tabs { display: flex; gap: 0; margin-bottom: 4px; } .slot-tabs button { border-radius: 0; border-right-width: 0; color: var(--text-dim); } .slot-tabs button:first-child { border-radius: 8px 0 0 8px; } .slot-tabs button:last-child { border-radius: 0 8px 8px 0; border-right-width: 1px; } .slot-tabs button.active { color: var(--accent-bright); background: var(--bg-panel); } .slot-hint { color: var(--text-dim); font-size: 0.8rem; margin-bottom: 8px; } .cm-editor { border: 1px solid var(--border); border-radius: 8px; font-size: 0.88rem; } .cm-editor.cm-focused { outline: none; border-color: var(--accent-dim); box-shadow: 0 0 0 3px rgba(212, 169, 78, 0.1); } .test-run .row { display: flex; gap: 12px; } .test-result { margin-top: 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; font-size: 0.85rem; } .test-result .label { text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.08em; color: var(--text-dim); margin-right: 6px; } .test-result pre { display: inline-block; margin: 2px 0; white-space: pre-wrap; word-break: break-word; font-family: Consolas, monospace; vertical-align: top; } .test-error { color: var(--danger); white-space: pre-wrap; } .script-attach { display: flex; align-items: baseline; gap: 8px; padding: 9px 12px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 6px; cursor: pointer; transition: border-color 0.15s; } .script-attach:hover { border-color: var(--border-bright); } .script-attach .dim { color: var(--text-dim); font-size: 0.82rem; } /* ---------- Memory bank (Phase 6) ---------- */ .memory-row { background: var(--bg-input); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; margin-bottom: 6px; } .memory-row.forgotten { opacity: 0.55; } .memory-text { font-size: 0.9rem; white-space: pre-wrap; } .memory-meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px; margin-top: 5px; font-size: 0.78rem; } .memory-meta .dim { color: var(--text-dim); } .memory-meta .action-tools { display: inline-flex; gap: 4px; margin-left: auto; } .memory-meta .action-tools button { padding: 1px 8px; font-size: 0.72rem; color: var(--text-dim); } .memory-meta .action-tools button:hover { color: var(--text); } .memory-badge { color: var(--accent); border: 1px solid var(--border); border-radius: 999px; padding: 0 8px; } .script-report { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 8px; } .script-error { color: var(--danger); font-size: 0.85rem; margin-bottom: 4px; white-space: pre-wrap; } .script-logs { background: var(--bg-input); border-radius: 8px; padding: 8px 10px; font-family: Consolas, monospace; font-size: 0.8rem; white-space: pre-wrap; word-break: break-word; margin: 6px 0; } /* ---------- Filter bar & tags ---------- */ .filter-bar { margin-bottom: 18px; } .search-input { max-width: 340px; } .tag-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; } .tag { padding: 2px 12px; font-size: 0.76rem; border-radius: 999px; color: var(--text-dim); } .tag.active { color: var(--accent-bright); border-color: var(--accent-dim); box-shadow: inset 0 0 10px rgba(212, 169, 78, 0.1); } .tag.small { display: inline-block; background: var(--bg-input); border: 1px solid var(--border); padding: 1px 9px; margin-right: 4px; cursor: default; } /* ---------- Modal ---------- */ .modal-overlay { position: fixed; inset: 0; background: rgba(5, 5, 9, 0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 50; animation: page-in 0.2s ease both; } .modal { width: min(480px, calc(100vw - 40px)); max-height: 80vh; overflow-y: auto; background: var(--bg-panel); border: 1px solid var(--border-bright); border-radius: 14px; padding: 26px 28px; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 40px rgba(212, 169, 78, 0.06); } .modal h2 { margin: 0 0 4px; font-family: var(--font-display); font-size: 1.2rem; color: var(--accent-bright); } .modal .modal-hint { margin: 0 0 18px; color: var(--text-dim); font-size: 0.88rem; } .modal .modal-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; } /* ---------- Auth (log in / sign up) ---------- */ .auth-modal { width: min(400px, calc(100vw - 40px)); text-align: center; padding-top: 22px; } .auth-crest { font-size: 1.1rem; color: var(--accent-dim); letter-spacing: 0.3em; margin-bottom: 6px; } .auth-modal h2 { font-size: 1.35rem; } .auth-modal .auth-hint { margin-bottom: 16px; line-height: 1.45; } /* Fields go back to left-aligned — only the header is centred. */ .auth-modal label.field { text-align: left; } .auth-tabs { display: flex; gap: 4px; padding: 4px; margin-bottom: 18px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 10px; } .auth-tab { flex: 1; padding: 7px 0; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--text-dim); font-family: var(--font-display); font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; } .auth-tab:hover { color: var(--text); background: rgba(212, 169, 78, 0.06); } .auth-tab.active { background: var(--bg-panel); border-color: var(--accent-dim); color: var(--accent-bright); } .auth-password { display: flex; gap: 8px; } .auth-password input { flex: 1; min-width: 0; } .auth-reveal { flex: none; padding: 0 12px; font-size: 0.78rem; color: var(--text-dim); } .auth-reveal:hover { color: var(--accent-bright); } .auth-help { display: block; margin-top: 6px; font-size: 0.76rem; color: var(--text-dim); } .auth-error { margin-top: 4px; padding: 9px 12px; border: 1px solid rgba(208, 101, 101, 0.45); border-radius: 8px; background: rgba(208, 101, 101, 0.09); color: var(--danger); font-size: 0.85rem; text-align: left; white-space: pre-wrap; } /* "Update from scenario" — a wider modal, since it lists a diff. */ .refresh-modal { width: min(620px, calc(100vw - 40px)); } .refresh-changes { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; font-size: 0.86rem; } .refresh-change + .refresh-change { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); } .refresh-old, .refresh-new { margin-top: 3px; font-family: var(--font-mono, monospace); font-size: 0.8rem; } .refresh-old { text-decoration: line-through; } .refresh-new { color: var(--accent-bright); } /* The button sits above the plot fields it would overwrite. */ .plot-source { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid var(--border); font-size: 0.82rem; } .plot-source .linklike { flex-shrink: 0; } /* ---------- Debug log (Settings) ---------- */ .debug-entry { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; overflow: hidden; } .debug-entry-head { display: flex; align-items: baseline; gap: 12px; padding: 9px 12px; cursor: pointer; font-size: 0.86rem; background: var(--bg-input); } .debug-entry-head .dim, .debug-entry-body .dim { color: var(--text-dim); font-size: 0.8rem; } .debug-status { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border-bright); color: var(--text-dim); } .debug-status.ok { color: #7dc9a2; border-color: #7dc9a244; } .debug-status.error { color: var(--danger); border-color: var(--danger); } .debug-status.pending { color: var(--accent); border-color: var(--accent-dim); } .debug-entry-body { padding: 10px 12px; font-size: 0.85rem; } .debug-entry-body pre { margin: 2px 0 8px; padding: 8px 10px; background: var(--bg-input); border-radius: 6px; white-space: pre-wrap; word-break: break-word; font-family: Consolas, monospace; font-size: 0.78rem; max-height: 300px; overflow-y: auto; } /* ---------- Play screen error/success banner ---------- Persistent (no auto-dismiss) and can carry a Retry action, so it stays separate from the transient global toasts further down this file. */ .play-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--bg-panel-glass); backdrop-filter: blur(10px); border: 1px solid var(--danger); color: var(--text); padding: 11px 20px; border-radius: 10px; z-index: 100; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5); animation: toast-in 0.25s ease both; } .play-toast.ok { border-color: var(--accent); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5), 0 0 20px var(--accent-glow); } @keyframes toast-in { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } } /* ============================================================ Illuminated tome — ambient layer, ornament, art plates, cards Everything below styles the home/library surfaces. It comes after the base .card rules on purpose: several selectors here tie with earlier ones on specificity and rely on source order to win. ============================================================ */ /* ---------- Ambient drifting motes ---------- */ /* Behind every page: fixed, unclickable, and out of the stacking flow. The canvas is sized in JS (see Embers.jsx); z-index 0 with the app's own surfaces at 1+ keeps it under content without needing a negative index (which would drop it behind the body background). */ .embers { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.85; } /* .topnav already carries z-index 10 — deliberately not touched here, so it keeps sitting above page content as well as above the canvas. */ .page { position: relative; z-index: 1; } /* A third gradient pool warming the middle of the ground. `background` is a shorthand, so background-attachment has to be restated or it resets to scroll and the gradients start sliding with the page. */ body { background: radial-gradient(1200px 700px at 15% -10%, rgba(212, 169, 78, 0.07), transparent 60%), radial-gradient(1000px 600px at 90% 110%, rgba(88, 76, 140, 0.1), transparent 55%), radial-gradient(760px 520px at 50% 42%, rgba(150, 119, 58, 0.05), transparent 62%), var(--bg); background-attachment: fixed; } /* ---------- Home banner ---------- */ .hall { display: flex; flex-direction: column; gap: 5px; padding: 6px 0 26px; margin-bottom: 4px; border-bottom: 1px solid var(--border); position: relative; } /* Gold hairline that fades out toward both ends, sat on the border above. */ .hall::after { content: ''; position: absolute; bottom: -1px; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, var(--accent-dim) 22%, var(--accent-dim) 78%, transparent); opacity: 0.55; } .hall-eyebrow { margin: 0; font-size: 0.68rem; letter-spacing: 0.24em; text-transform: uppercase; color: var(--accent-dim); font-weight: 600; } .hall-title { margin: 0; font-family: var(--font-display); font-size: clamp(1.7rem, 4.6vw, 2.3rem); font-weight: 600; letter-spacing: 0.05em; color: var(--accent-bright); text-shadow: 0 0 30px var(--accent-glow); text-wrap: balance; } .hall-sub { margin: 0; color: var(--text-dim); font-family: var(--font-story); font-size: 1.02rem; } /* ---------- Ornamental section rules ---------- */ .home-section { margin-top: 34px; } .rule-head { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; } .rule { flex: 1; display: flex; align-items: center; gap: 14px; margin: 0; font-family: var(--font-display); font-size: 0.82rem; font-weight: 600; letter-spacing: 0.26em; text-transform: uppercase; color: var(--accent-bright); white-space: nowrap; } /* Rules grow to fill whatever the label leaves — the label stays optically centred in its own space without needing a fixed width. */ .rule::before, .rule::after { content: ''; height: 1px; flex: 1; background: linear-gradient(90deg, transparent, var(--accent-dim) 45%, var(--accent-dim) 55%, transparent); } .rule::before { flex: 0 0 26px; } .rule > span { flex: none; } button.linklike.ornate { font-family: var(--font-display); font-size: 0.74rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-dim); white-space: nowrap; transition: color 0.15s; } button.linklike.ornate:hover { color: var(--accent-bright); text-decoration: none; } /* ---------- Art plates ---------- */ .art { width: 38px; height: 38px; flex: none; border-radius: 3px; border: 1px solid var(--accent-dim); box-shadow: inset 0 0 0 1px rgba(10, 10, 15, 0.7); overflow: hidden; display: grid; place-items: center; line-height: 1; } .art-lg { width: 46px; height: 46px; } .art-photo img { width: 100%; height: 100%; object-fit: cover; display: block; } .art-icon { font-size: 1.15rem; } .art-lg.art-icon { font-size: 1.4rem; } .art-mono { font-family: var(--font-display); font-size: 0.82rem; font-weight: 700; letter-spacing: 0.06em; color: rgba(232, 196, 118, 0.92); text-shadow: 0 1px 4px rgba(0, 0, 0, 0.65); } .art-lg.art-mono { font-size: 0.95rem; } /* ---------- Tome cards ---------- */ .card-grid.wide { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); } .card.tome { border-radius: 3px; padding: 16px 17px 14px; background: linear-gradient(168deg, rgba(212, 169, 78, 0.05), transparent 55%), var(--bg-panel); /* Inner dark gutter + a gold keyline: the double-rule border of a plate in an old book, done with shadows so it costs no extra element. */ box-shadow: inset 0 0 0 1px rgba(10, 10, 15, 0.9), inset 0 0 0 2px rgba(150, 119, 58, 0.22); } /* Filigree corner brackets. These override the base .card::before gradient bar (equal specificity, later in the file wins). */ .card.tome::before, .card.tome::after { content: ''; position: absolute; width: 12px; height: 12px; border: 1px solid var(--accent-dim); opacity: 0.5; transition: opacity 0.2s; background: none; inset: auto; } .card.tome::before { top: 6px; left: 6px; border-right: 0; border-bottom: 0; } .card.tome::after { bottom: 6px; right: 6px; border-left: 0; border-top: 0; } .card.tome:hover::before, .card.tome:hover::after { opacity: 1; } .card.tome:hover { border-color: var(--accent-dim); box-shadow: inset 0 0 0 1px rgba(10, 10, 15, 0.9), inset 0 0 0 2px rgba(212, 169, 78, 0.4), 0 12px 30px rgba(0, 0, 0, 0.5), 0 0 26px rgba(212, 169, 78, 0.08); } .card-head { display: flex; gap: 12px; align-items: center; margin-bottom: 10px; } /* Keep headings clear of the wax seal, which is absolutely positioned over the card's top-right corner. Only cards that actually carry one pay the indent, so scenario cards keep their full width. */ .card.tome:has(> .seal) .card-head { padding-right: 26px; } .card-headings { min-width: 0; } .card.tome h3 { margin: 0; font-family: var(--font-display); font-size: 1rem; font-weight: 600; letter-spacing: 0.045em; color: var(--accent); overflow-wrap: anywhere; } .card-from { margin: 3px 0 0; font-size: 0.75rem; color: var(--text-dim); overflow-wrap: anywhere; } /* Wax seal marking a story in progress. */ .seal { position: absolute; top: 12px; right: 12px; width: 24px; height: 24px; border-radius: 50%; background: radial-gradient(circle at 34% 30%, #b8553f, #7d2f24 72%); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.6), inset 0 -1px 3px rgba(0, 0, 0, 0.45); display: grid; place-items: center; font-size: 0.6rem; color: #f0d5b0; z-index: 1; } .snippet { margin: 0; font-family: var(--font-story); font-size: 0.98rem; line-height: 1.5; color: #b3ac9c; /* Clamp so a long beat can't stretch one card taller than its row. */ display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .snippet.muted { color: var(--text-dim); font-style: italic; } .snippet.dropcap::first-letter { float: left; font-family: var(--font-display); font-size: 2.3rem; line-height: 0.85; font-weight: 700; color: var(--accent); padding: 3px 7px 0 0; } .card-foot { display: flex; align-items: center; gap: 8px; margin-top: 13px; padding-top: 11px; border-top: 1px solid var(--border); font-size: 0.74rem; color: var(--text-dim); flex-wrap: wrap; } .card-foot .turns { font-variant-numeric: tabular-nums; } .card-foot .dot { opacity: 0.5; } .card-foot .resume { margin-left: auto; color: var(--accent-dim); font-family: var(--font-ui); letter-spacing: 0.1em; text-transform: uppercase; font-size: 0.68rem; font-weight: 600; transition: color 0.15s; } .card.tome:hover .resume { color: var(--accent-bright); } .tag-cluster { display: flex; gap: 5px; flex-wrap: wrap; min-width: 0; } .card-actions { margin-left: auto; display: inline-flex; gap: 5px; } button.tiny { padding: 2px 9px; font-size: 0.72rem; } button.primary.compact { padding: 3px 12px; font-size: 0.76rem; margin-left: auto; } /* Staggered card entrance. The per-card delay is set inline. */ .enter { animation: card-in 0.4s ease both; } @keyframes card-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } /* ---------- Loading skeletons ---------- */ .skeleton-card { cursor: default; pointer-events: none; animation: card-in 0.3s ease both; } .skeleton-card::before, .skeleton-card::after { content: none; } .skeleton-card:hover { transform: none; border-color: var(--border); box-shadow: none; } .sk { border-radius: 3px; background: linear-gradient(90deg, #1c1c2b 25%, #262639 50%, #1c1c2b 75%); background-size: 220% 100%; animation: shimmer 1.4s ease-in-out infinite; } .sk-plate { width: 38px; height: 38px; margin-bottom: 12px; } .sk-title { height: 13px; width: 58%; margin-bottom: 12px; } .sk-line { height: 9px; margin-bottom: 8px; } @keyframes shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } } /* ---------- Global toasts ---------- */ .toast-host { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); z-index: 120; display: flex; flex-direction: column-reverse; gap: 8px; align-items: center; width: max-content; max-width: min(92vw, 520px); pointer-events: none; } .toast { pointer-events: auto; display: flex; align-items: flex-start; gap: 9px; text-align: left; font-family: var(--font-ui); font-size: 0.86rem; line-height: 1.45; padding: 10px 16px; border-radius: 8px; background: var(--bg-panel-glass); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--border-bright); border-left: 3px solid var(--accent); color: var(--text); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5); animation: rise 0.25s ease both; } .toast:hover { border-color: var(--accent-dim); } .toast-error { border-left-color: var(--danger); } .toast-mark { color: var(--accent); flex: none; line-height: 1.5; } .toast-error .toast-mark { color: var(--danger); } @keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } /* ---------- Story text polish ---------- */ /* Drop cap opening the story's first narrated beat. */ .story .action.opening::first-letter { float: left; font-family: var(--font-display); font-size: 3.1rem; line-height: 0.82; font-weight: 700; color: var(--accent); padding: 5px 10px 0 2px; text-shadow: 0 0 22px var(--accent-glow); } /* Ornamental separator between turns — a diamond flanked by fading rules. Marks the beat change without the heaviness of a full