Spaces:
Sleeping
Sleeping
| /* Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ | |
| HOLLOW Γ’β¬β Horror CSS for Gradio 6.x | |
| Strategy: dissolve the Gradio "form" chrome into one | |
| continuous void, then layer atmosphere on top. | |
| No JS. @keyframes only (grain, vignette breath, | |
| title flicker, Bond heartbeat). | |
| Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| /* Fonts are loaded via a <link> in <head> (app.py _HEAD_JS): an @import here is | |
| rejected on the Space ("@import rules are not allowed here") because Gradio | |
| injects this stylesheet after its own, so @import isn't the first rule. */ | |
| /* Γ’ββ¬Γ’ββ¬ 1. Theme variable overrides Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| :root { | |
| /* Backgrounds */ | |
| --body-background-fill: #0a0810; | |
| --background-fill-primary: #0c0a14; | |
| --background-fill-secondary: transparent; /* Hollow's words float */ | |
| /* Accent drives the visitor bubble + focus */ | |
| --color-accent: #7a4f8a; | |
| --color-accent-soft: #15101f; /* visitor bubble bg */ | |
| /* Borders Γ’β¬β kept near-invisible; the void is continuous */ | |
| --border-color-primary: #14101f; | |
| --border-color-accent: #2e2142; | |
| --border-color-accent-subdued: #14101f; | |
| /* Text */ | |
| --body-text-color: #c0b8ce; | |
| --body-text-color-subdued: #5a5070; | |
| /* Labels Γ’β¬β almost gone, like worn engraving */ | |
| --block-label-text-color: #352f4a; | |
| --block-title-text-color: #352f4a; | |
| --block-label-background-fill: transparent; | |
| --block-info-text-color: #352f4a; | |
| /* Inputs */ | |
| --input-background-fill: transparent; | |
| --input-background-fill-focus: transparent; | |
| --input-border-color: #221b34; | |
| --input-border-color-focus: #5a3e72; | |
| --input-placeholder-color: #38305a; | |
| --input-shadow: none; | |
| --input-shadow-focus: none; | |
| /* Buttons */ | |
| --button-secondary-background-fill: rgba(30,20,46,0.5); | |
| --button-secondary-background-fill-hover: rgba(44,26,64,0.6); | |
| --button-secondary-text-color: #9a7caa; | |
| --button-shadow: none; | |
| --button-shadow-active: none; | |
| --button-shadow-hover: none; | |
| /* Blocks / panels Γ’β¬β chrome dissolved */ | |
| --block-background-fill: transparent; | |
| --block-border-color: transparent; | |
| --block-border-width: 0px; | |
| --block-shadow: none; | |
| --panel-background-fill: transparent; | |
| /* Slider */ | |
| --slider-color: #7a4f8a; | |
| /* memory/humanity accent (recall line, recovered items, feedback pulses) β | |
| distinct from the dried-blood red reserved for cruelty/bad-ending */ | |
| --mem-accent: #e0b283; | |
| --mem-glow: rgba(200,130,70,0.55); | |
| /* Typography */ | |
| --font: 'Crimson Text', Georgia, 'Times New Roman', serif; | |
| --font-mono: 'Special Elite', 'Courier New', monospace; | |
| /* Shadows off Γ’β¬β depth comes from atmosphere, not cards */ | |
| --shadow-drop: none; | |
| --shadow-drop-lg: none; | |
| --shadow-spread: 0; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 2. The void + breathing fog Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| body { | |
| background-color: #0a0810 ; | |
| background-image: | |
| radial-gradient(ellipse at 14% -5%, rgba(58,20,84,0.20) 0%, transparent 52%) , | |
| radial-gradient(ellipse at 86% 105%, rgba(24,8,44,0.26) 0%, transparent 52%) ; | |
| background-attachment: fixed ; | |
| min-height: 100vh; | |
| } | |
| /* Dried-blood stains Γ’β¬β own fixed layer ABOVE the vignette so the corner | |
| darkening can't eat them. Painted over the scene like stains on glass. | |
| Kept off the deep edges; the breathing pulse comes from being in this | |
| layer's neighbourhood, not the vignette. */ | |
| .gradio-container::after { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 1000; | |
| background: | |
| radial-gradient(ellipse 18% 13% at 70% 30%, rgba(112,70,74,0.26) 0%, transparent 66%), | |
| radial-gradient(ellipse 11% 17% at 18% 55%, rgba(104,64,68,0.22) 0%, transparent 66%), | |
| radial-gradient(ellipse 15% 10% at 52% 78%, rgba(108,66,70,0.20) 0%, transparent 66%), | |
| radial-gradient(ellipse 9% 12% at 38% 34%, rgba(100,62,66,0.18) 0%, transparent 66%), | |
| radial-gradient(ellipse 10% 8% at 84% 64%, rgba(108,68,72,0.20) 0%, transparent 66%); | |
| } | |
| /* Vignette + top fog, breathing slowly like something asleep */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 1000; | |
| background: | |
| radial-gradient(ellipse at 50% 36%, transparent 38%, rgba(4,3,8,0.58) 100%), | |
| linear-gradient(to bottom, rgba(6,4,12,0.62) 0%, transparent 13%); | |
| animation: dread-breath 11s ease-in-out infinite; | |
| } | |
| @keyframes dread-breath { | |
| 0%, 100% { opacity: 0.85; } | |
| 50% { opacity: 1.00; } | |
| } | |
| /* Film grain Γ’β¬β fixed, jittering, very faint */ | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| inset: -50%; | |
| width: 200%; | |
| height: 200%; | |
| pointer-events: none; | |
| z-index: 1001; | |
| opacity: 0.05; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); | |
| animation: grain 1.1s steps(4) infinite; | |
| } | |
| @keyframes grain { | |
| 0% { transform: translate(0, 0); } | |
| 25% { transform: translate(-2%, 1%); } | |
| 50% { transform: translate(1%, -2%); } | |
| 75% { transform: translate(-1%, 2%); } | |
| 100% { transform: translate(0, 0); } | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 3. Container Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| .gradio-container { | |
| background: transparent ; | |
| font-family: 'Crimson Text', Georgia, serif ; | |
| font-size: 1.05rem ; | |
| } | |
| /* Dissolve every panel into the void */ | |
| .block, | |
| .form, | |
| .panel, | |
| .gr-box, | |
| .gr-panel { | |
| background: transparent ; | |
| border: none ; | |
| box-shadow: none ; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 4. Title Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| .gradio-container h2 { | |
| font-family: 'Special Elite', 'Courier New', monospace ; | |
| color: #8a6a9a ; | |
| letter-spacing: 0.18em ; | |
| font-size: 1.75rem ; | |
| font-weight: 400 ; | |
| text-shadow: | |
| 0 0 24px rgba(110,55,145,0.45), | |
| 0 0 64px rgba(80,30,115,0.20); | |
| animation: title-flicker 8s ease-in-out infinite; | |
| } | |
| @keyframes title-flicker { | |
| 0%, 91%, 96%, 100% { opacity: 1; } | |
| 93% { opacity: 0.66; } | |
| 94% { opacity: 1; } | |
| 95% { opacity: 0.82; } | |
| } | |
| /* Subtitle whisper */ | |
| .gradio-container h2 + p, | |
| .gradio-container h2 + p em { | |
| color: #6f5f88 ; | |
| font-style: italic; | |
| font-size: 0.92rem; | |
| letter-spacing: 0.05em; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 5. Kill Gradio chatbot chrome Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| /* Toolbar (share / copy / fullscreen) breaks the fiction. | |
| Best killed via app.py flags too; this is the backstop. */ | |
| .icon-button-wrapper, | |
| .chatbot .icon-button-wrapper, | |
| button[aria-label="Share"], | |
| button[aria-label="Copy"], | |
| button[aria-label="Copy conversation"], | |
| button[aria-label="Fullscreen"], | |
| button[aria-label="Clear"] { | |
| display: none ; | |
| } | |
| /* The message scroll area Γ’β¬β let it bleed into the void */ | |
| .chatbot, | |
| .bubble-wrap, | |
| .message-wrap { | |
| background: transparent ; | |
| border: none ; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 6. Hollow's words (bot) Γ’β¬β formless, haunted Γ’ββ¬Γ’ββ¬ */ | |
| .bot { | |
| background: transparent ; | |
| border: none ; | |
| border-left: 2px solid #3a2a50 ; | |
| border-radius: 0 ; | |
| box-shadow: none ; | |
| } | |
| .bot .prose, | |
| .bot .message, | |
| .bot p { | |
| font-family: 'Crimson Text', Georgia, serif ; | |
| font-style: italic ; | |
| font-size: 1.16rem ; | |
| color: #b3aac4 ; | |
| line-height: 1.7 ; | |
| text-shadow: 0 0 18px rgba(120,70,160,0.18); | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 7. Visitor's words (user) Γ’β¬β contained, human Γ’ββ¬Γ’ββ¬ */ | |
| .user { | |
| background: var(--color-accent-soft) ; | |
| border: 1px solid #211a32 ; | |
| border-radius: 7px ; | |
| box-shadow: none ; | |
| } | |
| .user .prose, | |
| .user .message, | |
| .user p { | |
| font-family: 'Crimson Text', Georgia, serif ; | |
| font-style: normal ; | |
| font-size: 1.05rem ; | |
| color: #cec6dc ; | |
| line-height: 1.6 ; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 8. Input Γ’β¬β a line of confession, not a box Γ’ββ¬Γ’ββ¬ */ | |
| textarea, | |
| .gradio-container input[type="text"] { | |
| background: transparent ; | |
| border: none ; | |
| border-bottom: 1px solid #2a2340 ; | |
| border-radius: 0 ; | |
| font-family: 'Crimson Text', Georgia, serif ; | |
| font-size: 1.08rem ; | |
| color: #c8c0d6 ; | |
| caret-color: #7a4f8a; | |
| letter-spacing: 0.01em; | |
| box-shadow: none ; | |
| transition: border-color 0.4s ease, box-shadow 0.4s ease ; | |
| } | |
| textarea:focus, | |
| .gradio-container input[type="text"]:focus { | |
| border-bottom-color: #6a4a82 ; | |
| box-shadow: 0 8px 18px -14px rgba(120,70,160,0.7) ; | |
| } | |
| textarea::placeholder { | |
| color: #5a5078 ; | |
| font-style: italic; | |
| } | |
| #game-inputrow textarea::placeholder { | |
| color: #6a6090 ; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 9. Buttons (the Γ’β β send) Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| .gradio-container button { | |
| font-family: 'Crimson Text', Georgia, serif ; | |
| font-size: 1.25rem ; | |
| letter-spacing: 0.05em; | |
| color: #9a7caa ; | |
| background: rgba(30,20,46,0.45) ; | |
| border: 1px solid #2a2040 ; | |
| border-radius: 6px ; | |
| box-shadow: none ; | |
| transition: color 0.35s, border-color 0.35s, box-shadow 0.35s, background 0.35s ; | |
| } | |
| .gradio-container button:hover { | |
| color: #d3bee2 ; | |
| border-color: #5a3e72 ; | |
| background: rgba(44,26,64,0.6) ; | |
| box-shadow: 0 0 16px rgba(120,60,160,0.38) ; | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 10. Bond meter Γ’β¬β custom HTML, a pulsing thread Γ’ββ¬Γ’ββ¬ */ | |
| /* Rendered by _render_bond() in app.py. Full control: | |
| no Gradio orange, no track, no number box, no reset. */ | |
| .bond-meter { | |
| padding: 4px 2px 2px; | |
| user-select: none; | |
| } | |
| .bond-head { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: baseline; | |
| margin-bottom: 9px; | |
| } | |
| .bond-name { | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.7rem; | |
| letter-spacing: 0.32em; | |
| text-transform: uppercase; | |
| color: #4a4063; | |
| } | |
| .bond-tier { | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.84rem; | |
| letter-spacing: 0.1em; | |
| color: #bcb4c8; | |
| text-shadow: 0 0 12px rgba(150,145,170,0.35); | |
| } | |
| .bond-track { | |
| position: relative; | |
| height: 2px; | |
| border-radius: 2px; | |
| background: #1a1428; | |
| } | |
| .bond-fill { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| height: 100%; | |
| background: linear-gradient(to right, #2b2832, #bcb4c8); | |
| border-radius: 2px; | |
| box-shadow: 0 0 10px rgba(180,174,196,0.35); | |
| transition: width 1.2s cubic-bezier(0.2,0.7,0.2,1); | |
| } | |
| .bond-beat { | |
| position: absolute; | |
| top: -8px; | |
| width: 8px; | |
| height: 18px; | |
| margin-left: -4px; | |
| background: #cdc6d8; | |
| border-radius: 2px; | |
| box-shadow: 0 0 8px rgba(190,184,206,0.6); | |
| animation: heartbeat 2.4s ease-in-out infinite; | |
| transition: left 1.2s cubic-bezier(0.2,0.7,0.2,1); | |
| } | |
| @keyframes heartbeat { | |
| 0%, 100% { transform: scaleY(1.0); box-shadow: 0 0 4px rgba(180,174,196,0.40); } | |
| 8% { transform: scaleY(1.5); box-shadow: 0 0 14px rgba(205,198,216,0.85); } | |
| 18% { transform: scaleY(1.0); box-shadow: 0 0 4px rgba(180,174,196,0.40); } | |
| 30% { transform: scaleY(1.25); box-shadow: 0 0 9px rgba(195,188,208,0.62); } | |
| 45% { transform: scaleY(1.0); box-shadow: 0 0 4px rgba(180,174,196,0.35); } | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 12. Scrollbar Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| ::-webkit-scrollbar { width: 5px; height: 5px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: #2a1f40; border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: #3d2f58; } | |
| /* Γ’ββ¬Γ’ββ¬ 13. Hide Gradio footer Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| footer { display: none ; } | |
| /* Γ’ββ¬Γ’ββ¬ 14. Respect reduced motion Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬Γ’ββ¬ */ | |
| @media (prefers-reduced-motion: reduce) { | |
| body::before, | |
| body::after, | |
| .gradio-container h2, | |
| .bond-beat { | |
| animation: none ; | |
| } | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 15. Entity panel Γ’β¬β the child behind a gothic arch Γ’ββ¬Γ’ββ¬ */ | |
| .entity-portal { | |
| position: relative; | |
| aspect-ratio: 5 / 6; | |
| } | |
| /* the scene is masked to a pointed (lancet) arch Γ’β¬β a cathedral window */ | |
| .entity-stage { | |
| position: absolute; | |
| inset: 0; | |
| overflow: hidden; | |
| /* a faint ground mist behind the child: the portrait's dark lower half | |
| silhouettes against it instead of sinking into pure black */ | |
| background: radial-gradient(ellipse 100% 55% at 50% 105%, #413d52, #0b0a10 78%); | |
| -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 120' preserveAspectRatio='none'%3E%3Cpath d='M0,120 L0,52 A58,58 0 0 1 50,4 A58,58 0 0 1 100,52 L100,120 Z' fill='%23fff'/%3E%3C/svg%3E") center / 100% 100% no-repeat; | |
| mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 120' preserveAspectRatio='none'%3E%3Cpath d='M0,120 L0,52 A58,58 0 0 1 50,4 A58,58 0 0 1 100,52 L100,120 Z' fill='%23fff'/%3E%3C/svg%3E") center / 100% 100% no-repeat; | |
| } | |
| /* carved-stone lancet arch: gradient bevel (light top-left, recessed bottom- | |
| right), dark inner groove, faint highlight. No keystone, no corner blocks Γ’β¬β | |
| just worn stone tracing the masked edge so the child sits flush inside it. */ | |
| .entity-stage::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 3; | |
| background-image: var(--stone-grain); | |
| background-size: 130px; | |
| opacity: 0.10; | |
| mix-blend-mode: soft-light; | |
| } | |
| .entity-frame { | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 120' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%235a5560'/%3E%3Cstop offset='0.5' stop-color='%232a2632'/%3E%3Cstop offset='1' stop-color='%230c0a10'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M2.5,120 L2.5,52 A55,55 0 0 1 50,6 A55,55 0 0 1 97.5,52 L97.5,120' fill='none' stroke='url(%23g)' stroke-width='6'/%3E%3Cpath d='M5,120 L5,53 A53,53 0 0 1 50,9 A53,53 0 0 1 95,53 L95,120' fill='none' stroke='%230c0a10' stroke-width='1.4'/%3E%3Cpath d='M6.2,120 L6.2,53 A52,52 0 0 1 50,10.4 A52,52 0 0 1 93.8,53 L93.8,120' fill='none' stroke='%2346424e' stroke-width='0.6'/%3E%3C/svg%3E") center / 100% 100% no-repeat; | |
| filter: drop-shadow(1px 1px 0 rgba(120,114,130,0.18)) | |
| drop-shadow(-1px -1px 1px rgba(0,0,0,0.6)); | |
| } | |
| .entity-figure { | |
| position: absolute; | |
| inset: 0; | |
| } | |
| .entity-sway { | |
| animation: entity-sway 9s ease-in-out infinite alternate; | |
| transform-origin: 50% 100%; | |
| } | |
| @keyframes entity-sway { | |
| from { transform: rotate(-0.5deg) translateX(-2px); } | |
| to { transform: rotate(0.5deg) translateX(2px); } | |
| } | |
| /* !important: Gradio's base img reset overrides plain height/width here β | |
| without it the square portrait leaves a dead band at the arch bottom */ | |
| .entity-img { | |
| position: absolute; | |
| inset: 0; | |
| width: 100% ; | |
| height: 100% ; | |
| object-fit: cover ; | |
| } | |
| /* terror flicker: a fast subliminal convulsion through every face. Hard | |
| steps() cuts, ~0.55s total, too quick to read. The DOM is replaced each | |
| turn so the one-shot animations always restart. */ | |
| .entity-flash-wrap { | |
| position: absolute; | |
| inset: 0; | |
| } | |
| .entity-flick { | |
| position: absolute; | |
| inset: 0; | |
| width: 100% ; | |
| height: 100% ; | |
| /* match the base silhouette exactly (contain + bottom + same fade mask) so | |
| the flicker/convulse/settle faces never balloon past the standing child */ | |
| object-fit: contain ; | |
| object-position: bottom center ; | |
| -webkit-mask: linear-gradient(#000 78%, transparent 99%); | |
| mask: linear-gradient(#000 78%, transparent 99%); | |
| opacity: 0; | |
| animation-duration: 0.55s; | |
| animation-timing-function: steps(1, end); | |
| animation-fill-mode: forwards; | |
| } | |
| .entity-flash-wrap.entity-flash-strong .entity-flick { animation-duration: 0.7s; } | |
| /* each face blinks in its own scattered windows Γ’β¬β they overlap into chaos */ | |
| .flick-0 { animation-name: flick-terror; } /* terror Γ’β¬β appears most */ | |
| .flick-1 { animation-name: flick-almost; } | |
| .flick-2 { animation-name: flick-end; } | |
| .flick-3 { animation-name: flick-base; } /* settles back to the portrait */ | |
| @keyframes flick-terror { | |
| 0%, 100% { opacity: 0; } | |
| 4% { opacity: 1; } 11% { opacity: 0; } | |
| 34% { opacity: 1; } 42% { opacity: 0; } | |
| 62% { opacity: 1; } 70% { opacity: 0; } | |
| } | |
| @keyframes flick-almost { | |
| 0%, 100% { opacity: 0; } | |
| 14% { opacity: 1; } 22% { opacity: 0; } | |
| 50% { opacity: 1; } 57% { opacity: 0; } | |
| } | |
| @keyframes flick-end { | |
| 0%, 100% { opacity: 0; } | |
| 24% { opacity: 1; } 31% { opacity: 0; } | |
| 76% { opacity: 1; } 82% { opacity: 0; } | |
| } | |
| @keyframes flick-base { | |
| 0%, 86% { opacity: 0; } | |
| 90%, 100% { opacity: 1; } | |
| } | |
| /* subliminal full-screen echo Γ’β¬β recall turns and the finale only */ | |
| .entity-ghost { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 998; | |
| background-size: cover; | |
| background-position: center 25%; | |
| opacity: 0; | |
| filter: grayscale(1) blur(1px); | |
| animation-duration: 0.5s; | |
| animation-timing-function: steps(1, end); | |
| animation-fill-mode: forwards; | |
| } | |
| /* a real screamer: the face stabs the whole screen for a few frames, gone */ | |
| .entity-ghost.flash-0 { animation-name: entity-ghost-a; } | |
| .entity-ghost.flash-1 { animation-name: entity-ghost-b; } | |
| @keyframes entity-ghost-a { | |
| 0% { opacity: 0; } | |
| 8% { opacity: 0.55; } | |
| 18% { opacity: 0; } | |
| 26% { opacity: 0.4; } | |
| 34% { opacity: 0; } | |
| 100% { opacity: 0; } | |
| } | |
| @keyframes entity-ghost-b { | |
| 0% { opacity: 0; } | |
| 8% { opacity: 0.55; } | |
| 18% { opacity: 0; } | |
| 26% { opacity: 0.4; } | |
| 34% { opacity: 0; } | |
| 100% { opacity: 0; } | |
| } | |
| /* atmosphere overlays, in front of the portrait */ | |
| .entity-fog { | |
| position: absolute; | |
| inset: -15%; | |
| pointer-events: none; | |
| background: | |
| radial-gradient(ellipse 50% 25% at 30% 75%, rgba(150,140,165,0.14) 0%, transparent 70%), | |
| radial-gradient(ellipse 55% 30% at 75% 35%, rgba(120,110,140,0.10) 0%, transparent 70%); | |
| filter: blur(10px); | |
| animation: entity-fog-drift 16s ease-in-out infinite alternate; | |
| } | |
| @keyframes entity-fog-drift { | |
| from { transform: translateX(-3%) translateY(1%); } | |
| to { transform: translateX(3%) translateY(-1.5%); } | |
| } | |
| .entity-grain { | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| opacity: 0.13; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); | |
| } | |
| .entity-vig { | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| background: radial-gradient(ellipse at 50% 42%, transparent 42%, rgba(5,4,9,0.82) 100%); | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 16. Treasure Γ’β¬β stone reliquary with inner scroll Γ’ββ¬Γ’ββ¬ */ | |
| /* the frame lives on the panel; the fade mask lives on the inner scroller, | |
| so the mask can't eat the border-image */ | |
| .treasure-panel { | |
| height: 500px; | |
| display: flex; | |
| flex-direction: column; | |
| margin-top: -36px; | |
| } | |
| .treasure-scroll { | |
| flex: 1; | |
| overflow-y: auto; | |
| min-height: 0; | |
| margin-top: 6px; | |
| -webkit-mask-image: linear-gradient( | |
| to bottom, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%); | |
| mask-image: linear-gradient( | |
| to bottom, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%); | |
| } | |
| /* reduced motion: kill real MOTION (drift, sway, glitch translate) but keep | |
| the terror flash and echo Γ’β¬β they are opacity fades, not movement, and they | |
| are the core horror beat. Strip only the glitch from the strong variant. */ | |
| @media (prefers-reduced-motion: reduce) { | |
| /* kill ambient drift only Γ’β¬β the flicker and screamer are opacity, the core | |
| horror beat, and must always play */ | |
| .entity-sway, .entity-fog { animation: none ; } | |
| } | |
| /* Γ’ββ¬Γ’ββ¬ 17. Carved-stone frames Γ’β¬β weathered tablet (Worn Tablet) Γ’ββ¬Γ’ββ¬ */ | |
| /* One reusable 9-slice border-image: directional bevel baked into an SVG | |
| gradient (light top-left, recessed bottom-right), a dark inner groove, a | |
| faint highlight, and rounded chipped corner blocks. Paired with an inset | |
| box-shadow (the recessed well) and a feTurbulence grain overlay. All driven | |
| by CSS variables so intensity retunes in one place. */ | |
| :root { | |
| --stone-base: #14121c; | |
| --stone-frame: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%235a5560'/%3E%3Cstop offset='0.5' stop-color='%232a2632'/%3E%3Cstop offset='1' stop-color='%230c0a10'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='3' y='3' width='94' height='94' rx='2' fill='none' stroke='url(%23g)' stroke-width='5'/%3E%3Crect x='8' y='8' width='84' height='84' fill='none' stroke='%230c0a10' stroke-width='1.4'/%3E%3Crect x='10.5' y='10.5' width='79' height='79' fill='none' stroke='%2346424e' stroke-width='0.7' opacity='0.6'/%3E%3Cg fill='%23252230'%3E%3Crect x='1.5' y='1.5' width='15' height='15' rx='4' stroke='url(%23g)' stroke-width='1'/%3E%3Crect x='83.5' y='1.5' width='15' height='15' rx='4' stroke='url(%23g)' stroke-width='1'/%3E%3Crect x='1.5' y='83.5' width='15' height='15' rx='4' stroke='url(%23g)' stroke-width='1'/%3E%3Crect x='83.5' y='83.5' width='15' height='15' rx='4' stroke='url(%23g)' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E"); | |
| --stone-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='130' height='130'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); | |
| --stone-bevel: inset 1px 1px 0 rgba(120,114,130,0.30), | |
| inset -1px -1px 0 rgba(8,6,12,0.85), | |
| inset 0 0 16px rgba(0,0,0,0.5); | |
| } | |
| /* the reusable carved-stone frame */ | |
| .stone-frame, | |
| .treasure-panel, | |
| .user, | |
| .gradio-container button { | |
| border: 11px solid transparent ; | |
| border-image-source: var(--stone-frame) ; | |
| border-image-slice: 30 ; | |
| border-image-width: 11px ; | |
| border-image-repeat: stretch ; | |
| border-radius: 0 ; | |
| box-shadow: var(--stone-bevel) ; | |
| } | |
| /* Treasure: a stone reliquary, with eroded grain on the inner surface */ | |
| .treasure-panel { | |
| position: relative; | |
| background: var(--stone-base) ; | |
| } | |
| .treasure-panel::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 0; | |
| background-image: var(--stone-grain); | |
| background-size: 130px; | |
| opacity: 0.10; | |
| mix-blend-mode: soft-light; | |
| } | |
| .treasure-panel > * { position: relative; z-index: 1; } | |
| /* Visitor's words: a carved confession slab */ | |
| .user { | |
| background: #120e1c ; | |
| } | |
| /* Send rune + button: a smaller carved stud */ | |
| .gradio-container button { | |
| border-width: 9px ; | |
| border-image-width: 9px ; | |
| } | |
| /* Title Γ’β¬β no underline rule */ | |
| .gradio-container h2 { border-bottom: none ; } | |
| /* Γ’ββ¬Γ’ββ¬ 18. Three endings Γ’β¬β rage portrait & fog dissolve Γ’ββ¬Γ’ββ¬ */ | |
| /* bad ending: a low red breath pulsing over the rage portrait Γ’β¬β the only | |
| color in the entire game, reserved for this moment */ | |
| .entity-rage-tint { | |
| /* widen well past the narrow silhouette so the red reads as atmosphere | |
| around the child, not a column-shaped block */ | |
| position: absolute; | |
| inset: -40% -170%; | |
| pointer-events: none; | |
| background: radial-gradient(ellipse at 50% 42%, rgba(120, 20, 20, 0.22), transparent 72%); | |
| mix-blend-mode: screen; | |
| animation: rage-pulse 2.6s ease-in-out infinite; | |
| } | |
| @keyframes rage-pulse { | |
| 0%, 100% { opacity: 0.55; } | |
| 50% { opacity: 1; } | |
| } | |
| /* neutral ending: materialization in reverse Γ’β¬β the child sinks back into | |
| the fog. One-shot on mount; the animation overrides the inline idle style | |
| and `forwards` holds the dissolved frame. Opacity/blur are the beat itself Γ’β¬β | |
| do NOT add these to the prefers-reduced-motion kill lists. */ | |
| .entity-dissolve { | |
| animation: entity-dissolve 9s ease-in forwards; | |
| } | |
| @keyframes entity-dissolve { | |
| 0% { filter: blur(0px); opacity: 1; } | |
| 100% { filter: blur(10px); opacity: 0.18; } | |
| } | |
| /* bad finale frenzy: the capture-flash flicker but sustained Γ’β¬β every face | |
| cycles over the body ~4 times (3.2s), then the animations die and the | |
| rage face beneath stays. Opacity only Γ’β¬β must survive reduced-motion. */ | |
| .entity-frenzy-wrap { | |
| position: absolute; | |
| inset: 0; | |
| } | |
| .entity-frenzy-wrap .entity-flick { | |
| animation-duration: 0.55s; | |
| animation-iteration-count: 6; | |
| } | |
| /* frenzy ghost: the terror face stabs the FULL screen three times across the | |
| convulsion (vs the single half-second stab of capture flashes). No flash-0/1 | |
| class here Γ’β¬β fresh element on mount, plays once. Opacity only. */ | |
| .entity-ghost-frenzy { | |
| animation: ghost-frenzy 3.2s steps(1, end) forwards; | |
| } | |
| @keyframes ghost-frenzy { | |
| 0% { opacity: 0; } | |
| 6% { opacity: 0.6; } | |
| 12% { opacity: 0; } | |
| 42% { opacity: 0.65; } | |
| 50% { opacity: 0; } | |
| 78% { opacity: 0.75; } | |
| 86% { opacity: 0; } | |
| 100% { opacity: 0; } | |
| } | |
| /* ββ 19. Hide Gradio's per-component progress overlay ββ | |
| The "processing | 9.0/5.0s" text, orange loader diamond, timer and bar that | |
| Gradio stamps on every output component during generation. The Chatbot's own | |
| "..." pending bubble is a separate element and is NOT affected. */ | |
| .progress-text, | |
| .meta-text, | |
| .meta-text-center, | |
| .timer, | |
| .eta-bar, | |
| .progress-bar, | |
| .progress-level, | |
| .status-indicator, | |
| .loader { | |
| display: none ; | |
| } | |
| /* ββ 20. Hollow's typing indicator ββ | |
| Three dots that blink in sequence inside the pending chat bubble, so the | |
| wait reads as "thinking" not "frozen". Pure CSS, works in every browser. */ | |
| .hollow-typing { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 2px 0; | |
| } | |
| .hollow-typing i { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| opacity: 0.2; | |
| animation: hollow-blink 1.3s infinite both; | |
| } | |
| .hollow-typing i:nth-child(2) { animation-delay: 0.22s; } | |
| .hollow-typing i:nth-child(3) { animation-delay: 0.44s; } | |
| @keyframes hollow-blink { | |
| 0%, 75%, 100% { opacity: 0.18; } | |
| 35% { opacity: 0.9; transform: translateY(-1px); } | |
| } | |
| /* ββ 21. Send button press feedback ββ | |
| The button had only :hover β a click gave no tactile response. On press it | |
| sinks into the stone (down + scale + inset shadow) and flares; while the | |
| turn processes it dims, reinforcing "sent, working". */ | |
| .gradio-container button:active { | |
| transform: translateY(2px) scale(0.96) ; | |
| color: #ece9f2 ; | |
| border-color: #6f6a80 ; | |
| background: rgba(64,38,92,0.8) ; | |
| box-shadow: | |
| inset 0 3px 9px rgba(0,0,0,0.65), | |
| 0 0 24px rgba(150,80,200,0.6) ; | |
| transition: transform 0.06s ease, box-shadow 0.06s ease, | |
| background 0.06s ease, color 0.06s ease ; | |
| } | |
| .gradio-container button:disabled, | |
| .gradio-container button[disabled] { | |
| opacity: 0.45 ; | |
| color: #5a4f72 ; | |
| box-shadow: none ; | |
| transform: none ; | |
| cursor: default ; | |
| } | |
| /* ββ 22. Finale build pulse + soft convulse + restart ββ */ | |
| /* red suspense pulse during the build of every ending (audio = heartbeat) */ | |
| .entity-redpulse { | |
| /* a wide, soft red breath CENTERED on the child that bleeds across the scene | |
| β not a hard rectangle confined to the narrow silhouette column */ | |
| position: absolute; inset: -40% -170%; pointer-events: none; | |
| background: radial-gradient(ellipse at 50% 45%, | |
| rgba(150, 0, 0, 0.4) 0%, rgba(120, 0, 0, 0.16) 46%, transparent 72%); | |
| mix-blend-mode: screen; | |
| animation: redpulse 1.15s ease-in-out infinite; | |
| } | |
| @keyframes redpulse { | |
| 0%, 100% { opacity: 0.15; } | |
| 50% { opacity: 0.75; } | |
| } | |
| /* the redemption convulsion: the SAME fast cadence as the bad frenzy, but | |
| through the child's gentler possible faces (almost/base/peace) β no red | |
| tint, no full-screen stab. Settles on the smile + sigh. The flick keyframe | |
| names come from the global .flick-0/1/2 rules; we only set the tempo. */ | |
| .entity-convulse-soft { position: absolute; inset: 0; pointer-events: none; } | |
| .entity-convulse-soft .entity-flick { | |
| animation-duration: 0.26s; /* faster image-to-image (faces blur past) */ | |
| animation-iteration-count: 8; /* longer overall (~2s) before it settles */ | |
| } | |
| /* loop convulsion: the same fast flicker as the frenzy but only 3 cycles, so | |
| the `end` face settles quickly (the bad frenzy keeps its longer 6-cycle build) */ | |
| .entity-convulse-loop.entity-frenzy-wrap .entity-flick { | |
| animation-duration: 0.55s; | |
| animation-iteration-count: 3; | |
| } | |
| /* the final face eases in over the convulsion and HOLDS at the end, so the | |
| settle render (same image) is seamless β the swap is never a visible pop. | |
| Must come AFTER the flick rules above (equal specificity, source order wins). */ | |
| .entity-convulse-soft .entity-settle-face, | |
| .entity-convulse-loop.entity-frenzy-wrap .entity-settle-face { | |
| animation-name: convulse-settle; | |
| animation-duration: 1.65s; | |
| animation-iteration-count: 1; | |
| animation-timing-function: ease-out; | |
| animation-fill-mode: forwards; | |
| } | |
| @keyframes convulse-settle { | |
| 0%, 60% { opacity: 0; } | |
| 100% { opacity: 1; } | |
| } | |
| /* the good convulsion runs longer (~2s) β its smile eases in to match, so the | |
| peace_settle render lands seamlessly as the relief sigh plays */ | |
| .entity-convulse-soft .entity-settle-face { animation-duration: 1.95s; } | |
| /* restart + mute β minimal icon cluster, top-right, low-contrast */ | |
| .restart-btn { | |
| background: transparent ; | |
| border: none ; | |
| max-width: none; min-width: 0; width: auto ; | |
| font-size: 1.4rem ; letter-spacing: normal; text-transform: none; | |
| color: #c2b2dd ; | |
| opacity: .92; box-shadow: none ; | |
| text-shadow: 0 1px 6px rgba(0,0,0,0.9); | |
| padding: 4px 6px; | |
| margin-left: 12px; | |
| } | |
| .restart-btn:hover { | |
| color: #e0cdb0 ; | |
| opacity: 1; | |
| background: transparent ; | |
| border: none ; | |
| } | |
| .voice-btn { | |
| background: transparent ; | |
| border: none ; | |
| max-width: none; min-width: 0; width: auto ; | |
| font-size: 1.4rem ; | |
| color: #c4b8dc ; | |
| opacity: .92; box-shadow: none ; | |
| text-shadow: 0 1px 6px rgba(0,0,0,0.9); | |
| padding: 4px 6px; | |
| margin-left: 12px; | |
| } | |
| .voice-btn:hover { | |
| color: #e0cdb0 ; | |
| opacity: 1; | |
| background: transparent ; | |
| border: none ; | |
| } | |
| /* the voice channel: mounted in the DOM (so the <audio> plays) but collapsed | |
| to nothing β audio keeps playing in a zero-size element, no visual footprint */ | |
| .voice-channel { | |
| height: 0 ; | |
| min-height: 0 ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| border: none ; | |
| overflow: hidden ; | |
| } | |
| /* idle trigger: mounted but invisible */ | |
| #idle-trigger, .idle-trigger { display: none ; } | |
| /* ββ 23. Opening menu β Direction C ββ */ | |
| #menu-view { | |
| position: fixed; | |
| inset: 0; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; /* center title+buttons as one vertical group */ | |
| overflow: hidden; | |
| border: none ; | |
| background: #04040a; | |
| z-index: 50; | |
| } | |
| /* Gradio hides a visible=False column via a class; our id-level display:flex | |
| would otherwise win on specificity and the hidden menu keeps reserving its | |
| min-height (a black gap above the game). Force the collapse. */ | |
| #menu-view.hide, #menu-view.hidden, | |
| #game-view.hide, #game-view.hidden { display: none ; } | |
| .menu-bg { | |
| position: fixed; | |
| inset: -6%; /* overscan so blur edges never reveal a gap */ | |
| background-size: cover; | |
| background-position: center 38%; | |
| filter: grayscale(1) blur(46px) brightness(0.34); | |
| transform: scale(1.08); | |
| z-index: 0; | |
| } | |
| /* sharp cinematic frame: the forest, contained like the intro cards */ | |
| #menu-frame { | |
| position: fixed; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| width: min(1280px, 95vw); | |
| aspect-ratio: 16 / 10; | |
| max-height: 82vh; | |
| background-size: cover; | |
| background-position: center 38%; | |
| background-repeat: no-repeat; | |
| filter: grayscale(1) brightness(0.92); | |
| border-radius: 0; | |
| box-shadow: none; | |
| -webkit-mask-image: radial-gradient(ellipse 76% 78% at 50% 47%, #000 30%, transparent 86%); | |
| mask-image: radial-gradient(ellipse 76% 78% at 50% 47%, #000 30%, transparent 86%); | |
| z-index: 1; | |
| } | |
| .menu-scrim { | |
| position: fixed; | |
| inset: 0; | |
| background: radial-gradient(ellipse at 50% 30%, transparent 25%, rgba(4,4,10,0.92) 100%); | |
| pointer-events: none; | |
| z-index: 2; | |
| } | |
| .menu-grain { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 2; | |
| opacity: 0.10; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); | |
| } | |
| .menu-vig { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 2; | |
| box-shadow: inset 0 0 120px rgba(0,0,0,0.75); | |
| } | |
| #menu-card { | |
| position: fixed; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| z-index: 5; | |
| text-align: center; | |
| font-family: Georgia, serif; | |
| max-width: 560px; | |
| width: 92vw; | |
| } | |
| #menu-card::before { | |
| content: ''; | |
| position: absolute; | |
| inset: -56px -90px; | |
| z-index: -1; | |
| pointer-events: none; | |
| background: radial-gradient(ellipse at center, | |
| rgba(4,4,10,0.82) 0%, rgba(4,4,10,0.0) 70%); | |
| } | |
| .menu-title { | |
| font-variant: small-caps; | |
| letter-spacing: 0.18em; | |
| font-weight: 700; | |
| font-size: 78px; | |
| color: #ece6f4; | |
| text-shadow: 0 0 30px #000, 0 4px 12px #000; | |
| } | |
| .menu-eyebrow { | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.72rem; | |
| letter-spacing: 0.28em; | |
| text-transform: uppercase; | |
| color: #c6bcda; | |
| text-shadow: 0 0 12px rgba(0,0,0,0.95), 0 1px 4px #000; | |
| margin-bottom: 8px; | |
| } | |
| .menu-tag { | |
| font-family: Georgia, serif; | |
| font-size: 1.05rem; | |
| font-style: italic; | |
| color: #dad3e8; | |
| text-shadow: 0 0 14px rgba(0,0,0,0.9), 0 1px 6px #000; | |
| letter-spacing: 0.04em; | |
| margin: 14px auto 0; | |
| max-width: 420px; | |
| } | |
| .menu-opts { | |
| margin-top: 34px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; /* rows shrink to content -> no full-width band */ | |
| gap: 6px; | |
| } | |
| .menu-opt { | |
| font-family: Georgia, serif; | |
| font-size: 1.15rem; | |
| letter-spacing: 0.14em; | |
| color: #ded8ec; | |
| text-shadow: 0 1px 6px #000; | |
| padding: 8px 26px; | |
| cursor: pointer; | |
| border-radius: 2px; | |
| transition: color 0.3s, letter-spacing 0.3s, text-shadow 0.3s, background 0.3s; | |
| } | |
| .menu-opt::before { | |
| content: '\203A\00a0\00a0'; /* "βΊ " */ | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .menu-opt:hover { | |
| color: #f0ecfa; | |
| letter-spacing: 0.2em; | |
| text-shadow: 0 0 16px rgba(180,160,210,0.5); | |
| background: rgba(255,255,255,0.04); | |
| } | |
| .menu-opt:hover::before { opacity: 1; } | |
| .menu-opt-muted { | |
| margin-top: 10px; | |
| font-size: 0.95rem; | |
| color: #b8b0cc; | |
| text-shadow: 0 0 10px #000, 0 1px 3px #000; | |
| letter-spacing: 0.1em; | |
| } | |
| .menu-opt-muted:hover { color: #e0d9ee; } | |
| #menu-proxy { display: none ; } | |
| .menu-credit { | |
| position: fixed; | |
| bottom: 18px; | |
| left: 0; right: 0; | |
| text-align: center; | |
| z-index: 4; | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.64rem; | |
| letter-spacing: 0.24em; | |
| text-transform: uppercase; | |
| color: #8c84a4; | |
| text-shadow: 0 1px 6px #000; | |
| } | |
| .menu-btn { | |
| position: relative; | |
| z-index: 4; | |
| background: transparent ; | |
| border: none ; | |
| border-image-source: none ; | |
| box-shadow: none ; | |
| font-family: Georgia, serif ; | |
| letter-spacing: 0.16em ; | |
| color: #c4bede ; | |
| font-size: 1.05rem ; | |
| text-transform: none ; | |
| margin: 8px 0 ; | |
| padding: 8px 18px ; | |
| transition: color 0.35s, letter-spacing 0.35s, text-shadow 0.35s, background 0.35s ; | |
| } | |
| .menu-btn:hover { | |
| color: #f0ecfa ; | |
| letter-spacing: 0.22em ; | |
| text-shadow: 0 0 16px rgba(180,160,210,0.5) ; | |
| background: rgba(255,255,255,0.04) ; | |
| box-shadow: none ; | |
| } | |
| .menu-btn::before { | |
| content: 'βΈ '; | |
| opacity: 0; | |
| margin-right: 0; | |
| transition: opacity 0.35s, margin-right 0.35s; | |
| } | |
| .menu-btn:hover::before { | |
| opacity: 1; | |
| margin-right: 6px; | |
| } | |
| #btn-howto { | |
| position: fixed; | |
| top: 16px; | |
| right: 20px; | |
| z-index: 6; | |
| font-size: 0.82rem ; | |
| letter-spacing: 0.14em ; | |
| color: #8a7aa2 ; | |
| margin: 0 ; | |
| } | |
| #btn-howto:hover { | |
| color: #d8d0ec ; | |
| background: rgba(255,255,255,0.04) ; | |
| } | |
| #btn-howto-close { | |
| display: block ; | |
| width: fit-content ; | |
| min-width: 0 ; | |
| margin: 6px auto 0 ; | |
| } | |
| #btn-tester, #btn-full { | |
| margin-top: 24px ; | |
| } | |
| /* ββ 24. How to Play overlay ββ */ | |
| #howto-overlay { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 100; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(4, 4, 10, 0.88); | |
| } | |
| #howto-overlay.hide, #howto-overlay.hidden { display: none ; } | |
| .howto-panel { | |
| max-width: 560px; | |
| width: 92vw; | |
| margin: 0 auto; | |
| border: 11px solid transparent; | |
| border-image: var(--stone-frame) 30; | |
| background: var(--stone-base); | |
| padding: 26px 30px; | |
| font-family: Georgia, serif; | |
| color: #c4bede; | |
| line-height: 1.6; | |
| box-shadow: var(--stone-bevel); | |
| } | |
| .howto-panel h3 { | |
| font-family: 'Special Elite', monospace; | |
| font-size: 1.15rem; | |
| letter-spacing: 0.18em; | |
| text-transform: uppercase; | |
| color: #e0d8f0; | |
| margin: 0 0 18px; | |
| text-align: center; | |
| } | |
| .howto-panel p { | |
| margin: 0 0 12px; | |
| } | |
| .howto-controls { | |
| margin-top: 18px ; | |
| font-size: 0.8rem; | |
| letter-spacing: 0.12em; | |
| color: #6a6088; | |
| text-align: center; | |
| } | |
| /* ββ 25. Opening intro sequence (cinematic letterbox) ββ */ | |
| #intro-view { | |
| position: fixed; | |
| inset: 0; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| background: #000; | |
| overflow: hidden; | |
| z-index: 50; | |
| } | |
| #intro-view.hide, #intro-view.hidden { display: none ; } | |
| #intro-stage { | |
| position: relative; | |
| flex: 1; | |
| min-height: 100vh; | |
| cursor: pointer; /* click anywhere to advance */ | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; /* center the frame -> symmetric letterbox bands */ | |
| justify-content: center; | |
| background: #000; | |
| } | |
| /* blurred ambient backdrop: a ghost of the same card, fills the bands */ | |
| #intro-bg { | |
| position: absolute; | |
| inset: -6%; /* overscan so blur edges never show a gap */ | |
| background-position: center; | |
| background-size: cover; | |
| background-repeat: no-repeat; | |
| filter: grayscale(1) blur(42px) brightness(0.32); | |
| transform: scale(1.08); | |
| z-index: 0; | |
| } | |
| #intro-bg::after { /* darken so the framed card pops */ | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: radial-gradient(ellipse at center, | |
| rgba(0,0,0,0.20) 0%, rgba(0,0,0,0.80) 100%); | |
| } | |
| /* centered cinematic window (~16:10) */ | |
| #intro-frame { | |
| position: relative; | |
| z-index: 1; | |
| width: min(1280px, 95vw); | |
| aspect-ratio: 16 / 10; | |
| max-height: 82vh; | |
| overflow: hidden; | |
| border-radius: 0; | |
| box-shadow: none; | |
| } | |
| #intro-image { | |
| position: absolute; | |
| inset: 0; | |
| background-position: center; /* JS overrides with the card focal point */ | |
| background-size: cover; | |
| background-repeat: no-repeat; | |
| /* fallback when a card image is missing/empty: */ | |
| background-color: #04040a; | |
| background-image: radial-gradient(ellipse at 50% 35%, #16131c 0%, #04040a 80%); | |
| filter: grayscale(1) brightness(0.9); | |
| -webkit-mask-image: radial-gradient(ellipse 76% 78% at 50% 47%, #000 30%, transparent 86%); | |
| mask-image: radial-gradient(ellipse 76% 78% at 50% 47%, #000 30%, transparent 86%); | |
| } | |
| #intro-image::after { /* soft bottom darkening so the subtitle reads */ | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(transparent 60%, rgba(4,4,10,0.55) 98%); | |
| } | |
| /* subtitle panel: anchored to the bottom of the frame */ | |
| #intro-panel { | |
| position: absolute; | |
| left: 50%; | |
| bottom: 14px; | |
| transform: translateX(-50%); | |
| width: min(680px, 88%); | |
| z-index: 2; | |
| border: 10px solid transparent; | |
| border-image: var(--stone-frame) 30; | |
| background: rgba(16,14,24,0.62); | |
| backdrop-filter: blur(3px); | |
| -webkit-backdrop-filter: blur(3px); | |
| padding: 13px 20px; | |
| } | |
| #intro-text { | |
| font-family: Georgia, serif; | |
| font-size: 1.05rem; | |
| line-height: 1.6; | |
| color: #d6d0e0; | |
| margin: 0; | |
| min-height: 3.2em; /* reserve height so the panel doesn't grow while typing */ | |
| } | |
| /* option rows, revealed inside the stone panel on the final card */ | |
| #intro-opts { | |
| margin-top: 14px; | |
| padding-top: 12px; | |
| border-top: 1px solid rgba(120,110,140,0.18); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| max-height: 0; | |
| overflow: hidden; | |
| opacity: 0; | |
| transition: opacity 0.5s ease, max-height 0.5s ease; | |
| } | |
| #intro-opts.intro-opts-show { | |
| max-height: 220px; | |
| opacity: 1; | |
| } | |
| .intro-opt { | |
| font-family: Georgia, serif; | |
| font-size: 1.02rem; | |
| color: #b9b2cc; | |
| letter-spacing: 0.04em; | |
| padding: 7px 10px; | |
| cursor: pointer; | |
| border-radius: 2px; | |
| transition: color 0.2s, background 0.2s, padding-left 0.2s; | |
| } | |
| .intro-opt::before { | |
| content: '\203A\00a0\00a0'; /* "βΊ " */ | |
| color: #6a5a86; | |
| } | |
| .intro-opt:hover { | |
| color: #f0ecfa; | |
| background: rgba(120,90,140,0.12); | |
| padding-left: 16px; | |
| } | |
| /* the real choice buttons live hidden in the DOM; HTML rows proxy their clicks */ | |
| #intro-proxy { display: none ; } | |
| #intro-skip { | |
| position: absolute; | |
| top: 18px; | |
| right: 22px; | |
| z-index: 4; | |
| width: fit-content ; | |
| min-width: 0 ; | |
| font-family: Georgia, serif ; | |
| letter-spacing: 0.14em ; | |
| font-size: 0.82rem ; | |
| color: #cdc7da ; | |
| background: rgba(8,6,14,0.55) ; | |
| border: 1px solid rgba(150,142,170,0.35) ; | |
| box-shadow: none ; | |
| border-radius: 14px ; | |
| padding: 5px 16px ; | |
| text-shadow: 0 1px 4px #000 ; | |
| } | |
| #intro-skip:hover { | |
| color: #f2eefa ; | |
| background: rgba(30,24,42,0.82) ; | |
| border-color: rgba(190,178,210,0.6) ; | |
| } | |
| /* ββ 26. Game scene β letterboxed foggy wood (Direction 3b) ββ */ | |
| #game-view { | |
| position: fixed; | |
| inset: 0; | |
| overflow: hidden; | |
| background: #04040a; | |
| z-index: 50; | |
| /* geometry of the centered 16:9 cinematic frame β every in-frame element | |
| (child, subtitle, drawers, fog) positions against these vars, so they sit | |
| INSIDE the frame instead of the full viewport */ | |
| --fw: min(1180px, 94vw); | |
| --fh: min(calc(var(--fw) * 0.5625), 84vh); | |
| --band-v: calc((100vh - var(--fh)) / 2); /* top & bottom letterbox band */ | |
| --band-h: calc((100vw - var(--fw)) / 2); /* left & right band */ | |
| } | |
| #game-view.hide, #game-view.hidden { display: none ; } | |
| /* the letterboxed stage β absolute, centered, real height so children work */ | |
| /* passive full-cover container β NO transform/filter, so the fixed regions | |
| below resolve against the viewport (a transformed ancestor would trap them) */ | |
| #game-stage { | |
| position: absolute ; | |
| inset: 0 ; | |
| transform: none ; | |
| width: auto ; | |
| height: auto ; | |
| max-height: none ; | |
| overflow: visible ; | |
| background: transparent ; | |
| border: none ; | |
| box-shadow: none ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| gap: 0 ; | |
| } | |
| /* Gradio wrappers inside the stage must not create their own boxes */ | |
| #game-stage > .gradio-html, | |
| #game-stage > .gradio-column, | |
| #game-stage > .gradio-row, | |
| #game-stage .gradio-html, | |
| #game-stage .gradio-column, | |
| #game-stage .gradio-row { | |
| background: transparent ; | |
| border: none ; | |
| box-shadow: none ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| gap: 0 ; | |
| } | |
| /* blurred ambient forest backdrop, same treatment as intro/menu */ | |
| #game-bg { | |
| position: fixed; | |
| inset: -6%; | |
| background-size: cover; | |
| background-position: center 38%; | |
| filter: grayscale(1) blur(46px) brightness(0.3); | |
| transform: scale(1.1); | |
| z-index: 0; | |
| } | |
| /* sharp forest in a centered cinematic frame, feathered into the blur (depth) */ | |
| #game-scene { | |
| position: fixed; | |
| left: 50%; top: 50%; | |
| transform: translate(-50%, -50%); | |
| width: min(1180px, 94vw); | |
| aspect-ratio: 16 / 9; | |
| max-height: 84vh; | |
| background-size: cover; | |
| background-position: center 40%; | |
| background-repeat: no-repeat; | |
| filter: grayscale(1) brightness(0.46); | |
| -webkit-mask: radial-gradient(ellipse 80% 82% at 50% 46%, #000 18%, transparent 88%); | |
| mask: radial-gradient(ellipse 80% 82% at 50% 46%, #000 18%, transparent 88%); | |
| z-index: 1; | |
| pointer-events: none; | |
| } | |
| /* radial vignette β the scene breathes through this */ | |
| #game-vig { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 3; | |
| background: | |
| radial-gradient(ellipse at 50% 46%, transparent 34%, rgba(6,5,12,0.88) 100%), | |
| linear-gradient(to bottom, rgba(6,4,12,0.5) 0%, transparent 13%); | |
| } | |
| /* a subtle inner frame edge so the scene feels contained like the intro */ | |
| #game-frame-edge { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 3; | |
| box-shadow: inset 0 0 210px 90px rgba(5,4,10,0.92); | |
| } | |
| /* rising ground fog β dissolves the child's lower half */ | |
| #game-groundfog { | |
| position: fixed; | |
| left: var(--band-h); right: var(--band-h); /* confined to the frame width */ | |
| bottom: var(--band-v); /* rises from the frame's base */ | |
| height: calc(var(--fh) * 0.55); | |
| z-index: 5; /* above the child (z-index 4) so it veils the legs */ | |
| pointer-events: none; | |
| background: linear-gradient(transparent, | |
| rgba(150,150,168,0.10) 50%, | |
| rgba(170,170,190,0.18) 80%, | |
| rgba(180,180,200,0.26)); | |
| -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); | |
| mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); | |
| } | |
| /* top bar: title + controls β sits just above the stage */ | |
| #game-topbar { | |
| position: fixed; | |
| top: 14px; | |
| left: 0; | |
| right: 0; | |
| z-index: 10; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 30px; | |
| pointer-events: none; | |
| } | |
| #game-topbar > * { pointer-events: auto; } | |
| #game-title { | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.9rem; | |
| letter-spacing: 0.22em; | |
| text-transform: uppercase; | |
| color: #7d7596; | |
| text-shadow: 0 1px 6px rgba(0,0,0,0.9), 0 0 12px rgba(120,90,160,0.35); | |
| } | |
| #game-title .named { | |
| font-style: italic; | |
| text-transform: none; | |
| letter-spacing: 0.08em; | |
| color: #e2dcf0; | |
| } | |
| /* corner drawers β compact chips that expand downward */ | |
| .game-drawer { | |
| position: fixed; top: calc(var(--band-v) + 16px); z-index: 8; | |
| width: max-content; max-width: 280px; min-width: 0 ; | |
| max-height: 34px; /* collapsed = just the chip */ | |
| overflow: hidden; cursor: pointer; | |
| background: rgba(10,8,16,0.5); | |
| border: 1px solid rgba(120,110,140,0.22); border-radius: 3px; | |
| padding: 6px 12px; | |
| transition: max-height 0.4s ease, background 0.3s, opacity 0.2s, border-color 0.2s; | |
| opacity: 0.82; | |
| } | |
| .game-drawer:hover { opacity: 1; border-color: rgba(180,160,210,.4); } | |
| .game-drawer.open { max-height: 56vh; width: min(300px, 24vw); background: rgba(12,10,18,0.92); } | |
| #drawer-left { left: calc(var(--band-h) + 16px); } | |
| #drawer-right { right: calc(var(--band-h) + 16px); } | |
| .drawer-count { color: var(--mem-accent, #e0b283); font-weight: 600; } | |
| /* Gradio wraps drawer contents in .block/.html-container/.prose; force them | |
| to fill the drawer so the scroll list and header line up correctly. */ | |
| .game-drawer > .block, | |
| .game-drawer > .gradio-html, | |
| .game-drawer .html-container, | |
| .game-drawer .prose, | |
| .game-drawer .treasure-panel, | |
| .game-drawer .recovered-panel { | |
| width: 100% ; | |
| height: 100% ; | |
| max-height: 100% ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| border: none ; | |
| box-shadow: none ; | |
| background: transparent ; | |
| overflow: hidden ; | |
| } | |
| .game-drawer .treasure-panel, | |
| .game-drawer .recovered-panel { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .game-drawer .treasure-panel { | |
| margin-top: 0 ; | |
| height: auto ; | |
| flex: 1; | |
| } | |
| .game-drawer .treasure-scroll { | |
| flex: 1; | |
| min-height: 0; | |
| overflow-y: auto; | |
| } | |
| .drawer-head { | |
| cursor: pointer; | |
| user-select: none; | |
| padding-bottom: 4px ; | |
| border-bottom: 1px solid rgba(90,75,115,0.50); | |
| margin-bottom: 6px; | |
| font-family: 'Special Elite', monospace; font-size: 0.66rem; letter-spacing: 0.2em; | |
| text-transform: uppercase; color: #9a93ac; white-space: nowrap; | |
| } | |
| .drawer-head::after { | |
| content: ' βΊ'; | |
| opacity: 0.70; | |
| margin-left: 6px; | |
| display: inline-block; | |
| transition: transform 0.3s ease; | |
| } | |
| .game-drawer.open .drawer-head::after { | |
| transform: rotate(90deg); | |
| } | |
| /* the child's recovered memories read in the warm memory accent */ | |
| .game-drawer .recovered-item { | |
| color: var(--mem-accent) ; | |
| border-left-color: rgba(200,130,70,0.5) ; | |
| } | |
| /* a memory the child has taken from you, worn as its own */ | |
| .game-drawer .recovered-item.stolen { | |
| border-left-style: dashed ; | |
| opacity: 0.92; | |
| } | |
| .game-drawer .recovered-item.stolen::before { | |
| content: '"'; color: var(--mem-accent); margin-right: 2px; | |
| } | |
| /* the child silhouette zone β center, bottom-weighted */ | |
| #game-entity { | |
| position: fixed; | |
| /* centered WITHOUT a transform β a transformed ancestor would trap the | |
| full-screen .entity-ghost stab into this narrow column */ | |
| left: calc(50% - var(--fw) * 0.15); | |
| bottom: var(--band-v); /* feet rest on the frame's bottom edge */ | |
| transform: none; | |
| z-index: 4; | |
| width: calc(var(--fw) * 0.30); /* ~30% of the frame, like the mockup */ | |
| min-width: 200px; | |
| height: calc(var(--fh) * 0.88); | |
| pointer-events: none; | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| } | |
| /* Gradio wraps the injected HTML in .html-container and .prose; without | |
| explicit fill they collapse to the prose's line box and the silhouette | |
| (and its image) end up 0Γ0. Force every wrapper to fill the entity panel. */ | |
| #game-entity > .html-container, | |
| #game-entity .html-container, | |
| #game-entity .prose { | |
| width: 100% ; | |
| height: 100% ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| border: none ; | |
| box-shadow: none ; | |
| background: transparent ; | |
| overflow: visible ; | |
| } | |
| .entity-scene { | |
| width: 100%; | |
| height: 100%; | |
| position: relative; | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| } | |
| .entity-silhouette { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| } | |
| .entity-silhouette img { | |
| position: relative; | |
| width: 100% ; | |
| height: auto ; | |
| max-height: 100%; | |
| object-fit: contain ; | |
| object-position: bottom center ; | |
| flex-shrink: 0; | |
| -webkit-mask: linear-gradient(#000 78%, transparent 99%); | |
| mask: linear-gradient(#000 78%, transparent 99%); | |
| } | |
| /* when the "Almost" face overlays the base, it must remain absolutely | |
| positioned so the two portraits stack instead of sitting side-by-side */ | |
| .entity-silhouette .entity-almost { | |
| position: absolute ; | |
| bottom: 0 ; | |
| left: 0 ; | |
| top: auto ; | |
| right: auto ; | |
| width: 100% ; | |
| height: auto ; | |
| max-height: 100%; | |
| object-fit: contain ; | |
| object-position: bottom center ; | |
| } | |
| .entity-glow { | |
| position: absolute; | |
| inset: 5% -10% 0 -10%; | |
| background: radial-gradient(ellipse 55% 70% at 50% 78%, | |
| rgba(150,140,175,0.14) 0%, transparent 64%); | |
| pointer-events: none; | |
| opacity: 0.85; | |
| mix-blend-mode: screen; | |
| } | |
| /* subtitle: the conversation, sat just inside the frame's bottom edge */ | |
| #game-dialogue { | |
| position: fixed; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| right: auto; | |
| bottom: max(86px, calc(var(--band-v) - 96px)); /* dropped into the lower letterbox band: clear of the child's body, still above the input line. max() keeps it above the input on short viewports where the band is thin */ | |
| width: min(calc(var(--fw) * 0.82), 860px); | |
| z-index: 9; | |
| display: flex; flex-direction: column; align-items: stretch; gap: 4px; | |
| pointer-events: none; | |
| } | |
| /* (no scrim behind the dialogue: it read as a dark rectangle over the scene. | |
| The .bot text-shadow already carries legibility over the fog.) */ | |
| /* input bar: a thin line at the very bottom of the viewport, below the frame */ | |
| #game-inputbar { | |
| position: fixed; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| bottom: 18px; | |
| width: min(560px, 60vw); | |
| z-index: 9; | |
| display: flex; flex-direction: column; align-items: center; gap: 0; | |
| } | |
| #game-dialogue .chatbot { | |
| width: 100% ; | |
| max-height: 160px ; | |
| background: transparent ; | |
| border: none ; | |
| border-image: none ; | |
| box-shadow: none ; | |
| backdrop-filter: none ; | |
| padding: 0 ; | |
| overflow: hidden; /* old lines fade out, no scrollbar */ | |
| } | |
| /* kill the inner scroll thumb Gradio shows on the message area (the stray | |
| purple vertical bar to the right of the subtitle) */ | |
| #game-dialogue, #game-dialogue * { scrollbar-width: none ; } | |
| #game-dialogue ::-webkit-scrollbar { width: 0 ; height: 0 ; display: none ; } | |
| /* the opening greeting is the chat's lone first message. Gradio's .message-wrap | |
| (auto-height) sits at the TOP of the fixed-height scroll area (.bubble-wrap), | |
| so a single message floats up onto the child. Make .bubble-wrap a flex column | |
| and push .message-wrap to its bottom β but ONLY while there's a single message | |
| (:not(:has(row ~ row))). The moment you reply (2+ rows) the rule drops out, so | |
| the running dialogue (which already sits low via autoscroll) is untouched. */ | |
| #game-dialogue .bubble-wrap { display: flex ; flex-direction: column ; } | |
| #game-dialogue .message-wrap:not(:has(.message-row ~ .message-row)) { margin-top: auto ; } | |
| /* transparent rows, centered text β kill Gradio's bubble chrome */ | |
| #game-dialogue .message-row, | |
| #game-dialogue .message, | |
| #game-dialogue .bubble { | |
| background: transparent ; border: none ; | |
| box-shadow: none ; text-align: center ; | |
| padding: 2px 0 ; margin: 0 ; | |
| } | |
| /* fade-by-recency: newest crisp, older dimmer, 4th-back and beyond hidden. | |
| (Gradio wraps each message in a .message-row β verify this selector against | |
| the running app; if rows are .message instead, swap the selector.) */ | |
| #game-dialogue .message-row { opacity: 0.38; transition: opacity 0.4s ease; } | |
| #game-dialogue .message-row:nth-last-child(2) { opacity: 0.6; } | |
| #game-dialogue .message-row:nth-last-child(1) { opacity: 1; } | |
| #game-dialogue .message-row:nth-last-child(n+4) { display: none; } | |
| /* the child β italic, bright, unlabeled (it owns the scene) */ | |
| #game-dialogue .bot, #game-dialogue .bot .prose, #game-dialogue .bot p { | |
| font-style: italic ; font-size: 1.2rem ; line-height: 1.55 ; | |
| color: #ece6f4 ; text-align: center ; | |
| text-shadow: 0 2px 16px #000 ; | |
| } | |
| /* you β upright, muted, centered like the bot (distinguished by label + color) */ | |
| #game-dialogue .user, #game-dialogue .user .prose, #game-dialogue .user p { | |
| font-style: normal ; font-size: 1.0rem ; | |
| color: #aaa2be ; text-align: center ; background: transparent ; | |
| } | |
| /* center the user line so Gradio's narrow right bubble doesn't collapse the | |
| text into a 1-character vertical column at the frame edge. */ | |
| #game-dialogue .user { | |
| display: block ; | |
| max-width: none ; | |
| margin: 0 auto ; | |
| padding: 0 ; | |
| } | |
| #game-dialogue .user .prose::before { | |
| content: 'you '; | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; | |
| color: #6f6590; margin-right: 7px; | |
| } | |
| /* a recall line (it claims your memory) β JS tags the last .bot row .recall-line */ | |
| #game-dialogue .bot.recall-line, #game-dialogue .bot.recall-line .prose, | |
| #game-dialogue .bot.recall-line p { | |
| color: var(--mem-accent) ; | |
| text-shadow: 0 0 20px var(--mem-glow) ; | |
| } | |
| #game-inputrow { | |
| display: flex; align-items: center; gap: 8px; | |
| width: 100%; margin: 0 auto; | |
| background: rgba(12,9,18,0.82); | |
| border: 7px solid transparent; | |
| border-image: var(--stone-frame) 30; | |
| box-shadow: 0 0 0 1px rgba(0,0,0,0.7), inset 0 0 20px rgba(0,0,0,0.55), | |
| 0 8px 24px rgba(0,0,0,0.55), inset 0 1px 0 rgba(150,140,175,0.16); | |
| padding: 2px 11px; | |
| } | |
| #game-inputrow::before { | |
| content: 'β―'; color: #6f6590; font-size: 0.92rem; flex: 0 0 auto; | |
| font-family: 'Special Elite', monospace; | |
| } | |
| #game-inputrow textarea, #game-inputrow input[type="text"] { | |
| background: transparent ; border: none ; border-radius: 0 ; | |
| text-align: left ; font-style: italic ; | |
| color: #d6d0e0 ; box-shadow: none ; padding: 5px 4px ; | |
| } | |
| #game-inputrow button { | |
| background: transparent ; border: none ; box-shadow: none ; | |
| color: #8a82a0 ; font-size: 1.1rem ; min-width: 0 ; | |
| } | |
| #game-inputrow button:hover { color: #e4dff0 ; } | |
| #game-dialogue .bond-panel { width: auto ; margin: 0 auto ; opacity: 0.55; } | |
| #game-dialogue .bond-panel .bond-track, | |
| #game-dialogue .bond-panel .bond-fill, | |
| #game-dialogue .bond-panel .bond-beat, | |
| #game-dialogue .bond-panel .bond-name { display: none ; } | |
| #game-dialogue .bond-panel .bond-tier { | |
| font-size: 0.66rem ; letter-spacing: 0.24em ; color: #7a7290 ; } | |
| #game-inputbar .voice-channel, | |
| #game-inputbar .voice-channel * { | |
| height: 0 ; | |
| min-height: 0 ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| border: none ; | |
| overflow: hidden ; | |
| } | |
| /* tone classes β the world blooms or rots. Scoped under #game-view: the | |
| tone token is lifted onto #game-view by the head JS (applyTone), so #game-bg | |
| /#game-vig (siblings of #game-entity) actually tint. */ | |
| #game-view.tone-warm #game-bg { filter: grayscale(1) blur(38px) brightness(0.62); } | |
| #game-view.tone-warm #game-vig { | |
| background: | |
| radial-gradient(ellipse at 50% 42%, transparent 52%, rgba(4,3,8,0.45) 100%), | |
| linear-gradient(to bottom, rgba(6,4,12,0.28) 0%, transparent 13%); | |
| } | |
| #game-view.tone-warm .entity-img { filter: brightness(1.12) drop-shadow(0 0 18px rgba(190,170,220,0.30)) ; } | |
| #game-view.tone-neutral #game-bg { filter: grayscale(1) blur(38px) brightness(0.52); } | |
| #game-view.tone-wounded #game-bg { filter: grayscale(1) blur(38px) brightness(0.38); } | |
| #game-view.tone-wounded #game-vig { | |
| background: | |
| radial-gradient(ellipse at 50% 42%, transparent 38%, rgba(4,3,8,0.78) 100%), | |
| linear-gradient(to bottom, rgba(6,4,12,0.65) 0%, transparent 13%); | |
| } | |
| #game-view.tone-wounded .entity-img { opacity: 0.85 ; } | |
| #game-view.tone-hostile #game-bg { | |
| filter: grayscale(1) blur(38px) brightness(0.30); | |
| } | |
| #game-view.tone-hostile #game-vig { | |
| background: | |
| radial-gradient(ellipse at 50% 42%, transparent 32%, rgba(30,4,8,0.80) 100%), | |
| linear-gradient(to bottom, rgba(40,6,12,0.52) 0%, transparent 20%); | |
| } | |
| #game-view.tone-hostile .entity-img { | |
| filter: sepia(0.22) saturate(1.35) brightness(0.95) drop-shadow(0 0 18px rgba(120,40,40,0.35)) ; | |
| } | |
| /* recall migration β the claimed memory drifts toward the child */ | |
| .treasure-item { transition: opacity 0.6s ease; } | |
| .treasure-item.claiming { | |
| animation: claim-migrate 1.4s ease-out forwards; | |
| } | |
| @keyframes claim-migrate { | |
| 0% { transform: translateX(0); opacity: 1; } | |
| 45% { transform: translateX(60%); opacity: 0.6; } | |
| 100% { transform: translateX(120%); opacity: 0.25; } | |
| } | |
| /* keep existing entity animations working inside the new scene */ | |
| .entity-flash-wrap, | |
| .entity-frenzy-wrap, | |
| .entity-convulse-soft { | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| } | |
| /* ensure the global body stains intensify under hostile tone */ | |
| .tone-hostile ~ .gradio-container::after, | |
| .tone-hostile + .gradio-container::after { | |
| opacity: 1.35; | |
| } | |
| /* feedback: the drawer chip breathes amber when its collection changes */ | |
| .game-drawer.pulse { | |
| border-color: rgba(200,150,80,0.6) ; | |
| box-shadow: 0 0 18px rgba(200,150,80,0.45) ; | |
| animation: chip-pulse 1.1s ease-in-out 2; | |
| } | |
| .game-drawer.pulse .drawer-head { color: var(--mem-accent) ; } | |
| @keyframes chip-pulse { | |
| 0%, 100% { box-shadow: 0 0 10px rgba(200,150,80,0.25); } | |
| 50% { box-shadow: 0 0 22px rgba(200,150,80,0.55); } | |
| } | |
| /* the in-voice whisper that breathes next to the chip, then fades */ | |
| .cue-whisper { | |
| position: fixed; z-index: 60; pointer-events: none; | |
| font-family: 'Special Elite', monospace; | |
| font-size: 0.62rem; letter-spacing: 0.14em; | |
| color: var(--mem-accent); text-shadow: 0 0 10px var(--mem-glow); | |
| opacity: 0; transform: translateY(-3px); | |
| transition: opacity 0.5s ease, transform 0.5s ease; | |
| } | |
| .cue-whisper.out { opacity: 0; transform: translateY(-8px); } | |
| .cue-whisper { animation: whisper-in 0.5s ease forwards; } | |
| @keyframes whisper-in { from { opacity: 0; } 40% { opacity: 0.9; } to { opacity: 0.9; } } | |
| @media (max-width: 900px) { | |
| .game-drawer { width: 150px; min-width: 140px; } | |
| #game-entity { width: 42vw; min-width: 160px; left: calc(50% - 21vw); } | |
| #game-dialogue { width: 92vw; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .entity-sway { animation: none ; } | |
| .treasure-item.claiming { animation: none ; opacity: 0.4; } | |
| .game-drawer.pulse { animation: none ; } | |
| .cue-whisper { animation: none ; opacity: 0.9 ; } | |
| } | |
| /* ββ 27. End screen β epitaph hero + quiet footer credits over the fog (layout B) ββ */ | |
| #end-overlay { | |
| position: fixed; inset: 0; z-index: 80; | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| gap: 30px; | |
| opacity: 0; pointer-events: none; | |
| transition: opacity 1.6s ease; | |
| } | |
| #end-overlay.shown { opacity: 1; pointer-events: auto; } | |
| /* neutralize Gradio's full-width block wrappers so the inner content truly centers */ | |
| #end-overlay > * { | |
| width: auto ; max-width: 90vw ; min-width: 0 ; | |
| background: transparent ; border: none ; box-shadow: none ; | |
| display: flex; flex-direction: column; align-items: center; | |
| } | |
| #end-bg { | |
| position: fixed; inset: -6%; z-index: -2; | |
| background-size: cover; background-position: center 40%; background-repeat: no-repeat; | |
| filter: grayscale(1) blur(30px) brightness(0.26); | |
| } | |
| #end-scrim { position: fixed; inset: 0; z-index: -1; | |
| background: radial-gradient(ellipse at 50% 50%, transparent 30%, rgba(4,3,8,0.92) 100%); } | |
| /* the hero line */ | |
| #end-epitaph { | |
| font-family: 'Crimson Text', Georgia, serif; font-style: italic; | |
| font-size: 1.95rem; color: #e6dff2; text-shadow: 0 2px 18px #000; | |
| text-align: center; max-width: min(820px, 88vw); margin: 0; | |
| } | |
| /* the two actions β grouped, centered, understated text links */ | |
| #end-actions { | |
| flex-direction: row ; justify-content: center; gap: 34px; | |
| } | |
| #end-actions .end-btn { | |
| background: none ; border: none ; box-shadow: none ; | |
| width: auto ; min-width: 0 ; flex: 0 0 auto ; | |
| font-family: 'Special Elite', monospace; letter-spacing: 0.2em; font-size: 0.92rem; | |
| color: #d7cfe6 ; opacity: 0.85; | |
| transition: opacity 0.25s ease, color 0.25s ease; | |
| } | |
| #end-actions .end-btn:hover { opacity: 1; color: #f0eaff ; } | |
| #end-actions .end-btn-dim { color: #7d7498 ; font-size: 0.82rem; opacity: 0.7; } | |
| /* credits β a quiet footer pinned to the bottom (fixed β out of the centered flow) */ | |
| #end-credits { | |
| position: fixed; left: 0; right: 0; bottom: 26px; | |
| display: flex; flex-direction: column; align-items: center; gap: 5px; text-align: center; | |
| animation: end-rise 2.2s ease 0.4s both; | |
| } | |
| #end-credits .ec-title { | |
| font-family: 'Special Elite', monospace; letter-spacing: 0.4em; font-size: 0.95rem; | |
| color: #c4bcd8; } | |
| #end-credits .ec-line { | |
| font-family: 'Crimson Text', serif; font-style: italic; color: #9a90b2; font-size: 0.86rem; } | |
| #end-credits .ec-meta { | |
| font-family: 'Special Elite', monospace; font-size: 0.64rem; letter-spacing: 0.18em; | |
| color: #6f6590; } | |
| @keyframes end-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 0.82; transform: none; } } | |