From b7ec41b0cfafc0697abbecf45c0fe455c5296448 Mon Sep 17 00:00:00 2001 From: jamey Date: Sun, 1 Mar 2026 17:15:25 +0000 Subject: [PATCH] refactor admin CSS: replace utility classes with semantic styles Replace Tailwind utility soup across admin templates with semantic CSS classes. Add layout primitives (stack, row, cluster, grid), extract JS transition helpers into transitions.css, and refactor core_components, layouts, settings, newsletter, order_show, providers, and theme editor templates. Utility occurrences reduced from 290+ to 127 across admin files. All 1679 tests pass. Co-Authored-By: Claude Opus 4.6 --- assets/css/admin.css | 4 +- assets/css/admin/components.css | 1203 +++++++++ assets/css/admin/layout.css | 36 + assets/css/admin/transitions.css | 80 + .../components/core_components.ex | 25 +- lib/berrypod_web/components/layouts.ex | 12 +- .../components/layouts/admin.html.heex | 18 +- lib/berrypod_web/live/admin/newsletter.ex | 154 +- lib/berrypod_web/live/admin/order_show.ex | 128 +- .../live/admin/providers/form.html.heex | 30 +- .../live/admin/providers/index.html.heex | 46 +- lib/berrypod_web/live/admin/settings.ex | 219 +- .../live/admin/theme/index.html.heex | 2349 ++++++++--------- 13 files changed, 2661 insertions(+), 1643 deletions(-) create mode 100644 assets/css/admin/layout.css create mode 100644 assets/css/admin/transitions.css diff --git a/assets/css/admin.css b/assets/css/admin.css index b0da78a..d871d91 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -8,9 +8,11 @@ @import "./theme-layer2-attributes.css"; @import "./theme-semantic.css"; -/* Admin components, icons, and utilities */ +/* Admin components, layout, icons, and transitions */ @import "./admin/components.css"; +@import "./admin/layout.css"; @import "./admin/icons.css"; +@import "./admin/transitions.css"; @import "./admin/utilities.css"; /* LiveView loading state variants */ diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 447d384..ff4aa5a 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -8,6 +8,7 @@ display: grid; grid-template-columns: 0 1fr; height: 100%; + min-height: 100vh; } .admin-layout-toggle { @@ -222,6 +223,34 @@ border-radius: 9999px; } +/* ── Page header ── */ + +.admin-header { + padding-bottom: 1rem; +} + +.admin-header-with-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; +} + +.admin-header-title { + font-size: 1.125rem; + font-weight: 600; + line-height: 2rem; +} + +.admin-header-subtitle { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); +} + +.admin-header-actions { + flex: none; +} + /* ── Cards ── */ .admin-card { @@ -249,6 +278,134 @@ margin-top: 0.75rem; } +/* Card layout helpers */ +.admin-card-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; +} + +.admin-card-content { + flex: 1; + min-width: 0; +} + +.admin-card-subtitle { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); + margin-top: 0.25rem; +} + +.admin-card-meta { + display: flex; + flex-wrap: wrap; + column-gap: 1rem; + row-gap: 0.25rem; + margin-top: 0.5rem; + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +.admin-card-toolbar { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* ── Empty states ── */ + +.admin-empty-state { + text-align: center; + padding: 3rem 1rem; +} + +.admin-empty-state-icon { + width: 4rem; + height: 4rem; + margin-inline: auto; + margin-bottom: 1rem; + color: color-mix(in oklch, var(--t-text-primary) 30%, transparent); +} + +.admin-empty-state-title { + font-size: 1.25rem; + font-weight: 500; +} + +.admin-empty-state-text { + margin-top: 0.5rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + max-width: 28rem; + margin-inline: auto; +} + +.admin-empty-state-actions { + display: flex; + justify-content: center; + gap: 0.75rem; + margin-top: 1.5rem; +} + +/* ── Callout / info box ── */ + +.admin-callout { + border-radius: 0.5rem; + background: var(--t-surface-sunken); + padding: 1rem; + margin-bottom: 1.5rem; + font-size: 0.875rem; +} + +.admin-callout-title { + font-weight: 500; + margin-bottom: 0.5rem; +} + +.admin-callout-list { + list-style: decimal inside; + + & li + li { + margin-top: 0.25rem; + } +} + +/* ── Inline status indicators ── */ + +.admin-status-success { + color: var(--t-status-success, oklch(0.65 0.19 145)); + display: flex; + align-items: center; + gap: 0.25rem; +} + +.admin-status-error { + color: var(--t-status-error, #dc2626); + display: flex; + align-items: center; + gap: 0.25rem; +} + +/* ── Form layout helpers ── */ + +.admin-form-narrow { + max-width: 36rem; + margin-top: 1.5rem; +} + +.admin-form-actions { + display: flex; + gap: 0.5rem; + margin-top: 1.5rem; +} + +.admin-inline-group { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + /* ── Tables ── */ .admin-table-wrap { @@ -287,6 +444,16 @@ background-color: color-mix(in oklch, var(--t-surface-sunken) 30%, transparent); } +.admin-table-actions { + width: 0; + font-weight: 600; +} + +.admin-table-actions-row { + display: flex; + gap: 1rem; +} + /* ── Forms ── */ .admin-fieldset { @@ -419,6 +586,24 @@ border-radius: 0.5rem; font-size: 0.875rem; line-height: 1.5; + width: 20rem; + max-width: 20rem; + overflow-wrap: break-word; +} + +@media (min-width: 640px) { + .admin-alert { + width: 24rem; + max-width: 24rem; + } +} + +.admin-alert-title { + font-weight: 600; +} + +.admin-alert-spacer { + flex: 1; } .admin-alert-info { @@ -630,9 +815,18 @@ flex: 1; } +.admin-list-title { + font-weight: 700; +} + /* ── Form error text ── */ .admin-error { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.375rem; + font-size: 0.875rem; color: var(--t-status-error); } @@ -732,6 +926,299 @@ cursor: crosshair; } +/* ── Settings sections ── */ + +.admin-settings { + max-width: 42rem; +} + +.admin-section { + margin-top: 2.5rem; +} + +.admin-section-header { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.admin-section-title { + font-size: 1.125rem; + font-weight: 600; +} + +.admin-section-desc { + margin-top: 0.5rem; + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +.admin-section-body { + margin-top: 1rem; +} + +/* ── Status pills (inline badges in section headers) ── */ + +.admin-status-pill { + display: inline-flex; + align-items: center; + gap: 0.25rem; + border-radius: 9999px; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + box-shadow: inset 0 0 0 1px var(--_pill-ring); +} + +.admin-status-pill-green { + --_pill-ring: rgb(22 163 74 / 0.2); + background: #f0fdf4; + color: #15803d; +} + +.admin-status-pill-amber { + --_pill-ring: rgb(217 119 6 / 0.1); + background: #fffbeb; + color: #b45309; +} + +.admin-status-pill-blue { + --_pill-ring: rgb(37 99 235 / 0.2); + background: #eff6ff; + color: #1d4ed8; +} + +.admin-status-pill-red { + --_pill-ring: rgb(220 38 38 / 0.2); + background: #fef2f2; + color: #b91c1c; +} + +.admin-status-pill-purple { + --_pill-ring: rgb(147 51 234 / 0.2); + background: #faf5ff; + color: #7e22ce; +} + +.admin-status-pill-zinc { + --_pill-ring: color-mix(in oklch, var(--t-text-primary) 10%, transparent); + background: color-mix(in oklch, var(--t-surface-sunken) 50%, transparent); + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +/* ── Definition lists (key-value pairs) ── */ + +.admin-dl { + font-size: 0.875rem; +} + +.admin-dl-row { + display: flex; + gap: 0.5rem; + padding-block: 0.25rem; +} + +.admin-dl-term { + width: 7rem; + flex-shrink: 0; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +.admin-dl-value { + color: var(--t-text-primary); +} + +/* ── Tab navigation ── */ + +.admin-tabs { + display: flex; + gap: 0.5rem; + margin-top: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--t-surface-sunken); +} + +.admin-tab { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + font-weight: 500; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + + &:hover { + color: var(--t-text-primary); + border-bottom-color: var(--t-border-default); + } +} + +.admin-tab-active { + color: var(--t-text-primary); + border-bottom-color: var(--t-text-primary); +} + +.admin-tab-count { + margin-inline-start: 0.25rem; + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 40%, transparent); +} + +/* ── Status dot indicators (inline status with coloured dot) ── */ + +.admin-status-dot { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; +} + +.admin-status-dot::before { + content: ""; + width: 0.375rem; + height: 0.375rem; + border-radius: 9999px; + background: var(--_dot-color, var(--t-border-default)); +} + +.admin-status-dot-green { --_dot-color: #22c55e; color: #15803d; } +.admin-status-dot-amber { --_dot-color: #f59e0b; color: #b45309; } +.admin-status-dot-blue { --_dot-color: #3b82f6; color: #1d4ed8; } +.admin-status-dot-red { --_dot-color: #ef4444; color: #b91c1c; } +.admin-status-dot-muted { + --_dot-color: var(--t-border-default); + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +/* ── Stat cards (number + label) ── */ + +.admin-stat-card { + padding: 1rem; + border-radius: 0.5rem; + border: 1px solid var(--t-surface-sunken); + text-align: center; +} + +.admin-stat-value { + font-size: 1.5rem; + font-weight: 700; +} + +.admin-stat-label { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +/* ── Info box (warning/notice with ring) ── */ + +.admin-info-box { + border-radius: 0.375rem; + padding: 1rem; + font-size: 0.875rem; +} + +.admin-info-box-amber { + background: #fffbeb; + color: #92400e; + box-shadow: inset 0 0 0 1px rgb(217 119 6 / 0.1); +} + +.admin-info-box-amber pre { + margin-top: 0.5rem; + border-radius: 0.25rem; + background: #fef3c7; + padding: 0.5rem; + font-size: 0.75rem; + color: #78350f; + overflow-x: auto; +} + +/* ── Link variants ── */ + +.admin-link-danger { + font-size: 0.875rem; + color: #dc2626; + + &:hover { color: #991b1b; } +} + +.admin-link-subtle { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + + &:hover { color: var(--t-text-primary); } +} + +/* ── Toggle switch ── */ + +.admin-switch { + display: inline-flex; + align-items: center; + flex-shrink: 0; + cursor: pointer; + border-radius: 9999px; + transition: background-color 0.15s; + width: 2.75rem; + height: 1.5rem; + padding: 0.125rem; +} + +.admin-switch-on { background: #16a34a; } +.admin-switch-off { background: var(--t-border-default); } + +.admin-switch-thumb { + pointer-events: none; + display: inline-block; + width: 1.25rem; + height: 1.25rem; + border-radius: 9999px; + background: white; + box-shadow: 0 1px 2px rgb(0 0 0 / 0.1); + transition: transform 0.15s; +} + +.admin-switch-thumb-on { transform: translateX(1.25rem); } + +/* ── App layout (auth/setup pages) ── */ + +.app-main { + padding: 3rem 1rem; +} + +@media (min-width: 640px) { + .app-main { padding: 3rem 1.5rem; } +} + +@media (min-width: 1024px) { + .app-main { padding: 3rem 2rem; } +} + +.app-container { + max-width: 32rem; + margin-inline: auto; + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* ── Theme toggle ── */ + +.theme-toggle { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + border: 2px solid var(--t-border-default); + background: var(--t-border-default); + border-radius: 9999px; +} + +.theme-toggle-btn { + display: flex; + padding: 0.5rem; + cursor: pointer; + width: 33.333%; +} + /* ── Setup page ── */ .setup-page { @@ -2636,4 +3123,720 @@ white-space: nowrap; } +/* ── Generic admin helpers ── */ + +/* Main content area with responsive padding */ +.admin-main { + flex: 1; + padding: 1rem; + + @media (min-width: 40em) { + padding: 1.5rem; + } + @media (min-width: 64em) { + padding: 2rem; + } +} + +/* Centered max-width content container */ +.admin-container { + max-width: 64rem; + margin-inline: auto; +} + +/* Sidebar header (logo + user email) */ +.admin-sidebar-header { + padding: 1rem; + border-bottom: 1px solid var(--t-border-default); +} + +/* Sidebar footer (view shop, log out) */ +.admin-sidebar-footer { + padding: 0.5rem; + border-top: 1px solid var(--t-border-default); +} + +/* Sidebar nav area */ +.admin-sidebar-nav { + flex: 1; + padding: 0.5rem; +} + +/* Brand wordmark in sidebar */ +.admin-brand { + font-size: 1.125rem; + font-weight: 700; + letter-spacing: -0.025em; +} + +/* Secondary text for user email, help text, meta info */ +.admin-text-secondary { + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +.admin-text-tertiary { + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); +} + +/* Checkbox/toggle + label inline pair */ +.admin-check-label { + display: flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; +} + +.admin-toggle-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} + +/* ── Theme editor ── */ + +.theme-layout { + display: flex; + flex-direction: column; + min-height: 100vh; + background: var(--t-surface-sunken); + + @media (min-width: 64em) { + flex-direction: row; + height: 100vh; + } +} + +.theme-sidebar { + background: var(--t-surface-base); + border-right: 1px solid var(--t-border-default); + flex-shrink: 0; + transition: width 0.3s, padding 0.3s; + + @media (min-width: 64em) { + height: 100vh; + } +} + +.theme-sidebar-expanded { + width: 100%; + overflow-y: auto; + padding: 1.5rem; + + @media (min-width: 64em) { + width: 380px; + } +} + +.theme-sidebar-collapsed { + width: 3rem; + overflow: hidden; +} + +.theme-sidebar-collapsed-inner { + height: 100%; + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 1rem; +} + +.theme-back-link { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + margin-bottom: 1rem; + + &:hover { + color: var(--t-text-primary); + } +} + +.theme-header { + margin-bottom: 1.5rem; + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} + +.theme-title { + font-size: 1.25rem; + font-weight: 600; + letter-spacing: -0.025em; + margin-bottom: 0.5rem; + color: var(--t-text-primary); +} + +.theme-subtitle { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + line-height: 1.6; +} + +/* Collapse/expand toggle button */ +.theme-collapse-btn { + padding: 0.5rem; + border-radius: 0.5rem; + transition: background-color 0.1s; + flex-shrink: 0; + + &:hover { + background: var(--t-surface-sunken); + } +} + +.theme-collapse-icon { + width: 1.25rem; + height: 1.25rem; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); +} + +/* Section label (uppercase heading above option groups) */ +.theme-section-label { + display: block; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + margin-bottom: 0.75rem; +} + +/* Settings panel box with sunken background */ +.theme-panel { + background: var(--t-surface-sunken); + border-radius: 0.75rem; + padding: 1rem; + margin-bottom: 1.5rem; +} + +/* Field spacing — used for form elements within panels */ +.theme-field { + margin-bottom: 1rem; +} + +/* Bordered field subsection (logo upload area, etc.) */ +.theme-subsection { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--t-border-default); +} + +/* Chip button group */ +.theme-chips { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +/* Individual option chip */ +.theme-chip { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + border-radius: 0.5rem; + border: 2px solid transparent; + transition: all 0.15s; + text-transform: capitalize; + background: var(--t-surface-sunken); + color: var(--t-text-primary); + + &:hover { + background: var(--t-border-default); + } +} + +.theme-chip-active { + border-color: var(--t-text-primary); + background: var(--t-surface-base); +} + +/* Preset grid (2-column) */ +.theme-presets { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; +} + +.theme-preset { + padding: 0.75rem; + border-radius: 0.5rem; + text-align: left; + border: 2px solid transparent; + transition: all 0.15s; + background: var(--t-surface-sunken); + color: var(--t-text-primary); + + &:hover { + background: var(--t-border-default); + } +} + +.theme-preset-active { + border-color: var(--t-text-primary); + background: var(--t-surface-base); +} + +.theme-preset-name { + font-weight: 600; + font-size: 0.875rem; + text-transform: capitalize; +} + +.theme-preset-desc { + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +/* Colour picker inline row */ +.theme-color-row { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.theme-color-swatch { + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + cursor: pointer; + border: 0; + padding: 0; +} + +.theme-color-swatch-sm { + width: 2.25rem; + height: 2.25rem; +} + +.theme-color-value { + font-family: monospace; + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); +} + +/* File upload trigger label */ +.theme-upload-label { + display: block; + background: var(--t-surface-base); + border: 1px dashed var(--t-border-default); + border-radius: 0.5rem; + padding: 0.75rem; + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + text-align: center; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; + + &:hover { + border-color: color-mix(in oklch, var(--t-text-primary) 40%, transparent); + color: color-mix(in oklch, var(--t-text-primary) 80%, transparent); + } +} + +/* Upload progress row */ +.theme-progress { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.theme-progress-bar { + flex: 1; + height: 0.375rem; + background: var(--t-border-default); + border-radius: 9999px; + overflow: hidden; +} + +.theme-progress-fill { + height: 100%; + background: var(--t-accent); + transition: width 0.2s; +} + +/* Small round remove button (overlaid on thumbnails) */ +.theme-remove-btn { + position: absolute; + top: -0.375rem; + inset-inline-end: -0.375rem; + width: 1.125rem; + height: 1.125rem; + background: var(--t-text-primary); + color: var(--t-surface-base); + border-radius: 9999px; + font-size: 0.75rem; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +/* Thumbnail preview boxes */ +.theme-thumb { + position: relative; + display: flex; + align-items: center; + justify-content: center; + background: var(--t-surface-base); + border: 1px solid var(--t-border-default); + border-radius: 0.5rem; + overflow: hidden; +} + +.theme-thumb-logo { + width: 4rem; + height: 2.5rem; +} + +.theme-thumb-icon { + width: 2.5rem; + height: 2.5rem; +} + +.theme-thumb-header { + width: 100%; + height: 60px; + margin-top: 0.5rem; +} + +.theme-thumb img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.theme-thumb-cover img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Slider row (label + value display) */ +.theme-slider-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.theme-slider-label { + font-size: 0.75rem; + font-weight: 500; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); +} + +.theme-slider-value { + font-size: 0.75rem; + font-family: monospace; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); +} + +/* Customise accordion group */ +.theme-customise { + border-top: 1px solid var(--t-border-default); + margin-top: 1.5rem; + padding-top: 1rem; +} + +.theme-customise-summary { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.75rem 0; + cursor: pointer; + list-style: none; + + &::-webkit-details-marker { + display: none; + } +} + +.theme-customise-label { + font-size: 0.875rem; + font-weight: 600; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); + transition: color 0.1s; +} + +.theme-customise:hover .theme-customise-label, +.theme-customise[open] .theme-customise-label { + color: var(--t-text-primary); +} + +.theme-customise-chevron { + width: 1.25rem; + height: 1.25rem; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); + transition: transform 0.2s; +} + +.theme-customise[open] .theme-customise-chevron { + transform: rotate(180deg); +} + +/* Subsection group within customise accordion */ +.theme-group { + margin-bottom: 1.5rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid var(--t-surface-sunken); +} + +.theme-group-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.theme-group-icon { + width: 1rem; + height: 1rem; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); +} + +.theme-group-title { + font-size: 0.875rem; + font-weight: 600; + color: var(--t-text-primary); +} + +/* Current combination display */ +.theme-combination { + background: var(--t-surface-sunken); + border-radius: 0.75rem; + padding: 1rem; + margin-top: 1.5rem; +} + +.theme-combination-label { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); + margin-bottom: 0.5rem; +} + +.theme-combination-value { + font-size: 0.875rem; + color: var(--t-text-primary); + line-height: 1.6; +} + +.theme-combination-footnote { + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 40%, transparent); + margin-top: 0.5rem; +} + +/* Preview area */ +.theme-preview-area { + flex: 1; + padding: 1.5rem; + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--t-surface-sunken); +} + +.theme-preview-container { + max-width: 1200px; + margin-inline: auto; + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: hidden; +} + +/* Preview page tab switcher */ +.theme-preview-tabs { + display: flex; + gap: 0.25rem; + margin-bottom: 0.75rem; + background: var(--t-border-default); + padding: 0.25rem; + border-radius: 0.5rem; + width: fit-content; + flex-shrink: 0; +} + +.theme-preview-tab { + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 500; + border-radius: 0.375rem; + transition: all 0.15s; + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); + + &:hover { + color: var(--t-text-primary); + } +} + +.theme-preview-tab-active { + background: var(--t-surface-base); + color: var(--t-text-primary); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +/* Browser chrome frame */ +.theme-browser-chrome { + display: flex; + align-items: center; + background: linear-gradient(to bottom, var(--t-border-default), color-mix(in oklch, var(--t-border-default) 80%, transparent)); + border: 1px solid color-mix(in oklch, var(--t-text-primary) 20%, transparent); + border-bottom-color: color-mix(in oklch, var(--t-text-primary) 30%, transparent); + border-radius: 0.625rem 0.625rem 0 0; + padding: 0.625rem 0.875rem; + gap: 0.875rem; + flex-shrink: 0; +} + +.theme-browser-dots { + display: flex; + gap: 0.5rem; +} + +.theme-browser-dot { + width: 0.75rem; + height: 0.75rem; + border-radius: 9999px; +} + +.theme-browser-dot-close { + background: #ff5f57; + border: 1px solid #e14640; +} + +.theme-browser-dot-min { + background: #ffbd2e; + border: 1px solid #dfa123; +} + +.theme-browser-dot-max { + background: #28c940; + border: 1px solid #1aab29; +} + +.theme-browser-url { + flex: 1; + display: flex; + align-items: center; + gap: 0.5rem; + background: var(--t-surface-base); + border: 1px solid color-mix(in oklch, var(--t-text-primary) 20%, transparent); + border-radius: 0.375rem; + padding: 0.3125rem 0.75rem; +} + +.theme-browser-url-icon { + width: 0.875rem; + height: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); +} + +.theme-browser-url-text { + font-size: 0.875rem; + color: color-mix(in oklch, var(--t-text-primary) 60%, transparent); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Preview content area */ +.theme-preview-frame { + overflow: auto; + flex: 1; + border-radius: 0 0 0.5rem 0.5rem; + border: 1px solid color-mix(in oklch, var(--t-text-primary) 20%, transparent); + border-top: none; +} + +/* Upload cancel button */ +.theme-upload-cancel { + color: color-mix(in oklch, var(--t-text-primary) 40%, transparent); + + &:hover { + color: color-mix(in oklch, var(--t-text-primary) 70%, transparent); + } +} + +/* Alt text hint for missing alt */ +.theme-alt-warning { + font-size: 0.75rem; + color: var(--t-status-warning, #f59e0b); + margin-top: 0.25rem; +} + +/* Error text (upload errors) */ +.theme-error-text { + font-size: 0.75rem; + color: var(--t-status-error, #dc2626); + margin-top: 0.25rem; +} + +/* Radio card selector (logo mode picker) */ +.theme-radio-card { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border: 1px solid var(--t-border-default); + border-radius: 0.5rem; + cursor: pointer; + transition: border-color 0.15s, background 0.15s; + + &:hover { + border-color: color-mix(in oklch, var(--t-text-primary) 30%, transparent); + } +} + +.theme-radio-card-active { + border-color: var(--t-accent); + background: color-mix(in oklch, var(--t-accent) 5%, transparent); +} + +.theme-radio-dot { + width: 1.25rem; + height: 1.25rem; + border-radius: 9999px; + border: 2px solid var(--t-border-default); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: border-color 0.15s; +} + +.theme-radio-dot-active { + border-color: var(--t-accent); +} + +.theme-radio-dot-inner { + width: 0.5rem; + height: 0.5rem; + border-radius: 9999px; + background: transparent; + transition: background 0.15s; +} + +.theme-radio-dot-inner-active { + background: var(--t-accent); +} + +.theme-radio-title { + font-size: 0.875rem; + font-weight: 500; + color: var(--t-text-primary); +} + } /* @layer admin */ diff --git a/assets/css/admin/layout.css b/assets/css/admin/layout.css new file mode 100644 index 0000000..abbe68d --- /dev/null +++ b/assets/css/admin/layout.css @@ -0,0 +1,36 @@ +/* Admin layout primitives — composable building blocks mirroring shop/layout.css. + Each primitive does one layout job. Combine freely via CSS custom properties. */ + +@layer admin { + /* Vertical stack with consistent gap */ + .admin-stack { + display: flex; + flex-direction: column; + gap: var(--admin-stack-gap, 1rem); + } + + /* Horizontal flex row, no wrap — toolbars, inline groups, header bars */ + .admin-row { + display: flex; + align-items: center; + gap: var(--admin-row-gap, 0.5rem); + } + + /* Horizontal flex-wrap cluster — tags, badges, button groups */ + .admin-cluster { + display: flex; + flex-wrap: wrap; + gap: var(--admin-cluster-gap, 0.5rem); + align-items: center; + } + + /* Intrinsic responsive grid — cards, media thumbnails */ + .admin-grid { + display: grid; + grid-template-columns: repeat( + auto-fill, + minmax(min(var(--admin-grid-min, 16rem), 100%), 1fr) + ); + gap: var(--admin-grid-gap, 1rem); + } +} diff --git a/assets/css/admin/transitions.css b/assets/css/admin/transitions.css new file mode 100644 index 0000000..5d624f1 --- /dev/null +++ b/assets/css/admin/transitions.css @@ -0,0 +1,80 @@ +/* Transition and animation utilities for admin pages. + These are required by Phoenix JS.show/JS.hide in core_components.ex + and by various admin UI interactions (spinners, hover effects, etc.). + + Placed in @layer admin so they integrate with the cascade. */ + +@layer admin { + +/* ── Transition properties ── */ + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, + fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-\[left\] { + transition-property: left; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +/* ── Duration overrides ── */ + +.duration-200 { transition-duration: 200ms; } +.duration-300 { transition-duration: 300ms; } + +/* ── Easing overrides ── */ + +.ease-in { transition-timing-function: cubic-bezier(0.4, 0, 1, 1); } +.ease-out { transition-timing-function: cubic-bezier(0, 0, 0.2, 1); } + +/* ── Opacity states (JS.show / JS.hide) ── */ + +.opacity-0 { opacity: 0; } +.opacity-100 { opacity: 1; } + +/* ── Transform states (JS.show / JS.hide) ── */ + +.translate-y-4 { translate: 0 1rem; } +.translate-y-0 { translate: 0 0; } +.scale-95 { scale: 0.95; } +.scale-100 { scale: 1; } + +@media (min-width: 640px) { + .sm\:translate-y-0 { translate: 0 0; } + .sm\:scale-95 { scale: 0.95; } + .sm\:scale-100 { scale: 1; } +} + +/* ── Spin animation ── */ + +@keyframes spin { to { transform: rotate(360deg); } } + +.animate-spin { animation: spin 1s linear infinite; } + +@media (prefers-reduced-motion: no-preference) { + .motion-safe\:animate-spin { animation: spin 1s linear infinite; } +} + +} /* @layer admin */ diff --git a/lib/berrypod_web/components/core_components.ex b/lib/berrypod_web/components/core_components.ex index 27f9bf3..4319d1b 100644 --- a/lib/berrypod_web/components/core_components.ex +++ b/lib/berrypod_web/components/core_components.ex @@ -51,17 +51,17 @@ defmodule BerrypodWeb.CoreComponents do {@rest} >
<.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" /> <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
-

{@title}

+

{@title}

{msg}

-
+
@@ -271,7 +271,7 @@ defmodule BerrypodWeb.CoreComponents do # Helper used by inputs to generate form errors defp error(assigns) do ~H""" -

+

<.icon name="hero-exclamation-circle" class="size-5" /> {render_slot(@inner_block)}

@@ -287,16 +287,19 @@ defmodule BerrypodWeb.CoreComponents do def header(assigns) do ~H""" -
+
-

+

{render_slot(@inner_block)}

-

+

{render_slot(@subtitle)}

-
{render_slot(@actions)}
+
{render_slot(@actions)}
""" end @@ -352,8 +355,8 @@ defmodule BerrypodWeb.CoreComponents do > {render_slot(col, @row_item.(row))} - -
+ +
<%= for action <- @action do %> {render_slot(action, @row_item.(row))} <% end %> @@ -385,7 +388,7 @@ defmodule BerrypodWeb.CoreComponents do