/* ============================================================ * Tailwind v4 source — built into assets/static/css/app.css * ------------------------------------------------------------ * Stack: Tailwind CSS v4 + Alpine.js v3 + PenguinUI components * (https://www.penguinui.com). PenguinUI is copy-paste: paste a * component's markup into a template and it picks up the design * tokens defined in the @theme block below. * * Build: make css (one-off, minified) * make css-watch (rebuild on change while developing) * * The compiled output assets/static/css/app.css IS committed, so * the Docker image / loco static server need no Node at runtime. * ============================================================ */ @import "tailwindcss"; /* Scan every template so used utility classes are emitted. */ @source "../views"; /* penguinui-components/ is the read-only vendored PenguinUI library * (reference only — never {% include %}'d, never edited). Tailwind v4 * auto-detects it from the project root, so exclude it explicitly or * its 177 files bloat the build with classes we never render. */ @source not "../../penguinui-components"; /* PenguinUI toggles dark styles with a `dark:` variant. This app * already sets (see base.html), so * key the variant off that attribute instead of the OS setting. */ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); /* === PenguinUI design tokens ================================ * "Modern" starting palette. Swap any line for another Tailwind * color (e.g. --color-primary: var(--color-emerald-600)) or grab * a ready-made theme from https://www.penguinui.com/theme. * Components reference these as bg-primary, text-on-surface, * dark:bg-surface-dark, border-outline, etc. * ============================================================ */ @theme { /* light mode — Catppuccin Latte (https://catppuccin.com/palette) * Base #eff1f5, Mantle #e6e9ef, Surface1 #bcc0cc, Subtext1 #5c5f77, * Subtext0 #6c6f85, Text #4c4f69, Blue #1e66f5. */ --color-surface: #eff1f5; /* Base */ --color-surface-alt: #e6e9ef; /* Mantle */ --color-on-surface: #5c5f77; /* Subtext1 */ --color-on-surface-strong: #4c4f69; /* Text */ --color-primary: #1e66f5; /* Blue */ --color-on-primary: #eff1f5; /* Base */ --color-secondary: #6c6f85; /* Subtext0 */ --color-on-secondary: #eff1f5; /* Base */ --color-outline: #bcc0cc; /* Surface1 */ --color-outline-strong: #4c4f69; /* Text */ /* dark mode — Gruvbox dark palette (https://github.com/morhetz/gruvbox) * bg0 #282828, bg1 #3c3836, bg2 #504945, fg0 #fbf1c7, fg1 #ebdbb2, * fg2 #d5c4a1, fg3 #bdae93, bright blue #83a598, bg0_h #1d2021. */ --color-surface-dark: #282828; /* bg0 */ --color-surface-dark-alt: #3c3836; /* bg1 */ --color-on-surface-dark: #ebdbb2; /* fg1 */ --color-on-surface-dark-strong: #fbf1c7; /* fg0 */ --color-primary-dark: #83a598; /* bright blue */ --color-on-primary-dark: #1d2021; /* bg0_h */ --color-secondary-dark: #d5c4a1; /* fg2 */ --color-on-secondary-dark: #1d2021; /* bg0_h */ --color-outline-dark: #504945; /* bg2 */ --color-outline-dark-strong: #bdae93; /* fg3 */ /* shared status colors (same in both modes) */ --color-info: var(--color-sky-500); --color-on-info: var(--color-white); --color-success: var(--color-green-600); --color-on-success: var(--color-white); --color-warning: var(--color-amber-500); --color-on-warning: var(--color-white); --color-danger: var(--color-red-600); --color-on-danger: var(--color-white); /* shared design tokens */ --radius-radius: 0.375rem; } /* Hide Alpine x-cloak elements until Alpine initializes. */ [x-cloak] { display: none !important; } /* === Rich text editor (Quill "snow") ======================= * Vendored Quill (assets/static/vendor/quill) drives the admin * product short/long description fields. Stock snow already suits * the light theme; the admin panel is locked to data-theme="dark", * so the rules below repaint the toolbar/editor for dark. Editor * height is per-instance via the --rich-min-height custom prop set * by the ui::rich_editor macro. * ============================================================ */ .rich-editor .ql-editor { min-height: var(--rich-min-height, 12rem); font-size: 0.95rem; line-height: 1.6; } .ql-editor.ql-blank::before { font-style: normal; } [data-theme="dark"] .rich-editor { background: var(--color-surface-dark-alt); } [data-theme="dark"] .ql-toolbar.ql-snow, [data-theme="dark"] .ql-container.ql-snow { border-color: var(--color-outline-dark); } [data-theme="dark"] .ql-toolbar.ql-snow { background: var(--color-surface-dark); } [data-theme="dark"] .ql-container.ql-snow { color: var(--color-on-surface-dark); } [data-theme="dark"] .ql-snow .ql-stroke, [data-theme="dark"] .ql-snow .ql-stroke-miter { stroke: var(--color-on-surface-dark); } [data-theme="dark"] .ql-snow .ql-fill, [data-theme="dark"] .ql-snow .ql-stroke.ql-fill { fill: var(--color-on-surface-dark); } [data-theme="dark"] .ql-snow .ql-picker { color: var(--color-on-surface-dark); } [data-theme="dark"] .ql-snow .ql-picker-options { background: var(--color-surface-dark); border-color: var(--color-outline-dark); } /* active / hover toolbar state -> primary accent */ [data-theme="dark"] .ql-snow.ql-toolbar button:hover, [data-theme="dark"] .ql-snow.ql-toolbar button.ql-active, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-label:hover, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-label.ql-active, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-item:hover, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-item.ql-selected { color: var(--color-primary-dark); } [data-theme="dark"] .ql-snow.ql-toolbar button:hover .ql-stroke, [data-theme="dark"] .ql-snow.ql-toolbar button.ql-active .ql-stroke, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, [data-theme="dark"] .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke { stroke: var(--color-primary-dark); } [data-theme="dark"] .ql-snow.ql-toolbar button:hover .ql-fill, [data-theme="dark"] .ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--color-primary-dark); } [data-theme="dark"] .ql-snow .ql-tooltip { background-color: var(--color-surface-dark); border-color: var(--color-outline-dark); color: var(--color-on-surface-dark); box-shadow: 0 2px 8px rgb(0 0 0 / 0.45); } [data-theme="dark"] .ql-snow .ql-tooltip input[type=text] { background: var(--color-surface-dark-alt); border-color: var(--color-outline-dark); color: var(--color-on-surface-dark); } [data-theme="dark"] .ql-snow .ql-editor a, [data-theme="dark"] .ql-snow .ql-tooltip a { color: var(--color-primary-dark); } /* Image size controls under the editor. */ .rich-image-size-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; } .rich-image-size-controls.hidden { display: none; } .rich-image-size-controls button { border: 1px solid var(--color-outline); border-radius: var(--radius-radius); padding: 0.3rem 0.65rem; line-height: 1; font-size: 0.8rem; } .rich-image-size-controls button:hover { border-color: var(--color-primary); color: var(--color-primary); } [data-theme="dark"] .rich-image-size-controls button { border-color: var(--color-outline-dark); } [data-theme="dark"] .rich-image-size-controls button:hover { border-color: var(--color-primary-dark); color: var(--color-primary-dark); } /* Image sizing classes shared by the editor and rendered output. */ .rich-editor img, .rich-content img { display: block; max-width: 100%; height: auto; margin: 1rem auto; border-radius: var(--radius-radius); } .rich-editor img { cursor: pointer; } .rich-image-small { width: min(100%, 18rem); } .rich-image-medium { width: min(100%, 34rem); } .rich-image-full { width: 100%; } /* === Rendered rich content (storefront product description) = * Inherits text color from context so it works in both themes; * only structural spacing + link/heading treatment is set here. */ .rich-content { line-height: 1.7; } .rich-content h2 { margin: 1.25rem 0 0.6rem; font-size: 1.3rem; font-weight: 700; } .rich-content h3 { margin: 1rem 0 0.5rem; font-size: 1.1rem; font-weight: 700; } .rich-content p, .rich-content ul, .rich-content ol { margin: 0.6rem 0; } .rich-content ul { list-style: disc; padding-left: 1.4rem; } .rich-content ol { list-style: decimal; padding-left: 1.4rem; } .rich-content a { color: var(--color-primary); text-decoration: underline; } [data-theme="dark"] .rich-content a { color: var(--color-primary-dark); } .rich-content :first-child { margin-top: 0; } .rich-content :last-child { margin-bottom: 0; } /* Compact rich blurb for product cards: neutralize block spacing so the * line-clamp truncation stays tidy regardless of the authored markup. */ .rich-blurb :where(p, ul, ol, h2, h3) { margin: 0; } .rich-blurb :where(ul, ol) { padding-left: 1.1rem; }