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} >
{@title}
+{@title}
{msg}
+
<.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(@subtitle)}