@import "tailwindcss"; /* Petal design tokens — soft, warm, bubbly stationery aesthetic. Defined in @theme so they generate Tailwind utilities (bg-bg, text-plum, rounded-card, font-ui, etc.). See petal-spec.md → Design System. */ @theme { /* Palette */ --color-bg: #FDF6F0; /* warm cream canvas */ --color-surface: #FFFFFF; /* card/panel surfaces */ --color-surface-alt: #FFF0F5; /* rose-tinted alt surface */ --color-border: #F0E0EB; /* soft pink-grey border */ --color-plum: #3D2E39; /* warm dark plum text (not harsh black) */ --color-muted: #9B8CA3; /* muted lavender-grey */ --color-accent: #E8A0BF; /* soft rose — primary interactive */ --color-accent-hover: #D98AAF;/* deeper rose on hover */ /* Suggestion type colors */ --color-mint: #A8D8C8; /* grammar */ --color-peach: #F4B8A0; /* phrasing */ --color-lavender: #C5B4E8; /* idiom */ --color-sky: #A8CCE8; /* clarity */ --color-honey: #CE9B4F; /* voice */ --color-blossom: #E59ABF; /* collocation — warm blossom pink */ --color-sage: #B7C7B9; /* mechanics — calm sage, a gentle tidy-up nudge */ --color-success: #8FCFA8; /* saved / accepted */ /* Typography */ --font-ui: "Nunito", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; --font-body: "Lora", Georgia, serif; --font-mono: "JetBrains Mono", ui-monospace, monospace; /* Shape language */ --radius-card: 16px; --radius-modal: 24px; --radius-pill: 999px; --radius-input: 12px; /* Soft, warm, lifted shadow */ --shadow-soft: 0 4px 20px rgba(180, 130, 160, 0.12); } /* Night mode — toggled by adding `petal-night` to (useNightMode) once the clock passes ~11pm. We only re-point the palette tokens; every Tailwind color utility reads them via var(), so the whole UI shifts to a calm, dim, moonlit theme without touching component markup. Pastel accent/type colors are kept — they read beautifully on the dark plum ground. Falling petals become stars. */ html.petal-night { --color-bg: #14111E; /* deep night plum-indigo */ --color-surface: #211C30; /* raised panels */ --color-surface-alt: #2A2440; /* alt surface / hover wash */ --color-border: #38304E; /* dim lavender border */ --color-plum: #ECE3F2; /* ink → soft moonlit lavender-white */ --color-muted: #9F93B8; /* muted lavender-grey, lifted for contrast */ --color-accent: #E8A0BF; /* rose still primary — pops on dark */ --color-accent-hover: #F2B7D2;/* lighter on hover against the dark */ --shadow-soft: 0 6px 28px rgba(0, 0, 0, 0.45); } html, body, #root { height: 100%; } body { margin: 0; background: var(--color-bg); color: var(--color-plum); font-family: var(--font-ui); -webkit-font-smoothing: antialiased; /* Gentle dusk/dawn fade when night mode flips. */ transition: background 600ms ease, color 600ms ease; } /* Gentle, consistent motion across interactive elements */ button, a, input { transition: all 200ms ease; } /* Editor body — serif Lora for prose, warm plum ink, roomy line height. The title and headings use the Nunito UI face for contrast. */ .petal-prose { font-family: var(--font-body); font-size: 1.125rem; line-height: 1.75; color: var(--color-plum); } .petal-prose > * + * { margin-top: 0.9em; } .petal-prose h1, .petal-prose h2, .petal-prose h3 { font-family: var(--font-ui); font-weight: 800; line-height: 1.3; } .petal-prose h1 { font-size: 1.6em; } .petal-prose h2 { font-size: 1.3em; } .petal-prose h3 { font-size: 1.1em; } .petal-prose ul, .petal-prose ol { padding-left: 1.4em; } .petal-prose ul { list-style: disc; } .petal-prose ol { list-style: decimal; } .petal-prose a { color: var(--color-accent-hover); text-decoration: underline; } .petal-prose blockquote { border-left: 3px solid var(--color-border); padding-left: 1em; color: var(--color-muted); font-style: italic; } .petal-prose code { font-family: var(--font-mono); font-size: 0.9em; background: var(--color-surface-alt); padding: 0.1em 0.35em; border-radius: 6px; } /* Top formatting bar: a single line at rest so it stays slim, expanding to reveal every control on hover (or while a control inside it has focus, e.g. the link input or an open popover). The clipped right edge fades out as a hint that more is available. */ .petal-toolbar { flex-wrap: nowrap; overflow: hidden; max-width: 100%; -webkit-mask-image: linear-gradient(to right, #000 90%, transparent 100%); mask-image: linear-gradient(to right, #000 90%, transparent 100%); } .petal-toolbar:hover, .petal-toolbar:focus-within { flex-wrap: wrap; overflow: visible; -webkit-mask-image: none; mask-image: none; } /* Highlighter mark — inline background swatch behind the text. */ .petal-prose mark { border-radius: 4px; padding: 0.05em 0.15em; /* color comes from the inline style the Highlight extension writes */ } /* Inserted images: rounded, soft-shadowed, never wider than the column. The selected-node ring matches the rest of the rose theme. Scoped to the editor's own (not a class) since Tiptap doesn't always carry the configured class through to the rendered node. */ .petal-prose img { max-width: 100%; height: auto; border-radius: var(--radius-card); box-shadow: var(--shadow-soft); } .petal-prose img.ProseMirror-selectednode { outline: 2px solid var(--color-accent); outline-offset: 2px; } /* Tables: soft pink grid, rounded outer corners, a tinted header row. Targeted by element within the editor — Tiptap renders the without our configured class, so a `.petal-table` selector would never match. The wrapper Tiptap adds (.tableWrapper) handles horizontal overflow scroll. */ .petal-prose .tableWrapper { overflow-x: auto; } .petal-prose table { border-collapse: collapse; width: 100%; margin: 0.4em 0; overflow: hidden; border-radius: var(--radius-input); border: 1px solid var(--color-border); table-layout: fixed; } .petal-prose td, .petal-prose th { border: 1px solid var(--color-border); padding: 0.4em 0.6em; vertical-align: top; position: relative; min-width: 3em; } .petal-prose th { background: var(--color-surface-alt); font-weight: 700; text-align: left; } /* The cell being edited / the active selection inside a table. */ .petal-prose .selectedCell::after { content: ''; position: absolute; inset: 0; background: var(--color-surface-alt); opacity: 0.5; pointer-events: none; } /* The column-resize handle Tiptap draws between columns. */ .petal-prose .column-resize-handle { position: absolute; right: -2px; top: 0; bottom: -2px; width: 4px; background: var(--color-accent); pointer-events: none; } /* Placeholder shown on the empty first paragraph (Tiptap Placeholder ext). */ .petal-prose p.is-editor-empty:first-child::before { content: attr(data-placeholder); color: var(--color-muted); float: left; height: 0; pointer-events: none; } /* --- Suggestion decorations ------------------------------------------------- Inline highlights anchored by string at render time (not stored marks). Each type gets a soft underline in its palette color; hovering opens its card. */ .petal-suggestion { border-bottom: 2px solid transparent; border-radius: 2px; cursor: pointer; transition: background 200ms ease; /* gentle fade + slight upward float as decorations appear */ animation: petal-suggestion-in 260ms ease both; } .petal-suggestion:hover { background: var(--color-surface-alt); } .petal-suggestion-grammar { border-bottom-color: var(--color-mint); } .petal-suggestion-phrasing { border-bottom-color: var(--color-peach); } .petal-suggestion-idiom { border-bottom-color: var(--color-lavender); } .petal-suggestion-clarity { border-bottom-color: var(--color-sky); } .petal-suggestion-voice { border-bottom-color: var(--color-honey); } .petal-suggestion-collocation { border-bottom-color: var(--color-blossom); } .petal-suggestion-mechanics { border-bottom-color: var(--color-sage); } @keyframes petal-suggestion-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .petal-suggestion-card { animation: petal-suggestion-in 200ms ease both; } /* Text emphasized from its margin card (or a hover) — a soft wash so the card↔text link reads at a glance, both directions. */ .petal-suggestion-active { background: var(--color-surface-alt); box-shadow: 0 0 0 3px var(--color-surface-alt); border-radius: 3px; } /* --- Suggestion margin rail ------------------------------------------------- The right-hand "comment column": every outstanding suggestion as a card, vertically aligned to the text it flags, so the whole queue is visible at a glance instead of one-highlight-at-a-time on hover. It floats in the whitespace just right of the editor column and is only mounted when there's room for it (EditorCore measures the gap). Cards are absolutely positioned by a resolved top (anchor + collision avoidance), so the container only anchors the column. */ .petal-rail { position: absolute; top: 0; left: 100%; margin-left: 32px; width: 300px; /* Below the companion mascot (z-40): where a stacked card reaches the bottom-right corner it simply tucks behind the sleeping cat. */ z-index: 10; } .petal-rail-card { position: absolute; left: 0; right: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-left: 3px solid var(--color-border); /* type color set inline */ border-radius: var(--radius-card); box-shadow: var(--shadow-soft); padding: 0.7rem 0.85rem; /* `top` eases so re-stacking (accept/dismiss/edit) glides instead of jumping. The entrance uses fill `backwards` so its translateY doesn't linger and fight the active-state transform once it's done. */ transition: top 240ms cubic-bezier(0.2, 0.7, 0.3, 1), box-shadow 200ms ease, transform 200ms ease, border-color 200ms ease; animation: petal-suggestion-in 220ms ease backwards; } .petal-rail-card-active { transform: translateX(-5px); box-shadow: 0 8px 26px rgba(180, 130, 160, 0.24); border-top-color: var(--color-accent); border-right-color: var(--color-accent); border-bottom-color: var(--color-accent); } .petal-rail-x { color: var(--color-muted); font-size: 0.72rem; line-height: 1; padding: 2px 5px; border-radius: var(--radius-pill); } .petal-rail-x:hover { background: var(--color-surface-alt); color: var(--color-plum); } /* --- Find & Replace --------------------------------------------------------- In-document search (Ctrl/Cmd+F). Every match gets a soft honey wash; the current match is brighter with a rose ring so it stands out as you step through. Decorations, like every other highlight layer here. */ .petal-find-match { background: var(--color-highlight, #fff1a8); border-radius: 3px; box-shadow: 0 0 0 1px var(--color-border); } .petal-find-match-active { background: var(--color-peach); box-shadow: 0 0 0 2px var(--color-accent); } /* --- Spell check ------------------------------------------------------------ Browser-side nspell flags misspellings with a soft rose wavy underline (a gentler take on the classic red squiggle, to fit the pastel palette). Like the suggestion layer these are ProseMirror decorations, never stored marks. Clicking a flagged word opens its MisspellCard with corrections. */ .petal-misspelling { cursor: pointer; text-decoration: underline wavy var(--color-accent); text-decoration-skip-ink: none; text-underline-offset: 2px; } .petal-misspelling:hover { background: var(--color-surface-alt); border-radius: 2px; } .petal-misspell-card { animation: petal-suggestion-in 200ms ease both; } /* --- Word lookup popover ---------------------------------------------------- Right-click a word for its dictionary definition and synonyms. Same soft card treatment as the spelling popover; synonym pills swap the word on click. */ .petal-word-card { animation: petal-suggestion-in 200ms ease both; } /* --- ESL superpowers (Phase 9) ---------------------------------------------- Inline Chinese gloss on hover (a small dark tooltip under the word), and the selection rewrite bubble + its preview card. All share the soft pop-in. The gloss tip fades in a touch faster — it's a fleeting reading aid, not a panel. */ .petal-gloss-tip { animation: petal-suggestion-in 140ms ease both; } .petal-selection-bubble, .petal-rewrite-card { animation: petal-suggestion-in 200ms ease both; } /* --- Accept confetti -------------------------------------------------------- A tiny CSS-only burst played where a suggestion is accepted: four colored dots spray up-and-out, then fade. No JS animation — each dot reads its direction from --dx/--dy custom properties set inline. (Spec → Signature.) */ .petal-confetti { width: 0; height: 0; } .petal-confetti-dot { position: absolute; left: 0; top: 0; width: 7px; height: 7px; border-radius: var(--radius-pill); animation: petal-confetti 680ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards; } @keyframes petal-confetti { 0% { transform: translate(0, 0) scale(0.3); opacity: 0; } 18% { opacity: 1; } 100% { transform: translate(var(--dx), var(--dy)) scale(1); opacity: 0; } } /* --- Distraction-free mode --------------------------------------------------- The doc-list sidebar slides left and collapses to zero width; the editor canvas (centered, max-width) re-centers into the full pane. Width + transform animate together for a smooth slide. (Spec → Distraction-free mode.) */ .petal-sidebar { width: 260px; overflow: hidden; transition: width 280ms ease, transform 280ms ease, opacity 200ms ease; } .petal-sidebar-hidden { width: 0; transform: translateX(-24px); opacity: 0; pointer-events: none; } /* --- Companion kitten ------------------------------------------------------- The cozy corner mascot. Gently bobs while awake, settles and sways slowly while napping; its speech bubble pops in; little zzz drift up when asleep. */ .petal-companion { /* Mascot size scales with the viewport width: ~original on a laptop, up to ~2× on a large desktop. Tune the middle (vw) term to taste. */ --petal-companion-size: clamp(10rem, 17vw, 20rem); animation: petal-bob 3.2s ease-in-out infinite; transition: transform 200ms ease; } /* The Lottie art sits inside the round badge with a little breathing room. */ .petal-companion-art { width: 88%; height: 88%; } .petal-companion:hover { transform: translateY(-2px) scale(1.04); } .petal-companion-sleep { animation: petal-bob-slow 5s ease-in-out infinite; } @keyframes petal-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } } @keyframes petal-bob-slow { 0%, 100% { transform: translateY(0) rotate(-1deg); } 50% { transform: translateY(-2px) rotate(1deg); } } .petal-bubble { animation: petal-bubble-in 240ms cubic-bezier(0.2, 0.8, 0.3, 1.2) both; transform-origin: bottom right; } @keyframes petal-bubble-in { from { opacity: 0; transform: translateY(6px) scale(0.92); } to { opacity: 1; transform: translateY(0) scale(1); } } .petal-zzz { top: calc(var(--petal-companion-size) * 0.04); right: calc(var(--petal-companion-size) * 0.1); font-weight: 800; font-size: calc(var(--petal-companion-size) * 0.12); animation: petal-zzz 2.4s ease-in-out infinite; } @keyframes petal-zzz { 0% { opacity: 0; transform: translateY(0) scale(0.8); } 30% { opacity: 0.9; } 100% { opacity: 0; transform: translateY(-14px) scale(1.1); } } /* "New version available" banner — drifts down from the top like a petal. */ .petal-update { animation: petal-drop 360ms cubic-bezier(0.2, 0.8, 0.3, 1.2) both; } @keyframes petal-drop { from { opacity: 0; transform: translateY(-14px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } /* Blinking caret in the Ask Petal bubble while awaiting the first token. */ .petal-chat-caret { animation: petal-breathe 1s ease-in-out infinite; color: var(--color-accent); } /* Breathing rose dot shown in the StatusBar while a checkpoint runs. */ .petal-checkpoint-dot { animation: petal-breathe 2s ease-in-out infinite; } @keyframes petal-breathe { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } } /* --- Organization & search (Phase 10) -------------------------------------- Tag chips, the search results list, and per-row affordances. The row actions (tag / delete) fade in on hover for pointer users but are always visible on touch (no hover) — see the coarse-pointer block below. */ .petal-tag-chip { transition: background 160ms ease, color 160ms ease, transform 160ms ease; } .petal-search-results { animation: petal-suggestion-in 160ms ease both; } .petal-tag-picker { animation: petal-suggestion-in 160ms ease both; } .petal-row-action { opacity: 0; transition: opacity 160ms ease, background 160ms ease; } .group:hover .petal-row-action, .petal-row-action:focus-visible { opacity: 1; } .petal-row-action:hover { background: var(--color-surface-alt); } /* Clamp a search snippet to two lines. */ .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* --- Touch & tablet polish -------------------------------------------------- On coarse pointers (tablets, phones) there's no hover, so make tap targets comfortable and reveal affordances that would otherwise be hover-only. */ @media (pointer: coarse) { .petal-tap { min-height: 44px; } .petal-tap-sm { min-height: 36px; } /* Row actions can't rely on hover — keep them visible and roomy. */ .petal-row-action { opacity: 1; height: 36px; width: 36px; } /* Bigger, easier-to-hit suggestion/word/tag pills. */ .petal-tag-chip { padding-top: 0.25rem; padding-bottom: 0.25rem; } } /* --- Responsive sidebar (narrow screens) ------------------------------------ Below the tablet breakpoint the sidebar becomes an overlay drawer toggled by a hamburger in the header, instead of a permanent column. A scrim sits behind it. On wide screens the toggle + scrim are hidden and the sidebar is in-flow. */ .petal-sidebar-toggle { display: none; } .petal-scrim { display: none; } @media (max-width: 768px) { .petal-sidebar-toggle { display: inline-flex; } .petal-sidebar { position: fixed; top: 48px; /* below the 12-height header */ bottom: 0; left: 0; z-index: 30; background: var(--color-bg); box-shadow: var(--shadow-soft); } /* On mobile the sidebar is hidden by default; the drawer-open class slides it in. (Distraction-free's petal-sidebar-hidden still wins to keep it closed.) */ .petal-sidebar:not(.petal-drawer-open) { width: 0; transform: translateX(-24px); opacity: 0; pointer-events: none; } .petal-scrim.petal-scrim-show { display: block; position: fixed; inset: 48px 0 0 0; z-index: 20; background: rgba(61, 46, 57, 0.18); } } /* Print / Save-as-PDF: strip every bit of app chrome and editing decoration so only the title and the writing itself reach the page. This is Petal's PDF path — it uses the browser's own fonts, so CJK renders correctly with no server-side font embedding. */ /* --- Ambient falling petals ------------------------------------------------- The WebGL petal layer is a fixed, non-interactive overlay that drifts soft blossoms over the page. Kept gentle (low opacity) so it never competes with the writing. It hides itself in print and is skipped entirely for users who prefer reduced motion (the component renders nothing in that case). */ .petal-fall { opacity: 1; } /* Honor reduced-motion globally: still the looping ambient animations and drop the petal layer. One-shot entrance/feedback animations are left intact. */ @media (prefers-reduced-motion: reduce) { .petal-fall { display: none !important; } .petal-bob, .petal-bob-slow, .petal-zzz, .petal-checkpoint-dot { animation: none !important; } } @media print { .petal-fall, .petal-no-print, .petal-sidebar, .petal-suggestion-card, .petal-misspell-card, .petal-gloss-tip, .petal-selection-bubble, .petal-rewrite-card, .petal-confetti, .petal-companion { display: none !important; } html, body, #root { height: auto !important; overflow: visible !important; background: #fff !important; } /* Drop the in-editor highlight underlines/tints — they're guidance, not ink. */ .petal-suggestion, .petal-misspelling { background: none !important; text-decoration: none !important; border-bottom: none !important; } /* Let the writing use the full page width. */ .max-w-\[720px\] { max-width: none !important; } }