backend integration started
This commit is contained in:
525
src/index.css
525
src/index.css
@@ -654,3 +654,528 @@ main {
|
||||
@container drawer (min-width: 900px) {
|
||||
.import-scroll .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
/* ────────────────────────────────────────────────────────────────────────────
|
||||
Global catalogue — the rail and the product card
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Modelled on the storefront reference, translated to this console's tokens
|
||||
rather than its palette: the same proportions and the same rhythm, in
|
||||
Nearle's purple and the greys every other page uses. Lifting the reference's
|
||||
orange would have made one page look like a different product.
|
||||
──────────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* The search + rail-toggle row, lifted level with the page's tab row.
|
||||
42px is the measured distance between the two rows' centres. The bottom
|
||||
margin then restores the 24px the grid should keep below the tab row —
|
||||
without it the pull-up drags the whole grid up with it and the first row of
|
||||
cards slides under the tabs. */
|
||||
@media (min-width: 900px) {
|
||||
.catalogue-controls {
|
||||
margin-top: -42px;
|
||||
margin-bottom: 18px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.catalogue-layout {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
/* The rail is fixed, the grid takes what is left — a rail that grows with the
|
||||
window steals width from the photographs, which are the content. */
|
||||
.catalogue-layout[data-rail='open'] {
|
||||
grid-template-columns: 216px minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.catalogue-rail {
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 14px;
|
||||
background: var(--color-surface);
|
||||
position: sticky;
|
||||
top: 72px;
|
||||
/* The clip lives here, on the element that owns the radius. */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rail-scroll {
|
||||
padding: 6px;
|
||||
max-height: calc(100dvh - 98px);
|
||||
overflow-y: auto;
|
||||
/* A thin, quiet scrollbar. The default is wide enough to read as a second
|
||||
column beside a 216px rail. */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-line) transparent;
|
||||
}
|
||||
|
||||
.rail-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.rail-scroll::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: var(--color-line);
|
||||
}
|
||||
|
||||
.rail-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.rail-all,
|
||||
.rail-brand,
|
||||
.rail-category,
|
||||
.rail-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 9px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
color: var(--color-ink-1);
|
||||
}
|
||||
|
||||
.rail-all {
|
||||
padding: 9px 10px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rail-brand {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rail-brand-name {
|
||||
text-transform: capitalize;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rail-category {
|
||||
padding: 5px 10px 5px 20px;
|
||||
font-size: 12.5px;
|
||||
color: var(--color-ink-3);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.rail-all:hover,
|
||||
.rail-brand:hover,
|
||||
.rail-category:hover,
|
||||
.rail-more:hover {
|
||||
background: var(--color-surface-sunken);
|
||||
}
|
||||
|
||||
.rail-all[data-active='true'],
|
||||
.rail-brand[data-active='true'] {
|
||||
background: color-mix(in oklab, var(--color-brand) 8%, transparent);
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.rail-category[data-active='true'] {
|
||||
color: var(--color-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rail-group[data-open='true'] {
|
||||
background: color-mix(in oklab, var(--color-brand) 4%, transparent);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.rail-count {
|
||||
flex: none;
|
||||
font-size: 11px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.rail-more {
|
||||
padding: 8px 10px;
|
||||
margin-top: 2px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
border-radius: 0 0 9px 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-brand);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rail-skeleton {
|
||||
height: 13px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-surface-sunken);
|
||||
}
|
||||
|
||||
/* ── The card ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
.pcard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 14px;
|
||||
background: var(--color-surface);
|
||||
overflow: hidden;
|
||||
transition: border-color .18s, box-shadow .18s;
|
||||
}
|
||||
|
||||
.pcard:hover {
|
||||
border-color: color-mix(in oklab, var(--color-brand) 38%, transparent);
|
||||
box-shadow: 0 10px 24px -12px rgb(16 24 40 / .22);
|
||||
}
|
||||
|
||||
.pcard-media {
|
||||
position: relative;
|
||||
/* Flex, not grid, and the reason is not taste: a grid row is sized by its
|
||||
content unless told otherwise, so an oversized child grows the row past the
|
||||
container and a percentage max-height then resolves against the GROWN row —
|
||||
which is why a 240px pack shot stayed 220px tall inside a 150px band. In a
|
||||
flex container with a definite height, the child's percentage resolves
|
||||
against that height. */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
/* A fixed band rather than a square. At `aspect-ratio: 1` the photograph took
|
||||
more than half the card's height and pushed the name, the size and the
|
||||
price below the fold of a grid row — the picture identifies the product,
|
||||
but the text is what is read once it has. */
|
||||
height: 150px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pcard-media img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
/* Catalogue photography is shot on white; without this the photo's own
|
||||
ground reads as a grey panel inside the card. */
|
||||
mix-blend-mode: multiply;
|
||||
transition: transform .5s ease;
|
||||
}
|
||||
|
||||
.pcard:hover .pcard-media img {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.pcard-noimage {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--color-line);
|
||||
}
|
||||
|
||||
/* The click target, under the controls rather than around them. */
|
||||
.pcard-open {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ── The photo switcher ───────────────────────────────────────────────────── */
|
||||
|
||||
.pcard-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 3;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
transform: translateY(-50%);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 999px;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-ink-3);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity .16s, background .16s, color .16s;
|
||||
box-shadow: 0 2px 6px rgb(16 24 40 / .12);
|
||||
}
|
||||
|
||||
.pcard-arrow.is-left { left: 6px; }
|
||||
.pcard-arrow.is-right { right: 6px; }
|
||||
|
||||
/* Shown on hover, and on keyboard focus — otherwise the only way to reach a
|
||||
product's other photos would be a mouse. */
|
||||
.pcard:hover .pcard-arrow,
|
||||
.pcard-arrow:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.pcard-arrow:hover {
|
||||
background: var(--color-brand);
|
||||
border-color: var(--color-brand);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pcard-dots {
|
||||
position: absolute;
|
||||
bottom: 7px;
|
||||
left: 50%;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.pcard-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: var(--color-line);
|
||||
cursor: pointer;
|
||||
transition: width .2s, background .2s;
|
||||
}
|
||||
|
||||
.pcard-dot[data-active] {
|
||||
width: 13px;
|
||||
background: var(--color-brand);
|
||||
}
|
||||
|
||||
.pcard-owned {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 7px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .04em;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pcard-owned {
|
||||
right: 8px;
|
||||
background: #059669;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pcard-rail {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
opacity: 0;
|
||||
transform: translateX(6px);
|
||||
transition: opacity .18s, transform .18s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hidden while the owned badge holds that corner — two things stacked there
|
||||
is how a badge ends up unreadable. */
|
||||
.pcard:hover .pcard-rail {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.pcard[data-imported] .pcard-rail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pcard-railbtn {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-line);
|
||||
color: var(--color-ink-3);
|
||||
box-shadow: 0 2px 6px rgb(16 24 40 / .1);
|
||||
}
|
||||
|
||||
.pcard-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: 5px;
|
||||
padding: 11px 12px 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pcard-name {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
/* Medium, not bold. In the reference the product name is the quiet line and
|
||||
the photograph does the identifying — a grid of two dozen bold names reads
|
||||
as a list of headlines. */
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
color: var(--color-ink-1);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pcard-name:hover {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.pcard-meta {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 11.5px;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.pcard-sku {
|
||||
font-family: var(--font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pcard-size {
|
||||
flex: none;
|
||||
padding: 1px 6px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-surface-sunken);
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.pcard-foot {
|
||||
margin-top: auto;
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pcard-price {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-ink-1);
|
||||
}
|
||||
|
||||
.pcard-photos {
|
||||
flex: none;
|
||||
font-size: 11px;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
/* ── The action ───────────────────────────────────────────────────────────────
|
||||
An outlined pill that fills on hover, which is the reference's treatment and
|
||||
has no equivalent among Astryx's four Button variants (primary, secondary,
|
||||
ghost, destructive — all filled or transparent, none outlined). It is drawn
|
||||
here rather than approximated with the wrong stock variant, in Nearle's
|
||||
purple rather than the reference's orange: borrowing the palette would have
|
||||
made one page look like a different product.
|
||||
──────────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
.pcard-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: none;
|
||||
padding: 6px 11px;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 999px;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-ink-2, var(--color-ink-1));
|
||||
font: inherit;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: background .15s, border-color .15s, color .15s;
|
||||
}
|
||||
|
||||
.pcard-action:hover:not(:disabled) {
|
||||
background: var(--color-brand);
|
||||
border-color: var(--color-brand);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pcard-action:disabled {
|
||||
cursor: default;
|
||||
opacity: .55;
|
||||
}
|
||||
|
||||
.pcard-action.is-added {
|
||||
border-color: color-mix(in oklab, #059669 40%, transparent);
|
||||
background: color-mix(in oklab, #059669 8%, transparent);
|
||||
color: #047857;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (min-width: 560px) {
|
||||
.catalogue-layout .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (min-width: 1100px) {
|
||||
.catalogue-layout .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (min-width: 1400px) {
|
||||
.catalogue-layout .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
|
||||
/* ────────────────────────────────────────────────────────────────────────────
|
||||
Demo-mode flag — development only, dropped from a production build
|
||||
──────────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
.demo-flag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: none;
|
||||
padding: 4px 6px 4px 10px;
|
||||
border: 1px solid #f0b429;
|
||||
border-radius: 999px;
|
||||
background: #fef6e4;
|
||||
color: #8a5a00;
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .02em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.demo-flag-exit {
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
background: #8a5a00;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.demo-flag:hover .demo-flag-exit {
|
||||
background: #6b4600;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user