/* ==========================================================================
   Stelar Reality — components
   The full catalogue for the internal e-shop.

   RULES OBSERVED THROUGHOUT
   - Every selector is a single class, optionally plus an element, an
     attribute or a pseudo. No component reaches into another component by
     descendant selector; where a parent must influence a child it does so by
     setting an inherited custom property (see .card / --media-scale).
   - State is expressed with `is-` / `has-` prefixes.
   - No !important. No @layer. Cascade order does the work.
   - Breakpoints: 30rem / 48rem / 64rem / 75rem only (see tokens.css).
   - Corners come from --radius-control / --radius-subtle (tokens.css), never
     from a literal. The wedge motif is clip-path and is never rounded.
   ========================================================================== */


/* ==========================================================================
   1. BUTTONS
   Outline by default; the colours reverse on hover over ~360ms. Square, thin
   line, no pills, no shadows. Hierarchy comes from colour, not from shape.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: var(--control-h);
  padding-inline: 18px;
  font-family: var(--font-text);
  font-size: var(--type-ui-size);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  vertical-align: middle;
  cursor: pointer;
  border-width: var(--border-width);
  border-style: solid;
  border-radius: var(--radius-control);
  box-shadow: none;
  background-color: transparent;
  border-color: transparent;
  transition:
    background-color var(--motion-standard) var(--ease-standard),
    color var(--motion-standard) var(--ease-standard),
    border-color var(--motion-standard) var(--ease-standard),
    transform var(--motion-fast) var(--ease-standard);
}

.btn:hover { text-decoration: none; }
.btn:active { transform: scale(0.98); }

/* --- Primary: copper outline, copper fill on hover --- */
.btn-primary {
  color: var(--text-link);
  background-color: transparent;
  border-color: var(--color-copper);
}
.btn-primary:hover {
  color: var(--text-on-copper);
  background-color: var(--color-copper-hover);
  border-color: var(--color-copper-hover);
}
.btn-primary:active {
  color: var(--text-on-copper);
  background-color: var(--color-copper-active);
  border-color: var(--color-copper-active);
}

/* --- Secondary: ink outline, ink fill on hover --- */
.btn-secondary {
  color: var(--text-primary);
  background-color: transparent;
  border-color: var(--text-primary);
}
.btn-secondary:hover {
  color: var(--text-inverse);
  background-color: var(--surface-inverse);
  border-color: var(--surface-inverse);
}

/* --- Ghost: the quiet row-level action. Replaces the old .btn-action. --- */
.btn-ghost {
  color: var(--text-secondary);
  background-color: transparent;
  border-color: var(--border-subtle);
}
.btn-ghost:hover {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  border-color: var(--color-neutral-500);
}

/* --- Danger: tinted outline until committed, then full fill --- */
.btn-danger {
  color: var(--color-danger);
  background-color: transparent;
  border-color: var(--tone-danger-border);
}
.btn-danger:hover {
  color: var(--text-inverse);
  background-color: var(--color-danger);
  border-color: var(--color-danger);
}

/* --- Tertiary: a link that happens to be a button. No box, no height. --- */
.btn-tertiary {
  height: auto;
  padding-inline: 0;
  color: var(--text-link);
  background-color: transparent;
  border-color: transparent;
}
/* A hairline underline, not the default one: .btn is 600-weight, and a
   font-derived rule under a bold label lands thick enough to read as a border. */
.btn-tertiary:hover {
  color: var(--color-copper-active);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
}

/* --- On dark: white outline, white fill, ink label --- */
.btn-on-dark {
  color: var(--text-inverse);
  background-color: transparent;
  border-color: rgba(255, 255, 255, 0.7);
}
.btn-on-dark:hover {
  color: var(--text-primary);
  background-color: var(--color-white);
  border-color: var(--color-white);
}

/* --- Sizes ---
   Inline padding is the hard floor on how far a small button can shrink inside
   a flex row: the product card packs a −/+ pair and a number field into a
   fixed-width cell, and past this the quantity input collapses and stops
   rendering its value. Do not trade padding away for a narrower cell. */
.btn-sm {
  height: var(--control-h-sm);
  padding-inline: 12px;
  font-size: var(--type-body-sm-size);
  gap: 8px;
}
.btn-lg {
  height: var(--control-h-lg);
  padding-inline: 24px;
  font-size: 0.9375rem;
}

/* --- Icon-only --- */
.btn-icon {
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  gap: 0;
}
.btn-icon.btn-sm {
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  padding: 0;
}

/* --- Disabled: recoloured, never faded. Opacity makes every disabled
       control fail contrast at once and looks like a rendering bug. --- */
.btn:disabled,
.btn[disabled],
.btn.is-disabled {
  color: var(--text-muted);
  background-color: transparent;
  border-color: var(--border-subtle);
  opacity: 1;
  cursor: not-allowed;
}
.btn.is-disabled { pointer-events: none; }

/* --- Busy --- */
.btn[aria-busy="true"] {
  color: var(--text-muted);
  pointer-events: none;
}
.btn[aria-busy="true"]::before {
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border: 2px solid var(--color-neutral-200);
  border-block-start-color: var(--color-copper);
  border-radius: var(--radius-full);
  animation: spin 700ms linear infinite;
}


/* ==========================================================================
   2. CARDS & SURFACES
   White, square, hairline. Depth comes from tone and border, not shadow.
   ========================================================================== */

/* `overflow: hidden` is what lets the header, footer and a flush .card-media
   meet the rounded corner cleanly — the browser derives the padding-box arc
   from the radius minus the border, so nothing has to be hand-matched.
   Consequence to remember: a dropdown or popover placed inside a card would
   be clipped. None exists today; put one in a portal if that ever changes. */
.card {
  overflow: hidden;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  box-shadow: var(--shadow-none);
}

/* The inline padding reads --card-pad, same as .card-body below: a fixed 16px
   here put every card title 4px inside its own content, which is exactly wide
   enough to see and not wide enough to look deliberate. */
.card-header {
  padding: 10px var(--card-pad, 20px);
  background-color: var(--surface-primary);
  border-block-end: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-none);   /* the card's clip owns the corners */
  font-weight: 600;
}

/* 20px. Below the 24-40px the brand quotes, deliberately: that band is written
   for a marketing site, and this is a card-dense internal tool where four
   stacked cards at 32px spend a third of the viewport on inset alone. Tiles
   that pack three or more across a row dial it down further through --card-pad
   rather than by a descendant selector — same idiom as --media-scale above. */
.card-body { padding: var(--card-pad, 20px); }

.card-footer {
  padding: 12px var(--card-pad, 20px);
  background-color: var(--surface-secondary);
  border-block-start: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-none);   /* the card's clip owns the corners */
}

/* Stays square deliberately. Inside a product card the media is flush at the
   top but butts against .card-body halfway down; a uniform radius here would
   curve those inner corners away and open two notches of card-white at the
   junction. The parent's clip rounds the outer corners instead. Standalone
   variants (.is-thumb, the cart-line thumb) do get their own radius. */
.card-media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background-color: var(--color-neutral-050);
  border-radius: var(--radius-none);
}
.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Driven by an inherited custom property so the interactive parent never
     needs a descendant selector into this component. */
  transform: scale(var(--media-scale, 1));
  transition: transform var(--motion-slow) var(--ease-expressive);
}
.card-media.is-empty {
  display: grid;
  place-items: center;
  color: var(--color-neutral-500);
  font-size: 1.5rem;
}
/* Fixed-size thumbnail for table cells and compact rows. */
.card-media.is-thumb {
  display: block;
  width: 32px;
  font-size: 1rem;
  border-radius: var(--radius-control);
}

.card.is-interactive {
  transition:
    border-color var(--motion-standard) var(--ease-standard),
    box-shadow var(--motion-standard) var(--ease-standard);
}
.card.is-interactive:hover {
  --media-scale: 1.03;
  border-color: var(--color-neutral-500);
  box-shadow: var(--shadow-subtle);
}

.product-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  transition:
    border-color var(--motion-standard) var(--ease-standard),
    box-shadow var(--motion-standard) var(--ease-standard);
}
.product-card:hover {
  --media-scale: 1.03;
  border-color: var(--color-neutral-500);
  box-shadow: var(--shadow-subtle);
}

/* Titles are ink — copper stays reserved for actions (brand ruling). */
.product-card h3 a { color: var(--text-primary); }

/* Price and stock badge. Centre, not baseline: the badge is a fixed 22px box
   whose 11px label is centred inside it, so baseline-aligning it to a 14px
   price hangs the whole box ~1px low and drifts further the moment either
   type size changes. */
.product-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-start: auto;
  padding-block-start: var(--space-3);
  border-block-start: var(--border-width) solid var(--border-subtle);
}

/* The buy row spreads like the row above it, so the card has two full-width
   rows sharing both margins — otherwise the stock badge is the only thing in
   the card touching the trailing edge and reads as floating off it.
   flex-wrap is load-bearing (see the .btn-sm note): the stepper and the button
   must wrap in a narrow card rather than shrink, or the quantity input
   collapses. That is why this is not .u-between, which does not wrap. */
.product-card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* The listing's one remaining toolbar row: what you are looking at on the
   leading edge, how it is ordered on the trailing one. Wraps rather than
   squeezes — the count and the chips can outgrow a phone on their own. */
.listing-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* The "added to cart" line app.js reveals in place of a toast. Presence in the
   DOM is not the state — the [hidden] attribute is, and it has to beat the
   flex display below, so the two rules are ordered, not fought over. */
.inline-confirm {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--type-body-xs-size);
  color: var(--tone-success-text);
}
.inline-confirm[hidden] { display: none; }

/* A borderless tinted block — for asides and summaries that must not read
   as another card in an already card-heavy view. */
.panel {
  padding: 20px;
  background-color: var(--color-neutral-050);
  border: 0;
  border-radius: var(--radius-subtle);
}


/* ==========================================================================
   3. PAGE HEADER & SECTIONS
   ========================================================================== */

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-end: var(--space-6);
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  letter-spacing: var(--type-label-ls);
  text-transform: var(--type-label-transform);
  color: var(--color-neutral-500);
}
.eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 18px;
  height: 2px;
  background: var(--color-copper);
}

/* Signature motif — one wedge per page, beside the H1. Never repeated. */
.page-title {
  display: flex;
  align-items: center;
  gap: 0.875rem;
}
.page-title::before {
  content: "";
  flex: 0 0 auto;
  width: 40px;
  height: 4px;
  background: var(--color-copper);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.section { margin-block-end: var(--space-7); }

.section-title {
  margin-block-end: var(--space-4);
  font-family: var(--font-headline);
  font-size: var(--type-h3-size);
  font-weight: 700;
  letter-spacing: var(--type-heading-ls);
}


/* ==========================================================================
   4. FORMS
   ========================================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-block-end: var(--space-4);
}

.form-label {
  font-size: var(--type-body-sm-size);
  font-weight: 600;
  color: var(--text-primary);
}

.form-control {
  display: block;
  width: 100%;
  height: var(--control-h);
  padding: 0 12px;
  font-family: var(--font-text);
  font-size: var(--type-body-md-size);
  line-height: 1.5;
  color: var(--text-primary);
  background-color: var(--surface-primary);
  background-clip: padding-box;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
  box-shadow: none;
}
.form-control::placeholder {
  color: var(--color-neutral-500);
  opacity: 1;
}
/* The border IS the focus indicator here — base.css suppresses the ring on text
   fields so a focused control shows one edge, not a ring around a box. Which
   means no transition: this is the only thing that says "you are here", and it
   has to be true on the first frame, not eased in over 180ms. No box-shadow
   either — it would be clipped inside .table-wrap's scroll container. */
.form-control:focus {
  border-color: var(--color-copper);
  box-shadow: none;
}

textarea.form-control {
  height: auto;
  min-height: 80px;
  padding: 10px 12px;
  resize: vertical;
}

.form-control-sm {
  height: var(--control-h-sm);
  padding: 0 10px;
  font-size: var(--type-body-xs-size);
}

.form-select {
  display: block;
  width: 100%;
  height: var(--control-h);
  padding: 0 12px;
  font-family: var(--font-text);
  font-size: var(--type-body-md-size);
  line-height: 1.5;
  color: var(--text-primary);
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
  box-shadow: none;
}
/* The custom caret only applies to the single-value control. A [multiple] or
   [size] select renders as a list box: stripping its appearance removes the
   scrollbar and the padding reserves space for a caret that is never drawn. */
.form-select:not([multiple]):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: 32px;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23292927' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px 12px;
}
/* As .form-control:focus — the border is the whole focus signal, untransitioned. */
.form-select:focus {
  border-color: var(--color-copper);
  box-shadow: none;
}
.form-select-sm {
  height: var(--control-h-sm);
  padding: 0 10px;
  font-size: var(--type-body-xs-size);
}

/* Toolbar selects size to their widest option instead of stretching the row. */
.form-select-auto { width: auto; }

/* File input: the browser button is restyled as a ghost button. */
input[type="file"].form-control {
  display: flex;
  align-items: center;
  height: var(--control-h);
  padding: 0;
}
input[type="file"].form-control::file-selector-button {
  height: var(--control-h-sm);
  margin: 0 12px 0 5px;
  padding-inline: 16px;
  font-family: var(--font-text);
  font-size: var(--type-body-sm-size);
  font-weight: 600;
  line-height: 1;
  color: var(--text-secondary);
  background-color: transparent;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition:
    background-color var(--motion-standard) var(--ease-standard),
    color var(--motion-standard) var(--ease-standard),
    border-color var(--motion-standard) var(--ease-standard);
}
input[type="file"].form-control:hover::file-selector-button {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  border-color: var(--color-neutral-500);
}

/* --- Checkbox / radio ---
   The tick is a background-image rather than a mask-image: `mask` clips the
   whole element, which would erase the copper box along with everything that
   is not the tick. Inputs are replaced elements and cannot carry a
   pseudo-element to mask instead. */
.check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding-block: 6px;
  cursor: pointer;
}
.check input {
  flex: 0 0 auto;
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin: 0;
  background-color: var(--surface-primary);
  background-repeat: no-repeat;
  background-position: center;
  border: var(--border-width) solid var(--color-neutral-500);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    border-color var(--motion-fast) var(--ease-standard);
}
.check input[type="radio"] { border-radius: var(--radius-full); }
.check input:checked {
  background-color: var(--color-copper);
  border-color: var(--color-copper);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.5' d='m5 10.5 3.5 3.5L15 6.5'/%3e%3c/svg%3e");
  background-size: 14px 14px;
}
.check input[type="radio"]:checked {
  background-image: radial-gradient(circle at center, #FFFFFF 0 3.5px, transparent 3.5px);
}
.check input:disabled {
  border-color: var(--border-subtle);
  cursor: not-allowed;
}
.check input:disabled:checked {
  background-color: var(--color-neutral-500);
  border-color: var(--color-neutral-500);
}

/* --- Switch ---
   A binary state flipped in place, in a table cell — the admin products grid.
   The real <input type="checkbox"> stays in the DOM and does all the work:
   it carries the value, the accessible name and the keyboard behaviour, and
   :checked / :focus-visible on it drive everything below. Only its painting is
   suppressed.

   BRAND DEVIATION 5 (see tokens.css). The track is a capsule, so it reads
   --radius-full, which tokens.css scopes to "status dots, radios, spinners,
   avatars only"; .step::before already refused a round marker as the
   friendly-SaaS shape the brand rules out. Chosen deliberately over a clickable
   status chip because a switch is the only shape that says "this flips" without
   a label — and the alternative was a badge that looks inert.

   Focus is an outline on the track, never a box-shadow: these live inside
   .table-wrap, whose overflow-x clips shadows and would silently erase the
   ring on a scrolled table. */
.switch {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}
.switch input {
  /* Not display:none and not visibility:hidden — either one takes the control
     out of the tab order and out of the accessibility tree with it. */
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}
.switch-track {
  position: relative;
  display: block;
  width: 34px;
  height: 20px;
  background-color: var(--color-neutral-200);
  border-radius: var(--radius-full);
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.switch-track::before {
  content: "";
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  width: 14px;
  height: 14px;
  background-color: var(--surface-primary);
  border-radius: var(--radius-full);
  transition: transform var(--motion-fast) var(--ease-standard);
}
.switch input:checked + .switch-track { background-color: var(--color-copper); }
.switch input:checked + .switch-track::before { transform: translateX(14px); }
.switch input:focus-visible + .switch-track {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.switch input:disabled + .switch-track {
  opacity: 0.5;
  cursor: not-allowed;
}

.field-hint {
  font-size: var(--type-body-xs-size);
  color: var(--text-secondary);
}

/* display:block is load-bearing: an error message is visible whenever it is in
   the DOM. Presence in the markup is the state — never a class on a sibling,
   which is how server-rendered WTForms errors silently go missing. */
.field-error {
  display: block;
  font-size: var(--type-body-xs-size);
  color: var(--color-danger);
}

.form-control.is-invalid,
.form-select.is-invalid {
  border-color: var(--color-danger);
}

/* --- Stepper --- */
/* Corners are rounded per-child rather than by clipping the wrapper: the two
   buttons and the number field are flush to the edge, so `overflow: hidden`
   here would cut their focus outlines in half. */
.stepper {
  display: inline-flex;
  align-items: stretch;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
}
.stepper button:first-child {
  border-start-start-radius: var(--radius-control);
  border-end-start-radius: var(--radius-control);
}
.stepper button:last-child {
  border-start-end-radius: var(--radius-control);
  border-end-end-radius: var(--radius-control);
}
.stepper button {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--text-secondary);
  background: none;
  border: 0;
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard);
}
.stepper button:hover {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
}
.stepper input {
  width: 48px;
  height: 32px;
  padding: 0;
  text-align: center;
  font-size: var(--type-body-xs-size);
  font-weight: 600;
  color: var(--text-primary);
  background: none;
  border: 0;
  border-inline: var(--border-width) solid var(--border-subtle);
  appearance: textfield;
  -moz-appearance: textfield;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* --- Search --- */
.search {
  position: relative;
  display: block;
}
.search input { padding-inline-start: 42px; }
.search i {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 14px;
  transform: translateY(-50%);
  color: var(--color-neutral-500);
  pointer-events: none;
}

/* Same glyph, same place, but a real submit control — used where the field is
   a form of its own (the topbar) and there is no room for a worded button. The
   admin filter bars keep the inert <i> above: they have their own Filtrovat. */
.search button {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 5px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  /* An ancestor shrinks this by setting --search-btn (see .topbar-search). */
  width: var(--search-btn, 30px);
  height: var(--search-btn, 30px);
  color: var(--color-neutral-500);
  border-radius: var(--radius-control);
  transition: color var(--motion-fast) var(--ease-standard);
}
.search button:hover { color: var(--text-primary); }


/* ==========================================================================
   5. TABLES
   No striped variant — the brand forbids zebra fills. Structure is carried
   by hairlines and tabular figures.
   ========================================================================== */

/* The scroll container is already a clip, so the tinted sticky header meets
   the rounded top corners without any extra work. */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
}
/* Inside a .card the wrapper exists only to scroll: the card already draws the
   border and the radius, and a second pair reads as a box inside a box. The
   wrapper cannot simply be dropped — .card has overflow: hidden, so a wide
   table would be clipped instead of scrollable. */
.table-wrap.is-flush {
  border: 0;
  border-radius: var(--radius-none);
}

.table-wrap.is-sticky th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
}

.table {
  width: 100%;
  margin: 0;
  border-collapse: collapse;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.table th {
  padding: 6px 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  color: var(--text-muted);
  background-color: var(--color-neutral-050);
  border-block-end: var(--border-width) solid var(--border-subtle);
}
.table td {
  padding: 8px 12px;
  font-size: var(--type-body-sm-size);
  line-height: 1.45;
  vertical-align: middle;
  border-block-end: var(--border-width) solid var(--border-subtle);
}
.table tr:last-child td { border-block-end-width: 0; }

/* The transition sits on the row, not on :hover, so the fill eases out as well
   as in — the densest surface in the app was the only one that snapped. */
.table.is-hoverable tbody tr {
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.table.is-hoverable tbody tr:hover { background-color: var(--surface-hover); }

/* Rows and list items whose whole box opens a detail page (data-action
   "row:open" in app.js). The class carries only the cursor — the row's own
   <a> stays the accessible affordance. */
.is-clickable { cursor: pointer; }

.table-sm th,
.table-sm td { padding: 8px 12px; }

.is-numeric { text-align: right; }

.col-actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

.table-actions {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
}

/* --- Definition list: the single-record counterpart of a table ---
   Each row's hairline is drawn by two boxes, so dt and dd must MEET: a
   column-gap here breaks every rule into two dashes with a hole between them.
   The gutter is dt's own trailing padding instead. */
.data-list {
  display: grid;
  grid-template-columns: 1fr;
  margin: 0;
}
.data-list dt {
  padding-block: 8px 0;
  font-size: var(--type-body-xs-size);
  color: var(--text-secondary);
}
.data-list dd {
  margin: 0;
  padding-block: 0 10px;
  border-block-end: var(--border-width) solid var(--border-subtle);
}
/* The last row is closed by the card edge or by whatever follows — a rule of
   its own reads as a total that got underlined twice. */
.data-list dd:last-of-type { border-block-end: 0; }

@media (min-width: 30rem) {
  .data-list { grid-template-columns: minmax(120px, max-content) 1fr; }
  .data-list dt {
    padding-block: 10px;
    padding-inline-end: var(--space-5);
    border-block-end: var(--border-width) solid var(--border-subtle);
  }
  .data-list dd { padding-block: 10px; }
  .data-list dt:last-of-type { border-block-end: 0; }
}


/* ==========================================================================
   6. TOPBAR & PRIMARY NAV
   ========================================================================== */

.topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-topbar);
  background-color: var(--surface-primary);
  border-block-end: var(--border-width) solid var(--border-subtle);
}

.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  height: var(--topbar-h);
}

/* The auto margin, not the flex grow, is what puts the nav and the account
   cluster side by side at the trailing edge: the field stops growing at its
   max-width, and space-between would then spread the leftover evenly across
   all four children and leave the nav stranded mid-row. An auto margin eats
   the whole remainder before justify-content gets a say. */
.topbar-search {
  flex: 1 1 220px;
  max-width: 380px;
  margin-inline-end: auto;
}
/* Chrome, not a form field: --control-h is sized for pages you fill in, and at
   40px this was the tallest thing in a 60px bar. It matches the sort trigger at
   32px instead. Stays full height in the wrapped mobile row below 64rem, where
   it is the row and wants a touch target.

   Handed down as inherited custom properties, never as .topbar-search .form-control
   — .form-control and .search are shared components, and a two-class selector
   from an ancestor outranks any size variant a future form drops in here. */
@media (min-width: 64rem) {
  .topbar-search {
    --control-h: var(--control-h-sm);
    --search-btn: 26px;
  }
}

.topbar-brand {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}
.topbar-brand img {
  height: 34px;
  width: auto;
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.nav-link {
  position: relative;
  /* Flex, not inline text: every nav link is icon + label, and a flex row is
     the only way the two keep a gap once the drawer stacks them (whitespace
     between flex items is discarded). */
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Both hooks default to a no-op, which is what keeps .nav-link usable
     outside the nav: the account-menu <button> in .nav-meta and the admin
     profile link wear this class too, and only .nav-panel sets them. */
  align-self: var(--nav-link-self, auto);
  padding-inline: var(--nav-link-pad, 0);
  font-size: var(--type-ui-size);
  color: var(--text-secondary);
  transition: color var(--motion-fast) var(--ease-standard);
}
.nav-link:hover {
  color: var(--text-link);
  text-decoration: none;
}

/* Active marker, drawer form (the default — the desktop tab bar overrides it
   below 'min-width: 64rem'). A stacked list takes the leading-edge marker the
   sidebar already uses, not a horizontal bar: an inset shadow rather than a
   border, so the label does not shift 3px against its inactive siblings. */
.nav-link.is-active {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  border-radius: var(--radius-control);
  box-shadow: inset 3px 0 0 0 var(--color-copper);
}

.nav-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-body-sm-size);
  color: var(--text-secondary);
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--text-primary);
  background: none;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
}

.nav-panel {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* Shoulders for the links this panel owns, in both forms: room for the
     drawer's leading marker, and the tab's hit area on desktop. */
  --nav-link-pad: 12px;
  width: 280px;
  max-width: 85vw;
  padding: var(--space-5) var(--gutter);
  background-color: var(--surface-primary);
  border-inline-start: var(--border-width) solid var(--border-subtle);
  /* `transform` alone parks the drawer off-screen but leaves every link in
     the tab order, so a keyboard user tabs into an invisible menu. Visibility
     takes it out; the delayed transition lets it finish sliding out first. */
  visibility: hidden;
  transform: translateX(100%);
  transition:
    transform var(--motion-standard) var(--ease-standard),
    visibility 0s linear var(--motion-standard);
}
.nav-panel.is-open {
  visibility: visible;
  transform: translateX(0);
  transition: transform var(--motion-standard) var(--ease-standard);
}

@media (min-width: 64rem) {
  .nav-toggle { display: none; }
  .nav-panel {
    position: static;
    z-index: auto;
    flex-direction: row;
    align-items: center;
    /* 12px shoulders on each link already separate the labels; a --space-5 gap
       on top of them would push adjacent tabs 48px apart. 4px keeps
       label-to-label at 28px and lets the tabs almost touch, which is what
       makes the row read as a tab bar. */
    gap: var(--space-1);
    width: auto;
    max-width: none;
    padding: 0;
    border: 0;
    visibility: visible;
    transform: none;
    transition: none;
    /* Stretch the panel, then its links, to the full 60px topbar so the marker
       has a bottom edge to sit on. */
    align-self: stretch;
    --nav-link-self: stretch;
  }

  /* Objednávky and Košík are icon-only in the tab bar; the word survives as
     the link's aria-label and its tooltip. The drawer has room, so it keeps
     the word — which is why this collapses here and not the other way round. */
  .nav-label { display: none; }

  /* Horizontal nav, so the marker becomes a bar under the tab. -1px pulls it
     over the topbar's own hairline: the copper replaces that line for the
     width of the tab instead of stacking a second line above it. */
  .nav-link.is-active {
    background-color: transparent;
    box-shadow: none;
  }
  .nav-link.is-active::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: -1px;
    height: 2px;
    background: var(--color-copper);
  }
}

/* Below the drawer breakpoint the header wraps to two rows: brand and the
   account cluster on the first, the search field across the second. Keep
   --topbar-h in step with what this actually measures — the floating flash
   stack and the sticky checkout rail are positioned from it. */
@media (max-width: 63.98rem) {
  /* 10 + 44 (the toggle buttons set row one) + 10 + 40 (--control-h) + 10. */
  :root { --topbar-h: 114px; }

  .topbar-inner {
    flex-wrap: wrap;
    height: auto;
    padding-block: 10px;
    row-gap: 10px;
  }
  .topbar-search {
    order: 3;
    flex-basis: 100%;
    max-width: none;
    margin-inline-end: 0;
  }
}

/* Snapped to the 30rem contract breakpoint (the brand sheet quotes 36rem;
   the contract wins — one fewer reflow point across the whole app). */
@media (max-width: 29.98rem) {
  .topbar-brand img { height: 34px; }
}

/* --- Mini cart --- */
.mini-cart {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-secondary);
  transition: color var(--motion-fast) var(--ease-standard);
}
.mini-cart:hover {
  color: var(--text-link);
  text-decoration: none;
}

.mini-cart-count {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 4px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-on-copper);
  background-color: var(--color-copper);
  border-radius: var(--radius-control);
}
.mini-cart-count[hidden] { display: none; }
.mini-cart-count.is-bumped { animation: bump var(--motion-standard) var(--ease-expressive); }


/* ==========================================================================
   7. ADMIN SIDEBAR & TOPBAR
   ========================================================================== */

.sidebar {
  position: sticky;
  inset-block-start: 0;
  align-self: start;
  display: flex;
  flex-direction: column;
  max-height: 100dvh;
  overflow-y: auto;
  padding-block: var(--space-5);
  background-color: var(--surface-primary);
  border-inline-end: var(--border-width) solid var(--border-subtle);
}

.sidebar-brand {
  display: flex;
  align-items: center;
  padding-inline: var(--space-5);
  margin-block-end: var(--space-6);
}
.sidebar-brand img {
  height: 34px;
  width: auto;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding-inline: var(--space-5);
  font-size: var(--type-ui-size);
  color: var(--text-secondary);
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard);
}
.sidebar-link:hover {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  text-decoration: none;
}
.sidebar-link.is-active {
  color: var(--text-primary);
  font-weight: 700;
  background-color: var(--color-neutral-050);
  /* Inset box-shadow rather than a border, so the 3px marker does not shift
     the label by 3px relative to its inactive siblings. */
  box-shadow: inset 3px 0 0 0 var(--color-copper);
}

.sidebar-foot {
  margin-block-start: auto;
  /* No inline padding: .sidebar-link rows carry their own, and doubling it
     misaligns the footer entries against the nav above. */
  padding: var(--space-5) 0 0;
  border-block-start: var(--border-width) solid var(--border-subtle);
  font-size: var(--type-body-xs-size);
  color: var(--text-secondary);
}

@media (max-width: 63.98rem) {
  .sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    /* Fixed drawer must fill inset-block: 0 — the desktop align-self: start
       would otherwise shrink it to content height (Box Alignment §5.1). */
    align-self: stretch;
    z-index: var(--z-overlay);
    width: var(--sidebar-w);
    max-height: none;
    transform: translateX(-100%);
    transition: transform var(--motion-standard) var(--ease-standard);
  }
  .sidebar.is-open { transform: translateX(0); }
}

.scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: rgba(41, 41, 39, 0.45);
  opacity: 1;
  transition: opacity var(--motion-standard) var(--ease-standard);
}
.scrim[hidden] { display: none; }

.admin-topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  /* min-height, not height: long page titles wrap on narrow screens. */
  min-height: 52px;
  padding-block: var(--space-2);
  padding-inline: var(--gutter);
  background-color: var(--surface-primary);
  border-block-end: var(--border-width) solid var(--border-subtle);
}


/* ==========================================================================
   8. MENU (dropdown)
   ========================================================================== */

.menu-anchor { position: relative; }

.menu {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + 4px);
  z-index: var(--z-dropdown);
  min-width: 200px;
  padding-block: 4px;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  box-shadow: var(--shadow-subtle);
}
.menu[hidden] { display: none; }

/* Anchored to the trigger's leading edge instead — for pickers that sit on the
   page's leading edge, where the default would throw the panel off to the left
   of the control it belongs to. */
.menu-start {
  inset-inline-start: 0;
  inset-inline-end: auto;
}

/* THE ONLY html.js-scoped rules in this stylesheet. Nothing can toggle a
   disclosure without JavaScript, so a panel that stays hidden takes its links
   with it — and for the sort and filter pickers those links are the only way
   to reach the control at all. Without JS the panel stops being an overlay and
   renders inline as a plain list; the trigger goes, since it would do nothing.
   Beats the [hidden] rule above on specificity, which is the point.

   Scoped to .menu-picker, NOT to .menu: the account menu is the same component
   in a fixed-height topbar row, and unfolding four links and two separators
   inline there bursts the header on every page. */
html:not(.js) .menu-picker .menu[hidden] {
  display: block;
  position: static;
  min-width: 0;
  border: 0;
  box-shadow: none;
}
html:not(.js) .menu-picker [data-action="menu:toggle"] { display: none; }

.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px 14px;
  font-size: var(--type-ui-size);
  color: var(--text-secondary);
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard);
}
.menu-item:hover {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  text-decoration: none;
}
/* The chosen option in a menu that stands in for a <select>. The check is the
   marker; weight and ink only back it up, so the row does not reflow. */
.menu-item.is-active {
  color: var(--text-primary);
  font-weight: 600;
}
.menu-item.is-active i {
  margin-inline-start: auto;
  color: var(--color-copper);
}

.menu-sep {
  height: 0;
  margin-block: 4px;
  border: 0;
  border-block-start: var(--border-width) solid var(--border-subtle);
}


/* ==========================================================================
   9. DIALOG (<dialog>)
   ========================================================================== */

/* padding:0 is load-bearing, not cosmetic. Backdrop-click dismissal is
   detected by comparing the click coordinates against the dialog's own
   border box; any padding on <dialog> becomes part of that box, so clicks in
   the padding band read as "inside the dialog" and the panel never closes.
   All inner spacing belongs to .dialog-panel and its parts. */
.dialog {
  padding: 0;
  border: 0;
  background: transparent;
  /* Explicit width: a <dialog> is fit-content by default, and a panel made
     only of form fields would collapse to ~300px. */
  width: min(640px, calc(100vw - 32px));
  max-width: min(640px, calc(100vw - 32px));
  max-height: calc(100dvh - 64px);
  color: var(--text-primary);
}
.dialog-lg { max-width: min(880px, calc(100vw - 32px)); }

.dialog::backdrop { background: rgba(41, 41, 39, 0.45); }

/* Clipped so the header's divider and the tinted footer meet the rounded
   corners. Safe here because every focusable control sits inside .dialog-body,
   which is inset by var(--space-5) — well clear of the edge. */
.dialog-panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: inherit;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  box-shadow: var(--shadow-cinematic);
}

.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 14px 20px;
  border-block-end: var(--border-width) solid var(--border-subtle);
}

.dialog-title {
  font-family: var(--font-headline);
  font-size: var(--type-h4-size);
  font-weight: 600;
  letter-spacing: var(--type-heading-ls);
}

.dialog-body {
  flex: 1;
  /* min-height: 0 — same flex automatic-minimum trap as .admin-main.
     Without it the body never scrolls and the whole panel scrolls instead. */
  min-height: 0;
  padding: var(--space-4);
  overflow-y: auto;
}

.dialog-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 20px;
  background-color: var(--color-neutral-050);
  border-block-start: var(--border-width) solid var(--border-subtle);
}

dialog[open] .dialog-panel {
  animation: dialog-in var(--motion-fast) var(--ease-expressive);
}


/* ==========================================================================
   10. BADGES
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  padding: 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
  vertical-align: middle;
  border-radius: var(--radius-control);
}

.badge-neutral {
  color: var(--tone-neutral-text);
  background-color: var(--tone-neutral-bg);
  border: var(--border-width) solid var(--tone-neutral-border);
}
.badge-copper {
  color: var(--tone-copper-text);
  background-color: var(--tone-copper-bg);
  border: var(--border-width) solid var(--tone-copper-border);
}
.badge-success {
  color: var(--tone-success-text);
  background-color: var(--tone-success-bg);
  border: var(--border-width) solid var(--tone-success-border);
}
.badge-warning {
  color: var(--tone-warning-text);
  background-color: var(--tone-warning-bg);
  border: var(--border-width) solid var(--tone-warning-border);
}
.badge-danger {
  color: var(--tone-danger-text);
  background-color: var(--tone-danger-bg);
  border: var(--border-width) solid var(--tone-danger-border);
}
.badge-info {
  color: var(--tone-info-text);
  background-color: var(--tone-info-bg);
  border: var(--border-width) solid var(--tone-info-border);
}
.badge-outline {
  color: var(--text-secondary);
  background-color: transparent;
  border: var(--border-width) solid var(--border-subtle);
}
.badge-solid {
  color: var(--text-on-copper);
  background-color: var(--color-copper);
  border: var(--border-width) solid var(--color-copper);
}

/* Order-status badges — tinted, hairline, with a status dot so state is
   never signalled by colour alone. Tones are the canonical mapping in
   templates/_macros.html; do not hand-write a status if-chain. */
.badge-status {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.3125rem 0.5625rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
  vertical-align: middle;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
}
.badge-status::before {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentColor;
}
.badge-status.is-pending    { color: #585857; background: #F5F5F5; border-color: #E0E0E0; }
.badge-status.is-processing { color: #7A3D0F; background: #FAF2EC; border-color: #E9D3C0; }
.badge-status.is-shipped    { color: #2C4A5A; background: #EFF4F7; border-color: #D3E0E7; }
.badge-status.is-delivered  { color: #1E4D38; background: #EEF5F1; border-color: #CFE2D8; }
.badge-status.is-cancelled  { color: #7A2620; background: #FBF0EF; border-color: #EBCFCC; }

.badge-count {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 4px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-on-copper);
  background-color: var(--color-copper);
  border-radius: var(--radius-control);
}


/* ==========================================================================
   11. FLASH MESSAGES
   ========================================================================== */

.flash-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block-end: var(--space-5);
}
.flash-stack.is-floating {
  position: fixed;
  inset-block-start: calc(var(--topbar-h) + 16px);
  inset-inline-end: 16px;
  z-index: var(--z-toast);
  max-width: 380px;
  margin-block-end: 0;
}

.flash[hidden] { display: none; }
.flash {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 10px 14px;
  font-size: var(--type-ui-size);
  color: var(--text-primary);
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  /* The tone stripe is an inset shadow rather than a thicker inline-start
     border: a 3px edge meeting 1px edges miters into a visible wedge across a
     rounded corner, while an inset shadow follows the arc. Same idiom as
     .sidebar-link.is-active and .radio-card:has(input:checked). The 18px
     padding replaces what the 3px border used to contribute (1 + 18 = 19). */
  --flash-accent: var(--color-neutral-500);
  padding-inline-start: 16px;
  box-shadow: inset 3px 0 0 0 var(--flash-accent);
  transition:
    opacity var(--motion-fast) var(--ease-standard),
    transform var(--motion-fast) var(--ease-standard);
}
.flash-success {
  color: var(--tone-success-text);
  background-color: var(--tone-success-bg);
  border-color: var(--tone-success-border);
  --flash-accent: var(--color-success);
}
.flash-warning {
  color: var(--tone-warning-text);
  background-color: var(--tone-warning-bg);
  border-color: var(--tone-warning-border);
  --flash-accent: var(--color-warning);
}
.flash-danger {
  color: var(--tone-danger-text);
  background-color: var(--tone-danger-bg);
  border-color: var(--tone-danger-border);
  --flash-accent: var(--color-danger);
}
.flash-info {
  color: var(--tone-info-text);
  background-color: var(--tone-info-bg);
  border-color: var(--tone-info-border);
  --flash-accent: var(--color-info);
}

.flash-close {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin-inline-start: auto;
  padding: 0;
  color: currentColor;
  background: none;
  border: 0;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--motion-fast) var(--ease-standard);
}
.flash-close:hover { opacity: 1; }

.flash.is-leaving {
  opacity: 0;
  transform: translateY(-4px);
}


/* ==========================================================================
   12. PAGINATION
   ========================================================================== */

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  margin-block-start: var(--space-6);
}

.page-link {
  display: grid;
  place-items: center;
  min-width: 32px;
  min-height: 32px;
  padding: 0 10px;
  font-size: var(--type-body-sm-size);
  color: var(--text-secondary);
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  /* Square in the middle on purpose: the -1px below collapses adjacent
     hairlines into one segmented bar, and a uniform radius would open a
     lens-shaped gap at every seam. Only the two outer ends own a corner. */
  border-radius: var(--radius-none);
  margin-inline-start: -1px;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    color var(--motion-fast) var(--ease-standard);
}
.page-link:first-child {
  border-start-start-radius: var(--radius-control);
  border-end-start-radius: var(--radius-control);
}
.page-link:last-child {
  border-start-end-radius: var(--radius-control);
  border-end-end-radius: var(--radius-control);
}
.page-link:hover {
  color: var(--text-primary);
  background-color: var(--color-neutral-050);
  text-decoration: none;
}
.page-link.is-active {
  color: var(--text-on-copper);
  background-color: var(--color-copper-hover);
  border-color: var(--color-copper-hover);
}
.page-link.is-disabled {
  color: var(--text-muted);
  pointer-events: none;
}


/* ==========================================================================
   13. BREADCRUMB
   ========================================================================== */

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  font-size: var(--type-body-sm-size);
  color: var(--text-muted);
  list-style: none;
}
.breadcrumb li {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The wedge is a separator, so it goes on every item except the first. */
.breadcrumb li:not(:first-child)::before {
  content: "";
  float: none;
  flex: 0 0 auto;
  width: 10px;
  height: 6px;
  padding: 0;
  background: var(--color-copper);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.breadcrumb li:last-child { color: var(--text-primary); }


/* ==========================================================================
   14. EMPTY STATE
   ========================================================================== */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-4);
  padding: var(--space-6) 20px;
  background-color: var(--color-neutral-050);
  border-radius: var(--radius-subtle);
}

.empty-state-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  font-size: 1.25rem;
  color: var(--color-neutral-500);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
}

.empty-state-title {
  font-family: var(--font-headline);
  font-size: var(--type-h4-size);
  font-weight: 600;
  letter-spacing: var(--type-heading-ls);
  color: var(--text-primary);
}

.empty-state-text {
  max-width: 44ch;
  margin-inline: auto;
  color: var(--text-secondary);
}

.empty-state-action { margin-block-start: var(--space-2); }


/* ==========================================================================
   15. STAT CARDS
   ========================================================================== */

/* The tone marker is an inset shadow, not a 3px inline-start border, for the
   same reason as .flash: a thick edge against three hairlines miters into a
   wedge across the card's rounded corner. Routing the colour through a custom
   property keeps the marker and the hover elevation from fighting over the one
   box-shadow property, and it aligns the content with every other card in the
   same .stat-grid instead of indenting it by 3px. */
.stat-card {
  --stat-accent: var(--border-subtle);
  /* Three-or-more across a grid: the roomier card padding would push the
     dashboard's tile row past a laptop fold. */
  --card-pad: 16px;
  box-shadow: inset 3px 0 0 0 var(--stat-accent);
  transition:
    border-color var(--motion-standard) var(--ease-standard),
    box-shadow var(--motion-standard) var(--ease-standard);
}
.stat-card:hover {
  box-shadow: inset 3px 0 0 0 var(--stat-accent), var(--shadow-sm);
}
.stat-card.is-primary { --stat-accent: var(--color-copper); }
.stat-card.is-success { --stat-accent: var(--color-success); }
.stat-card.is-warning { --stat-accent: var(--color-warning); }
.stat-card.is-info    { --stat-accent: var(--color-info); }
.stat-card.is-danger  { --stat-accent: var(--color-danger); }

.stat-icon {
  font-size: 1.25rem;
  color: var(--border-subtle);
}

.stat-value {
  font-family: var(--font-headline);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.stat-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-neutral-500);
}


/* ==========================================================================
   16. SPINNER & SKELETON
   ========================================================================== */

.spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid var(--color-neutral-200);
  border-block-start-color: var(--color-copper);
  border-radius: var(--radius-full);
  animation: spin 700ms linear infinite;
}
.spinner-lg {
  width: 24px;
  height: 24px;
  border-width: 3px;
}

.skeleton {
  background-color: var(--color-neutral-050);
  border-radius: var(--radius-control);
  background-image: linear-gradient(
    90deg,
    var(--color-neutral-050) 0%,
    var(--color-neutral-200) 50%,
    var(--color-neutral-050) 100%
  );
  background-size: 200% 100%;
  animation: shimmer 1400ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  /* The global block freezes the animation mid-sweep; blank it out instead. */
  .skeleton { background-image: none; }
}


/* ==========================================================================
   17. STEPS
   ========================================================================== */

.steps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  counter-reset: step;
  /* One shorthand: a separate margin-block-end here used to be silently
     overwritten by a `margin: 0` further down the same rule. */
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
}

.step {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--type-body-sm-size);
  color: var(--text-muted);
  counter-increment: step;
}
.step::before {
  content: counter(step);
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  color: var(--text-muted);
  border: var(--border-width) solid var(--border-subtle);
  /* Not --radius-full: a circular step marker is the friendly-SaaS shape the
     brand explicitly rules out. Full-round stays for dots and avatars. */
  border-radius: var(--radius-control);
}
/* Wedge separator between steps — the divisional ray, used as punctuation. */
.step:not(:first-child)::after {
  content: "";
  order: -1;
  flex: 0 0 auto;
  width: 18px;
  height: 2px;
  margin-inline-end: var(--space-1);
  background: var(--border-subtle);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.step.is-done {
  color: var(--text-secondary);
}
.step.is-done::before {
  content: "\2713";
  color: var(--text-on-copper);
  background-color: var(--color-copper);
  border-color: var(--color-copper);
}
.step.is-current {
  color: var(--text-primary);
  font-weight: 700;
}
.step.is-current::before {
  color: var(--text-primary);
  border-color: var(--text-primary);
}
.step.is-todo { color: var(--text-muted); }
/* A done step is a link back to itself. Ink, not copper: it is chrome, and the
   copper in this row is already spent on the tick beside it.

   The underline is not decoration. base.css strips underlines app-wide, and
   this link replaced checkout's "Upravit košík" button — it is now the only way
   back to the cart, so it has to be visibly a link without being hovered. */
.step a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.step a:hover { color: var(--text-primary); }


/* ==========================================================================
   18. TIMELINE
   ========================================================================== */

.timeline {
  margin: 0;
  padding: 0;
  padding-inline-start: 24px;
  border-inline-start: var(--border-width) solid var(--border-subtle);
  list-style: none;
}

.timeline-item {
  position: relative;
  padding-block: 12px;
}
/* The dot straddles the rail: half the dot (4px) plus the rail (1px) plus the
   24px indent = 29px back from the item's own inline start. */
.timeline-item::before {
  content: "";
  position: absolute;
  inset-block-start: 19px;
  inset-inline-start: -29px;
  width: 8px;
  height: 8px;
  background: var(--color-neutral-500);
  border-radius: var(--radius-full);
}
.timeline-item.is-current::before {
  background: var(--color-copper);
  box-shadow: 0 0 0 3px var(--tone-copper-bg);
}

.timeline-time {
  font-size: var(--type-body-xs-size);
  color: var(--text-muted);
}

/* margin-block, not margin-block-start: a <p> keeps its prose bottom margin
   otherwise, and that 16px opened a hole between the note and the author line
   under it. */
.timeline-note {
  margin-block: 2px 0;
  color: var(--text-secondary);
}


/* ==========================================================================
   19. RADIO CARD & REVEAL
   ========================================================================== */

.radio-card {
  display: block;
  padding: 12px 16px;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
  cursor: pointer;
  /* Card-shaped, so it eases at the card tempo, not the small-control one. */
  transition:
    border-color var(--motion-standard) var(--ease-standard),
    background-color var(--motion-standard) var(--ease-standard);
}
.radio-card:hover { border-color: var(--color-neutral-500); }
/* :has() is Baseline across every browser this app supports. Without it the
   card simply stays hairline — the radio inside still shows the selection,
   so the fallback is degraded, not broken.

   Selection is a copper border plus a tinted fill, NOT the inset ring this
   used to draw: a 2px ring sitting immediately inside a 1px border is two
   concentric edges with nothing between them, which is the double-border
   effect this pass exists to remove. */
.radio-card:has(input:checked) {
  border-color: var(--color-copper);
  background-color: var(--tone-copper-bg);
}

.reveal {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--motion-standard) var(--ease-standard);
}
.reveal.is-open { grid-template-rows: 1fr; }
/* min-height:0 is what lets the 0fr row actually collapse. */
.reveal > * {
  min-height: 0;
  overflow: hidden;
}


/* ==========================================================================
   20. APP-SPECIFIC BLOCKS
   ========================================================================== */

.credits-panel {
  padding: 20px;
  background-color: var(--surface-primary);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-subtle);
}
.credits-value {
  font-family: var(--font-headline);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}
.credits-label {
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  letter-spacing: var(--type-label-ls);
  text-transform: var(--type-label-transform);
  color: var(--color-neutral-500);
}

/* The order lists (/orders and the admin user detail) used to be a bespoke
   .order-row grid. They are plain .table now: two money columns side by side
   need a header row to mean anything, and the table component already had one.
   Nothing replaced .order-row — it and its five children were deleted. */

.subnav {
  position: sticky;
  /* 52px is .admin-topbar's min-height — keep the two in step, or this sticks
     below the topbar and page content scrolls through the gap. */
  inset-block-start: 52px;
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
  background-color: var(--surface-primary);
  border-block-end: var(--border-width) solid var(--border-subtle);
}
.subnav-link {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--type-ui-size);
  color: var(--text-secondary);
  transition: color var(--motion-fast) var(--ease-standard);
}
.subnav-link:hover {
  color: var(--text-primary);
  text-decoration: none;
}
.subnav-link.is-active {
  color: var(--text-primary);
  font-weight: 700;
}
.subnav-link.is-active::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 2px;
  background: var(--color-copper);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding-inline: 8px;
  font-size: var(--type-body-xs-size);
  color: var(--text-secondary);
  background-color: var(--color-neutral-050);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
}
.chip-remove {
  display: inline-grid;
  place-items: center;
  /* 24px box, 16px glyph: the negative margin keeps the visual size while
     giving the anchor a hit area that clears the 24px WCAG target minimum. */
  width: 24px;
  height: 24px;
  margin: -4px;
  padding: 0;
  color: var(--text-muted);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-standard);
}
.chip-remove:hover { color: var(--text-primary); }

/* The divisional ray. One per composition — never a repeating bullet. */
.wedge {
  display: block;
  flex: 0 0 auto;
  width: 40px;
  height: 4px;
  background: var(--color-copper);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.wedge-sm {
  width: 18px;
  height: 2px;
}

.divider {
  height: 0;
  margin-block: var(--space-5);
  border: 0;
  border-block-start: var(--border-width) solid var(--border-subtle);
}

.avatar {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-secondary);
  background-color: var(--color-neutral-050);
  border-radius: var(--radius-full);
}


/* ==========================================================================
   21. ICONS (Phosphor)
   ========================================================================== */

i[class*="ph-"] { vertical-align: -0.125em; }

.icon-xl  { font-size: 2rem; }
.icon-2xl { font-size: 3rem; }
.icon-3xl { font-size: 4rem; }
.icon-fw  { width: 1em; text-align: center; }


/* ==========================================================================
   22. KEYFRAMES
   ========================================================================== */

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes dialog-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

@keyframes bump {
  0%, 100% { transform: scale(1); }
  30%      { transform: scale(1.25); }
}


/* ==========================================================================
   23. DOCUMENT SHELL
   The three pieces the shell needs that no other component covers: the skip
   link, the scroll lock an open overlay sets on <html>, and the error page.
   ========================================================================== */

.skip-link {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: var(--gutter);
  z-index: var(--z-toast);
  padding: 10px 16px;
  font-size: var(--type-ui-size);
  font-weight: 600;
  color: var(--text-inverse);
  background-color: var(--surface-inverse);
  /* Bottom corners only — the top edge sits flush against the viewport. */
  border-end-start-radius: var(--radius-control);
  border-end-end-radius: var(--radius-control);
  transform: translateY(-120%);
  transition: transform var(--motion-fast) var(--ease-standard);
}
.skip-link:focus {
  transform: translateY(0);
  text-decoration: none;
}

/* Set by app.js while an off-canvas panel is open, so the page behind the
   scrim cannot be scrolled away under the user's finger. */
html.is-locked { overflow: hidden; }

.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  max-width: 46ch;
  margin-inline: auto;
  padding-block: var(--space-8);
  text-align: center;
}


/* ==========================================================================
   24. TOUCH TARGETS
   Below the desktop breakpoint every interactive row clears 44px, the
   smallest reliable touch target. Above it, density wins.
   ========================================================================== */

@media (max-width: 63.98rem) {
  .btn { min-height: 44px; }
  .btn-sm { min-height: 38px; }
  .nav-link,
  .menu-item,
  .sidebar-link { min-height: 44px; }
}

/* --- §24 Cart lines — table alignment without a table's overflow ------- */
/* Grid on desktop: thumb | name | unit price | qty | line total | action.
   Below 48rem the row wraps to two lines (thumb+name, then the numbers). */
/* The column track is a custom property on the list so the two row types read
   the same value — the read-only shape (an order, not a cart) has no stepper
   and no remove button, and reserving their columns anyway left the CELKEM
   figures floating short of the card edge instead of lining up with the
   summary beside them. */
.cart-lines {
  display: flex;
  flex-direction: column;
  --line-cols: 40px 1fr 96px 116px 96px 36px;
}
.cart-lines.is-readonly { --line-cols: 40px 1fr 96px 88px 96px; }
.cart-lines-head,
.cart-line {
  display: grid;
  grid-template-columns: var(--line-cols);
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-2) 0;
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.cart-lines-head {
  padding-block: var(--space-2);
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted);
}
.cart-line:last-child { border-bottom: 0; }
.cart-line .card-media { aspect-ratio: 1 / 1; width: 40px; border-radius: var(--radius-control); }
@media (max-width: 47.98rem) {
  .cart-lines-head { display: none; }
  .cart-lines-head,
  .cart-line { grid-template-columns: 40px 1fr auto; }
  .cart-line > :nth-child(n+3) { grid-column: 2 / -1; justify-self: end; }
}

/* --- §26 Lightbox — full-size product image inside a .dialog -----------
   Radius reset to none: a card frame is chrome and can be softened, but a
   photo shown at full size is content, and clipping its corners is the one
   move that would make this look like a stock SaaS gallery. */
.lightbox .dialog-panel {
  background: transparent;
  border: 0;
  border-radius: var(--radius-none);
  box-shadow: none;
}
.lightbox img { display: block; max-width: 100%; max-height: calc(100dvh - 96px); object-fit: contain; }
