Spaces:
Running
Running
| /* LANDING β Foundry chassis. docs/SITE_REDESIGN.md. | |
| No figure is written here. Anything numeric on this page is rendered from | |
| model-registry.ts at runtime or it is absent, so adding a model updates the | |
| page by itself and a stale number cannot be committed. */ | |
| *, *::before, *::after { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: var(--body); | |
| font-size: var(--step-0); | |
| line-height: 1.65; | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| a { color: var(--accent); text-decoration: none; } | |
| a:hover { color: var(--accent-hi); } | |
| :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--rad-s); } | |
| .wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; } | |
| /* ββ eyebrow βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .eyebrow { | |
| display: inline-flex; align-items: center; gap: 8px; | |
| font-family: var(--mono); font-size: var(--step--1); | |
| letter-spacing: 0.08em; text-transform: uppercase; color: var(--dim); | |
| } | |
| .pulse { | |
| width: 7px; height: 7px; border-radius: 50%; background: var(--accent); | |
| box-shadow: 0 0 0 0 var(--accent-dim); animation: pulse 2.4s ease-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 #f0a86066; } | |
| 70% { box-shadow: 0 0 0 9px #f0a86000; } | |
| 100% { box-shadow: 0 0 0 0 #f0a86000; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { .pulse { animation: none; } } | |
| /* ββ nav βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .nav { | |
| position: sticky; top: 0; z-index: 40; | |
| background: color-mix(in srgb, var(--bg) 88%, transparent); | |
| backdrop-filter: blur(10px); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .nav-in { display: flex; align-items: center; gap: 28px; height: 60px; } | |
| .nav-logo { | |
| font-family: var(--mono); font-weight: 500; color: var(--text); | |
| letter-spacing: -0.02em; margin-right: auto; | |
| } | |
| .nav-logo span { color: var(--accent); } | |
| .nav a:not(.nav-logo):not(.nav-cta) { color: var(--muted); font-size: var(--step--1); } | |
| .nav a:not(.nav-logo):not(.nav-cta):hover { color: var(--text); } | |
| .nav-cta { | |
| font-size: var(--step--1); padding: 7px 15px; border-radius: var(--rad-s); | |
| border: 1px solid var(--accent); color: var(--accent); | |
| } | |
| .nav-cta:hover { background: var(--accent); color: var(--bg); } | |
| .nav-links { display: flex; gap: 22px; align-items: center; } | |
| /* On phones the nav used to collapse to ONLY "Open chat" β the single action | |
| that costs gigabytes β while Docs and everything explanatory vanished. Docs | |
| stays; the heavyweight links are the ones that can go. */ | |
| @media (max-width: 720px) { .nav-links a:not(.nav-cta):not([href="docs.html"]) { display: none; } } | |
| /* ββ hero ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .hero { padding: 84px 0 64px; } | |
| .hero h1 { | |
| font-family: var(--display); | |
| font-size: var(--step-3); | |
| font-weight: 400; | |
| line-height: 1.08; | |
| letter-spacing: -0.02em; | |
| margin: 20px 0 0; | |
| text-wrap: balance; | |
| } | |
| .hero h1 em { font-style: italic; color: var(--accent); } | |
| .hero p { | |
| color: var(--muted); max-width: 62ch; margin: 20px 0 0; font-size: var(--step-1); | |
| line-height: 1.6; | |
| } | |
| .hero-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; } | |
| .btn { | |
| font-family: var(--body); font-size: var(--step-0); | |
| padding: 11px 20px; border-radius: var(--rad-s); border: 1px solid var(--border-hi); | |
| color: var(--text); background: transparent; cursor: pointer; | |
| } | |
| .btn:hover { border-color: var(--accent); color: var(--accent); } | |
| .btn-primary { background: var(--accent); border-color: var(--accent); color: #1a1206; font-weight: 500; } | |
| .btn-primary:hover { background: var(--accent-hi); border-color: var(--accent-hi); color: #1a1206; } | |
| /* ββ kernel window βββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .kw { | |
| margin-top: 44px; border: 1px solid var(--border); border-radius: var(--rad); | |
| background: var(--surface); overflow: hidden; | |
| } | |
| .kw-bar { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 0 14px; height: 40px; border-bottom: 1px solid var(--border); | |
| background: var(--surface-2); | |
| } | |
| .kw-dots { display: flex; gap: 6px; margin-right: 6px; } | |
| .kw-dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--border-hi); } | |
| .kw-tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; } | |
| .kw-tabs::-webkit-scrollbar { display: none; } | |
| .kw-tab { | |
| font-family: var(--mono); font-size: var(--step--1); color: var(--dim); | |
| background: none; border: 0; padding: 6px 11px; border-radius: var(--rad-s); | |
| cursor: pointer; white-space: nowrap; | |
| } | |
| .kw-tab:hover { color: var(--muted); } | |
| .kw-tab[aria-selected="true"] { color: var(--accent); background: var(--accent-dim); } | |
| .kw-body { overflow-x: auto; } | |
| .kw-body pre { | |
| margin: 0; padding: 20px 22px; | |
| font-family: var(--mono); font-size: var(--step--1); line-height: 1.7; | |
| color: var(--muted); | |
| } | |
| .kw-body pre[hidden] { display: none; } | |
| .kw-foot { | |
| display: flex; justify-content: space-between; align-items: center; gap: 12px; | |
| padding: 10px 16px; border-top: 1px solid var(--border); | |
| font-family: var(--mono); font-size: var(--step--1); color: var(--dim); | |
| flex-wrap: wrap; | |
| } | |
| /* WGSL syntax β one hue family, so the code reads as material rather than | |
| as a rainbow. Comments recede; the accent is spent on identifiers. */ | |
| .t-key { color: #c58bff; } | |
| .t-fn { color: var(--accent-hi); } | |
| .t-num { color: var(--ok); } | |
| .t-str { color: #9fd3ff; } | |
| .t-com { color: var(--dim); font-style: italic; } | |
| .t-attr { color: #7e8aa0; } | |
| .t-type { color: #8fd0ff; } | |
| /* ββ capability band βββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .band { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--border); | |
| border: 1px solid var(--border); border-radius: var(--rad); overflow: hidden; } | |
| @media (max-width: 840px) { .band { grid-template-columns: repeat(2, 1fr); } } | |
| @media (max-width: 480px) { .band { grid-template-columns: 1fr; } } | |
| .band a { background: var(--surface); padding: 22px; display: block; color: inherit; } | |
| .band a:hover { background: var(--surface-2); } | |
| .band h3 { | |
| font-family: var(--mono); font-size: var(--step--1); text-transform: uppercase; | |
| letter-spacing: 0.08em; color: var(--accent); margin: 0 0 8px; font-weight: 500; | |
| } | |
| .band p { margin: 0; color: var(--muted); font-size: var(--step--1); line-height: 1.6; } | |
| /* ββ sections ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| section { padding: 72px 0; } | |
| .sec-head { margin-bottom: 32px; } | |
| .sec-head h2 { | |
| font-family: var(--display); font-weight: 400; font-size: var(--step-2); | |
| margin: 12px 0 0; letter-spacing: -0.01em; | |
| } | |
| .sec-head p { color: var(--muted); margin: 12px 0 0; max-width: 66ch; } | |
| /* ββ model browser βββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| /* The container IS the keyboard interface β index.html tells the reader the | |
| arrow keys move between models, and this is the element that must be | |
| focused for that to work. Removing its ring left no way to tell. */ | |
| #model-browser:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } | |
| .mb-stage { display: flex; align-items: center; gap: 8px; } | |
| .mb-arrow { | |
| flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; | |
| background: var(--surface); border: 1px solid var(--border); color: var(--muted); | |
| font-size: 22px; line-height: 1; cursor: pointer; font-family: var(--body); | |
| } | |
| .mb-arrow:hover { border-color: var(--accent); color: var(--accent); } | |
| .mb-slide { | |
| flex: 1 1 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 32px; | |
| align-items: center; min-height: 380px; | |
| border: 1px solid var(--border); border-radius: var(--rad); | |
| background: linear-gradient(180deg, var(--surface-2), var(--surface)); | |
| padding: 28px 32px; | |
| } | |
| @media (max-width: 780px) { | |
| .mb-slide { grid-template-columns: 1fr; text-align: center; min-height: 0; } | |
| .mb-stats { justify-items: center; } | |
| .mb-variants, .mb-info { justify-content: center; align-items: center; } | |
| } | |
| .mb-art { position: relative; padding-bottom: 66px; } | |
| .mb-mascot { display: block; width: 100%; height: 320px; position: relative; z-index: 1; } | |
| @media (max-width: 780px) { .mb-mascot { height: 220px; } } | |
| /* The pedestal: a class-coloured light pool under the figure β the character | |
| stands ON something, which is most of what makes a select screen read as a | |
| stage rather than a card. */ | |
| .mb-pedestal { | |
| position: absolute; left: 50%; bottom: 46px; transform: translateX(-50%); | |
| width: 78%; height: 64px; border-radius: 50%; | |
| background: radial-gradient(ellipse at center, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 34%, transparent) 0%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 10%, transparent) 55%, | |
| transparent 72%); | |
| filter: blur(2px); | |
| } | |
| /* Nameplate under the stage, class-coloured keyline. */ | |
| .mb-plate { | |
| position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); | |
| text-align: center; padding: 8px 22px 10px; min-width: 60%; | |
| border: 1px solid color-mix(in srgb, var(--cs-accent, var(--accent)) 45%, var(--border)); | |
| border-radius: 10px; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 7%, var(--surface)); | |
| } | |
| .mb-panel { | |
| border: 1px solid var(--border); border-radius: 12px; | |
| background: linear-gradient(180deg, var(--surface-2), transparent); | |
| padding: 16px 18px 14px; | |
| } | |
| .mb-row-label { | |
| font-family: var(--mono); font-size: var(--step--1); color: var(--dim); | |
| text-transform: uppercase; letter-spacing: 0.09em; margin: 10px 0 6px; | |
| } | |
| .mb-row-label:first-child { margin-top: 0; } | |
| .mb-info { display: flex; flex-direction: column; gap: 14px; } | |
| .mb-name { font-family: var(--display); font-size: var(--step-2); line-height: 1.1; } | |
| .mb-params { font-family: var(--mono); font-size: var(--step--1); color: var(--dim); margin-top: 2px; } | |
| .mb-variants { display: flex; gap: 6px; flex-wrap: wrap; } | |
| /* Memory builds β the character screen's loadout row. Same pill grammar as | |
| the quantisation picker: a build is a choice about THIS character, and the | |
| selected one re-poses the figure (leaner, streamed experts drifting out). */ | |
| .mb-modes { display: flex; gap: 6px; flex-wrap: wrap; } | |
| .mb-variant { | |
| font-family: var(--mono); font-size: var(--step--1); cursor: pointer; | |
| padding: 5px 12px; border-radius: 99px; | |
| border: 1px solid var(--border-hi); background: transparent; color: var(--muted); | |
| } | |
| .mb-variant:hover { color: var(--text); border-color: var(--accent); } | |
| .mb-variant[aria-selected="true"] { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); } | |
| .mb-stats { display: grid; grid-template-columns: repeat(3, auto); gap: 22px; margin: 14px 0 4px; justify-content: start; } | |
| @media (max-width: 780px) { .mb-stats { justify-content: center; } } | |
| /* At 390px the three-column stats row set a ~204px min-content floor which, | |
| plus slide padding and the two 42px arrows, needed ~368px of a 342px | |
| viewport β the body scrolled sideways. Flex-wrap has no min-content floor: | |
| each stat drops to the next line instead of pushing the page. min-width: 0 | |
| on the grid/flex items is what lets the slide actually shrink. */ | |
| @media (max-width: 480px) { | |
| .mb-stats { display: flex; flex-wrap: wrap; gap: 10px 20px; } | |
| .mb-slide { min-width: 0; padding: 20px 18px; } | |
| .mb-art, .mb-info { min-width: 0; } | |
| .mb-arrow { width: 34px; height: 34px; font-size: 18px; } | |
| } | |
| .mb-stats dt { | |
| font-family: var(--mono); font-size: var(--step--1); color: var(--dim); | |
| text-transform: uppercase; letter-spacing: 0.07em; | |
| } | |
| .mb-stats dd { margin: 4px 0 0; font-family: var(--mono); font-size: var(--step-0); color: var(--text); } | |
| .mb-hw { color: var(--dim); font-size: var(--step--1); } | |
| .mb-cached { color: var(--ok); font-size: var(--step--1); margin: 14px 0 0; } | |
| .mb-ram { color: var(--warn); font-size: var(--step--1); margin: 14px 0 0; } | |
| .mb-cta { | |
| align-self: stretch; text-align: center; font-size: var(--step-0); | |
| padding: 0.85rem 1.2rem; | |
| background: var(--cs-accent, var(--accent)); | |
| border-color: var(--cs-accent, var(--accent)); | |
| } | |
| .mb-cta:hover { filter: brightness(1.12); } | |
| @media (max-width: 780px) { .mb-cta { align-self: center; } } | |
| /* The roster rail β party lineup instead of dots. Each slot carries the | |
| character's own snapshotted face (--thumb, painted async); cached models | |
| were snapshotted lit, which is the visual for "unlocked". */ | |
| .mb-dots { display: flex; gap: 10px; justify-content: center; margin-top: 22px; flex-wrap: wrap; } | |
| .mb-dot { | |
| width: 64px; height: 76px; padding: 0 0 4px; cursor: pointer; | |
| border: 1px solid var(--border-hi); border-radius: 10px; | |
| background: | |
| var(--thumb, radial-gradient(circle at 50% 40%, var(--border-hi) 0%, transparent 60%)) | |
| center 2px / 58px 58px no-repeat, | |
| var(--surface); | |
| display: flex; align-items: flex-end; justify-content: center; | |
| transition: border-color 0.15s, transform 0.15s; | |
| } | |
| .mb-dot:hover { border-color: var(--muted); transform: translateY(-2px); } | |
| .mb-dot[aria-selected="true"] { | |
| border-color: var(--cs-accent, var(--accent)); | |
| box-shadow: 0 0 0 1px var(--cs-accent, var(--accent)), | |
| 0 6px 18px color-mix(in srgb, var(--cs-accent, var(--accent)) 25%, transparent); | |
| } | |
| .mb-dot-name { | |
| font-family: var(--mono); font-size: 9px; color: var(--dim); | |
| max-width: 60px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; | |
| } | |
| .mb-dot[aria-selected="true"] .mb-dot-name { color: var(--text); } | |
| @media (max-width: 480px) { .mb-dot { width: 52px; height: 64px; } .mb-dot-name { font-size: 8px; } } | |
| .note { color: var(--muted); font-size: var(--step--1); margin-top: 16px; } | |
| /* ββ kernel wall βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| /* Five columns so the ten tiles fill exactly two rows β auto-fill left a | |
| half-empty third row that read as a rendering fault. */ | |
| .wall { display: grid; grid-template-columns: repeat(5, 1fr); gap: 1px; | |
| background: var(--border); border: 1px solid var(--border); border-radius: var(--rad); | |
| overflow: hidden; } | |
| @media (max-width: 900px) { .wall { grid-template-columns: repeat(3, 1fr); } } | |
| @media (max-width: 560px) { .wall { grid-template-columns: repeat(2, 1fr); } } | |
| .wall a { | |
| background: var(--surface); padding: 16px 14px; color: var(--muted); | |
| font-family: var(--mono); font-size: var(--step--1); display: block; | |
| } | |
| .wall a:hover { background: var(--surface-2); color: var(--accent); } | |
| .wall b { display: block; color: var(--text); font-weight: 500; margin-bottom: 4px; } | |
| .wall a:hover b { color: var(--accent); } | |
| .wall span { color: var(--dim); } | |
| /* ββ proof strip βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .proof { | |
| border: 1px solid var(--border); border-left: 2px solid var(--accent); | |
| border-radius: var(--rad); background: var(--surface); padding: 26px 28px; | |
| } | |
| .proof h2 { font-family: var(--display); font-weight: 400; font-size: var(--step-1); margin: 0; } | |
| .proof p { color: var(--muted); margin: 10px 0 0; max-width: 70ch; } | |
| /* ββ footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| footer { border-top: 1px solid var(--border); padding: 56px 0 72px; } | |
| .foot-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; } | |
| @media (max-width: 780px) { .foot-grid { grid-template-columns: 1fr; gap: 28px; } } | |
| footer h4 { | |
| font-family: var(--mono); font-size: var(--step--1); text-transform: uppercase; | |
| letter-spacing: 0.08em; color: var(--dim); margin: 0 0 12px; font-weight: 500; | |
| } | |
| footer ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; } | |
| footer li a { color: var(--muted); font-size: var(--step--1); } | |
| footer li a:hover { color: var(--accent); } | |
| .foot-about { color: var(--muted); font-size: var(--step--1); max-width: 44ch; } | |
| .foot-bottom { | |
| margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--border); | |
| display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; | |
| font-family: var(--mono); font-size: var(--step--1); color: var(--dim); | |
| } | |
| /* ββ THE GAME ENTRANCE βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| #models is the site: a full-viewport character select. The chrome is the | |
| circuit-rune asset set in /entrance (bg painting, pedestal ring, corner | |
| brackets, divider) β monochrome SVGs applied through CSS masks so every | |
| ornament tints to the class accent of the model on stage. Data unchanged: | |
| every value is the registry row. */ | |
| #models { padding: 0; } | |
| #models .cs-root { | |
| position: relative; | |
| min-height: 100dvh; | |
| display: grid; | |
| grid-template-columns: minmax(270px, 350px) 1fr minmax(250px, 320px); | |
| grid-template-rows: auto 1fr auto auto; | |
| grid-template-areas: | |
| 'plate plate roster' | |
| 'sheet stage roster' | |
| 'enter enter roster' | |
| 'note note note'; | |
| gap: 10px 24px; | |
| padding: calc(var(--nav-h, 64px) + 14px) 30px 24px; | |
| background: | |
| radial-gradient(ellipse at 50% 92%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 13%, transparent) 0%, | |
| transparent 52%), | |
| url('/entrance/bg.svg') center / cover no-repeat, | |
| var(--bg); | |
| overflow: hidden; | |
| } | |
| /* Atmosphere: two fog bands drifting opposite ways, and a dust field. All | |
| accent-neutral; the accent lives in the radial wash above. */ | |
| .cs-fog { position: absolute; inset: 0; pointer-events: none; z-index: 0; } | |
| .cs-fog i { | |
| position: absolute; width: 90vw; height: 34vh; border-radius: 50%; | |
| background: radial-gradient(ellipse at center, #18223a55 0%, transparent 65%); | |
| filter: blur(30px); | |
| } | |
| .cs-fog i:nth-child(1) { left: -20vw; bottom: 4vh; animation: fogA 46s ease-in-out infinite alternate; } | |
| .cs-fog i:nth-child(2) { right: -25vw; bottom: 12vh; animation: fogB 58s ease-in-out infinite alternate; } | |
| @keyframes fogA { from { transform: translateX(0); } to { transform: translateX(9vw); } } | |
| @keyframes fogB { from { transform: translateX(0); } to { transform: translateX(-11vw); } } | |
| .cs-dust { | |
| position: absolute; inset: -10% 0 0 0; pointer-events: none; z-index: 0; opacity: 0.5; | |
| background-image: | |
| radial-gradient(1.2px 1.2px at 12% 30%, #cfe0ff88 50%, transparent 51%), | |
| radial-gradient(1px 1px at 34% 68%, #cfe0ff66 50%, transparent 51%), | |
| radial-gradient(1.4px 1.4px at 58% 22%, #cfe0ff77 50%, transparent 51%), | |
| radial-gradient(1px 1px at 71% 55%, #cfe0ff55 50%, transparent 51%), | |
| radial-gradient(1.2px 1.2px at 86% 34%, #cfe0ff77 50%, transparent 51%), | |
| radial-gradient(1px 1px at 45% 84%, #cfe0ff44 50%, transparent 51%); | |
| background-size: 900px 700px; | |
| animation: dust 90s linear infinite; | |
| } | |
| @keyframes dust { from { background-position: 0 0; } to { background-position: -900px 350px; } } | |
| /* Nameplate: display face over an ornamental divider. */ | |
| #models .mb-plate { | |
| grid-area: plate; position: relative; z-index: 1; justify-self: center; | |
| text-align: center; margin-top: 4px; padding: 0 26px 14px; | |
| border: 0; background: none; | |
| } | |
| #models .mb-name { | |
| font-family: var(--display); font-size: clamp(1.9rem, 3.6vw, 2.9rem); | |
| line-height: 1.05; text-shadow: 0 2px 24px #000c; | |
| } | |
| #models .mb-params { | |
| font-family: var(--mono); font-size: var(--step--1); letter-spacing: 0.14em; | |
| text-transform: uppercase; margin-top: 6px; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 75%, var(--text)); | |
| } | |
| #models .mb-plate::after { | |
| content: ''; display: block; height: 16px; margin: 10px auto 0; width: min(360px, 80%); | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 80%, #fff); | |
| -webkit-mask: url('/entrance/divider.svg') center / contain no-repeat; | |
| mask: url('/entrance/divider.svg') center / contain no-repeat; | |
| } | |
| /* The stage. */ | |
| #models .mb-stage { grid-area: stage; display: flex; align-items: stretch; gap: 4px; min-height: 0; position: relative; z-index: 1; } | |
| #models .mb-art { flex: 1; padding: 0; display: flex; align-items: center; justify-content: center; position: relative; } | |
| #models .mb-mascot { height: min(54dvh, 500px); max-width: 100%; position: relative; z-index: 2; } | |
| #models .mb-pedestal { | |
| position: absolute; left: 50%; bottom: 2%; transform: translateX(-50%); | |
| width: min(560px, 86%); height: 150px; border-radius: 0; filter: none; | |
| background: radial-gradient(ellipse at center, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 30%, transparent) 0%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 8%, transparent) 52%, | |
| transparent 70%); | |
| } | |
| #models .mb-pedestal::after { | |
| content: ''; position: absolute; inset: 0; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| -webkit-mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| animation: ringPulse 5s ease-in-out infinite; | |
| } | |
| @keyframes ringPulse { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.5; } } | |
| #models .mb-arrow { | |
| align-self: center; width: 52px; height: 76px; border-radius: 12px; | |
| background: transparent; border: 1px solid transparent; font-size: 34px; | |
| color: var(--dim); z-index: 2; | |
| } | |
| #models .mb-arrow:hover { color: var(--cs-accent, var(--accent)); border-color: var(--border); background: #0b101cbb; } | |
| /* The sheet: glass plate with rune corners. */ | |
| #models .mb-info { grid-area: sheet; align-self: center; z-index: 1; } | |
| #models .mb-panel { | |
| position: relative; border: 1px solid color-mix(in srgb, var(--cs-accent, var(--accent)) 22%, var(--border)); | |
| border-radius: 4px; padding: 20px 20px 16px; | |
| background: linear-gradient(180deg, #0c1220ee, #090e18e6); | |
| box-shadow: inset 0 1px 0 #ffffff0a, 0 18px 50px #0009; | |
| backdrop-filter: blur(4px); | |
| } | |
| #models .mb-panel::before, #models .mb-panel::after { | |
| content: ''; position: absolute; width: 40px; height: 40px; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| -webkit-mask: url('/entrance/corner.svg') 0 0 / contain no-repeat; | |
| mask: url('/entrance/corner.svg') 0 0 / contain no-repeat; | |
| } | |
| #models .mb-panel::before { top: -5px; left: -5px; } | |
| #models .mb-panel::after { bottom: -5px; right: -5px; transform: rotate(180deg); } | |
| #models .mb-row-label { | |
| font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.16em; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, var(--dim)); | |
| text-transform: uppercase; margin: 14px 0 7px; | |
| } | |
| #models .mb-row-label:first-child { margin-top: 0; } | |
| #models .mb-stats { display: block; margin: 12px 0 2px; } | |
| #models .mb-stats > div { | |
| display: flex; justify-content: space-between; align-items: baseline; | |
| padding: 6px 2px; border-bottom: 1px solid #ffffff0d; | |
| } | |
| #models .mb-stats dt { | |
| font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.12em; color: var(--dim); | |
| } | |
| #models .mb-stats dd { | |
| margin: 0; font-family: var(--mono); font-size: var(--step-0); | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 45%, var(--text)); | |
| } | |
| #models .mb-variant { | |
| border-radius: 3px; border-color: #ffffff1c; background: #ffffff07; | |
| } | |
| #models .mb-variant[aria-selected="true"] { | |
| border-color: var(--cs-accent, var(--accent)); | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 18%, transparent); | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, #fff); | |
| } | |
| #models .mb-cached { color: var(--ok); font-size: var(--step--1); margin: 10px 0 0; } | |
| #models .mb-ram { font-size: var(--step--1); margin: 8px 0 0; } | |
| /* Roster rail. */ | |
| #models .cs-roster-head { | |
| grid-area: roster; align-self: start; z-index: 1; | |
| font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.18em; | |
| text-transform: uppercase; color: var(--dim); | |
| padding: 2px 0 0 16px; height: 20px; | |
| } | |
| #models .mb-roster { | |
| grid-area: roster; z-index: 1; margin: 26px 0 0; padding: 0 4px 4px 14px; | |
| display: flex; flex-direction: column; gap: 8px; justify-content: flex-start; | |
| overflow-y: auto; border-left: 1px solid #ffffff12; | |
| scrollbar-width: thin; scrollbar-color: var(--border-hi) transparent; | |
| } | |
| #models .mb-dot { | |
| width: 100%; height: auto; min-height: 64px; padding: 7px 10px; | |
| display: flex; flex-direction: row; align-items: center; gap: 11px; | |
| text-align: left; border-radius: 4px; cursor: pointer; | |
| border: 1px solid #ffffff14; | |
| background: linear-gradient(180deg, #ffffff08, transparent 60%), #0a0f1acc; | |
| transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s; | |
| position: relative; | |
| } | |
| #models .mb-dot:hover { border-color: var(--muted); transform: translateX(-2px); } | |
| #models .mb-dot[aria-selected="true"] { | |
| border-color: var(--cs-accent, var(--accent)); | |
| box-shadow: inset 0 0 0 1px var(--cs-accent, var(--accent)), | |
| 0 0 22px color-mix(in srgb, var(--cs-accent, var(--accent)) 30%, transparent); | |
| } | |
| #models .mb-dot[data-cached]::after { | |
| content: 'READY'; position: absolute; top: 5px; right: 7px; | |
| font-family: var(--mono); font-size: 0.5rem; letter-spacing: 0.12em; | |
| color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 55%, transparent); | |
| border-radius: 3px; padding: 1px 4px; | |
| } | |
| .mb-dot-face { | |
| flex: 0 0 46px; height: 46px; border-radius: 4px; | |
| background: var(--thumb, radial-gradient(circle at 50% 40%, var(--border-hi) 0%, transparent 60%)) | |
| center / cover no-repeat, #0d1322; | |
| border: 1px solid #ffffff10; | |
| } | |
| .mb-dot-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; } | |
| .mb-dot-text b { font-size: 0.8rem; font-weight: 600; color: var(--text); | |
| white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| .mb-dot-text i { font-style: normal; font-family: var(--mono); font-size: 0.6rem; | |
| color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| /* ENTER. */ | |
| #models .cs-enter { grid-area: enter; display: flex; justify-content: center; padding: 4px 0 2px; z-index: 1; } | |
| #models .cs-enter .mb-cta { | |
| min-width: 340px; text-align: center; letter-spacing: 0.14em; text-transform: uppercase; | |
| font-weight: 700; font-size: var(--step-0); padding: 0.95rem 1.4rem; | |
| border-radius: 4px; position: relative; overflow: hidden; | |
| color: #0b0d10; | |
| background: linear-gradient(180deg, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 80%, #fff) 0%, | |
| var(--cs-accent, var(--accent)) 55%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, #000) 100%); | |
| border: 1px solid color-mix(in srgb, var(--cs-accent, var(--accent)) 60%, #fff); | |
| box-shadow: 0 10px 34px color-mix(in srgb, var(--cs-accent, var(--accent)) 40%, transparent), | |
| inset 0 1px 0 #ffffff66; | |
| } | |
| #models .cs-enter .mb-cta:hover { filter: brightness(1.1); } | |
| #models .cs-enter .mb-cta::after { | |
| content: ''; position: absolute; top: 0; bottom: 0; width: 40%; | |
| left: -60%; transform: skewX(-20deg); | |
| background: linear-gradient(90deg, transparent, #ffffff55, transparent); | |
| transition: left 0.5s ease; | |
| } | |
| #models .cs-enter .mb-cta:hover::after { left: 130%; } | |
| #models .cs-note { grid-area: note; text-align: center; z-index: 1; } | |
| /* The logo opening. */ | |
| .cs-splash { | |
| position: absolute; inset: 0; z-index: 30; | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; | |
| background: radial-gradient(ellipse at 50% 42%, #0d1424 0%, var(--bg) 70%); | |
| animation: csSplashOut 2.6s ease forwards; | |
| } | |
| .cs-emblem { | |
| width: 84px; height: 84px; color: var(--accent); | |
| animation: csEmblem 1.4s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| } | |
| .cs-logo { | |
| font-family: var(--display); font-size: clamp(3rem, 9vw, 6.5rem); color: var(--text); | |
| animation: csLogoIn 1.6s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| } | |
| .cs-logo b { color: var(--accent); } | |
| .cs-sub { | |
| font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.22em; | |
| text-transform: uppercase; color: var(--dim); | |
| animation: csSub 1s ease 0.7s both; | |
| } | |
| @keyframes csEmblem { | |
| 0% { opacity: 0; transform: rotate(-90deg) scale(0.6); } | |
| 100% { opacity: 1; transform: rotate(0deg) scale(1); } | |
| } | |
| @keyframes csLogoIn { | |
| 0% { opacity: 0; transform: scale(1.22); letter-spacing: 0.16em; filter: blur(12px); } | |
| 55% { opacity: 1; filter: blur(0); } | |
| 100% { opacity: 1; transform: scale(1); letter-spacing: 0.01em; } | |
| } | |
| @keyframes csSub { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } | |
| @keyframes csSplashOut { 0%, 78% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cs-fog i, .cs-dust, #models .mb-pedestal::after { animation: none; } | |
| } | |
| /* Phones: stage first, roster as a strip, sheet, enter. */ | |
| @media (max-width: 900px) { | |
| #models .cs-root { | |
| grid-template-columns: 1fr; | |
| grid-template-areas: 'plate' 'stage' 'roster' 'sheet' 'enter' 'note'; | |
| padding: calc(var(--nav-h, 64px) + 8px) 14px 18px; | |
| gap: 8px; | |
| } | |
| #models .mb-mascot { height: min(36dvh, 300px); } | |
| #models .cs-roster-head { display: none; } | |
| #models .mb-roster { | |
| flex-direction: row; overflow-x: auto; overflow-y: hidden; margin-top: 0; | |
| border-left: 0; border-top: 1px solid #ffffff12; padding: 10px 2px 4px; | |
| } | |
| #models .mb-dot { width: auto; min-width: 168px; flex: 0 0 auto; } | |
| #models .cs-enter .mb-cta { min-width: 0; width: 100%; } | |
| } | |
| /* ββ ROUND TWO: depth, transitions, identity βββββββββββββββββββββββββββββββ */ | |
| /* The floor reflection β Chromium's box-reflect, which is the browser class | |
| the engine requires anyway. Elsewhere it simply does not draw. */ | |
| #models .mb-mascot { | |
| -webkit-box-reflect: below -6px linear-gradient(transparent 62%, rgba(255,255,255,0.10)); | |
| } | |
| /* Selection transition. go() re-arms .cs-in, so switching characters flashes | |
| the pedestal and walks the plate and sheet back in β selection FEELS like | |
| selection. Names are transform/opacity only; nothing relayouts. */ | |
| #models .mb-art.cs-in .mb-pedestal::after { animation: ringFlash 0.7s ease-out, ringPulse 5s ease-in-out 0.7s infinite; } | |
| @keyframes ringFlash { | |
| 0% { opacity: 1; transform: scale(0.92); filter: brightness(2.2); } | |
| 100% { opacity: 0.85; transform: scale(1); filter: brightness(1); } | |
| } | |
| #models .mb-art.cs-in .mb-mascot { animation: charIn 0.5s cubic-bezier(0.2, 1.4, 0.4, 1); } | |
| @keyframes charIn { | |
| 0% { opacity: 0; transform: translateY(14px) scale(0.965); } | |
| 100% { opacity: 1; transform: translateY(0) scale(1); } | |
| } | |
| #models .mb-plate.cs-in .mb-name { animation: plateIn 0.45s ease both; } | |
| #models .mb-plate.cs-in .mb-params { animation: plateIn 0.45s ease 0.08s both; } | |
| @keyframes plateIn { | |
| 0% { opacity: 0; transform: translateY(-8px); letter-spacing: 0.08em; } | |
| 100% { opacity: 1; transform: translateY(0); } | |
| } | |
| #models .mb-panel.cs-in > * { animation: rowIn 0.4s ease both; } | |
| #models .mb-panel.cs-in > *:nth-child(2) { animation-delay: 0.05s; } | |
| #models .mb-panel.cs-in > *:nth-child(3) { animation-delay: 0.10s; } | |
| #models .mb-panel.cs-in > *:nth-child(4) { animation-delay: 0.15s; } | |
| #models .mb-panel.cs-in > *:nth-child(5) { animation-delay: 0.20s; } | |
| #models .mb-panel.cs-in > *:nth-child(n+6) { animation-delay: 0.25s; } | |
| @keyframes rowIn { | |
| 0% { opacity: 0; transform: translateX(-10px); } | |
| 100% { opacity: 1; transform: translateX(0); } | |
| } | |
| /* Class sigil beside the class line. */ | |
| #models .mb-params { display: inline-flex; align-items: center; gap: 8px; justify-content: center; } | |
| .mb-sigil { display: inline-flex; width: 16px; height: 16px; } | |
| .mb-sigil svg { width: 100%; height: 100%; } | |
| /* Realm corners: tiny mono statements, both literally true. */ | |
| .cs-corner { | |
| position: absolute; bottom: 10px; z-index: 1; | |
| font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.14em; | |
| text-transform: uppercase; color: color-mix(in srgb, var(--dim) 70%, transparent); | |
| pointer-events: none; | |
| } | |
| .cs-corner-l { left: 16px; } | |
| .cs-corner-r { right: 16px; } | |
| /* READY glint: a light pass over the tag every few seconds β unlocked should | |
| glimmer. Masked to the tag only. */ | |
| #models .mb-dot[data-cached]::after { overflow: hidden; } | |
| #models .mb-dot[data-cached] { isolation: isolate; } | |
| #models .mb-dot[data-cached]::before { | |
| content: ''; position: absolute; top: 3px; right: 5px; width: 44px; height: 14px; | |
| background: linear-gradient(110deg, transparent 30%, #ffffff2e 50%, transparent 70%) no-repeat; | |
| background-size: 200% 100%; | |
| animation: readyGlint 4.5s ease-in-out infinite; | |
| pointer-events: none; z-index: 1; | |
| } | |
| @keyframes readyGlint { | |
| 0%, 72% { background-position: 180% 0; } | |
| 92%, 100% { background-position: -80% 0; } | |
| } | |
| /* Enter: charge on hover, visible keyboard focus. */ | |
| #models .cs-enter .mb-cta { transition: transform 0.18s ease, filter 0.18s ease, box-shadow 0.18s ease; } | |
| #models .cs-enter .mb-cta:hover { | |
| transform: translateY(-1px) scale(1.015); | |
| box-shadow: 0 14px 44px color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, transparent), | |
| inset 0 1px 0 #ffffff77; | |
| } | |
| #models .cs-enter .mb-cta:focus-visible { | |
| outline: 2px solid #fff; outline-offset: 3px; | |
| } | |
| /* Parallax layers move on transform β give them will-change and keep the | |
| fog's own drift composed with the pointer offset via nesting (the <i> | |
| children animate, the container translates). */ | |
| .cs-fog, .cs-dust, #models .mb-art { will-change: transform; transition: transform 0.3s ease-out; } | |
| @media (prefers-reduced-motion: reduce) { | |
| #models .mb-art.cs-in .mb-mascot, | |
| #models .mb-plate.cs-in .mb-name, #models .mb-plate.cs-in .mb-params, | |
| #models .mb-panel.cs-in > *, #models .mb-dot[data-cached]::before { animation: none; } | |
| } | |
| /* ββ ROUNDS 3-6: the aura, the juice, the frame ββββββββββββββββββββββββββββ */ | |
| /* The aura: motes of the class light rising off the pedestal, their pace set | |
| by the model's measured rate (--cs-tempo β the same number the mascot's | |
| idle clock reads). A fast little model boils; a 35B smoulders. */ | |
| .cs-aura { position: absolute; left: 50%; bottom: 8%; width: min(480px, 70%); height: 60%; | |
| transform: translateX(-50%); pointer-events: none; z-index: 1; } | |
| .cs-aura i { | |
| position: absolute; bottom: 0; width: 4px; height: 4px; border-radius: 50%; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| opacity: 0; | |
| animation: mote calc(5.2s / var(--cs-tempo, 1)) ease-in infinite; | |
| } | |
| .cs-aura i:nth-child(1) { left: 6%; animation-delay: 0.0s; } | |
| .cs-aura i:nth-child(2) { left: 16%; animation-delay: 1.1s; width: 3px; height: 3px; } | |
| .cs-aura i:nth-child(3) { left: 27%; animation-delay: 2.3s; } | |
| .cs-aura i:nth-child(4) { left: 38%; animation-delay: 0.6s; width: 5px; height: 5px; } | |
| .cs-aura i:nth-child(5) { left: 50%; animation-delay: 1.7s; width: 3px; height: 3px; } | |
| .cs-aura i:nth-child(6) { left: 61%; animation-delay: 2.9s; } | |
| .cs-aura i:nth-child(7) { left: 70%; animation-delay: 0.3s; } | |
| .cs-aura i:nth-child(8) { left: 80%; animation-delay: 1.9s; width: 3px; height: 3px; } | |
| .cs-aura i:nth-child(9) { left: 89%; animation-delay: 2.6s; } | |
| .cs-aura i:nth-child(10) { left: 95%; animation-delay: 1.3s; width: 5px; height: 5px; } | |
| @keyframes mote { | |
| 0% { opacity: 0; transform: translateY(0) scale(1); } | |
| 12% { opacity: 0.9; } | |
| 100% { opacity: 0; transform: translateY(-46vh) scale(0.4); } | |
| } | |
| /* ARMED: a cached character's circle runs a rotating highlight β the same | |
| OPFS truth as the READY tag, drawn as ceremony. Conic light clipped by the | |
| very ring mask. */ | |
| #models .mb-art[data-armed] .mb-pedestal::before { | |
| content: ''; position: absolute; inset: 0; | |
| background: conic-gradient(from 0deg, | |
| transparent 0deg, transparent 290deg, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 90%, #fff) 330deg, | |
| transparent 360deg); | |
| -webkit-mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| animation: armedSpin 6s linear infinite; | |
| } | |
| @keyframes armedSpin { to { transform: rotate(360deg); } } | |
| /* Lore under the nameplate: computed from the spec, styled as inscription. */ | |
| .cs-lore { | |
| margin-top: 8px; font-size: 0.8rem; font-style: italic; | |
| color: color-mix(in srgb, var(--text) 62%, transparent); | |
| letter-spacing: 0.02em; | |
| } | |
| /* Juice: chips stamp when chosen, roster cards press, ENTER presses. */ | |
| #models .mb-variant[aria-selected="true"] { animation: stamp 0.22s ease; } | |
| @keyframes stamp { 0% { transform: scale(1.08); } 100% { transform: scale(1); } } | |
| #models .mb-dot:active { transform: translateX(-2px) scale(0.98); } | |
| #models .cs-enter .mb-cta:active { transform: translateY(1px) scale(0.995); box-shadow: 0 6px 20px color-mix(in srgb, var(--cs-accent, var(--accent)) 35%, transparent); } | |
| /* The cinematic frame: a breath of dark at the top and bottom edges, so the | |
| scene reads composed rather than cropped. */ | |
| #models .cs-root::before, #models .cs-root::after { | |
| content: ''; position: absolute; left: 0; right: 0; height: 90px; | |
| pointer-events: none; z-index: 2; | |
| } | |
| #models .cs-root::before { top: 0; background: linear-gradient(180deg, #05070dcc, transparent); } | |
| #models .cs-root::after { bottom: 0; background: linear-gradient(0deg, #05070d99, transparent); } | |
| /* Screen-reader announcer. */ | |
| .cs-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cs-aura i, #models .mb-art[data-armed] .mb-pedestal::before, | |
| #models .mb-variant[aria-selected="true"] { animation: none; } | |
| .cs-aura i { opacity: 0.35; transform: translateY(-8vh); } | |
| } | |
| /* ββ ROUND 7: THE ENVIRONMENT ββββββββββββββββββββββββββββββββββββββββββββββ | |
| The realm gains architecture. Silhouette layers between backdrop and | |
| stage, each on its own parallax rate: obelisk skyline far, rune columns | |
| near, the character floating between them under an aerial ring. */ | |
| .cs-spires { | |
| position: absolute; left: -3%; right: -3%; bottom: 0; height: 42vh; | |
| background: url('/entrance/spires.svg') bottom center / cover no-repeat; | |
| opacity: 0.85; pointer-events: none; z-index: 0; | |
| } | |
| .cs-col { | |
| position: absolute; bottom: -2vh; width: min(240px, 16vw); height: 92vh; | |
| background: url('/entrance/column.svg') bottom / contain no-repeat; | |
| pointer-events: none; z-index: 1; opacity: 0.9; | |
| } | |
| .cs-col-l { left: max(6px, 2vw); } | |
| .cs-col-r { right: max(6px, 2vw); transform: scaleX(-1); } | |
| /* The aerial ring: a great slow wheel behind the character, in the class | |
| light at whisper opacity. The armed floor ring is ceremony; this is | |
| architecture. */ | |
| .cs-backring { | |
| position: absolute; left: 50%; top: 46%; width: min(64dvh, 620px); aspect-ratio: 1; | |
| translate: -50% -50%; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, #fff); | |
| -webkit-mask: url('/entrance/backring.svg') center / contain no-repeat; | |
| mask: url('/entrance/backring.svg') center / contain no-repeat; | |
| opacity: 0.14; z-index: 0; | |
| animation: backringSpin 80s linear infinite; | |
| } | |
| @keyframes backringSpin { to { rotate: 360deg; } } | |
| /* Nameplate banner wings behind the name. */ | |
| #models .mb-plate { padding-top: 10px; } | |
| .cs-banner { | |
| position: absolute; left: 50%; top: 16px; transform: translateX(-50%); | |
| width: min(640px, 92vw); height: 80px; pointer-events: none; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 65%, #fff); | |
| -webkit-mask: url('/entrance/banner.svg') center / contain no-repeat; | |
| mask: url('/entrance/banner.svg') center / contain no-repeat; | |
| opacity: 0.5; z-index: -1; | |
| } | |
| /* Portrait frames on the roster faces. */ | |
| .mb-dot-face { position: relative; } | |
| .mb-dot-face::after { | |
| content: ''; position: absolute; inset: -3px; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, #9fb4d8); | |
| -webkit-mask: url('/entrance/pframe.svg') center / contain no-repeat; | |
| mask: url('/entrance/pframe.svg') center / contain no-repeat; | |
| opacity: 0.35; transition: opacity 0.15s; | |
| } | |
| #models .mb-dot[aria-selected="true"] .mb-dot-face::after, | |
| #models .mb-dot:hover .mb-dot-face::after { opacity: 0.9; } | |
| /* The cinematic edges upgraded: the dark breath now carries the rune border | |
| strip along its inner line. */ | |
| #models .cs-root::before { | |
| background: | |
| linear-gradient(180deg, #05070dcc, transparent), | |
| linear-gradient(180deg, transparent 76px, transparent 76px); | |
| } | |
| #models .cs-root > .cs-edge-t, #models .cs-root > .cs-edge-b { display: none; } | |
| #models .cs-root::after { | |
| background: linear-gradient(0deg, #05070d99, transparent); | |
| } | |
| .cs-borderline-t, .cs-borderline-b { | |
| position: absolute; left: 8%; right: 8%; height: 12px; z-index: 2; pointer-events: none; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 40%, #6c7f9f); | |
| -webkit-mask: url('/entrance/border.svg') center / auto 100% repeat-x; | |
| mask: url('/entrance/border.svg') center / auto 100% repeat-x; | |
| opacity: 0.4; | |
| } | |
| .cs-borderline-t { top: calc(var(--nav-h, 64px) + 2px); } | |
| .cs-borderline-b { bottom: 8px; } | |
| /* The splash's ring burst: the aerial ring blooming open behind the emblem. */ | |
| .cs-splash { position: absolute; } | |
| .cs-splash-ring { | |
| position: absolute; width: 420px; aspect-ratio: 1; | |
| background: var(--accent); | |
| -webkit-mask: url('/entrance/backring.svg') center / contain no-repeat; | |
| mask: url('/entrance/backring.svg') center / contain no-repeat; | |
| animation: splashRing 2.2s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| opacity: 0.22; | |
| } | |
| @keyframes splashRing { | |
| 0% { transform: scale(0.4) rotate(-40deg); opacity: 0; } | |
| 40% { opacity: 0.3; } | |
| 100% { transform: scale(1.05) rotate(8deg); opacity: 0.18; } | |
| } | |
| /* The realm's cursor: a small trace-diamond. Entrance only. */ | |
| #models .cs-root, #models .cs-root a, #models .cs-root button { | |
| cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22"><path d="M11 1 L21 11 L11 21 L1 11 Z" fill="none" stroke="white" stroke-width="2"/><path d="M11 7 L15 11 L11 15 L7 11 Z" fill="white"/></svg>') 11 11, auto; | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cs-backring, .cs-splash-ring { animation: none; } | |
| } | |
| @media (max-width: 900px) { | |
| .cs-col { display: none; } | |
| .cs-spires { height: 26vh; opacity: 0.6; } | |
| .cs-backring { width: min(78vw, 380px); top: 40%; } | |
| .cs-banner { height: 56px; opacity: 0.4; } | |
| .cs-borderline-t, .cs-borderline-b { display: none; } | |
| } | |
| /* ββ ROUND 8: THE ALIGNMENT PASS βββββββββββββββββββββββββββββββββββββββββββ | |
| One rhythm: an 8px gutter system, a hard max-width so ultrawide screens | |
| frame the scene instead of stretching it, the sheet and roster hung from | |
| the same top line, and the enter slab locked to the stage's centre line. */ | |
| #models .cs-root { | |
| --cs-gutter: 24px; | |
| max-width: 1720px; margin: 0 auto; | |
| grid-template-columns: minmax(280px, 340px) minmax(0, 1fr) minmax(260px, 310px); | |
| gap: 8px var(--cs-gutter); | |
| padding: calc(var(--nav-h, 64px) + 12px) max(28px, 4vw) 22px; | |
| } | |
| /* Sheet and roster share a top line: both start where the stage starts. */ | |
| #models .mb-info { align-self: start; padding-top: 34px; } | |
| #models .cs-roster-head { padding-top: 8px; height: 26px; } | |
| #models .mb-roster { margin-top: 34px; } | |
| /* The plate is the one centred element; give it a fixed rhythm below. */ | |
| #models .mb-plate { margin: 2px 0 0; padding-bottom: 6px; } | |
| /* Enter hangs centred under the stage column only, not the whole row β | |
| optically aligned with the character above it. */ | |
| #models .cs-enter { grid-column: 2; justify-self: center; } | |
| #models .cs-corner-l { left: max(28px, 4vw); } | |
| #models .cs-corner-r { right: max(28px, 4vw); } | |
| /* Border strips share the content inset. */ | |
| .cs-borderline-t, .cs-borderline-b { left: max(28px, 4vw); right: max(28px, 4vw); } | |
| /* Stat rows: value column right-aligned on a shared edge, tabular digits. */ | |
| #models .mb-stats dd { font-variant-numeric: tabular-nums; text-align: right; } | |
| #models .mb-stats dt { padding-right: 12px; } | |
| /* Chips wrap on the same 8px rhythm. */ | |
| #models .mb-variants, #models .mb-modes { gap: 8px; } | |
| @media (max-width: 900px) { | |
| #models .cs-enter { grid-column: 1; justify-self: stretch; } | |
| #models .mb-info { padding-top: 0; } | |
| #models .mb-roster { margin-top: 0; } | |
| } | |
| /* ββ ROUND 15: THE PARTY READS AS CLASSES ββ */ | |
| #models .mb-dot { padding-right: 26px; } | |
| .mb-dot-sigil { | |
| position: absolute; right: 7px; bottom: 6px; width: 13px; height: 13px; opacity: 0.75; | |
| } | |
| .mb-dot-sigil svg { width: 100%; height: 100%; } | |
| /* ββ ROUND 17: THE SPLASH YIELDS ββ */ | |
| .cs-splash { cursor: pointer; } | |
| /* ββ ROUND 9: ATTENTION ββ */ | |
| #models .mb-art { transition: rotate 0.4s ease, transform 0.3s ease-out; perspective: 900px; } | |
| /* ββ ROUND 11: THE SELECTION WIPE ββ A sheet of the class light crossing the | |
| stage on every selection β the cut between characters reads as a cut. */ | |
| .cs-wipe { | |
| position: absolute; inset: 0; z-index: 3; pointer-events: none; opacity: 0; | |
| background: linear-gradient(100deg, transparent 30%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 26%, transparent) 50%, | |
| transparent 70%); | |
| transform: translateX(-100%); | |
| } | |
| .cs-wipe.cs-go { animation: csWipe 0.55s ease-out; } | |
| @keyframes csWipe { | |
| 0% { opacity: 1; transform: translateX(-100%); } | |
| 100% { opacity: 1; transform: translateX(100%); } | |
| } | |
| /* ββ ROUND 16: THE ENGAGE ββ Clicking ENTER flashes the realm in the class | |
| light and falls to black before the chat page takes over β an exit that | |
| answers the entrance. Reduced motion (and modified clicks) skip it. */ | |
| .cs-engage { | |
| position: absolute; inset: 0; z-index: 40; pointer-events: none; opacity: 0; | |
| background: | |
| radial-gradient(ellipse at 50% 60%, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 45%, transparent) 0%, transparent 60%), | |
| #05070d; | |
| } | |
| .cs-engage.cs-go { animation: csEngage 0.34s ease-in forwards; } | |
| @keyframes csEngage { | |
| 0% { opacity: 0; } | |
| 45% { opacity: 0.65; } | |
| 100% { opacity: 1; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { .cs-wipe.cs-go, .cs-engage.cs-go { animation: none; } } | |
| /* ββ ROUND 10: STAT ICONOGRAPHY + HONEST BARS ββ Icons name the stat; the | |
| bars are within-character fractions only (context: shipped ceiling over | |
| the checkpoint's own max; memory: the build's residency) β never a | |
| cross-model ranking. */ | |
| .mb-stat-ico { display: inline-flex; width: 12px; height: 12px; margin-right: 7px; | |
| vertical-align: -1px; color: color-mix(in srgb, var(--cs-accent, var(--accent)) 60%, var(--dim)); } | |
| .mb-stat-ico svg { width: 100%; height: 100%; } | |
| .mb-bar { display: block; height: 3px; margin-top: 5px; border-radius: 2px; | |
| background: #ffffff12; overflow: hidden; } | |
| .mb-bar i { display: block; height: 100%; border-radius: 2px; | |
| background: linear-gradient(90deg, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, transparent), | |
| var(--cs-accent, var(--accent))); | |
| transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1); } | |
| #models .mb-stats dd { min-width: 128px; } | |
| /* ββ ROUND 13: THE LOADOUT GAUGE ββ Filled diamonds for what the build | |
| keeps, hollow for what it streams β ordinal, not a measurement. */ | |
| .mb-gauge { font-size: 0.6rem; letter-spacing: 0.08em; margin-right: 7px; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, var(--dim)); } | |
| /* ββ ROUND 12: IDLE LIFE ββ Left alone, the realm breathes: the stage sways | |
| a few pixels on a long period and the aura thickens slightly. Any input | |
| snaps the camera back to the hand. */ | |
| #models .cs-root.cs-idle .mb-art { animation: idleSway 14s ease-in-out infinite; } | |
| @keyframes idleSway { | |
| 0%, 100% { transform: translate(0, 0); } | |
| 25% { transform: translate(6px, -3px); } | |
| 75% { transform: translate(-6px, 2px); } | |
| } | |
| #models .cs-root.cs-idle .cs-aura i { animation-duration: calc(3.8s / var(--cs-tempo, 1)); } | |
| /* ββ ROUND 14: REALM WEATHER ββ A falling trace crosses the high sky every | |
| ~16 s β thin, fast, gone. And the fog drinks a hint of the class light, so | |
| the weather itself changes when the character does. */ | |
| .cs-comet { | |
| position: absolute; top: 12%; left: -8%; width: 130px; height: 2px; | |
| background: linear-gradient(90deg, transparent, #cfe0ffbb 60%, #fff); | |
| border-radius: 2px; opacity: 0; pointer-events: none; z-index: 0; | |
| transform: rotate(9deg); | |
| animation: comet 16s linear infinite; | |
| animation-delay: 5s; | |
| } | |
| @keyframes comet { | |
| 0%, 93% { opacity: 0; left: -8%; top: 12%; } | |
| 94% { opacity: 0.9; } | |
| 100% { opacity: 0; left: 108%; top: 26%; } | |
| } | |
| .cs-fog i { | |
| background: radial-gradient(ellipse at center, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 12%, #18223a) 0%, transparent 65%); | |
| opacity: 0.36; | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cs-comet, #models .cs-root.cs-idle .mb-art { animation: none; } | |
| } | |
| /* ββ CHAT MODE ββ ENTER pressed: the roster and the sheet step aside, the | |
| conversation takes the right column, and the character stays on stage β | |
| summoning heartbeat while the weights land, faster heartbeat while it | |
| thinks, one bounce per real token while it speaks. Requires chat-ui.css | |
| (linked by index.html) for the message bubbles and the composer; below is | |
| only the panel chrome and the stagecraft. */ | |
| #models .cs-root.cs-chatting { | |
| /* The nav is sticky IN FLOW (60px + border), so a flat 100dvh hangs the | |
| composer 61px below the fold. In chat mode the composer is the primary | |
| control β the chamber must end above it. The tightened padding-top gives | |
| the reclaimed room to the conversation. */ | |
| height: calc(100dvh - 61px); | |
| min-height: 0; | |
| padding-top: 22px; | |
| grid-template-columns: 1fr minmax(420px, 600px); | |
| grid-template-rows: auto 1fr auto; | |
| grid-template-areas: | |
| 'plate chat' | |
| 'stage chat' | |
| 'note chat'; | |
| } | |
| /* #models-prefixed: the base rules carry the #models id, and a bare class | |
| pair loses that specificity fight β the roster stayed visible and its | |
| orphaned grid-area spawned implicit tracks that crushed the real columns | |
| (caught by the first browser pass). */ | |
| #models .cs-chatting .mb-info, #models .cs-chatting .mb-roster, | |
| #models .cs-chatting .cs-roster-head, #models .cs-chatting .cs-enter, | |
| #models .cs-chatting .mb-arrow, #models .cs-chatting .cs-corner-r { display: none; } | |
| /* The engage flash no longer hands off to another page β it lifts to reveal | |
| the chamber. Starts opaque (the .cs-go fade ran under it), holds long | |
| enough to cover the swap, then clears. */ | |
| #models .cs-root.cs-chatting .cs-engage.cs-go { animation: csEngageReveal 0.9s ease forwards; } | |
| @keyframes csEngageReveal { | |
| 0%, 30% { opacity: 1; } | |
| 100% { opacity: 0; visibility: hidden; } | |
| } | |
| /* The panel: the sheet's glass and rune corners, grown into an instrument. */ | |
| #models .cs-chat { | |
| /* min-width 0: grid items default to min-content width, and the nowrap | |
| composer-hint line was wider than a phone β the panel overflowed the | |
| viewport instead of shrinking into its column. */ | |
| grid-area: chat; z-index: 2; min-height: 0; min-width: 0; position: relative; | |
| /* It is a <section>, and the page's generic section rule pads 72px into | |
| the panel β zero it; the head/composer carry their own spacing. */ | |
| padding: 0; | |
| display: flex; flex-direction: column; | |
| border: 1px solid color-mix(in srgb, var(--cs-accent, var(--accent)) 22%, var(--border)); | |
| border-radius: 4px; | |
| background: linear-gradient(180deg, #0c1220ee, #090e18e6); | |
| box-shadow: inset 0 1px 0 #ffffff0a, 0 18px 50px #0009; | |
| backdrop-filter: blur(4px); | |
| animation: csChatIn 0.5s cubic-bezier(0.2, 1, 0.3, 1) 0.1s both; | |
| } | |
| @keyframes csChatIn { | |
| from { opacity: 0; transform: translateX(26px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| #models .cs-chat::before, #models .cs-chat::after { | |
| content: ''; position: absolute; width: 40px; height: 40px; z-index: 3; pointer-events: none; | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| -webkit-mask: url('/entrance/corner.svg') 0 0 / contain no-repeat; | |
| mask: url('/entrance/corner.svg') 0 0 / contain no-repeat; | |
| } | |
| #models .cs-chat::before { top: -5px; left: -5px; } | |
| #models .cs-chat::after { bottom: -5px; right: -5px; transform: rotate(180deg); } | |
| /* Panel header: sigil, name, build, live badge, tools. */ | |
| .cs-chat-head { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 12px 14px; border-bottom: 1px solid #ffffff0f; | |
| } | |
| .cs-chat-sigil { flex: none; width: 22px; height: 22px; | |
| color: var(--cs-accent, var(--accent)); } | |
| .cs-chat-sigil svg { width: 100%; height: 100%; } | |
| .cs-chat-id { display: flex; flex-direction: column; gap: 1px; min-width: 0; margin-right: auto; } | |
| .cs-chat-id b { font-family: var(--display); font-weight: 600; font-size: 1.02rem; | |
| line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| .cs-chat-id i { font-style: normal; font-family: var(--mono); font-size: 0.58rem; | |
| letter-spacing: 0.12em; text-transform: uppercase; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, var(--text)); | |
| white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| .cs-chat-tool { | |
| flex: none; font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em; | |
| text-transform: uppercase; color: var(--dim); text-decoration: none; | |
| padding: 5px 9px; border: 1px solid #ffffff1c; border-radius: 3px; background: #ffffff07; | |
| cursor: pointer; transition: color 0.15s, border-color 0.15s; | |
| } | |
| .cs-chat-tool:hover { color: var(--text); border-color: var(--muted); } | |
| .cs-chat-tool:disabled { opacity: 0.4; cursor: not-allowed; } | |
| /* Live badge β written by loading-ui.ts (Summoning β % β Ready). */ | |
| .cs-chat .badge { | |
| flex: none; display: inline-flex; align-items: center; gap: 6px; | |
| font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase; | |
| padding: 4px 9px; border: 1px solid #ffffff1c; border-radius: 999px; color: var(--dim); | |
| font-variant-numeric: tabular-nums; white-space: nowrap; | |
| } | |
| .cs-chat .badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); } | |
| .cs-chat .badge.loading { color: #fbbf24; border-color: #fbbf2440; } | |
| .cs-chat .badge.loading .dot { background: #fbbf24; animation: csBadgePulse 1.4s infinite; } | |
| .cs-chat .badge.ready { color: var(--cs-accent, var(--accent)); | |
| border-color: color-mix(in srgb, var(--cs-accent, var(--accent)) 40%, transparent); } | |
| .cs-chat .badge.ready .dot { background: var(--cs-accent, var(--accent)); } | |
| .cs-chat .badge.error { color: var(--err); border-color: color-mix(in srgb, var(--err) 45%, transparent); } | |
| .cs-chat .badge.error .dot { background: var(--err); } | |
| @keyframes csBadgePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } | |
| /* The rite card: download + compile progress, then it folds away. */ | |
| .cs-boot { padding: 16px 16px 14px; border-bottom: 1px solid #ffffff0f; } | |
| .cs-boot.cs-done { display: none; } | |
| .cs-boot-title { font-family: var(--display); font-size: 1.05rem; margin-bottom: 2px; } | |
| .cs-boot-status { font-size: 0.78rem; color: var(--text-dim, #a9b3c6); min-height: 1.2em; margin-bottom: 10px; } | |
| .cs-boot-track { height: 4px; border-radius: 2px; background: #ffffff12; overflow: hidden; } | |
| .cs-boot-track i { | |
| display: block; height: 100%; width: 0; border-radius: 2px; | |
| background: linear-gradient(90deg, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, transparent), | |
| var(--cs-accent, var(--accent))); | |
| transition: width 0.25s ease; | |
| } | |
| .cs-boot-detail { margin-top: 7px; font-family: var(--mono); font-size: 0.64rem; | |
| color: var(--dim); font-variant-numeric: tabular-nums; } | |
| .cs-boot-note { margin-top: 7px; font-size: 0.72rem; color: var(--warn, #d9a441); } | |
| .cs-boot-log { margin-top: 9px; } | |
| .cs-boot-log summary { | |
| font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase; | |
| color: var(--dim); cursor: pointer; user-select: none; | |
| } | |
| .cs-boot-log summary:hover { color: var(--text-dim, #a9b3c6); } | |
| .cs-boot-log pre { | |
| margin-top: 6px; max-height: 130px; overflow-y: auto; | |
| padding: 8px 10px; border: 1px solid #ffffff0f; border-radius: 3px; background: #070b13; | |
| font-family: var(--mono); font-size: 0.62rem; line-height: 1.5; color: var(--text-dim, #a9b3c6); | |
| white-space: pre-wrap; | |
| } | |
| .cs-boot-error { display: none; margin-top: 10px; padding: 9px 11px; | |
| border: 1px solid color-mix(in srgb, var(--err) 40%, transparent); border-radius: 3px; | |
| background: color-mix(in srgb, var(--err) 9%, transparent); | |
| font-size: 0.76rem; line-height: 1.5; color: color-mix(in srgb, var(--err) 60%, #fff); } | |
| .cs-boot-error.visible { display: block; } | |
| /* Conversation column: bubbles ride the panel glass, not their own ground. */ | |
| .cs-chat .chat-main { flex: 1; min-height: 0; } | |
| .cs-chat .welcome.cs-wait, .cs-chat .welcome.hidden { display: none; } | |
| .cs-chat .welcome { padding: 1.4rem 0.2rem 1rem; } | |
| .cs-welcome-title { font-family: var(--display); font-size: 1.3rem; margin-bottom: 6px; } | |
| .cs-welcome-lore { font-size: 0.8rem; font-style: italic; line-height: 1.55; | |
| color: color-mix(in srgb, var(--text) 62%, transparent); margin-bottom: 14px; } | |
| .cs-sug-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } | |
| .cs-chat .suggest { | |
| text-align: left; font-size: 0.76rem; line-height: 1.4; color: var(--text-dim, #a9b3c6); | |
| padding: 10px 12px; border: 1px solid #ffffff14; border-radius: 3px; background: #ffffff06; | |
| cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s; | |
| } | |
| .cs-chat .suggest:hover { | |
| color: var(--text); background: color-mix(in srgb, var(--cs-accent, var(--accent)) 8%, transparent); | |
| border-color: color-mix(in srgb, var(--cs-accent, var(--accent)) 45%, transparent); | |
| } | |
| /* Composer, re-cut to the entrance's edge language (chat-ui.css carries the | |
| mechanics; only the chrome changes here). */ | |
| .cs-chat .composer-wrap { background: transparent; border-top: 1px solid #ffffff0f; padding: 11px 13px 10px; } | |
| .cs-chat .composer { background: #0a101ccc; border-radius: 3px; | |
| border-color: color-mix(in srgb, var(--cs-accent, var(--accent)) 26%, var(--border)); } | |
| .cs-chat .composer:focus-within { background: #0c1322; border-color: var(--cs-accent, var(--accent)); } | |
| .cs-chat .composer-btn { border-radius: 3px; } | |
| .cs-chat .composer-hint { | |
| display: flex; justify-content: space-between; gap: 4px 10px; flex-wrap: wrap; text-align: left; | |
| font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.06em; color: var(--dim); | |
| max-width: none; | |
| } | |
| .cs-chat-stats { font-variant-numeric: tabular-nums; white-space: nowrap; } | |
| /* ββ Stagecraft: what the realm does while the model works ββββββββββββββββ */ | |
| /* Summoning: the circle beats slowly and the ring runs the armed sweep β the | |
| character is being drawn through. */ | |
| #models .cs-summoning .mb-pedestal::after { animation: ringPulse 1.5s ease-in-out infinite; } | |
| #models .cs-summoning .mb-pedestal::before { | |
| content: ''; position: absolute; inset: 0; | |
| background: conic-gradient(from 0deg, | |
| transparent 0deg, transparent 290deg, | |
| color-mix(in srgb, var(--cs-accent, var(--accent)) 90%, #fff) 330deg, | |
| transparent 360deg); | |
| -webkit-mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| mask: url('/entrance/ring.svg') center bottom / 100% auto no-repeat; | |
| animation: armedSpin 2.6s linear infinite; | |
| } | |
| /* Thinking: prefill β the ring tightens to a fast shallow breath. */ | |
| #models .cs-thinking .mb-pedestal::after { animation: ringPulse 0.9s ease-in-out infinite; } | |
| #models .cs-thinking .mb-pedestal { filter: brightness(1.18); } | |
| /* Generating: the floor burns steady and bright; the bounce on stage is the | |
| token stream itself (mascot.pulse per emitted token). */ | |
| #models .cs-generating .mb-pedestal { filter: brightness(1.3); } | |
| #models .cs-generating .mb-pedestal::after { animation: none; opacity: 1; } | |
| #models .mb-pedestal { transition: filter 0.4s ease; } | |
| /* Phones: the character folds to a small familiar above the conversation. */ | |
| @media (max-width: 900px) { | |
| #models .cs-root.cs-chatting { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto auto 1fr; | |
| grid-template-areas: 'plate' 'stage' 'chat'; | |
| } | |
| #models .cs-chatting .mb-mascot { height: 110px; -webkit-box-reflect: none; } | |
| #models .cs-chatting .mb-pedestal { height: 60px; } | |
| #models .cs-chatting .cs-lore { display: none; } | |
| #models .cs-chatting .mb-plate { padding-bottom: 4px; } | |
| #models .cs-chatting .mb-name { font-size: 1.3rem; } | |
| .cs-sug-grid { grid-template-columns: 1fr; } | |
| .cs-chat-tool#new-chat-btn { display: none; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| #models .cs-chat, #models .cs-root.cs-chatting .cs-engage.cs-go { animation: none; } | |
| #models .cs-summoning .mb-pedestal::before { animation: none; } | |
| } | |
| /* ββ DOWNLOADED = READY ββ The READY tag gains a fill so it reads at roster | |
| distance, and a variant chip that is already on this device carries a dot β | |
| both from the same OPFS probe as everything else. */ | |
| #models .mb-dot[data-cached]::after { | |
| background: color-mix(in srgb, var(--ok) 16%, transparent); | |
| } | |
| #models .mb-variant[data-cached]::after { | |
| content: 'β'; margin-left: 7px; font-size: 0.5rem; vertical-align: 2px; | |
| color: var(--ok); | |
| } | |
| /* ββ GAME ROUND: abilities, saves, listening, the token stream βββββββββββββ */ | |
| /* Abilities: the spec's mechanics as passives β rune bullet, name, clause. */ | |
| #models .mb-abilities { list-style: none; margin: 12px 0 0; padding: 0; | |
| display: flex; flex-direction: column; gap: 6px; } | |
| #models .mb-abilities li { display: flex; align-items: baseline; gap: 8px; | |
| font-size: 0.74rem; line-height: 1.4; } | |
| #models .mb-abilities li::before { content: 'β'; flex: none; font-size: 0.5rem; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 80%, #fff); } | |
| #models .mb-abilities b { flex: none; font-weight: 600; letter-spacing: 0.02em; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 55%, var(--text)); } | |
| #models .mb-abilities span { color: var(--dim); } | |
| /* Save slot: a stored conversation marks its character β roster glyph + | |
| sheet line. Presence only, never a count. */ | |
| #models .mb-dot[data-save] .mb-dot-text b::after { | |
| content: ' β'; font-size: 0.7em; vertical-align: 1px; | |
| color: #cfe0ffaa; | |
| } | |
| #models .mb-save { font-size: var(--step--1); margin: 10px 0 0; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 60%, var(--text)); } | |
| /* LISTENING: keys are landing β the character turns toward the panel. The | |
| ears perk through the mascot's hover state; this is the body language. */ | |
| #models .cs-chatting.cs-listening .mb-art { rotate: y 9deg; } | |
| #models .mb-art { transition: transform 0.3s ease-out, rotate 0.45s ease; } | |
| /* THE TOKEN STREAM: while the model speaks, glyphs fly from the character's | |
| mouth into the conversation. Visible ONLY under cs-generating β the stream | |
| exists exactly when tokens do. */ | |
| .cs-stream { display: none; } | |
| #models .cs-chatting.cs-generating .cs-stream { display: block; | |
| position: absolute; left: 26%; right: 0; top: 0; bottom: 0; | |
| pointer-events: none; z-index: 2; } | |
| .cs-stream i { | |
| position: absolute; left: 0; top: 44%; | |
| font-style: normal; font-size: 11px; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| text-shadow: 0 0 6px color-mix(in srgb, var(--cs-accent, var(--accent)) 60%, transparent); | |
| opacity: 0; | |
| animation: tokFly 1.15s linear infinite; | |
| } | |
| .cs-stream i:nth-child(2) { animation-delay: 0.19s; font-size: 8px; top: 45.5%; } | |
| .cs-stream i:nth-child(3) { animation-delay: 0.38s; top: 43%; } | |
| .cs-stream i:nth-child(4) { animation-delay: 0.57s; font-size: 8px; top: 46%; } | |
| .cs-stream i:nth-child(5) { animation-delay: 0.76s; top: 44.5%; } | |
| .cs-stream i:nth-child(6) { animation-delay: 0.95s; font-size: 9px; top: 43.5%; } | |
| @keyframes tokFly { | |
| 0% { transform: translateX(0) translateY(0); opacity: 0; } | |
| 12% { opacity: 0.9; } | |
| 55% { transform: translateX(22vw) translateY(-1.2vh); opacity: 0.85; } | |
| 92% { opacity: 0.5; } | |
| 100% { transform: translateX(38vw) translateY(-0.6vh); opacity: 0; } | |
| } | |
| /* SUMMON FAILURE: the circle goes cold. The rite card carries the flavour | |
| line; the diagnostic stays in the error panel. */ | |
| #models .cs-boot-failed .mb-pedestal { filter: grayscale(0.7) brightness(0.55); } | |
| #models .cs-boot-failed .mb-pedestal::after { animation: none; opacity: 0.3; } | |
| #models .cs-boot-failed .mb-pedestal::before { display: none; } | |
| @media (max-width: 900px) { | |
| /* The stream's geometry assumes stage-left/panel-right; stacked phones | |
| have neither, and the abilities pad the sheet. */ | |
| .cs-stream { display: none ; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .cs-stream { display: none ; } | |
| #models .cs-chatting.cs-listening .mb-art { rotate: none; } | |
| } | |
| /* ββ EXTENDED ROUND: sleep, the constellation, the deeds rail ββββββββββββββ */ | |
| /* ASLEEP (50 s idle): the ring cools and slows with the character β the | |
| sleepy face itself is the mascot's mood, this is the room's light. */ | |
| #models .cs-asleep .mb-pedestal { filter: brightness(0.7) saturate(0.8); } | |
| #models .cs-asleep .mb-pedestal::after { animation-duration: 10s; } | |
| #models .cs-asleep .cs-fog i { opacity: 0.2; } | |
| /* THE CONSTELLATION: the lane's sigil, huge and faint, behind the stage. | |
| Masked accent β it recolours with the character and drifts on parallax. */ | |
| .cs-sigilbg { | |
| position: absolute; z-index: 0; pointer-events: none; | |
| left: 50%; top: 50%; | |
| width: min(74vh, 58vw); height: min(74vh, 58vw); | |
| margin: calc(min(74vh, 58vw) / -2) 0 0 calc(min(74vh, 58vw) / -2); | |
| background: color-mix(in srgb, var(--cs-accent, var(--accent)) 80%, #fff); | |
| opacity: 0.05; | |
| -webkit-mask-size: contain; mask-size: contain; | |
| -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; | |
| -webkit-mask-position: center; mask-position: center; | |
| } | |
| #models .cs-chatting .cs-sigilbg { left: 27%; } | |
| /* THE DEEDS RAIL: things that happened on this device. Unearned = a dim rune | |
| with the trigger in its tooltip (a quest list, not decoration); earned = | |
| lit, named. No counts anywhere. */ | |
| #models .cs-deeds { | |
| grid-area: note; z-index: 1; justify-self: center; | |
| display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; | |
| padding: 2px 0; | |
| } | |
| .cs-deed { | |
| font-family: var(--mono); font-size: 0.56rem; letter-spacing: 0.12em; | |
| text-transform: uppercase; color: color-mix(in srgb, var(--dim) 55%, transparent); | |
| cursor: default; | |
| } | |
| .cs-deed i { font-style: normal; display: none; margin-left: 5px; } | |
| .cs-deed[data-on] { | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 75%, #fff); | |
| text-shadow: 0 0 8px color-mix(in srgb, var(--cs-accent, var(--accent)) 45%, transparent); | |
| } | |
| .cs-deed[data-on] i { display: inline; } | |
| @media (max-width: 900px) { #models .cs-deeds { display: none; } } | |
| /* ββ THE ROOM ββ Hosting from inside the game: the strip in the panel, and | |
| one small mascot per connected guest flanking the character. */ | |
| .cs-room { padding: 12px 14px; border-bottom: 1px solid #ffffff0f; } | |
| .cs-room-consent p { font-size: 0.76rem; line-height: 1.55; color: var(--text-dim, #a9b3c6); | |
| margin: 0 0 10px; } | |
| .cs-room-linkrow { display: flex; gap: 6px; align-items: center; } | |
| .cs-room-linkrow input { | |
| flex: 1; min-width: 0; padding: 6px 9px; font-family: var(--mono); font-size: 0.66rem; | |
| color: var(--text); background: #0a101ccc; border-radius: 3px; | |
| border: 1px solid color-mix(in srgb, var(--cs-accent, var(--accent)) 30%, var(--border)); | |
| } | |
| .cs-room-members { margin-top: 8px; font-family: var(--mono); font-size: 0.62rem; | |
| letter-spacing: 0.08em; color: var(--dim); } | |
| .cs-room-log { list-style: none; margin: 8px 0 0; padding: 0; display: flex; | |
| flex-direction: column; gap: 4px; } | |
| .cs-room-log li { display: flex; gap: 8px; align-items: baseline; font-size: 0.7rem; | |
| color: var(--text-dim, #a9b3c6); overflow: hidden; white-space: nowrap; } | |
| .cs-room-log b { font-family: var(--mono); font-size: 0.6rem; font-weight: 400; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 70%, var(--text)); flex: none; } | |
| .cs-room-log span { overflow: hidden; text-overflow: ellipsis; } | |
| .cs-room-log i { font-style: normal; font-family: var(--mono); font-size: 0.6rem; | |
| color: var(--dim); margin-left: auto; flex: none; } | |
| .cs-tool-live { border-color: var(--cs-accent, var(--accent)) ; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 80%, #fff) ; } | |
| /* Guests on stage: small familiars on the ring's edge. */ | |
| .cs-guest-mascot { position: absolute; width: 90px; height: 90px; z-index: 1; | |
| pointer-events: none; animation: guestIn 0.5s ease both; } | |
| .cs-guest-0 { left: 8%; bottom: 10%; } | |
| .cs-guest-1 { right: 8%; bottom: 10%; } | |
| .cs-guest-2 { left: 2%; bottom: 34%; } | |
| .cs-guest-3 { right: 2%; bottom: 34%; } | |
| .cs-guest-4 { left: 16%; bottom: 0; } | |
| .cs-guest-5 { right: 16%; bottom: 0; } | |
| @keyframes guestIn { from { opacity: 0; transform: translateY(10px) scale(0.8); } | |
| to { opacity: 1; transform: none; } } | |
| @media (max-width: 900px) { .cs-guest-mascot { display: none; } } | |
| /* The room path under ENTER: quiet sibling, same class light. */ | |
| #models .cs-enter { flex-direction: column; align-items: center; gap: 8px; } | |
| #models .mb-cta-room { | |
| font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.1em; | |
| text-transform: uppercase; text-decoration: none; cursor: pointer; | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 65%, var(--dim)); | |
| padding: 4px 10px; border: 1px solid transparent; border-radius: 3px; | |
| transition: color 0.15s, border-color 0.15s; | |
| } | |
| #models .mb-cta-room:hover { | |
| color: color-mix(in srgb, var(--cs-accent, var(--accent)) 85%, #fff); | |
| border-color: color-mix(in srgb, var(--cs-accent, var(--accent)) 40%, transparent); | |
| } | |
| /* ββ LENS ROUND 2026-08-17 FIXES βββββββββββββββββββββββββββββββββββββββββββ */ | |
| /* The cinematic bottom fade must not wash the chat panel's composer β the | |
| panel IS the bottom of the screen in chat mode. */ | |
| #models .cs-root.cs-chatting::after { display: none; } | |
| @media (prefers-reduced-motion: reduce) { | |
| /* The mood states re-armed the ring pulse that the base reduce block had | |
| disabled; and guests should simply appear. */ | |
| #models .cs-summoning .mb-pedestal::after, | |
| #models .cs-thinking .mb-pedestal::after { animation: none; } | |
| .cs-guest-mascot { animation: none; } | |
| } | |