ppt-tool/frontend/app/globals.css
Vadym Samoilenko ff9cdffc32 Phase 5: Fix export, slide edit, static files; add README
- Fix PPTX/PDF export: Puppeteer URL port mismatch (80 → 3000)
- Fix backend export_utils to use NEXT_INTERNAL_URL env var
- Add Chromium to frontend Dockerfile for Docker-based export
- Fix slide edit socket hang up with asyncio.wait_for() timeouts
- Add FastAPI StaticFiles mounts for /static and /app_data
- Add Next.js rewrite for /static/ to proxy to backend
- Show template thumbnail in master decks admin page
- Add error logging to ReviewWorkflow component
- Add Docker env vars for web service (APP_DATA_DIRECTORY, app_data volume)
- Add project README in English

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:40:36 +00:00

743 lines
No EOL
15 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: var(--font-inter), var(--font-roboto), sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
/* Brand-adaptive colors (set dynamically via useBrandTheme hook) */
--brand-primary: #5146E5;
--brand-secondary: #E9E8F8;
--brand-accent: #3D35B0;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
body {
@apply bg-background text-foreground;
}
}
strong {
@apply font-black;
}
::selection {
background-color: hsl(var(--chart-1));
color: white;
}
/* Hide input number arrows */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
thead,
tbody tr {
display: table;
width: 100%;
table-layout: fixed;
/* even columns width , fix width of table too*/
}
thead {
width: calc(100% - 1em)
/* scrollbar is average 1em/16px width, remove it from thead width */
}
/* Add this to your global CSS or a specific CSS module */
@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
.typing-effect {
overflow: hidden;
/* Ensures the text is hidden until revealed */
white-space: nowrap;
/* Prevents text from wrapping */
display: inline-block;
/* Ensures the width is respected */
animation: typing 2s steps(10, end);
/* Adjust duration and steps for effect */
animation-fill-mode: forwards;
/* Retain the final state of the animation */
animation-delay: 1s;
/* Optional: delay before starting the animation */
}
.typing-effect-complete {
border-right: none;
/* Remove the cursor after animation */
}
.blinking-cursor {
animation: blink 1s step-end infinite;
}
@keyframes blink {
from,
to {
opacity: 1;
}
50% {
opacity: 0;
}
}
.hide-scrollbar::-webkit-scrollbar {
@apply hidden;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.custom_scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom_scrollbar::-webkit-scrollbar-thumb {
background-color: #d1d5db;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.custom_scrollbar::-webkit-scrollbar-thumb:hover {
background-color: #9ca3af;
}
.custom_scrollbar::-webkit-scrollbar-track {
background-color: #f9fafb;
border-radius: 8px;
}
.custom_scrollbar::-webkit-scrollbar-corner {
background-color: transparent;
}
/* Firefox scrollbar styling */
.custom_scrollbar {
scrollbar-width: thin;
scrollbar-color: #d1d5db #f9fafb;
}
/* word animation */
@keyframes slideUp {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
@keyframes slideDown {
0% {
transform: translateY(-50%);
}
100% {
transform: translateY(0);
}
}
.animate-slideUp {
animation: slideUp 20s linear infinite;
}
.animate-slideDown {
animation: slideDown 20s linear infinite;
}
/* Add hover pause */
.animate-slideUp:hover,
.animate-slideDown:hover {
animation-play-state: paused;
}
/* box animation */
.research-mode-bg {
position: relative;
overflow: hidden;
}
.research-mode-bg::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: radial-gradient(circle, rgba(56, 48, 161, 0.1) 0%, transparent 70%);
transform: translate(-50%, -50%);
border-radius: 50%;
animation: rippleEffect 0.6s ease-out forwards;
pointer-events: none;
}
@keyframes rippleEffect {
0% {
width: 0;
height: 0;
opacity: 0.8;
}
100% {
width: 200%;
height: 200%;
opacity: 1;
}
}
/* Markdown Styles */
.markdown-content {
@apply prose prose-slate max-w-none;
}
/* .markdown-content h1 {
@apply text-xl font-bold mb-4 text-gray-900;
}
.markdown-content h2 {
@apply text-lg font-bold mb-3 text-gray-900;
}
.markdown-content h3 {
@apply text-base font-bold mb-2 text-gray-900;
}
.markdown-content h4 {
@apply text-sm font-bold mb-2 text-gray-900;
}
.markdown-content h5 {
@apply text-xs font-bold mb-1 text-gray-900;
}
.markdown-content h6 {
@apply text-xs font-semibold mb-1 text-gray-900;
}
.markdown-content p {
@apply mb-4 text-base text-gray-700;
}
.markdown-content ul {
@apply list-disc pl-6 mb-4;
}
.markdown-content ol {
@apply list-decimal pl-6 mb-4;
}
.markdown-content li {
@apply mb-1;
}
.markdown-content strong,
.markdown-content b {
@apply font-bold text-gray-900;
}
.markdown-content em {
@apply italic;
}
.markdown-content blockquote {
@apply border-l-4 border-gray-300 pl-4 italic my-4;
}
.markdown-content code {
@apply bg-gray-100 px-1 py-0.5 rounded font-mono text-sm;
}
.markdown-content pre {
@apply bg-gray-100 p-4 rounded-lg my-4 overflow-x-auto;
}
.markdown-content a {
@apply text-blue-600 hover:text-blue-800 underline;
}
.markdown-content table {
@apply min-w-full border border-gray-300 my-4;
}
.markdown-content th {
@apply bg-gray-100 border border-gray-300 px-4 py-2 font-bold;
}
.markdown-content td {
@apply border border-gray-300 px-4 py-2;
} */
/* Override Tailwind Typography prose heading sizes for markdown editor */
.prose h1 {
font-size: 18px !important;
font-weight: bold !important;
margin-bottom: 1rem !important;
color: rgb(17 24 39) !important;
}
.prose h2 {
font-size: 16px !important;
font-weight: bold !important;
margin-bottom: 0.75rem !important;
color: rgb(17 24 39) !important;
}
.prose h3 {
font-size: 14px !important;
font-weight: bold !important;
margin-bottom: 0.5rem !important;
color: rgb(17 24 39) !important;
}
.prose h4 {
font-size: 12px !important;
font-weight: bold !important;
margin-bottom: 0.5rem !important;
color: rgb(17 24 39) !important;
}
.prose h5 {
font-size: 11px !important;
font-weight: bold !important;
margin-bottom: 0.25rem !important;
color: rgb(17 24 39) !important;
}
.prose h6 {
font-size: 10px !important;
font-weight: 600 !important;
margin-bottom: 0.25rem !important;
color: rgb(17 24 39) !important;
}
/* MDXEditor styles */
.mdxeditor-toolbar-group {
@apply flex items-center gap-1 p-1;
}
.mdxeditor-toolbar {
@apply flex items-center gap-2 p-2 border-b;
}
.mdxeditor-button {
@apply p-1 rounded hover:bg-gray-100 transition-colors;
}
.mdxeditor-button[data-active=true] {
@apply bg-gray-100;
}
/* tippy-box */
.tippy-box {
max-width: 100% !important;
}
.is-editor-empty:first-child::before {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
/* code editor */
.container_editor_area {
tab-size: 4ch;
max-height: 400px;
overflow: auto;
margin: 1.67em 0;
}
.container__content {
width: 440px;
max-width: 100%;
padding: 10px;
text-align: center;
}
.container__content_area {
tab-size: 4ch;
/* max-height: 600px; */
overflow: auto;
margin: 1.67em 0;
}
.container__editor {
font-variant-ligatures: common-ligatures;
background-color: #fafafa;
border-radius: 3px;
}
.container__editor textarea {
outline: 0;
}
/* Syntax highlighting */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #90a4ae;
}
.token.punctuation {
color: #9e9e9e;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #e91e63;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #4caf50;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #795548;
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #3f51b5;
}
.token.function {
color: #f44336;
}
.token.regex,
.token.important,
.token.variable {
color: #ff9800;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/* Hamster wheel loading animation — from Uiverse.io by Nawsome */
.wheel-and-hamster {
--dur: 1s;
position: relative;
width: 12em;
height: 12em;
font-size: 14px;
}
.wheel,
.hamster,
.hamster div,
.spoke {
position: absolute;
}
.wheel,
.spoke {
border-radius: 50%;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.wheel {
background: radial-gradient(100% 100% at center,hsla(0,0%,60%,0) 47.8%,hsl(0,0%,60%) 48%);
z-index: 2;
}
.hamster {
animation: hamster var(--dur) ease-in-out infinite;
top: 50%;
left: calc(50% - 3.5em);
width: 7em;
height: 3.75em;
transform: rotate(4deg) translate(-0.8em,1.85em);
transform-origin: 50% 0;
z-index: 1;
}
.hamster__head {
animation: hamsterHead var(--dur) ease-in-out infinite;
background: hsl(30,90%,55%);
border-radius: 70% 30% 0 100% / 40% 25% 25% 60%;
box-shadow: 0 -0.25em 0 hsl(30,90%,80%) inset,
0.75em -1.55em 0 hsl(30,90%,90%) inset;
top: 0;
left: -1.5em;
width: 2.75em;
height: 2.5em;
transform-origin: 100% 50%;
}
.hamster__ear {
animation: hamsterEar var(--dur) ease-in-out infinite;
background: hsl(0,90%,85%);
border-radius: 50%;
box-shadow: -0.25em 0 hsl(30,90%,55%) inset;
top: -0.25em;
right: -0.25em;
width: 0.75em;
height: 0.75em;
transform-origin: 50% 75%;
}
.hamster__eye {
animation: hamsterEye var(--dur) linear infinite;
background-color: hsl(0,0%,0%);
border-radius: 50%;
top: 0.375em;
left: 1.25em;
width: 0.5em;
height: 0.5em;
}
.hamster__nose {
background: hsl(0,90%,75%);
border-radius: 35% 65% 85% 15% / 70% 50% 50% 30%;
top: 0.75em;
left: 0;
width: 0.2em;
height: 0.25em;
}
.hamster__body {
animation: hamsterBody var(--dur) ease-in-out infinite;
background: hsl(30,90%,90%);
border-radius: 50% 30% 50% 30% / 15% 60% 40% 40%;
box-shadow: 0.1em 0.75em 0 hsl(30,90%,55%) inset,
0.15em -0.5em 0 hsl(30,90%,80%) inset;
top: 0.25em;
left: 1.5em;
width: 4.5em;
height: 3em;
transform-origin: 17% 50%;
transform-style: preserve-3d;
}
.hamster__limb--fr,
.hamster__limb--fl {
clip-path: polygon(0 0,100% 0,70% 80%,60% 100%,0% 100%,40% 80%);
top: 2em;
left: 0.5em;
width: 1em;
height: 1.5em;
transform-origin: 50% 0;
}
.hamster__limb--fr {
animation: hamsterFRLimb var(--dur) linear infinite;
background: linear-gradient(hsl(30,90%,80%) 80%,hsl(0,90%,75%) 80%);
transform: rotate(15deg) translateZ(-1px);
}
.hamster__limb--fl {
animation: hamsterFLLimb var(--dur) linear infinite;
background: linear-gradient(hsl(30,90%,90%) 80%,hsl(0,90%,85%) 80%);
transform: rotate(15deg);
}
.hamster__limb--br,
.hamster__limb--bl {
border-radius: 0.75em 0.75em 0 0;
clip-path: polygon(0 0,100% 0,100% 30%,70% 90%,70% 100%,30% 100%,40% 90%,0% 30%);
top: 1em;
left: 2.8em;
width: 1.5em;
height: 2.5em;
transform-origin: 50% 30%;
}
.hamster__limb--br {
animation: hamsterBRLimb var(--dur) linear infinite;
background: linear-gradient(hsl(30,90%,80%) 90%,hsl(0,90%,75%) 90%);
transform: rotate(-25deg) translateZ(-1px);
}
.hamster__limb--bl {
animation: hamsterBLLimb var(--dur) linear infinite;
background: linear-gradient(hsl(30,90%,90%) 90%,hsl(0,90%,85%) 90%);
transform: rotate(-25deg);
}
.hamster__tail {
animation: hamsterTail var(--dur) linear infinite;
background: hsl(0,90%,85%);
border-radius: 0.25em 50% 50% 0.25em;
box-shadow: 0 -0.2em 0 hsl(0,90%,75%) inset;
top: 1.5em;
right: -0.5em;
width: 1em;
height: 0.5em;
transform: rotate(30deg) translateZ(-1px);
transform-origin: 0.25em 0.25em;
}
.spoke {
animation: spoke var(--dur) linear infinite;
background: radial-gradient(100% 100% at center,hsl(0,0%,60%) 4.8%,hsla(0,0%,60%,0) 5%),
linear-gradient(hsla(0,0%,55%,0) 46.9%,hsl(0,0%,65%) 47% 52.9%,hsla(0,0%,65%,0) 53%) 50% 50% / 99% 99% no-repeat;
}
@keyframes hamster {
from, to { transform: rotate(4deg) translate(-0.8em,1.85em); }
50% { transform: rotate(0) translate(-0.8em,1.85em); }
}
@keyframes hamsterHead {
from, 25%, 50%, 75%, to { transform: rotate(0); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(8deg); }
}
@keyframes hamsterEye {
from, 90%, to { transform: scaleY(1); }
95% { transform: scaleY(0); }
}
@keyframes hamsterEar {
from, 25%, 50%, 75%, to { transform: rotate(0); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(12deg); }
}
@keyframes hamsterBody {
from, 25%, 50%, 75%, to { transform: rotate(0); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(-2deg); }
}
@keyframes hamsterFRLimb {
from, 25%, 50%, 75%, to { transform: rotate(50deg) translateZ(-1px); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(-30deg) translateZ(-1px); }
}
@keyframes hamsterFLLimb {
from, 25%, 50%, 75%, to { transform: rotate(-30deg); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(50deg); }
}
@keyframes hamsterBRLimb {
from, 25%, 50%, 75%, to { transform: rotate(-60deg) translateZ(-1px); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(20deg) translateZ(-1px); }
}
@keyframes hamsterBLLimb {
from, 25%, 50%, 75%, to { transform: rotate(20deg); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(-60deg); }
}
@keyframes hamsterTail {
from, 25%, 50%, 75%, to { transform: rotate(30deg) translateZ(-1px); }
12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(10deg) translateZ(-1px); }
}
@keyframes spoke {
from { transform: rotate(0); }
to { transform: rotate(-1turn); }
}