.product-config{width:min(calc(100% - 30px),1180px);margin:auto;padding:clamp(2.5rem,6vw,5rem) 0}.product-config__head{display:grid;gap:1.5rem;margin-bottom:2rem}.product-config__head h1{font-size:clamp(2.8rem,7vw,5rem);letter-spacing:-.04em;margin:.8rem 0}.product-config__head p{color:var(--concept-muted);max-width:58ch}.config-progress{display:grid;grid-template-columns:1fr auto;gap:.45rem;font-size:.8rem}.config-progress i{grid-column:1/-1;height:3px;background:var(--concept-line)}.config-progress em{display:block;width:14.2%;height:100%;background:var(--concept-brand);transition:width .25s}.product-config__layout{display:grid;border:1px solid var(--concept-line);border-radius:26px;overflow:hidden;background:var(--concept-surface);box-shadow:var(--concept-shadow)}.config-preview{padding:clamp(1.2rem,4vw,2.5rem);background:linear-gradient(145deg,#e8ddcf,#f9f7f2);display:grid;grid-template-rows:auto 1fr auto;min-height:500px}.config-preview__toolbar{display:flex;gap:.4rem;justify-content:center}.config-preview__toolbar button{padding:.45rem .8rem;border:1px solid var(--concept-line);border-radius:999px;background:white;color:var(--concept-muted)}.config-preview__toolbar button.is-active{background:var(--concept-ink);color:white}.config-preview__stage{display:grid;place-items:center}.preview-object{width:min(68vw,270px);aspect-ratio:1;position:relative;display:grid;place-items:center;border:3px solid #43220f;border-radius:24px;background:#a95f2d;box-shadow:inset 0 0 0 7px #bd7543,0 24px 42px #43220f38;transition:.25s}.preview-object:before{content:"";position:absolute;inset:14px;border:2px dashed #3b2418;border-radius:inherit}.preview-object>span{position:absolute;top:24px;font:700 1.3rem var(--serif)}.preview-object>i{font:700 1.5rem var(--serif);font-style:normal;z-index:1}.preview-object>b{position:absolute;bottom:26px;font-size:.8rem;letter-spacing:.16em}.preview-object.is-round{border-radius:50%}.preview-object.is-back [data-preview-design],.preview-object.is-back [data-preview-qr]{display:none}.preview-object.is-table-stand{aspect-ratio:4/5;border-radius:12px;transform:perspective(500px) rotateY(-7deg)}.config-preview>p{text-align:center;color:var(--concept-muted);font-size:.75rem}.config-panel{padding:clamp(1.4rem,4vw,3rem);display:flex;flex-direction:column;min-height:500px}.config-step{display:grid;gap:1.2rem}.config-step h2{font-size:clamp(2rem,5vw,3.3rem)}.config-step>p{color:var(--concept-muted)}.config-options{display:grid;gap:.7rem}.config-option{display:grid;grid-template-columns:auto 1fr auto;gap:.8rem;align-items:center;padding:1rem;border:1px solid var(--concept-line);border-radius:14px;background:white;cursor:pointer}.config-option:hover,.config-option.is-selected{border-color:var(--concept-brand);box-shadow:inset 0 0 0 1px var(--concept-brand)}.config-option input{accent-color:var(--concept-brand)}.config-option img{width:58px;height:58px;border-radius:9px;object-fit:cover}.config-option span small{display:block;color:var(--concept-muted)}.config-option em{font-style:normal;font-weight:700}.config-colors{display:flex;flex-wrap:wrap;gap:.75rem}.config-color{width:52px;height:52px;border:3px solid white;border-radius:50%;box-shadow:0 0 0 1px var(--concept-line);cursor:pointer}.config-color.is-selected{box-shadow:0 0 0 3px var(--concept-brand)}.config-upload{padding:1rem;border:1px dashed var(--concept-brand);border-radius:14px}.config-upload small{display:block;color:var(--concept-muted)}.ready-summary{display:grid;gap:1rem}.ready-summary img{width:100%;max-height:240px;object-fit:cover;border-radius:14px}.summary-list{border-top:1px solid var(--concept-line)}.summary-list div{display:flex;justify-content:space-between;gap:1rem;padding:.7rem 0;border-bottom:1px solid var(--concept-line)}.config-totals{display:flex;justify-content:space-between;margin-top:auto;padding-top:1.2rem;border-top:1px solid var(--concept-line)}.config-totals b{font-size:1.25rem}.config-nav{display:flex;gap:.7rem;margin-top:1rem}.config-nav button{min-height:46px;padding:0 1rem;border:1px solid var(--concept-ink);border-radius:12px;background:transparent}.config-nav [data-config-next],.config-nav [data-config-submit]{margin-left:auto;background:var(--concept-brand);border-color:var(--concept-brand);color:white}.config-nav [hidden]{display:none}.configurator-page .examples-modal{display:none!important}@media(min-width:860px){.product-config__head{grid-template-columns:1fr 260px;align-items:end}.product-config__layout{grid-template-columns:.9fr 1.1fr}.config-preview{border-right:1px solid var(--concept-line)}.config-options--products{grid-template-columns:repeat(3,1fr)}.config-options--products .config-option{grid-template-columns:1fr;align-content:start}.config-options--products .config-option img{width:100%;height:130px}}

/* ---------------------------------------------------------------------------
   Tag: dimension callout
   ---------------------------------------------------------------------------
   The tag box is (artboard mm x --tag-unit) and every shape is centred on a shared
   50 mm artboard, so a shape w mm wide renders exactly (w x --tag-unit) wide. That
   is what these lines are sized from — the shape's own millimetres, not a share of
   the stage, which is far wider than the tag and was why the lines used to overhang
   the outline on both sides.

   --tag-unit is defined once and used for the tag box too, so the two cannot drift. */
/* On the root, not the stage: the full preview clones the tag into a dialog
   outside the stage, and the clone is sized by this same unit. */
[data-config-root="tag"] { --tag-unit: 3.4px; }
[data-config-root="tag"] .keyring-preview__tag.has-dieline {
  width: calc(var(--tag-mm, 50) * var(--tag-unit));
  height: calc(var(--tag-mm, 50) * var(--tag-unit));
}

.tag-dim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  --w: calc(var(--tag-w-mm, 30) * var(--tag-unit, 3.4px));
  --h: calc(var(--tag-h-mm, 30) * var(--tag-unit, 3.4px));
}
.tag-dim[hidden] { display: none; }
.tag-dim__line {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--concept-muted, #687176);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0.85;
}

/* Width: a rule under the shape, spanning exactly its edges, ticked at each end. */
.tag-dim__line--w {
  left: 50%;
  top: calc(50% + var(--h) / 2 + 13px);
  width: var(--w);
  transform: translateX(-50%);
  border-top: 1px solid currentColor;
  padding-top: 3px;
}
.tag-dim__line--w::before,
.tag-dim__line--w::after {
  content: "";
  position: absolute;
  top: -4px;
  width: 1px;
  height: 9px;
  background: currentColor;
}
.tag-dim__line--w::before { left: 0; }
.tag-dim__line--w::after { right: 0; }

/* Height: the same rule on its side, to the right of the shape. */
.tag-dim__line--h {
  top: 50%;
  left: calc(50% + var(--w) / 2 + 13px);
  height: var(--h);
  transform: translateY(-50%);
  border-left: 1px solid currentColor;
  padding-left: 5px;
}
.tag-dim__line--h::before,
.tag-dim__line--h::after {
  content: "";
  position: absolute;
  left: -4px;
  height: 1px;
  width: 9px;
  background: currentColor;
}
.tag-dim__line--h::before { top: 0; }
.tag-dim__line--h::after { bottom: 0; }

/* The stage clips, and a phone leaves no room beside a 45 mm shape for a line plus
   its label. The readout under the stage still states the size. */
@media (max-width: 560px) {
  .tag-dim { display: none; }
}

.tag-size-readout {
  margin: 10px 0 0;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink, #111315);
  font-variant-numeric: tabular-nums;
}

.tag-summary-open { margin-top: 10px; width: 100%; }

/* ---- full preview dialog ---- */
/* 1006 like the design dialog: above the cookie banner (legal.css, 1000), which at 60
   covered this sheet's close button on a first visit, and below the cookie modal (1010). */
.tag-summary-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1006;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(17, 19, 21, 0.44);
  padding: 0;
}
.tag-summary-backdrop[hidden] { display: none; }
@media (min-width: 560px) {
  .tag-summary-backdrop { align-items: center; padding: 20px; }
}
.tag-summary {
  background: var(--surface, #fffdf9);
  border: 1px solid var(--line, #ded5c9);
  border-radius: 18px 18px 0 0;
  width: 100%;
  max-width: 460px;
  max-height: 92vh;
  overflow-y: auto;
  padding: 18px 18px 16px;
}
@media (min-width: 560px) { .tag-summary { border-radius: 18px; } }
.tag-summary:focus { outline: none; }
.tag-summary header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.tag-summary header h2 { margin: 2px 0 0; font-size: 19px; }
.tag-summary header button {
  border: 1px solid var(--line, #ded5c9);
  background: transparent;
  border-radius: 9px;
  width: 32px;
  height: 32px;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted, #687176);
}

/* The shape, drawn from the same <defs> the live preview uses. Sized by its own
   share of the artboard so a 16 mm rectangle is visibly smaller here too. */
.tag-summary__figure {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 22px;
  flex-wrap: wrap;
  padding: 16px 0 6px;
}
.tag-summary__face {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  --tag-unit: 5px;
}
.tag-summary__face figcaption {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--concept-muted, #687176);
}
/* The clone keeps the live preview's own classes and needs nothing here beyond a
   width to fill. It is a copy of the stage, so it already carries the tag, the
   dimension lines and the same masks. */
.tag-summary__face .keyring-preview__stage { width: 100%; }
.tag-summary__face { flex: 1 1 240px; max-width: 300px; }
.tag-summary__scalenote {
  display: block;
  font-size: 11.5px;
  color: var(--concept-muted, #687176);
  text-align: center;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--concept-line, #ded5c9);
  margin: 0 0 4px;
}
.tag-summary__rows { margin: 0; }
.tag-summary__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft, #eee8de);
}
.tag-summary__row:last-child { border-bottom: 0; }
.tag-summary__row dt {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted, #687176);
  flex: 0 0 auto;
}
.tag-summary__row dd {
  margin: 0;
  text-align: right;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink, #111315);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
}
.tag-summary__row dd small {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted, #687176);
  text-align: right;
  max-width: 26ch;
  line-height: 1.35;
}
.tag-summary__swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  border: 1px solid var(--line, #ded5c9);
  background-size: cover;
  vertical-align: -2px;
  margin-inline-end: 7px;
}
.tag-summary footer {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.tag-summary__note {
  margin: 0;
  font-size: 12px;
  color: var(--muted, #687176);
  line-height: 1.45;
}

/* The tag's engrave window is not square on every shape, so it takes an inset per
   axis. --safe-inset-y falls back to --safe-inset, which is what the keyring (one
   square window for both its shapes) keeps using. */
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__safe {
  inset: var(--safe-inset-y, var(--safe-inset, 19%)) var(--safe-inset, 19%);
}

/* ---- tag: die-line thumbnails in the option list, and in the picker preview ----
   The generic square/circle glyph could only ever be right for two of the seven
   shapes. These draw the real outline, and because every tag shape sits on the same
   50 mm artboard they are also to scale against one another — so the list shows the
   buyer that a rectangle is smaller than a fish without saying so. */
.keyring-option__shape {
  display: block;
  width: 100%;
  height: 46px;
  margin-bottom: 2px;
}
.keyring-option__shape svg {
  width: 100%;
  height: 100%;
  display: block;
}
/* The shape options are a picture plus two lines of text, so they centre rather
   than sitting on the grid the swatch options use. Both product roots are named
   on every rule in this picker: the tag and the keyring build the same markup, and
   scoping the styling to one of them is what left the keyring drawing its leathers
   as plain text rows and its shapes side-on. The holder is not listed because it
   still builds the older plain-row picker. */
[data-config-root="tag"] .keyring-options .keyring-option,
[data-config-root="keyring"] .keyring-options .keyring-option {
  display: grid;
  align-content: center;
  text-align: center;
}

/* ---- tag: leather cards ----
   Every selector here is prefixed with [data-config-root="tag"] deliberately. The
   base .keyring-options / .keyring-option rules live in keyring-configurator.css,
   which is pulled in by configurator-basket.css AFTER this file — so at equal
   specificity the base wins and the chips kept their full-width card. The attribute
   prefix outranks it instead of relying on source order.

   Narrow cards on an auto-fill grid: the whole range lands on one screen, so
   choosing a leather is comparing rather than scrolling. */
[data-config-root="tag"] .keyring-options--swatches,
[data-config-root="keyring"] .keyring-options--swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 0.5rem;
}
[data-config-root="tag"] .keyring-options--swatches .keyring-option,
[data-config-root="keyring"] .keyring-options--swatches .keyring-option {
  display: block;
  padding: 0.3rem;
  text-align: center;
}
[data-config-root="tag"] .keyring-option__chip,
[data-config-root="keyring"] .keyring-option__chip {
  display: block;
  width: 100%;
  height: 40px;
  border-radius: 7px;
  background-size: cover;
  background-position: center;
  /* A hairline, not a border colour: white leather on a cream panel needs an edge,
     and anything heavier reads as a selection state. */
  box-shadow: inset 0 0 0 1px rgba(17, 19, 21, 0.14);
}
[data-config-root="tag"] .keyring-option__chipname,
[data-config-root="keyring"] .keyring-option__chipname {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--concept-ink, #111315);
}

/* Visually hidden, still announced. The class was already used by all three
   configurator partials for the live-region that reads out the current side, but it
   was never defined anywhere — so that line, and the colour names on the leather
   chips, rendered as visible text. Defining it once fixes both.

   The clip/1px technique rather than display:none or visibility:hidden, because
   those two remove the element from the accessibility tree as well, which would
   silence the live region this exists to serve. */
.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- tag: the engraving contour ----
   The shape's own outline pulled in by the safety margin from tagEngrave.json, cut
   by a mask rather than approximated with a rectangle — a box drawn on a bone or a
   fish claims room that is not there at the ends. Dashed and faint: it is a guide
   for placing artwork, not part of the product. */
[data-config-root="tag"] .tag-safe-contour { display: none; }
[data-config-root="tag"] .tag-safe-contour.is-active { display: inline; }
[data-config-root="tag"] .tag-safe-contour rect {
  fill: none;
  stroke: rgba(255, 255, 255, 0.85);
  stroke-width: 26;
  stroke-dasharray: 90 70;
  /* The band the mask leaves is what gets stroked, so this traces the usable area. */
  paint-order: stroke;
}

/* ---- tag: the design dialog's preview ----
   Base styling is a 128px circle with a "2.5 cm" caption baked into CSS — the
   keyring, which has one round shape. A tag is one of seven outlines between 16 and
   45 mm, so it draws the real die-line and captions itself with the real size. */
[data-config-root="tag"] .keyring-modal-preview.has-dieline {
  width: calc(var(--tag-mm, 50) * var(--tag-unit));
  height: calc(var(--tag-mm, 50) * var(--tag-unit));
  border: 0;
  border-radius: 0;
  background: none;
  position: relative;
  display: block;
}
[data-config-root="tag"] .keyring-modal-preview.has-dieline > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.22));
}
/* Whatever goes on this face is confined to the engraving area, so the dialog shows
   the constraint rather than describing it. */
[data-config-root="tag"] .keyring-modal-preview.has-dieline > *:not(svg) {
  position: absolute;
  inset: var(--safe-inset-y, var(--safe-inset, 19%)) var(--safe-inset, 19%);
  display: grid;
  place-items: center;
}
[data-config-root="tag"] .keyring-modal-preview.has-dieline .keyring-art-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
[data-config-root="tag"] .keyring-modal-preview.has-dieline::after {
  content: attr(data-size-label);
}

/* ---- tag: clip the engraving to the contour ----
   --tag-safe-mask is the shape's outline with the 2.8 mm margin carved off the
   inside, supplied per shape by _data/dielines.js. Applying it to the engraved
   layers means artwork cannot sit outside the usable area even when the box that
   positions it says there is room — which is exactly the case near the ends of the
   bone and the fish.

   -webkit- prefix first: Safari still needs it, and without the pair the mask is a
   no-op there, which fails open — the artwork would look placeable where it is not. */
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__art,
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__text,
[data-config-root="tag"] .keyring-modal-preview.has-dieline > *:not(svg) {
  -webkit-mask-image: var(--tag-safe-mask, none);
  mask-image: var(--tag-safe-mask, none);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
/* The mask is drawn against the whole artboard, so the layer it clips has to cover
   the same box. The engraving is positioned inside it by the safe inset instead. */
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__art,
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__text {
  inset: 0;
  padding: var(--safe-inset-y, var(--safe-inset, 19%)) var(--safe-inset, 19%);
  display: grid;
  place-items: center;
  box-sizing: border-box;
}
[data-config-root="tag"] .keyring-modal-preview.has-dieline > *:not(svg) {
  inset: 0;
  padding: var(--safe-inset-y, var(--safe-inset, 19%)) var(--safe-inset, 19%);
  box-sizing: border-box;
}

/* The art layer is sometimes the <img> itself, not a wrapper around one — see
   sideMarkup(). The base rule sizes that image at 72% of its container, which fights
   the inset:0 above: the browser honours left/right AND width, so the image ends up
   72% of the whole artboard pinned to a corner instead of fitted to the engraving
   area. Size it to the padded box and let object-fit do the fitting. */
[data-config-root="tag"] .keyring-modal-preview.has-dieline > .keyring-art-image,
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-preview__art > .keyring-art-image,
[data-config-root="tag"] .keyring-preview__tag.has-dieline .keyring-art-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* Uploaded-file and blank placeholders are text, and were being stretched by the
   same rule; keep them centred at their natural size. */
[data-config-root="tag"] .keyring-modal-preview.has-dieline > .keyring-modal-blank,
[data-config-root="tag"] .keyring-modal-preview.has-dieline > .keyring-modal-upload,
[data-config-root="tag"] .keyring-modal-preview.has-dieline > .keyring-modal-name {
  display: grid;
  place-items: center;
  text-align: center;
}

/* One line under each group heading saying what the step does. The third group can
   legitimately be empty — nobody has to buy an accessory — and an empty card with a
   Change button on it says nothing about what changing it would offer. */
[data-config-root="tag"] .keyring-group__hint,
[data-config-root="keyring"] .keyring-group__hint {
  margin: 0.15rem 0 0.55rem;
  color: var(--concept-muted, #6b6459);
  font-size: 0.78rem;
  line-height: 1.45;
}
