backend integration started

This commit is contained in:
2026-08-26 18:37:28 +05:30
parent 517a99d577
commit 3545819be8
50 changed files with 3461 additions and 3241 deletions

View File

@@ -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;
}