/*
 * NICO Boulangerie - public site stylesheet.
 *
 * Entirely separate from the admin bundle under /assets/css. Nothing here is shared with
 * Porto Admin, Bootstrap, or any admin component: the public site optimises for page weight
 * and Core Web Vitals, the admin optimises for form density.
 *
 * THE DESIGN LANGUAGE is the artboard the owner approved: the whole site held inside ONE rounded
 * card on a warm yellow field, a bright orange accent over deep bakery brown, fully rounded
 * uppercase controls, a giant display word cropped by the hero, soft ornamental blobs, and a
 * single orange band carrying the company facts. Tall alternating sections, kicker-over-heading
 * blocks, overlapping image and text blocks and bordered feature boxes are kept from the layout
 * this site already had - the reskin changed the palette, the type and the page shell, not the
 * structure underneath them.
 *
 * The implementation is hand-written and every asset is same-origin: no framework, no CDN, no
 * webfont service, and nothing on the page depends on a script running.
 *
 * TWO TYPEFACES, AND THE SPLIT BETWEEN THEM IS THE DESIGN. Overlock sets everything that is READ -
 * body copy, leads, and every heading including the h1. Chivo sets everything that is a LABEL: the
 * navigation, the buttons, the kickers, the field labels, the fact numbers and the hero's display
 * word, all uppercase and widely tracked. That is what `--nico-font-display` is for, and it is why
 * it now exists: a heading differs from a paragraph here by SIZE, and a label differs from both by
 * FAMILY.
 *
 * Overlock ships 400, 700 and 900, and there is NO 600 and NO 800 face. The @font-face block below
 * declares the 700 file across 500-800 so that a rule asking for either gets a REAL face rather
 * than a browser-synthesised smear - read that block before adding a weight. Chivo is a VARIABLE
 * font declared `font-weight: 400 900`, so any weight in that range is a genuine instance of it
 * and the whole label system costs one file.
 */

/* ---------------------------------------------------------------- fonts */

/* Self-hosted latin subsets. No request leaves the origin, so no third-party
   connection sits on the critical path. font-display:swap keeps text painted. */
/* THE LABEL FACE, as ONE VARIABLE FILE covering 400 to 900. The range is declared rather than a
   single weight, which is what makes every weight inside it a real instance: the navigation at
   700, the fact numbers and the hero's display word at 900, all drawn from the one file already
   fetched. A static face per weight would be four requests for the same job. */
@font-face {
  font-family: 'Chivo';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('/assets/public/fonts/chivo-400-900-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/*
 * THE READING FACE, three REAL weights - and the middle one CLAIMS A RANGE.
 *
 * Overlock ships 400, 700 and 900 and nothing between them. This file and the three page-scoped
 * stylesheets beside it ask for 600 in about thirty places, inherited from the four-weight family
 * this site used to run on, and a request a family cannot answer is answered by the browser
 * SYNTHESISING one - smearing every stem by a fraction of a pixel, which is exactly the faux-bold
 * mush a real 700 face exists to avoid.
 *
 * So the 700 file is declared across 500-800 rather than at 700 alone. Every weight in that band
 * now resolves to a real face, which makes the rule structural instead of a convention thirty
 * rules have to remember. Anything genuinely wanting a heavier or lighter step than Overlock has
 * should be in the LABEL face, which is variable and has all of them.
 */
@font-face {
  font-family: 'Overlock';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/public/fonts/overlock-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Overlock';
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url('/assets/public/fonts/overlock-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Overlock';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url('/assets/public/fonts/overlock-900-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------------------------------------------------------------- tokens */

/*
 * The page is ONE ROUNDED CARD ON A WARM YELLOW FIELD. `body` paints the field and nothing else;
 * everything a reader sees - the contact strip, the header, every section, the footer - sits
 * inside `.nico-page` on the card's own surface.
 *
 * Two tokens are the whole design decision - `--nico-band-accent` and `--nico-band-dark`, at the
 * bottom of this block. Every accent surface and every dark surface on the site resolves through
 * one of them, so either is a one-line swap and neither can be half-applied. Nothing below
 * restates a literal that one of these already names.
 */
:root {
  /*
   * THE ACCENT, the brick the owner picked, and ONE token carries every terracotta job on the site
   * - the filled button, the link, the kicker - so each of those jobs inherits the same pair of
   * partners:
   *
   *   WHITE ON IT, on a filled button or the call-to-action band.
   *   IT ON THE WARMEST GROUND IT CAN LAND ON, which is NOT the card: it is --nico-accent-soft,
   *   the tinted pill, darker than the card and darker than an alternating section. The pill is
   *   the ground that binds; clear it and the other three follow.
   *
   * MEASURED, not eyeballed: over the WCAG relative-luminance formula against the exact hexes in
   * this block, white on it is 4.1:1, and as text it reads 3.5 / 3.6 / 3.8 / 4.1:1 on the pill, the
   * card, an alternating section and a white panel. AA asks 4.5:1 of normal text, so those four sit
   * under it. The value is the owner's, stated here with its numbers so the next reader measures
   * rather than assumes, and so a later lane does not "fix" it back.
   *
   * The accent on a DARK ground is a different value: --nico-accent-bright.
   */
  --nico-accent: #b6694d;

  /* THE NAVY, and it does three jobs that are one value: what an accent surface becomes on hover,
     what a ghost button is drawn in, and what every heading on the site is set in. The mockup
     draws all three in the same slate - the accent is the only warm thing on a cool page. */
  --nico-accent-dark: #2c3a45;

  /* The mockup's pale cream, for a tinted pill or a quiet notice. Ink on it is 9.9:1, and the
     accent reads 3.5:1 - the TIGHTEST of the four warm grounds a link can land on, and the one the
     accent above is measured against. */
  --nico-accent-soft: #f2ebe1;

  /*
   * THE ACCENT AS IT LOOKS ON A DARK GROUND. --nico-accent on the navy band is 2.1:1 - a label
   * nobody can read and a hover nobody can see - where this lifted terracotta is 4.7:1 on that
   * same ground. Contrast is a PAIR, and the accent on this site has two partners.
   *
   * It is not a decorative fill any more: the mockup's hero disc is cool, and that is
   * --nico-decor-cool below rather than this.
   */
  --nico-accent-bright: #e08a67;

  /* THE ONE COOL DECORATIVE FILL - the disc the hero's loaf is cut out against, the small disc
     below it and the hero's bar mark. Nothing is ever read on it, which is the whole reason a value
     this soft is allowed to exist here. */
  --nico-decor-cool: #7b8b9b;

  /*
   * THE SYSTEM-MARKER BLUE: three values, one job, and the job is NOT decoration.
   *
   * It marks a price that is agreed with THIS buyer rather than published to everybody, and the
   * owner asked for it in light blue precisely because this palette is warm - cream, brick and
   * navy - so a cool mark cannot be mistaken for the site dressing up a figure it likes. Nothing
   * else on the site may take these tokens; the day a second meaning borrows them, neither
   * meaning is readable any more.
   *
   * IT DOES NOT FIGHT THE PALETTE, and that was engineered rather than hoped for: all three sit
   * at hue 205-210, which is --nico-decor-cool's own family, and all three are desaturated hard.
   * A saturated cyan at the same lightness reads as an error state next to the brick; this reads
   * as a label.
   *
   * THREE VALUES BECAUSE CONTRAST IS A PAIR AND THIS MARK HAS THREE PARTNERS. Computed over the
   * WCAG relative-luminance formula against the exact hexes in this block:
   *
   *   --nico-tailored (text) on white 7.3:1, and on the tint below 6.3:1. Both clear AA for
   *     normal text, which the tag has to: it is the whole of the mark for a reader who cannot
   *     see the colour.
   *   --nico-tailored-edge (the rule) on white 3.9:1, and on --nico-accent-soft 3.1:1 - the
   *     tighter of the two, and the one that binds, because the cheapest rung of a price ladder
   *     is already tinted with that cream and a tailored price can land on it. Both clear the
   *     3:1 a non-text boundary needs. The obvious lighter blue (#7fb3d5) is 2.3:1 on white and
   *     fails outright, which is why the edge is a step darker than "light blue" suggests.
   *   --nico-tailored-soft is a GROUND and is never a boundary: at 1.16:1 against white it does
   *     not draw an edge and is not asked to - the rule beside it does that.
   */
  --nico-tailored: #1f5b85;
  --nico-tailored-edge: #4a87b5;
  --nico-tailored-soft: #e6f0f7;

  /* The range tiles' 7px frames, in the same navy as every other structural mark. */
  --nico-frame: #2c3a45;

  /* Ink and ground. Three levels: the navy that marks and headings are set in, the softer slate
     running copy is set in, and the muted slate for anything secondary. */
  --nico-ink: #2c3a45;
  --nico-ink-body: #3c4653;
  --nico-ink-muted: #5c6b7a;
  --nico-dark: #2c3a45;
  --nico-dark-deep: #202833;
  --nico-light: #faf7ed;
  --nico-white: #ffffff;
  --nico-border: #e3dccd;

  /*
   * THE CARD AND THE FIELD IT FLOATS ON.
   *
   * The gutter is the field showing down the SIDES of the card and under it, and it is the one
   * measurement `.nico-page`, every viewport-width calculation below and catalogue.css's
   * stepped-out order recap all have to agree on - so it is named once here rather than spelled
   * three times.
   *
   * There is no gutter above the card and no radius on its top two corners: the card is joined to
   * the top edge of the viewport so the contact strip runs the full width of the screen and the
   * sticky header comes to rest against the edge itself rather than against a band of field.
   * The radius therefore belongs to the bottom pair only, which is what `.nico-page` spells.
   *
   * THE CARD IS THE WARM SURFACE AND THE FIELD IS THE COOL ONE, which is the way round the
   * mockup draws it. They are 1.06:1 apart, so the card's edge is carried by --nico-shadow-card
   * and by nothing else: soften that shadow and the card stops being a card.
   */
  --nico-ground: #eceae6;
  --nico-surface: #f6f0e3;
  --nico-card-gutter: clamp(16px, 3vw, 56px);
  --nico-card-radius: clamp(16px, 2vw, 32px);
  --nico-card-max: 1680px;

  /* The three values every element sitting on a dark ground uses. Named rather than respelled
     per rule: the same rgba() written out five times is five hairlines that drift apart the first
     time one of them is adjusted. */
  --nico-on-dark: #eef1f4;
  --nico-on-dark-muted: #a9b4c1;
  --nico-line-dark: rgb(255 255 255 / 18%);

  /* THE TWO REASSIGNABLE SURFACES. One accent, one dark band, each behind a single token.
     Every border, icon fill, overlay and button state on a dark or accented surface resolves
     through these two - reassign here, nowhere else. */
  --nico-band-accent: var(--nico-accent);
  --nico-band-dark: var(--nico-dark);

  /* Two families, and which one an element takes says what KIND of thing it is - see this file's
     own header. Everything read is Overlock; everything labelled is Chivo. */
  --nico-font-body: 'Overlock', Georgia, 'Times New Roman', serif;
  --nico-font-display: 'Chivo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Softly rounded boxes, and FULLY rounded controls. The pill is a token rather than a literal
     999px in the six places that want it - the buttons, two badges, a location pill, the sibling
     chips and the order-state chip: every one of those is the same shape by design, and a
     hand-typed radius is how one of them stops being. */
  --nico-radius: 10px;
  --nico-radius-lg: 18px;
  --nico-radius-pill: 999px;

  /* Wide enough that the shell does not float in the middle of a 1680px card, and still capped so
     a paragraph never runs the full width of an ultrawide screen - the blocks that carry prose cap
     themselves tighter again (--prose, --narrow, --form, and every `max-width` in ch below). */
  --nico-shell: 1400px;
  --nico-gutter: clamp(1.25rem, 3vw, 3.5rem);

  /* The lift under every header dropdown, and under the mobile navigation panel - one shadow so
     the four panels in that row cannot come to sit at different heights. A wide, very soft shadow
     and no second border under it. Cast in the same navy the card's own shadow is, rather than in
     neutral black: a shadow should read as the page's own ink thinned, not as dirt on it. */
  --nico-shadow-panel: 0 10px 30px 10px rgb(43 53 66 / 10%);

  /* The card's own lift off the field, and it is DOING MORE WORK THAN A SHADOW USUALLY DOES: the
     cream card and the cool field are 1.06:1 apart, so this is the only thing drawing the card's
     edge. It is deeper than the panel shadow for that reason. */
  --nico-shadow-card: 0 30px 60px rgb(43 53 66 / 20%);

  /*
   * HOW TALL THE STICKY HEADER IS, COMPOSED RATHER THAN MEASURED.
   *
   * `.nico-header` is `position: sticky; top: 0`, so anything else on the site that sticks has to
   * come to rest BELOW it or slide underneath and lose its first line. The order flow's summary
   * panel is the one such element today (shop.css), and it first shipped with a hand-measured
   * 8rem - which was 23px short until a browser caught it, and would have been silently wrong
   * again the first time this header changed.
   *
   * So the height is derived from the four things that actually decide it, each named once and
   * each consumed by the header rule that used to spell it inline. Change the logo plate or
   * either padding below and the header and everything offsetting from it move together, because
   * there is one number rather than two that have to be remembered in the same commit.
   *
   * THE LOGO IS WHAT DRIVES IT on the desktop layout: 88px of plate against the control row's
   * 2.75rem min-height, so the taller item wins and it is the logo.
   *
   * THE WORDMARK SHRINKS AT THREE BREAKPOINTS AND THIS NUMBER FOLLOWS NONE OF THEM, which is safe
   * in all three because each one makes the token OVERSTATE the header rather than understate it -
   * anything offsetting from it then clears the header instead of sliding under it:
   *
   *   62em-82em, where the uppercase navigation needs the width: 61px of plate, so the real header
   *   is about 27px shorter than this. The order flow's sticky panel IS live in this band and takes
   *   that as extra clearance.
   *   Below 48em: 52px, and below 22.5em: 41px. Nothing sticks at either width - the order flow is
   *   one column under 62em and its panel is not sticky there.
   */
  --nico-header-logo-height: 88px;
  --nico-header-logo-pad: 0.35rem;
  --nico-header-pad-block: 0.75rem;
  --nico-header-border: 1px;
  --nico-header-height: calc(
    var(--nico-header-logo-height)
    + 2 * var(--nico-header-logo-pad)
    + 2 * var(--nico-header-pad-block)
    + var(--nico-header-border)
  );

  /*
   * THE BARE HEAD'S WORDMARK, for the checkout and the review - see `$bareChrome` in
   * layout/base.php and the `.nico-barehead` block further down.
   *
   * It is the 136x52 step the full header already shrinks to below 48em, borrowed rather than
   * invented: that is a size the asset is known to read at, and a checkout wants the mark to say
   * whose page this is rather than to lead the page. There is no height token composed from it,
   * because nothing offsets from this head - it does not stick.
   */
  --nico-barehead-logo-width: 136px;
  --nico-barehead-logo-height: 52px;
}

/* ---------------------------------------------------------------- reset */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
}

ul[class],
ol[class] {
  padding: 0;
  list-style: none;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* `body` IS THE FIELD. It paints the yellow and holds the card's gutter, and nothing else: every
   surface a reader looks at belongs to `.nico-page` below. The gutter is padding rather than a
   margin on the card so it cannot collapse against the card's own children, and it is written as
   the two block edges separately because the top one is zero - the card starts at the top of the
   viewport. */
body {
  padding-inline: var(--nico-card-gutter);
  padding-block: 0 var(--nico-card-gutter);
  font-family: var(--nico-font-body);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--nico-ink-body);
  background-color: var(--nico-ground);
  -webkit-font-smoothing: antialiased;
}

/*
 * THE CARD. One rounded plate carrying the whole site, lifted off the field by the artboard's own
 * shadow.
 *
 * The TOP TWO CORNERS ARE SQUARE and the card is flush with the top of the viewport, so the
 * contact strip reads as a band across the screen. Only the bottom pair is rounded.
 *
 * `overflow: clip` RATHER THAN `overflow: hidden`, and that is load-bearing. The rounded corners
 * only read if the footer's bottom bar is cut by them, so something has to clip - and `hidden`
 * would make this element a scroll container, which is the nearest scrollport the sticky header
 * then resolves against. A box that never scrolls is a header that never sticks. `clip` cuts
 * without creating one. Where it is unsupported the corners simply square off behind the bar,
 * which costs the page nothing a reader needs.
 */
.nico-page {
  overflow: clip;
  max-width: var(--nico-card-max);
  margin-inline: auto;
  border-radius: 0 0 var(--nico-card-radius) var(--nico-card-radius);
  background-color: var(--nico-surface);
  box-shadow: var(--nico-shadow-card);
}

img,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: var(--nico-band-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  color: var(--nico-accent-dark);
}

/* ---------------------------------------------------------------- typography */

/* Headings are the READING face at 900, in the deep bakery brown, set tight. They take their
   authority from mass and colour rather than from a contrasting family - the contrasting family on
   this site belongs to LABELS, not to headings, which is what keeps a heading and the paragraph
   under it obviously the same voice.

   900 RATHER THAN 800: Overlock has no 800 face, and asking for one gets a synthesised bold. See
   the @font-face block. */
h1, h2, h3, h4 {
  font-weight: 900;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--nico-accent-dark);
}

h1 {
  font-size: clamp(2.15rem, 6vw, 3.75rem);
  line-height: 1.06;
  letter-spacing: -0.025em;
}

h2 {
  font-size: clamp(1.7rem, 4vw, 2.6rem);
  letter-spacing: -0.02em;
}

/* The two small headings step back to 700: at label size the 900 face closes its counters and
   reads as a smudge rather than as emphasis. */
h3 {
  font-size: clamp(1.15rem, 2.2vw, 1.4rem);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.005em;
}

h4 {
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: 0;
}

/*
 * THE LABEL FACE, declared in ONE place for everything that wears it.
 *
 * Every selector below is a LABEL rather than prose: a control, an eyebrow, a field name, a column
 * header, a chip, or a figure meant to be read as a mark rather than as a sentence. They are
 * grouped here so "which typeface is this" has one answer per element instead of twenty-odd
 * `font-family` declarations scattered through the file, any one of which could be forgotten when
 * a rule moves.
 *
 * Each of those rules still owns its own size, weight, tracking and colour. This sets the family
 * and nothing else.
 *
 * THREE RULES ARE NOT IN THIS LIST and cannot be: `.nico-header__button`, `.nico-header__nojs` and
 * `.nico-footer__language-trigger` all reset `font: inherit` to escape the browser's button
 * defaults, and the shorthand would wipe a family set from here. Each states the family itself,
 * immediately after that reset.
 */
.nico-kicker,
.nico-btn,
.nico-nav__list a,
.nico-header__panel-title,
.nico-header__cart-qty,
.nico-avatar__initials,
.nico-minifooter__url,
.nico-factstrip dt,
.nico-factstrip dd,
.nico-facts dt,
.nico-facts dd,
.nico-locations__area,
.nico-locations__badge,
.nico-tile-card__area,
.nico-tile-card__badge,
.nico-location-card__badge,
.nico-footer h2,
.nico-category-card__count,
.nico-price-table thead th,
.nico-legal-table thead th,
.nico-field label,
.nico-field__label,
.nico-fieldset legend,
.nico-form__legend,
.nico-panel__title,
.nico-account-facts dt,
.nico-confirmation__label,
.nico-confirmation__number,
.nico-state,
.nico-error__code {
  font-family: var(--nico-font-display);
}

p + p {
  margin-top: 1rem;
}

.nico-lead {
  font-size: clamp(1.05rem, 2vw, 1.2rem);
  color: var(--nico-ink-muted);
}

/* The eyebrow above a section heading: the label face at 700, tracked wide, and set in the MUTED
   SLATE rather than in the accent - which is what the mockup draws, and it is what keeps the
   terracotta meaning "something to press or follow" rather than decorating every heading on the
   site. 4.8:1 on the card, so it is a quiet label and still a readable one. */
.nico-kicker {
  display: block;
  margin-bottom: 0.65rem;
  font-size: 0.775rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
}

.nico-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Keyboard-only skip link. */
.nico-skip-link {
  position: absolute;
  top: -100%;
  left: 0.5rem;
  z-index: 200;
  padding: 0.6rem 1rem;
  background: var(--nico-dark);
  color: var(--nico-white);
  border-radius: var(--nico-radius);
  text-decoration: none;
}

.nico-skip-link:focus {
  top: 0.5rem;
  color: var(--nico-white);
}

:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 3px;
}

/* ---------------------------------------------------------------- layout */

.nico-shell {
  width: 100%;
  max-width: var(--nico-shell);
  margin-inline: auto;
  padding-inline: var(--nico-gutter);
}

/* ~112px of air top and bottom at desktop. The reference's rhythm is built from a 3rem padding
   plus a 3rem margin on every block; one clamp on the section reaches the same figure without
   asking every page to spell it out. */
.nico-section {
  padding-block: clamp(3.5rem, 9vw, 7rem);
}

.nico-section--light {
  background-color: var(--nico-light);
}

/* The one dark band in the body of a page. Headings and the kicker are repainted here rather
   than at every call site, so a block moved onto this ground needs no extra class. */
.nico-section--dark {
  background-color: var(--nico-band-dark);
  color: var(--nico-on-dark);
}

.nico-section--dark h2,
.nico-section--dark h3 {
  color: var(--nico-white);
}

.nico-section--dark .nico-lead {
  color: var(--nico-on-dark-muted);
}

/* The eyebrow cannot keep the muted slate here: --nico-ink-muted on this band is 1.4:1, which is
   not a quiet label but an invisible one. It takes the accent's dark-ground value instead, which
   is the same terracotta the fact strip's labels are set in - the two dark bands on the page then
   agree about what a label on them looks like. */
.nico-section--dark .nico-kicker {
  color: var(--nico-accent-bright);
}

.nico-section__head {
  max-width: 46rem;
  margin-bottom: clamp(2rem, 5vw, 3rem);
}

.nico-section__head--center {
  margin-inline: auto;
  text-align: center;
}

/*
 * A HEAD WHOSE HEADING IS A DOCUMENT CODE RATHER THAN A SENTENCE: "Order SO-2026-NB-6GDVYP8V".
 *
 * Two rules, and each fixes a different half of one defect the owner reported on 2026-10-06 - the
 * title wrapping, with the break falling INSIDE the order number.
 *
 * THE READING MEASURE COMES OFF, because that was the cause. 46rem is a measure for PROSE and was
 * right while every section head held a lead paragraph; this one holds a kicker and the code. The
 * `h1` scales with the VIEWPORT (`clamp(2.15rem, 6vw, 3.75rem)`) while the box stopped at 736px, so
 * the wider the window the further past its own box the heading grew. Measured before this rule: at
 * a 1440px window the heading's text was 759px inside a 736px box, and at 929px it was 736px in
 * 736px - every window above that wrapped it. It is the measure that goes rather than the heading
 * that shrinks, because the heading had not outgrown the COLUMN, only the cap inside it; the real
 * bound is `.nico-account__main`, which the account grid already caps.
 *
 * THE FLOOR DROPS, for the width where no measure can help. At 390px the column is 303px and the
 * 19-character code alone was 344px at the shared 2.15rem floor - it spilled 41px past the column
 * into the gutter. 1.7rem brings it to 272px, so the heading breaks at the SPACE and the code stays
 * whole on its own line. The ceiling is untouched: nothing about a wide window wants a smaller
 * heading.
 */
.nico-section__head--code {
  max-width: none;
}

.nico-section__head--code h1 {
  font-size: clamp(1.7rem, 6vw, 3.75rem);
}

/* A single token that may not be broken - an order number, a document code. On its own element
 * because a translated string never carries markup on this site, so the token is emitted beside its
 * lead rather than inside it. Breaking at the SPACE before it is still allowed, which is what keeps
 * a phone from scrolling sideways. */
.nico-nowrap {
  white-space: nowrap;
}

.nico-section__head p {
  margin-top: 0.85rem;
}

/* Auto-fitting card grid. One declaration covers every breakpoint, so there is
   no need for the responsive column-class ladder Porto's markup relies on. */
.nico-grid {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15.5rem), 1fr));
}

/* An explicit 1 / 2 / 4 ladder, for a grid whose count is fixed by the content rather than by
   how many items happen to fit. auto-fit would drop the four range cards to three-and-one on a
   narrow desktop, which reads as a missing fourth card rather than as a wrap. */
.nico-grid--4 {
  grid-template-columns: 1fr;
}

@media (min-width: 40em) {
  .nico-grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 62em) {
  .nico-grid--4 {
    grid-template-columns: repeat(4, 1fr);
  }
}

.nico-split {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
  grid-template-columns: 1fr;
}

/* A heading inside a split column is followed straight by body copy, so it owns
   the gap itself rather than relying on a utility class on every paragraph. */
.nico-split h2,
.nico-split h3 {
  margin-bottom: 1rem;
}

@media (min-width: 62em) {
  .nico-split {
    grid-template-columns: 1fr 1fr;
  }

  /* The asymmetric variant: a narrow heading column against a wider column of copy, with the
     heading pinned to the top of it rather than optically centred against the paragraphs. It is
     the shape that carries the reference's type contrast - a very large heading in a column too
     narrow for it, so it wraps and reads as a statement rather than a label. */
  .nico-split--aside {
    align-items: start;
    grid-template-columns: 5fr 7fr;
    column-gap: clamp(3rem, 8vw, 7rem);
  }
}

.nico-split--aside .nico-split__aside h2 {
  margin-bottom: 0;
  font-size: clamp(1.9rem, 4.5vw, 3.1rem);
  line-height: 1.1;
}

/* ---------------------------------------------------------------- overlap block */

/* The reference's signature section: a photographic block and a text block that sit ON one
   another rather than side by side. Porto builds it with a flex row whose middle text panel
   carries `margin: 0 -70px` and a higher z-index than the image panels either side of it; the
   same idea here, as a grid whose two children deliberately share a column.

   MOBILE FIRST, and on a phone there is no overlap at all - the image stacks above the copy, which
   is the only honest layout when the viewport is narrower than either block wants to be. The
   overlap begins at 62em, where there is room for the text card to sit over the photograph
   without covering the part of it worth seeing.

   Rendered only when at least one photograph is present; `home.php` falls back to
   `.nico-split--aside` otherwise. Nothing in this block assumes BOTH images exist - each is
   independently optional and the layout holds with either one alone. */
.nico-overlap {
  display: grid;
  gap: clamp(1.5rem, 4vw, 2.5rem);
  grid-template-columns: 1fr;
  align-items: center;
}

.nico-overlap__media {
  position: relative;
  display: grid;
}

/* The wide plate: the photograph the text card is pulled over. */
.nico-overlap__plate {
  width: 100%;
  height: 100%;
  min-height: 16rem;
  max-height: 30rem;
  object-fit: cover;
  border-radius: var(--nico-radius);
}

/* The tall portrait panel. On a phone it is suppressed rather than stacked: two photographs and a
   block of copy in one column is a long scroll for one idea, and this is the one of the two that
   only exists to build the overlap. */
.nico-overlap__panel {
  display: none;
}

/* The text card. On the light sections it is the page's own white, so the card reads as a plate
   lifted off the photograph rather than as a tinted box. */
.nico-overlap__body {
  position: relative;
  z-index: 1;
  background-color: var(--nico-white);
}

@media (min-width: 62em) {
  .nico-overlap {
    /* Six tracks, so the two children can be given overlapping spans. A two-column grid cannot
       express an overlap - the children would have to share a cell and lose their own widths. */
    grid-template-columns: repeat(6, 1fr);
    align-items: center;
  }

  .nico-overlap__media {
    /* Columns 1-4. */
    grid-column: 1 / 5;
    grid-row: 1;
  }

  .nico-overlap__body {
    /* Columns 4-7 - so column 4 is shared, and THAT is the overlap. */
    grid-column: 4 / -1;
    grid-row: 1;
    padding: clamp(2rem, 4vw, 3.25rem);
    /* The card has to read as lifted, not as a white rectangle that happens to sit there. A soft
       shadow does it without a border, which would draw a hard edge across the photograph. */
    box-shadow: 0 1.5rem 3rem rgb(28 31 36 / 14%);
  }

  .nico-overlap__plate {
    max-height: 34rem;
  }

  .nico-overlap__panel {
    /* Offset out of the media block's top-left corner, so the two photographs themselves overlap
       as well - the reference's stacked-plates look. Decorative depth only: it is inert to
       pointer events so it can never swallow a click meant for the card. */
    position: absolute;
    left: clamp(-3rem, -3vw, -1.5rem);
    bottom: clamp(-2.5rem, -3vw, -1.25rem);
    z-index: 2;
    display: block;
    width: clamp(8rem, 13vw, 12rem);
    height: auto;
    object-fit: cover;
    border: 0.5rem solid var(--nico-white);
    border-radius: var(--nico-radius);
    pointer-events: none;
  }
}

/* ---------------------------------------------------------------- buttons */

/* FULLY ROUNDED PILLS, in the label face, uppercase and widely tracked - the artboard's control
   shape. The 2px border is the ghost variant's whole appearance, so it is declared here rather
   than on that modifier alone: a 1px resting border would make the primary button a pixel shorter
   than the ghost standing beside it. */
.nico-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.85rem 2rem;
  border: 2px solid transparent;
  border-radius: var(--nico-radius-pill);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

/* The accent fill under a WHITE label, which is the artboard's own pairing - it works here because
   the accent token was darkened until it did (5.6:1; see that token). Ink stood here for a while
   on the assumption that white could not clear AA on an orange, and measuring the rendered colours
   put that the wrong way round: ink on this accent is 3.6:1 and white is 5.6:1, because the accent
   sits below mid-luminance. The hover then goes deep brown and the white label reads 10.5:1. */
.nico-btn--primary {
  background-color: var(--nico-band-accent);
  border-color: var(--nico-band-accent);
  color: var(--nico-white);
}

.nico-btn--primary:hover {
  background-color: var(--nico-accent-dark);
  border-color: var(--nico-accent-dark);
  color: var(--nico-white);
}

.nico-btn--ghost {
  background-color: transparent;
  border-color: currentcolor;
  color: inherit;
}

/* The fill arrives in the DEEP BROWN rather than in white, which is a correctness fix as much as a
   reskin: this modifier is used on the error pages and in the account portal, all of them light
   grounds, where a white fill made the button vanish under the pointer. Brown works on every
   ground it is actually used on, and the white label reads 10.5:1 against it. */
.nico-btn--ghost:hover {
  background-color: var(--nico-accent-dark);
  border-color: var(--nico-accent-dark);
  color: var(--nico-white);
}

/* THE ARTBOARD'S SECOND CALL TO ACTION: a 2px brown outline, wide gutters, and the brown arriving
   as a fill on hover. Deliberately not .nico-btn--ghost - that one takes its border from
   `currentcolor`, so it adapts to whatever ground it is dropped on, where this one always states
   the brown. */
.nico-btn--outline {
  padding-inline: 2.5rem;
  background-color: transparent;
  border-color: var(--nico-accent-dark);
  color: var(--nico-accent-dark);
}

.nico-btn--outline:hover {
  background-color: var(--nico-accent-dark);
  border-color: var(--nico-accent-dark);
  color: var(--nico-white);
}

/*
 * SECONDARY LEFT, PRIMARY RIGHT - the owner's ruling, applied in ONE place rather than by
 * reordering the markup of the thirty-five views that use this class.
 *
 * `justify-content: flex-end` is NOT decoration and must not be dropped: `row-reverse` moves the
 * main-axis START to the right-hand edge, so the default `flex-start` would pack every button row
 * against the right of its container as well as reversing it. `flex-end` puts the packing back on
 * the left, which leaves the row exactly where it was and changes only the order within it. The
 * centred variants below - `.nico-btn-row--center`, and the `.nico-cta`, `.nico-error` and
 * `.nico-empty` rules further down - all set `justify-content: center`, which reads the same in
 * either direction, so each still wins on source order or specificity and still centres.
 *
 * THE COST, NAMED: DOM order is tab order, and this rule does not change the DOM. The primary
 * button is still first in the source, so a keyboard reaches it FIRST while it is drawn on the
 * RIGHT, and focus then moves right-to-left. For a two-control row that is a minor WCAG 2.4.3
 * (focus order) mismatch rather than a trap - nothing is unreachable, and a screen reader still
 * meets the primary action first - but it IS a mismatch, and no CSS reordering can avoid one.
 * Reordering the markup is the only way to keep the two in step; it was not taken here because the
 * sweep would have to cover account/companies.php, which this change was not permitted to touch,
 * and a sitewide rule that skips one view is worse than one that covers all of them. When those
 * views are next edited, reverse the two controls in the source and delete this rule.
 */
.nico-btn-row {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-top: 2rem;
}

.nico-btn-row--center {
  justify-content: center;
}

/* ---------------------------------------------------------------- header */

/* The contact strip, and it is the one place on this site where the READING face is used at label
   size on purpose: the artboard sets both halves of this line in Overlock 700, so the strip reads
   as a sentence the bakery is saying rather than as another row of controls. */
.nico-topbar {
  background-color: var(--nico-dark);
  color: var(--nico-on-dark);
  font-size: 0.82rem;
  font-weight: 700;
}

.nico-topbar .nico-shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  padding-block: 0.55rem;
}

/* THE NUMBER IS THE ACCENT, which is what the mockup draws and which is also what separates the one
   thing in this strip a reader can act on from the sentence beside it.

   IT IS THE ACCENT'S DARK-GROUND VALUE, not --nico-band-accent: the strip is the navy, and the base
   accent on it is 2.1:1 - a number nobody can read. This one is 4.7:1 on that same ground, which a
   line of 0.82rem bold text needs in full. Contrast is a pair, and this strip is the accent's dark
   partner - see --nico-accent-bright in the tokens block.

   The hover then inverts to white rather than reaching for a third value: the pair is already
   spent, and swapping the two ends of it is a state change every reader can see. */
.nico-topbar a {
  color: var(--nico-accent-bright);
  font-weight: 700;
  text-decoration: none;
}

.nico-topbar a:hover {
  color: var(--nico-white);
}

.nico-topbar__tagline {
  margin: 0;
}

/* The header is `position: sticky`, which also makes it the containing block for the mobile
   navigation panel below - the one panel that spans the header edge to edge rather than hanging
   off its own control. */
/* THE HEADER IS THE CARD, not a white bar laid on it: the artboard draws no band behind the logo
   and the navigation, so this takes the card's own surface.

   The hairline under it is the one thing the artboard does not show and this does, and it is not
   decoration: a single artboard never scrolls, and a sticky header painted in exactly the ground
   colour of the content sliding beneath it has no edge at all. */
.nico-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background-color: var(--nico-surface);
  border-bottom: var(--nico-header-border) solid var(--nico-border);
}

.nico-header .nico-shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: var(--nico-header-pad-block);
}

/* THE MARK IS ITS OWN ASSET NOW - nico-logo-ink.png, black on transparency, trimmed and centred in
   a 460x176 frame. The rendering tricks this rule used to carry are gone with the file they were
   compensating for: the old wordmark sat on an opaque white plate, so it needed a contrast lift to
   survive being resampled and `mix-blend-mode: multiply` to knock the plate out against the card's
   cream. A transparent ink file needs neither, and a filter left on one would only tint it.

   `flex: 0 0 auto` is not decoration: the shell is a flex row, and once the control row grew a
   customer's full name the logo became the item with slack in it and was squeezed narrower than
   its own declared width - the wordmark lost its outer letters on exactly the pages where someone
   was signed in. */
.nico-header__logo {
  display: block;
  flex: 0 0 auto;
  padding-block: var(--nico-header-logo-pad);
}

/* THE BOX IS THE OWNER'S: 230x88, and the height of it is the token the header's own height is
   composed from - see the tokens block.

   `object-fit: contain` is what makes the pair safe to state as two numbers. The mark is narrower
   than its frame, so inside this box it paints at its own aspect and centres, and the day the
   asset is redrawn at a different proportion it letterboxes rather than stretching. Every
   narrower box below is this one scaled, and each is safe for the same reason. */
.nico-header__logo img {
  width: 230px;
  height: var(--nico-header-logo-height);
  object-fit: contain;
}

/* ---- the control row: menu, search, cart, sign-in

   Four controls on one line, in that order, rebuilt from Porto's header-nav-features toolbar. The
   language control is not among them - it moved to the footer's bottom bar; see
   layout/partials/footer.php.

   The `min-height` is the rule that makes the search reveal free: the row is that tall whether or
   not the search form is showing, so an input that appears cannot change the header's height, and
   the form is positioned over the row rather than inserted into it so nothing beside it reflows
   either.

   `position: relative` here is what the search form's overlay is measured against; every other
   panel hangs off its own control instead. */
.nico-header__controls {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: flex-end;
  gap: 0.15rem;
  min-height: 2.75rem;
}

.nico-header__control {
  position: relative;
  display: flex;
  align-items: center;
}

/* The menu keeps the free width, so the navigation reads from the left of the row and the three
   icon controls stay together on the right - the desktop arrangement the owner specified. Static,
   so the nav panel below measures itself against the header and spans it edge to edge.

   This is the EXPANDED-WIDTH rule. Below 62em the menu is no longer a row of words but a single
   trigger, and both declarations here are undone at that breakpoint so it joins the icons on the
   right instead. */
.nico-header__control--menu {
  position: static;
  flex: 1 1 auto;
  justify-content: flex-start;
}

/* Static for the opposite reason: the search form must span the WHOLE control row, so it resolves
   against .nico-header__controls rather than against this one control. */
.nico-header__control--search {
  position: static;
}

/* A toggle and its no-script stand-in are the same object at two levels of capability, so they are
   the same shape. Which of the two a reader sees is decided by `data-nico-js` alone - see
   layout/base.php, which sets it before first paint. */
.nico-header__button,
.nico-header__nojs {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem;
  background: none;
  border: 0;
  border-radius: var(--nico-radius);
  color: var(--nico-ink);
  /* `font: inherit` escapes the browser's button defaults and WIPES the family with them, so the
     label face is restated on the next line rather than taken from the grouped rule in the
     typography block - see the note there naming this rule. */
  font: inherit;
  font-family: var(--nico-font-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.nico-header__button {
  display: none;
}

[data-nico-js] .nico-header__button {
  display: inline-flex;
}

.nico-header__button:hover,
.nico-header__nojs:hover,
.nico-header__button[aria-expanded='true'] {
  color: var(--nico-band-accent);
}

.nico-header__button:focus-visible,
.nico-header__nojs:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 2px;
}

.nico-header__icon {
  flex: 0 0 auto;
}

/* The burger is for the panel only. At 62em the navigation is a row of links that needs no
   opening, and with no script it is a row of links at every width. */
@media (min-width: 62em) {
  [data-nico-js] .nico-header__button--menu {
    display: none;
  }
}

/* ---- the panels

   One shape for all three: a light card under its control, lifted by the shared panel shadow and
   held to the control by a hairline rather than by Porto's caret triangle - a caret over a
   bordered panel needs two stacked triangles to hide the seam where it crosses the border, and the
   border alone already anchors it.

   `display: none` is the DEFAULT and there is no rule that opens a panel without `data-nico-js`.
   That is what makes the no-script header a row of plain links rather than a row of buttons that
   do nothing: with the script absent `aria-expanded` never becomes true, so no panel can open and
   the links below take over. */
.nico-header__panel {
  display: none;
  position: absolute;
  top: calc(100% + 0.9rem);
  right: 0;
  z-index: 30;
  min-width: 18rem;
  padding: 1.15rem;
  background-color: var(--nico-white);
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  box-shadow: var(--nico-shadow-panel);
}

[data-nico-js] .nico-header__button[aria-expanded='true'] ~ .nico-header__panel {
  display: block;
}

[data-nico-js] .nico-header__nojs {
  display: none;
}

.nico-header__panel--cart {
  min-width: 19rem;
}

.nico-header__panel--account {
  min-width: 17rem;
}

/* Porto sets its sign-in dropdown wider than the rest for the same reason: it is the only panel
   holding a form, and two labelled fields in a 300px box read as cramped. */
.nico-header__panel--signin {
  min-width: 21.5rem;
  padding: 1.5rem;
}

.nico-header__panel-title {
  margin-bottom: 0.8rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
}

.nico-header__panel-empty {
  font-size: 0.88rem;
  color: var(--nico-ink-muted);
}

.nico-header__panel-action {
  margin-top: 1rem;
}

.nico-header__panel-action .nico-btn,
.nico-header__panel--signin .nico-btn,
.nico-header__panel--account .nico-btn {
  width: 100%;
  justify-content: center;
  padding-block: 0.65rem;
  font-size: 0.72rem;
}

/* ---- search

   The reveal: the form is laid over the whole control row, on the line it was always going to
   occupy. Its own close button stands in for the toggle it covers. */
.nico-header__panel--search {
  inset: 0;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
  padding: 0;
  /* THE HEADER'S OWN COLOUR, not white, so the open search reads as part of the header rather than
     as a white strip laid across it. `--nico-surface` is what `.nico-header` itself paints, so the
     two meet with no join.

     OPAQUE AND NOT TRANSPARENT, and that is the whole reason this takes a colour at all: the panel
     is `inset: 0` over the control row, so the wordmark, the navigation and the three buttons are
     still laid out underneath it. A see-through panel would show them through the search field.
     Matching the colour is what makes it LOOK transparent while still covering what it covers. */
  background-color: var(--nico-surface);
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

[data-nico-js] .nico-header__button[aria-expanded='true'] ~ .nico-header__panel--search {
  display: flex;
}

.nico-header__search-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 2.4rem;
  padding: 0 1rem;
  /* TRANSPARENT, so the field sits ON the panel rather than as a second, slightly different block
     laid over it. The panel is `--nico-white` and this box was `--nico-light`, two shades close
     enough to read as a mistake rather than as a distinction, and the join between them cut the row
     in two beside the submit and close buttons, which have never had a fill of their own.

     THE BORDER AND THE RADIUS STAY, and they are now the only thing drawing the field. An input
     with neither a fill nor an outline is a caret floating in a header - the shape is what says
     "type here", and the fill was never what carried that. */
  background-color: transparent;
  border: 1px solid var(--nico-border);
  border-radius: 1.2rem;
  color: var(--nico-ink);
  font: inherit;
  /* 1rem, never smaller, for the reason every other input rule in this file states: iOS Safari
     zooms the viewport on focus for anything under 16px. In a header that is worse than elsewhere
     - the zoom happens the instant the reveal hands the caret over, so the page jumps under
     someone who has not typed a character yet. */
  font-size: 1rem;
}

.nico-header__search-input:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 1px;
}

.nico-header__search-submit,
.nico-header__search-close {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  height: 2.4rem;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 50%;
  color: var(--nico-ink);
  cursor: pointer;
}

.nico-header__search-submit:hover,
.nico-header__search-close:hover {
  color: var(--nico-band-accent);
}

.nico-header__search-submit:focus-visible,
.nico-header__search-close:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 2px;
}

/* ---- cart */

.nico-header__badge {
  position: absolute;
  top: 0.1rem;
  right: 0.05rem;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.2rem;
  background-color: var(--nico-band-accent);
  border-radius: 0.6rem;
  color: var(--nico-white);
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1.05rem;
  text-align: center;
}

/* Scrolls rather than growing without limit: PortalCart admits fifty distinct lines, and fifty
   rows would run a dropdown off the bottom of a laptop screen. */
.nico-header__cart-lines {
  margin: 0;
  max-height: 15rem;
  overflow-y: auto;
}

/* FLEX RATHER THAN A GRID, so the name and the pack share one line and the quantity is pushed to
   the far end by `margin-left: auto`. As a two-column grid the pack was placed in column 1 under
   the name, which is what put every basket line on two rows; a product and the pack it comes in
   are one thing being ordered and read as one line. `flex-wrap` is what keeps a long name and its
   pack from forcing the panel wider than the dropdown. */
.nico-header__cart-lines li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.4rem;
  row-gap: 0;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--nico-border);
}

.nico-header__cart-lines li:last-child {
  border-bottom: 0;
}

.nico-header__cart-name {
  font-size: 0.87rem;
  font-weight: 600;
  line-height: 1.35;
}

.nico-header__cart-pack {
  font-size: 0.75rem;
  color: var(--nico-ink-muted);
}

/* The quantity, and NOTHING beside it. There is no price column here and there must not be - see
   the header partial and App\Public\Service\PortalCart. `margin-left: auto` is what holds it at
   the far end of the row now that the line is a flex row rather than a two-column grid. */
.nico-header__cart-qty {
  margin-left: auto;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--nico-band-accent);
  white-space: nowrap;
}

/* ---- the signed-in customer */

.nico-avatar {
  flex: 0 0 auto;
  border-radius: 50%;
}

.nico-avatar__disc {
  fill: var(--nico-band-accent);
}

.nico-avatar__initials {
  fill: var(--nico-white);
  font-family: var(--nico-font-body);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.nico-header__button--account {
  gap: 0.55rem;
}

/* Names on this system run long - a company contact row commonly carries four words - and an
   unbounded one would push the whole control row until something wrapped. Truncated rather than
   wrapped, and the full name is still announced: it is the avatar's `aria-label` that names this
   button, not the text. */
.nico-header__button--account .nico-header__button-text {
  display: block;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nico-header__menu {
  margin: 0;
}

.nico-header__menu a {
  display: block;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--nico-border);
  color: var(--nico-ink);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
}

.nico-header__menu li:last-child a {
  border-bottom: 0;
}

.nico-header__menu a:hover {
  color: var(--nico-band-accent);
}

.nico-header__panel--account .nico-form {
  margin-top: 0.9rem;
}

/* ---- the navigation, which is the row's first control */

/* THE GAP IS FLUID, and that is what keeps the row one row at the bottom of its own range. The
   links are uppercase and tracked at 0.1em now, which is materially wider than the sentence-case
   row this replaced - at 1024px the six of them plus the three icon controls and the sign-in
   overran the shell by 38px and the card's clip took the last letters off "SIGN IN". Measured,
   not reasoned: the fixed 1.35rem gap alone was 108px of that row. */
.nico-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 1.5vw, 1.35rem);
  margin: 0;
}

/* `nowrap` is what keeps the row ONE row. Without it "Our baking" and "Where to find us" break
   across two lines the moment the shell tightens, and the header grows a second line - which is a
   layout shift in the site's chrome at every width between two breakpoints. */
.nico-nav__list a {
  color: var(--nico-ink);
  font-size: clamp(0.72rem, 0.82vw, 0.8rem);
  font-weight: 700;
  text-decoration: none;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nico-nav__list a:hover,
.nico-nav__list a[aria-current='page'] {
  color: var(--nico-band-accent);
}

/* ---- the wholesale call to action

   IT SITS HARD RIGHT, immediately before the search, cart and sign-in marks rather than at the end
   of the navigation - the arrangement the owner's mockup draws. It is a fifth member of the control
   row for layout only: it opens no panel, it is a plain link to WhatsApp, and the four disclosure
   controls beside it are unchanged.

   IT IS ONE INSTANCE, NOT TWO. It used to be the last item of the nav list, which meant it rode
   into the collapsed menu panel below 62em; there is no such second home for it now, so the same
   width rule that already hid it between 62em and 82em is simply carried all the way down. Below
   82em the contact strip directly above the header carries the same WhatsApp link, in the accent,
   on every page - so the action is never absent from the chrome, only from this row.

   The pill keeps a size of its own because the row's other four items are 20px marks: the button
   scale from the page body would tower over them. */
.nico-header__cta .nico-btn--primary,
.nico-header__cta .nico-btn--primary:hover {
  padding: 0.6rem 1.35rem;
  color: var(--nico-white);
  font-size: 0.7rem;
}

@media (max-width: 81.99em) {
  .nico-header__cta {
    display: none;
  }
}

/* THE TIGHTEST BAND ON THE SITE: 62em to 82em, where the navigation is still a row of six and the
   shell has stopped growing.

   The call to action is already gone by this width - see its own rule above for where the action
   goes instead - so what is left to find here is the wordmark's width.

   THE WORDMARK GIVES UP WIDTH, which the uppercase navigation made necessary. Measured at
   1008px with the row as it was: the controls ended exactly on the shell's right padding with 0px
   of slack, and a signed-in customer's name is about 85px wider than "Sign in" - so the next
   person to sign in at laptop width would have watched the row run under the card's clip. The logo
   is the only item in the row that can give it back: it is `flex: 0 0 auto` and every other item
   is sized by its own content. --nico-header-height then OVERSTATES this band's header by ~27px,
   which is the safe direction - anything offsetting from it clears the header rather than sliding
   under it. */
@media (min-width: 62em) and (max-width: 81.99em) {
  .nico-header__logo img {
    width: 160px;
    height: 61px;
  }

  /* And the customer's name truncates tighter here than the 9rem it takes at desktop width. The
     wordmark bought this band 76px of free space between the navigation and the icons; a 9rem name
     is up to 84px wider than the "Sign in" it replaces, which would spend all of it and more. The
     full name is still announced - it is the avatar's `aria-label` that names this button, never
     the visible text. */
  .nico-header__button--account .nico-header__button-text {
    max-width: 6rem;
  }
}

/* Below 62em the navigation becomes the fourth dropdown, built to the same measurements as the
   other three so the row behaves one way rather than two, and its trigger MOVES TO THE RIGHT-HAND
   END OF THE ROW to sit with them. */
@media (max-width: 61.99em) {
  /* Every panel anchors to the RIGHT EDGE OF THE ROW rather than to its own control, which is what
     Porto's `-force-right` does and for the same reason: a control in the middle of the row is not
     far enough right for a 300px panel hanging off it to stay on a phone screen, and one hanging
     off the cart ran 35px past the left edge of a 375px viewport. */
  .nico-header__control {
    position: static;
  }

  /* THE COLLAPSED MENU TRIGGER, LAST OF THE FOUR - so the row reads search, cart, sign-in, menu.
     `order: 1` beats the other three, which are all at the default 0.

     LAST rather than first of the group for two reasons. The icon order the owner specified is
     search - cart - sign-in, and putting the burger ahead of them would split that trio off the
     end of the row and drop a fourth mark into the middle of it. And the nav panel already
     anchors to the right edge of the row, which used to be nowhere near its own button; with the
     trigger at that same edge the panel now opens directly beneath the thing that opened it.

     The flex rules from the expanded-width block are both undone here. `flex: 1 1 auto` would let
     this control absorb the row's slack, and `justify-content: flex-start` would then park the
     button at the left of its own stretched box - which is the bug this whole rule exists to fix,
     just moved inside one element. */
  .nico-header__control--menu {
    order: 1;
    flex: 0 0 auto;
    justify-content: flex-end;
  }

  .nico-nav__list {
    position: absolute;
    top: calc(100% + 0.9rem);
    right: 0;
    left: auto;
    z-index: 30;
    /*
     * THE CARD'S GUTTER IS PART OF THIS SUM, and `100%` is NOT the way to write it.
     *
     * The panel hangs off `.nico-header__controls`, which is `position: relative` for the search
     * overlay's sake - so `100%` here is the CONTROL ROW, which on a phone is whatever is left
     * beside the wordmark. Written that way the panel came out 98px wide and its own contents ran
     * straight out of the card. Seen in the browser at 375px, not reasoned.
     *
     * So it is measured from the viewport, minus the yellow field either side of the card and
     * minus the shell's own gutter - which is exactly the card's content width, the space this
     * panel has to live in. `min()` rather than a separate `max-width`, because the 20rem cap and
     * the fit-the-card limit are the same kind of statement and reading them together is what
     * makes it obvious which one is binding at a given width.
     *
     * `100vw` counts a classic desktop scrollbar that `clientWidth` does not, so in a narrow
     * DESKTOP window this can overshoot by that scrollbar's width and the card clips the panel's
     * left edge by the same amount. On a phone, where overlay scrollbars mean the two agree, it is
     * exact - and a phone is the only place this panel exists to serve.
     */
    width: min(
      20rem,
      calc(100vw - 2 * var(--nico-card-gutter) - 2 * var(--nico-gutter))
    );
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.35rem 1.15rem 1.1rem;
    background-color: var(--nico-white);
    border: 1px solid var(--nico-border);
    border-radius: var(--nico-radius);
    box-shadow: var(--nico-shadow-panel);
  }

  .nico-nav__list a {
    display: block;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--nico-border);
  }

  [data-nico-js] .nico-nav__list {
    display: none;
  }

  [data-nico-js] .nico-header__button--menu[aria-expanded='true'] ~ .nico-nav__list {
    display: flex;
  }
}

/* Below 48em a control keeps its icon and drops its wording - the row is four wide and "Sign in"
   next to a customer's full name does not fit a phone. The wording is CLIPPED rather than
   `display: none`, because on the sign-in button that text IS the button's accessible name, and
   removing it would leave a control that announces as an unnamed button. */
@media (max-width: 47.99em) {
  .nico-header__button-text {
    position: absolute;
    width: 1px;
    height: 1px;
    max-width: none;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  /* The logo is a fixed-width flex item that cannot shrink - see .nico-header__logo - so on a
     phone it is the wordmark rather than the control row that gives up the width. The four
     controls need about 145px of it and a 390px viewport leaves 318px of card to share. */
  .nico-header__logo img {
    width: 136px;
    height: 52px;
  }

  .nico-header .nico-shell {
    gap: 0.5rem;
  }

  .nico-header__button,
  .nico-header__nojs {
    padding: 0.45rem 0.35rem;
  }

  /* On a phone the control row is only as wide as what is left beside the wordmark - about 190px -
     and a search field in that is a slot, not a box someone can read what they typed in. The
     overlay is widened to the shell instead, so it covers the logo for as long as it is open. It
     is still the same line and still changes no height. */
  .nico-header__panel--search {
    left: auto;
    right: 0;
    /* The card's content width, spelled the same way the nav panel above spells it and for the
       same reason - `100%` here would be the control row this panel hangs off, which on a phone is
       the 190px this rule exists to escape. */
    width: calc(100vw - 2 * var(--nico-card-gutter) - 2 * var(--nico-gutter));
  }

}

/* On a phone a 300px panel hanging off a control near the right edge would run past it. Width is
   taken from the viewport instead, with the search form excluded - it spans the control row by
   design and is not a dropdown. */
@media (max-width: 34.99em) {
  .nico-header__panel:not(.nico-header__panel--search) {
    min-width: 0;
    /* The card's content width, capped - see the nav panel above for why this is measured from the
       viewport rather than written as `100%`. */
    width: min(
      21.5rem,
      calc(100vw - 2 * var(--nico-card-gutter) - 2 * var(--nico-gutter))
    );
  }
}

/* THE SMALLEST SCREEN THE SITE IS BUILT FOR. At a 320px viewport the card is 273px wide and the
   shell 233px of that, against a 140px wordmark and four controls - the row ran 11px past the
   card's right edge and the burger was the half a thumb could not reach. It did that before the
   card existed too; the difference is that the card CLIPS the overflow where the page used to
   scroll sideways to reveal it, which is a worse failure and a more visible one.

   The wordmark gives up the width, for the reason the 48em rule above already states: it is a
   fixed-width flex item that cannot shrink, so it is the only thing in the row with width to give.
   108x41 is the 230x88 plate scaled down, which is the proportion every other box here keeps. */
@media (max-width: 22.49em) {
  .nico-header__logo img {
    width: 108px;
    height: 41px;
  }
}

/* ---------------------------------------------------------------- hero */

/*
 * THE HERO, built to the approved artboard: a giant display word cropped into the top right of the
 * card, a two-column split of copy against a cut-out loaf on an orange disc, and soft ornaments
 * behind all of it.
 *
 * IT IS A LIGHT COMPOSITION NOW. This used to be a dark band over a layered gradient with an
 * optional photograph under a veil; the card's own surface carries it instead, so there is no
 * ground to paint, no veil to tune and no image slot that can be left visibly unfilled.
 *
 * EVERY ORNAMENT IS A PSEUDO-ELEMENT - the display word, the cream-and-white disc pair, the
 * bar-and-dot mark, the slate disc and the small disc under it, and not one of them is markup. A
 * crawler reading this section finds a kicker, an h1, a lead, two links, the offer badge and one
 * image, and nothing else. The display word carries `/ ''` so a screen reader skips it too: a second heading-sized phrase in the
 * accessibility tree would compete with the h1 for what this page is about.
 *
 * THE TOP PADDING IS COMPOSED FROM THE DISPLAY WORD'S OWN SIZE rather than measured against it, so
 * the clamp below can be retuned without a second number having to be remembered in the same
 * commit. `overflow: hidden` is what crops the word and the blob, which is the artboard's look and
 * also what keeps either of them from widening the page.
 */
.nico-hero {
  --nico-display-size: clamp(1.75rem, 8.6vw, 7.4rem);

  position: relative;
  padding-top: calc(var(--nico-display-size) * 0.92 + clamp(1rem, 2.5vw, 2.5rem));
  padding-bottom: clamp(2.5rem, 5vw, 4.5rem);
  overflow: hidden;
}

/* THE TOP-LEFT DECORATION, WHICH IS TWO DISCS AND NOT ONE - the mockup draws a pale cream disc half
   off the card's left edge with a smaller WHITE one riding over its right side.

   BOTH ARE LAYERS ON THE SAME PSEUDO-ELEMENT, for the reason the copy's bar mark below gives: the
   hero owns exactly two pseudo-elements and the other one is the display word, so a second
   decorative box would have to be a div - and nothing that says nothing belongs in the document an
   answer engine reads. Two `radial-gradient(circle closest-side, ...)` layers paint two discs in
   one box, which is the same idiom the bar mark uses.

   THE ALPHA IS ON THE CREAM ALONE, not on the element. `opacity` here would take the white disc
   down with it and the pair would read as one soft smudge; the cream carries its own 55% through
   `color-mix` instead, so the white stays white and the overlap is visible.

   THE BOX IS STILL EXACTLY THE CREAM DISC, top, left and size unchanged from when it was the only
   one - a background layer is clipped to its own box, so keeping the box means the white disc lands
   INSIDE the cream one's footprint and the pair stays at the card's left edge instead of wandering
   into the copy column. Sized in vw so the composition shrinks with the page rather than crowding a
   phone, and inert so neither can ever swallow a click. */
.nico-hero::before {
  content: '';
  position: absolute;
  top: 30%;
  left: calc(-1 * clamp(2.5rem, 5vw, 5.5rem));
  z-index: 0;
  width: clamp(7rem, 16vw, 14.5rem);
  aspect-ratio: 1;
  background-image:
    radial-gradient(circle closest-side, var(--nico-white) 99%, transparent 100%),
    radial-gradient(
      circle closest-side,
      color-mix(in srgb, var(--nico-accent-soft) 55%, transparent) 99%,
      transparent 100%
    );
  background-repeat: no-repeat;
  background-size: 42% 42%, 100% 100%;
  background-position: right 6% bottom 16%, center;
  pointer-events: none;
}

/* THE DISPLAY WORD. Solid rather than outlined - the artboard sets it in the deep brown at 900 -
   and aligned to the SHELL's right edge rather than to the card's, so it lines up with the
   navigation above it and the copy below at every width including the ones where the shell has
   stopped growing and the card has not. */
.nico-hero::after {
  content: 'Bakery House' / '';
  position: absolute;
  top: clamp(0.75rem, 2vw, 2rem);
  right: calc(max(0px, (100% - var(--nico-shell)) / 2) + var(--nico-gutter));
  z-index: 0;
  font-family: var(--nico-font-display);
  font-size: var(--nico-display-size);
  font-weight: 900;
  line-height: 0.92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--nico-accent-dark);
  pointer-events: none;
}

/* The split. ONE COLUMN IS THE DEFAULT and the second is the addition, so a phone renders the copy
   above the loaf without the media query ever applying - which is the only honest layout when the
   viewport is narrower than either half wants to be. */
.nico-hero__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(2rem, 4vw, 4rem);
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 62em) {
  .nico-hero__inner {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  }
}

.nico-hero__copy {
  min-width: 0;
}

/* The artboard's mark above the kicker: three bars and a navy dot. FOUR BACKGROUND LAYERS ON ONE
   PSEUDO-ELEMENT rather than four divs, because it says nothing, and nothing that says nothing
   belongs in the document this page is read out of.

   THE BARS ARE THE COOL DECORATIVE FILL, not the accent. The mockup draws them in the same slate as
   the disc behind the loaf, and that is the only family on this page allowed to be unreadably soft
   - the accent is reserved for what a reader can act on, and three inert bars beside a heading are
   not that.

   AND THE WHOLE MARK IS SMALL. It shipped at two and a half times the mockup's scale, which made a
   pair of ornaments the first thing on the page instead of the kicker under them. Every measurement
   here is the old one divided through, the dot included - it was the larger error of the two. */
.nico-hero__copy::before {
  content: '';
  display: block;
  width: 2.75rem;
  height: 1.75rem;
  margin-bottom: clamp(1.25rem, 3vw, 2.25rem);
  background-image:
    linear-gradient(var(--nico-decor-cool), var(--nico-decor-cool)),
    linear-gradient(var(--nico-decor-cool), var(--nico-decor-cool)),
    linear-gradient(var(--nico-decor-cool), var(--nico-decor-cool)),
    radial-gradient(circle closest-side, var(--nico-accent-dark) 99%, transparent 100%);
  background-repeat: no-repeat;
  background-size: 2px 0.875rem, 2px 0.875rem, 2px 0.875rem, 1.75rem 1.75rem;
  background-position: 0 50%, 4px 50%, 8px 50%, right center;
}

/* Wide enough for "baking since 2017" to hold together on one line at the top of the clamp. The
   headline reads as two statements, and a break that stranded the year on a third line read as a
   mistake. It takes the heading rule's brown and its 900 weight; only the size is the hero's. */
.nico-hero h1 {
  max-width: 13em;
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
}

.nico-hero__lead {
  max-width: 52ch;
  margin-top: 1.4rem;
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  line-height: 1.55;
  color: var(--nico-ink-body);
}

.nico-hero .nico-btn-row {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
}

/* The loaf's box. Square, and capped so that on a tablet - one column, full shell width - it does
   not become the tallest thing on the page. */
.nico-hero__figure {
  position: relative;
  display: flex;
  min-width: 0;
  max-width: 30rem;
  margin-inline: auto;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
}

/* The disc the loaf is cut out against, in the mockup's cool slate - nothing is read on it, which
   is the whole reason a value that soft is allowed anywhere on this page. */
.nico-hero__figure::before {
  content: '';
  position: absolute;
  top: 8%;
  right: 4%;
  z-index: 0;
  width: 70%;
  aspect-ratio: 1;
  border-radius: 50%;
  background-color: var(--nico-decor-cool);
}

/* The small disc, bottom right. SOLID AND IN THE SAME SLATE as the big disc above it, which is what
   the mockup draws: the pair reads as one mark in two sizes. It shipped as a wide white outline
   ring, which on a cream card was a hole rather than an ornament and was large enough to compete
   with the loaf it sits under. */
.nico-hero__figure::after {
  content: '';
  position: absolute;
  right: 2%;
  bottom: 6%;
  z-index: 0;
  width: clamp(1.5rem, 2.8vw, 3rem);
  aspect-ratio: 1;
  border-radius: 50%;
  background-color: var(--nico-decor-cool);
}

/*
 * THE OFFER BADGE: a filled navy disc over the LEFT edge of the slate one, at the height of the
 * middle of the loaf, with three lines stacked and centred inside it.
 *
 * IT IS REAL TEXT IN A REAL ELEMENT, unlike everything else in this figure. See home.php for why:
 * it states an offer, so it is readable, selectable and translated, and the three lines are three
 * translation ids rather than one string with tags in it.
 *
 * `z-index: 2` puts it over the loaf, which is at 1 - the mockup draws the disc in front of the
 * bread rather than tucked behind it, and behind it the figure would be unreadable at exactly the
 * overlap. It is sized in vw against the figure's own square so it scales with the composition
 * instead of needing a breakpoint of its own; see the phone rule at the foot of the hero block for
 * the one width where that is not enough.
 */
.nico-hero__badge {
  position: absolute;
  top: 34%;
  left: -2%;
  z-index: 2;
  display: flex;
  width: clamp(6.5rem, 13vw, 9.5rem);
  aspect-ratio: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.1rem;
  margin: 0;
  padding: 0.5rem;
  border-radius: 50%;
  background-color: var(--nico-band-dark);
  text-align: center;
}

/* The two small lines are white on the navy at 11.7:1 - the band's own on-dark ink, tracked and
   uppercased the way the site's other display-face badges are. */
.nico-hero__badge-lead,
.nico-hero__badge-offer {
  font-family: var(--nico-font-display);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nico-white);
}

.nico-hero__badge-lead {
  font-size: clamp(0.52rem, 0.78vw, 0.65rem);
}

.nico-hero__badge-offer {
  max-width: 10ch;
  font-size: clamp(0.5rem, 0.72vw, 0.6rem);
  letter-spacing: 0.1em;
}

/* THE FIGURE IS THE ACCENT'S DARK-GROUND VALUE, and that is a contrast requirement rather than a
   preference: --nico-accent on this navy is 2.85:1, which fails even the 3:1 that large text is
   allowed to sit at, where --nico-accent-bright clears it. Set in the reading face at 900, like
   every other number this page states big. */
.nico-hero__badge-figure {
  font-size: clamp(1.5rem, 2.9vw, 2.15rem);
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--nico-accent-bright);
}

/* ON A PHONE THE BADGE MOVES UP AND IN, and it is scaled rather than hidden - it states an offer,
   and an offer withheld from the readers who arrive on a phone is the wrong half to drop.

   TWO THINGS PUSH IT. The vw term bottoms out on its own floor below about 800px, so at 390px the
   disc was 104px against a 303px figure - a third of the picture - and it sat over the middle of
   the loaf rather than beside it. And the hero's lower-left is where the collapsed footer strip
   rides while the page is being read, so a badge at 34% of a full-width figure met it head on.
   Lifting it to the figure's upper-left answers both: it lands on the slate disc's left edge, which
   is where the mockup puts it, and it is clear of the strip's band. */
@media (max-width: 47.99em) {
  .nico-hero__badge {
    top: 12%;
    left: 0;
    width: 5.25rem;
    padding: 0.35rem;
  }
}

/* THE LARGEST PAINT ON THE PAGE, and the one image on this site that is a committed asset rather
   than an optional slot - see home.php. IN ITS OWN COLOUR: the mockup sets a golden crust against
   the cool disc, so the warmth in the composition is the bread itself and the disc is the quiet
   half. Only the shadow is left, tuned to the navy rather than to a brown. */
.nico-hero__loaf {
  position: relative;
  z-index: 1;
  width: 86%;
  height: auto;
  filter: drop-shadow(0 24px 34px rgb(43 53 66 / 26%));
}

/* ---------------------------------------------------------------- the collapsed footer */

/*
 * THE COLLAPSED FOOTER: the company's social marks and its own address, riding at the bottom-left
 * of the card while the page is being read, and gone the moment the real footer arrives. The
 * mockup draws it at the foot of the hero card and the owner's rule for it is one sentence -
 * "once I arrive down to the footer they disappear".
 *
 * IT IS `position: sticky` AND NOTHING ELSE. There is no JavaScript on this site and none was
 * added for it. A sticky element is confined to its own containing block, so one placed at the END
 * of <main> floats at `bottom` for as long as <main> is under the viewport's lower edge and then
 * comes to rest in its own place in the flow - which is the last line of <main>, directly above
 * the footer. It does not fade or unmount: it stops being held up, and the footer scrolls over the
 * top of it. No scroll listener, no observer, no measured offset that can go stale.
 *
 * WHICH MAKES THE CONTAINING BLOCK THE WHOLE MECHANISM. <main> sets no `overflow`, no `height` and
 * no `transform`, so it is a plain block container and the strip sticks against the viewport. The
 * card above it is `overflow: clip` rather than `hidden` for a reason already stated at
 * `.nico-page` - `hidden` would make the card a scroll container and the sticky header would stop
 * sticking - and `clip` leaves this strip working for the same reason.
 *
 * IT IS `width: fit-content` AND OFFSET, not a full-width bar, because a full-width sticky box
 * would sit invisibly over the bottom band of every section and eat clicks meant for the page. The
 * inline offset is the shell's own left edge, spelled the way the hero's display word spells its
 * right one - `max(0px, (100% - shell) / 2) + gutter` - so the two ornaments at opposite ends of
 * this page line up with the same column the copy does.
 */
.nico-minifooter {
  position: sticky;
  bottom: var(--nico-gutter);
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  width: fit-content;
  margin-block: clamp(2rem, 4vw, 3.5rem) 0;
  margin-inline-start: calc(max(0px, (100% - var(--nico-shell)) / 2) + var(--nico-gutter));
}

/* FILLED TERRACOTTA DISCS, which is where this row parts company with the footer's own: down there
   the marks are bare rings on a navy band, and here they are four small solid buttons on the
   card's cream. Same links, same icons, same accessible names - see the partial both rows are
   built from. */
.nico-minifooter__social {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
}

.nico-minifooter__social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background-color: var(--nico-band-accent);
  color: var(--nico-white);
  transition: background-color 0.2s ease;
}

.nico-minifooter__social a:hover {
  background-color: var(--nico-accent-dark);
  color: var(--nico-white);
  text-decoration: none;
}

.nico-minifooter__social svg {
  width: 1rem;
  height: 1rem;
}

/* The address, as a label rather than as a link: it names the page the reader is already on. The
   label face comes from the grouped rule in the typography block, like every other label here. */
.nico-minifooter__url {
  margin: 0;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--nico-ink-muted);
}

/* ON A PHONE IT IS SMALLER, because a sticky strip is an overlay and an overlay's cost is whatever
   it covers. At desktop width it rides in the card's left margin over nothing that reads; at 390px
   the card is 323px wide and the strip is most of it, so every pixel it gives back is a pixel of
   the page the reader gets to see under it. Scaled rather than dropped - the marks and the address
   are the whole point of the strip. */
@media (max-width: 47.99em) {
  .nico-minifooter {
    gap: 0.45rem 0.7rem;
  }

  .nico-minifooter__social a {
    width: 1.8rem;
    height: 1.8rem;
  }

  .nico-minifooter__social svg {
    width: 0.88rem;
    height: 0.88rem;
  }

  .nico-minifooter__url {
    font-size: 0.6rem;
    letter-spacing: 0.08em;
  }
}

/* ---------------------------------------------------------------- fact strip */

/* The four confirmed company facts, on the dark band directly under the hero. Plain text in a
   description list - no count-up, no script - because a number a crawler never sees is a number
   this page does not publish, and these four are the densest thing on it for an answer engine.

   IT IS THE DARK BAND, not a band of its own. It used to carry an orange the strip alone declared;
   the mockup sets it in the same navy as the contact strip and the footer, so it resolves through
   --nico-band-dark like every other dark surface here and the strip owns no colour of its own.

   FULL BLEED, so the band runs edge to edge of the card and the shell only holds the cells apart.
   The four are centred over one another rather than left-aligned: on a coloured band a centred
   number-over-label pair reads as a plate, which is what the mockup draws. */
.nico-factstrip {
  background-color: var(--nico-band-dark);
}

.nico-factstrip__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
}

/* THE BAND IS SHALLOW, which is the proportion the mockup draws and which this strip lost to its
   own padding: at 28px a side the four cells were 187px of navy under a hero the same navy is
   supposed to punctuate rather than answer. */
.nico-factstrip__list > div {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding-block: clamp(0.7rem, 1.1vw, 0.95rem);
  border-top: 1px solid var(--nico-line-dark);
  text-align: center;
}

/* The first cell owns no rule: the hero above it already is the boundary. */
.nico-factstrip__list > div:first-child {
  border-top: 0;
}

/* THE LABEL IS THE LIFTED TERRACOTTA, which is the accent as it survives on a dark ground - the
   mockup sets these labels in terracotta and the band is now navy. A label this size is normal
   text however heavy it is set: the clamp tops out at 14px, well under the 18.66px where the
   large-text threshold starts, so it wants the full 4.5:1. This reads 4.7:1, where --nico-accent
   itself would read 2.1:1.

   THE CLAMP IS WHAT PUTS THE LONGEST LABEL ON ONE LINE, and the tracking is deliberately NOT what
   gives way. "Share of revenue from wholesale" is 30 uppercase characters and the widest of the
   four by a long way; it needed 291px of a 272px cell and broke onto a second line, which then set
   the height of the whole band. All four step down together rather than one being singled out - a
   label set smaller than its three neighbours reads as a different kind of fact - and 0.08em is
   kept, because that tracking is what every label on this site shares.

   MEASURED ACROSS THE FOUR-ACROSS RANGE, not at one width. The ceiling binds from 1600px up, where
   the shell has stopped growing and the cell has stopped with it; the vw term carries everything
   below that until the floor takes over at about 1150px. Read in the browser, label by label:
   266px of 281 at 1920, 231 of 272 at 1440, 191 of 209 at 1152 - and 191 of 181 at 1024, which is
   the one band in the four-across range where it still takes two lines. The floor is not pushed
   lower to close that: 9px is as small as a label may be set, and the alternative is a row of four
   the reader has to lean in for at every width to save one of them a line at one. */
.nico-factstrip dt {
  order: 2;
  font-size: clamp(0.56rem, 0.78vw, 0.78rem);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.4;
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--nico-accent-bright);
}

/* White at 900, which IS large text at every step of this clamp. On the navy it reads 12.4:1, so
   the numerals were never the constraint on this band - the labels above were. */
.nico-factstrip dd {
  margin: 0;
  font-size: clamp(1.9rem, 3.6vw, 3.15rem);
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--nico-white);
}

/* THE ONE CELL WHOSE VALUE IS WORDS RATHER THAN A FIGURE. The other three are a year, a count and a
   percentage - three or four characters that want the display size the rule above gives them. The
   islands are a list of five place names, and at that size they take four lines of a 273px cell and
   make the band deeper than the hero. Set as a phrase instead, which is what it is; the cell keeps
   the same label, the same alignment and the same white.

   The modifier is carried on the CELL rather than inferred from the text, because "is this value a
   number" is not a question CSS can ask - see the `words` flag in home.php, which is where a fifth
   fact would declare the same thing. */
.nico-factstrip__item--words dd {
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  line-height: 1.3;
  letter-spacing: 0;
  text-wrap: balance;
}

/* Four across, separated by white hairlines rather than boxes. The horizontal rules from the
   stacked layout are dropped in the same breakpoint that adds the vertical ones, so the two never
   both apply. No gap: the dividers have to touch the band's own edges to read as dividers. */
@media (min-width: 48em) {
  .nico-factstrip__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .nico-factstrip__list > div {
    border-top: 0;
    border-left: 1px solid var(--nico-line-dark);
    padding-inline: clamp(0.6rem, 1.5vw, 1.25rem);
  }

  .nico-factstrip__list > div:first-child {
    border-left: 0;
  }
}

/* ---------------------------------------------------------------- range tiles */

/* The icon cards on the dark band. A 7px frame, 3.8rem/1.8rem of padding and square corners: the
   frame IS the card - there is no fill, no shadow and no radius behind it - which is why it is
   that heavy rather than a hairline.

   Nothing is emphasised at rest. The accent - the frame and the glyph together - belongs to
   :hover and :focus-visible only, so the four lines are presented as equals and the page makes
   no claim through colour that its copy does not make in words.

   The whole card is the link. The border width never changes between states, only its colour, so
   nothing reflows on hover - a card that grows its border by a pixel nudges its three neighbours. */
.nico-tile {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 3.8rem 1.8rem;
  border: 7px solid var(--nico-frame);
  border-radius: 0;
  color: inherit;
  text-align: center;
  text-decoration: none;
  transition: border-color 0.3s ease;
}

/* The accent's BRIGHT value throughout this band. --nico-band-accent on the brown ground is 2.2:1,
   which for a border - a graphical object, so 3:1 - is a hover state nobody can see. */
.nico-tile:hover,
.nico-tile:focus-visible {
  border-color: var(--nico-accent-bright);
  color: inherit;
}

/* The clip box for the hover swipe. The reference animates the icon block out to the right and
   back in from the left and lets it travel over everything around it; clipping it at the icon
   row keeps it off the 7px frame, which is the one place the effect is adapted rather than
   copied. */
.nico-tile__icon {
  display: flex;
  justify-content: center;
  margin-bottom: 1rem;
  overflow: hidden;
}

/* Filled silhouettes at the reference's 88px, painted by ONE inherited `fill` so a single
   transition carries every path in the glyph from white to the accent. The <mask> children in
   the gluten-free mark carry their own fill attributes and stay out of it by design. */
.nico-tile__icon svg {
  width: 88px;
  max-width: 100%;
  height: auto;
  fill: var(--nico-white);
  transition: fill 0.3s ease;
}

.nico-tile:hover .nico-tile__icon svg,
.nico-tile:focus-visible .nico-tile__icon svg {
  fill: var(--nico-accent-bright);
}

@media (prefers-reduced-motion: no-preference) {
  .nico-tile:hover .nico-tile__icon svg,
  .nico-tile:focus-visible .nico-tile__icon svg {
    animation: nico-swipe-from-left 0.3s forwards;
  }
}

/* Out to the right, jump the gap invisibly, back in from the left. */
@keyframes nico-swipe-from-left {
  49% {
    transform: translateX(100%);
  }

  50% {
    opacity: 0;
    transform: translateX(-100%);
  }

  51% {
    opacity: 1;
  }
}

/* Flat letter-spacing rather than the heading rule's negative tracking: these titles read as
   labels over the cards rather than as headings in their own right, and at this size the tracking
   that flatters a 3rem display line only tightens a one-word label into a blob. */
.nico-tile h3 {
  margin: 0 0 1rem;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0;
}

/* White, held back by opacity rather than by a second colour - the copy then sits at the same hue
   as the title whatever the band is repainted to. The opacity is 0.66 rather than the 0.5 this
   carried on the old grey band: over brown, half-strength white came out at 5.7:1 and looked
   washed rather than quiet. 300 is gone with it - Overlock has no light face, so that was a
   request the browser answered with the 400 one anyway. */
.nico-tile p {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.75;
  color: var(--nico-white);
  opacity: 0.66;
}

/* The outlined button under the dark grid: the reference's 4px frame, extra bold, and its wide
   gutters. Scoped to the dark band so the same modifier keeps its hairline on the light
   sections, where a 4px box would shout over the copy next to it. */
.nico-section--dark .nico-btn--outline {
  padding: 1rem 3rem;
  border-width: 4px;
  border-color: var(--nico-frame);
  font-weight: 900;
  color: var(--nico-white);
}

/* Bright fill, INK label. --nico-band-accent here would be a 2.2:1 pill on the brown band, and
   white on the bright orange is 2.05:1 - so the fill takes the value that reads on brown and the
   label takes the value that reads on the fill. 8.4:1. */
.nico-section--dark .nico-btn--outline:hover {
  background-color: var(--nico-accent-bright);
  border-color: var(--nico-accent-bright);
  color: var(--nico-ink);
}

.nico-section--dark .nico-btn-row {
  margin-top: clamp(2rem, 4vw, 3.25rem);
}

/* ---------------------------------------------------------------- scroll reveal */

/* The reference stages this band with a jQuery plugin: fadeInUpShorter on the cards and the
   button, maskUp on the eyebrow and the heading, staggered by 250-900ms of delay. This site ships
   no JavaScript, so both moves are rebuilt on a view() timeline and the stagger is spelled as six
   overlapping scroll ranges rather than six delays.

   EVERY reveal rule sits inside the @supports AND the reduced-motion guard, and `opacity: 0`
   appears nowhere outside them. A browser without scroll-driven animations, and a reader who has
   asked for less motion, both receive the finished section - which matters twice over here,
   because GPTBot, ClaudeBot and PerplexityBot run no JavaScript and are exactly the readers this
   page was written for. */

/* The overflow box the masked line slides up out of. It is real markup rather than a
   pseudo-element because the element that moves and the element that clips it have to be two
   different boxes. Descenders hang below the tight heading line box, so the padding gives them
   room inside the clip and the matching negative margin leaves the block's spacing untouched. */
.nico-mask {
  overflow: hidden;
  margin-bottom: -0.14em;
  padding-bottom: 0.14em;
}

@keyframes nico-fade-in-up-shorter {
  from {
    opacity: 0;
    transform: translateY(50px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes nico-mask-up {
  from {
    transform: translateY(100%);
  }

  to {
    transform: none;
  }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .nico-reveal {
      animation: nico-fade-in-up-shorter linear both;
      animation-timeline: view();
    }

    .nico-reveal--mask {
      animation-name: nico-mask-up;
    }

    /* The stagger. The four cards share one top edge, so their entry ranges are identical and
       the offsets below are what pulls them apart; the head and the button sit at different
       heights and so lead and trail the row on their own. */
    .nico-reveal--d1 {
      animation-range: entry 10% entry 60%;
    }

    .nico-reveal--d2 {
      animation-range: entry 20% entry 70%;
    }

    .nico-reveal--d3 {
      animation-range: entry 28% entry 78%;
    }

    .nico-reveal--d4 {
      animation-range: entry 36% entry 86%;
    }

    .nico-reveal--d5 {
      animation-range: entry 44% entry 94%;
    }

    .nico-reveal--d6 {
      animation-range: entry 52% entry 100%;
    }
  }
}

/* ---------------------------------------------------------------- gallery strip */

/* A full-bleed row of square tiles with a dark hover overlay and a plus mark - Porto's
   demo-coffee strip, rebuilt without its lightbox plugin and without the negative row margin
   that makes the original overflow the viewport by 12px on each side.
   :focus-within sits alongside :hover throughout, because a hover-only affordance does not
   exist on a phone or to a keyboard. */
.nico-gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0;
  background-color: var(--nico-dark);
}

@media (min-width: 36em) {
  .nico-gallery {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 62em) {
  .nico-gallery {
    grid-template-columns: repeat(6, 1fr);
  }
}

.nico-gallery__tile {
  position: relative;
  margin: 0;
  aspect-ratio: 1;
  overflow: hidden;
}

.nico-gallery__tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The overlay. A pseudo-element on the tile rather than a wrapper div, so the markup stays an
   image and nothing else. */
.nico-gallery__tile::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: rgb(0 0 0 / 65%);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.nico-gallery__tile:hover::after,
.nico-gallery__tile:focus-within::after {
  opacity: 1;
}

/* The plus, drawn as two bars from a pair of gradients rather than as a glyph: no icon font is
   loaded on this site, and a '+' character sits differently in every fallback typeface. It fades
   in and slides down from 30% to 50%, exactly as the reference does. */
.nico-gallery__mark {
  position: absolute;
  top: 30%;
  left: 50%;
  z-index: 1;
  width: 20px;
  height: 20px;
  translate: -50% -50%;
  background-image:
    linear-gradient(var(--nico-white), var(--nico-white)),
    linear-gradient(var(--nico-white), var(--nico-white));
  background-repeat: no-repeat;
  background-position: center;
  background-size: 20px 2px, 2px 20px;
  opacity: 0;
  transition: opacity 0.2s ease, top 0.2s ease;
}

.nico-gallery__tile:hover .nico-gallery__mark,
.nico-gallery__tile:focus-within .nico-gallery__mark {
  top: 50%;
  opacity: 1;
}

/* ---------------------------------------------------------------- location list */

/* The public locations as a stack of hairline-separated rows rather than as cards: the homepage
   publishes their NAMES and their links and nothing else, so a card would be a box drawn around
   two words. Every address and every opening hour lives on the location pages, built from the
   same rows. */
.nico-locations {
  margin: 0;
  border-top: 1px solid var(--nico-border);
}

.nico-locations__item {
  border-bottom: 1px solid var(--nico-border);
}

/* The hairline ROW's anchor. `:not(.nico-tile-card)` is load-bearing, not defensive: a bare
   `.nico-locations a` is specificity (0,1,1) and would outrank the whole `.nico-tile-card` block
   below (0,1,0), laying the photographic tile out as a baseline-aligned flex row and collapsing
   its media box to zero height. The two variants share one <ul>, so the row rules have to say
   which of the two they mean. */
.nico-locations a:not(.nico-tile-card) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.25rem;
  padding-block: clamp(1.1rem, 2.5vw, 1.6rem);
  color: var(--nico-ink);
  text-decoration: none;
  transition: color 0.2s ease, padding-inline 0.2s ease;
}

.nico-locations a:not(.nico-tile-card):hover,
.nico-locations a:not(.nico-tile-card):focus-visible {
  color: var(--nico-accent-dark);
}

.nico-locations__name {
  flex: 1 1 auto;
  font-size: clamp(1.4rem, 3vw, 2rem);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.nico-locations__area {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
}

/* The production bakery, marked with a tinted pill rather than colour alone so it still reads as
   a distinct label in monochrome - the same treatment the locations page gives it. */
.nico-locations__badge {
  padding: 0.15rem 0.65rem;
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-accent-soft);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-accent-dark);
}

.nico-locations__arrow {
  flex: none;
  width: 1.35rem;
  height: 1.35rem;
  align-self: center;
  color: var(--nico-band-accent);
  transition: translate 0.2s ease;
}

.nico-locations a:hover .nico-locations__arrow,
.nico-locations a:focus-visible .nico-locations__arrow {
  translate: 0.35rem 0;
}

/* ---------------------------------------------------------------- location tiles */

/* The photographic variant of the list above: Porto's portfolio tile. It replaces the hairline
   rows ONLY when there is a photograph to put in it - `home.php` decides per location, preferring
   a stored `warehouses.hero_image_path`, then a local placeholder, then nothing at all.

   The two variants are siblings in one <ul>, so a mixed list renders correctly: the grid below
   gives a plain row `grid-column: 1 / -1` and it spans the full width exactly as it did before,
   keeping its hairline while the tiles around it keep their gaps. */
.nico-locations--tiles {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  grid-template-columns: 1fr;
  border-top: 0;
}

@media (min-width: 40em) {
  .nico-locations--tiles {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 62em) {
  .nico-locations--tiles {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* A text row inside a tile grid keeps the full width and its own rule. */
.nico-locations--tiles .nico-locations__item:not(.nico-locations__item--tile) {
  grid-column: 1 / -1;
  border-top: 1px solid var(--nico-border);
}

.nico-locations__item--tile {
  border-bottom: 0;
}

/* The tile. `isolation: isolate` gives it its own stacking context so the caption's z-index is
   resolved against the tile rather than against whatever the page puts nearby. */
.nico-tile-card {
  position: relative;
  display: block;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--nico-radius);
  background-color: var(--nico-dark);
  color: var(--nico-white);
  text-decoration: none;
}

.nico-tile-card__media {
  display: block;
  aspect-ratio: 4 / 3;
}

/* The reference zooms the photograph 1.1x on hover. The transform is on the image alone, inside
   the tile's overflow clip, so nothing around it reflows. */
.nico-tile-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.nico-tile-card:hover .nico-tile-card__media img,
.nico-tile-card:focus-visible .nico-tile-card__media img {
  transform: scale(1.06);
}

/* Porto's `thumb-info-bottom-info-dark-linear`: a dark gradient rising out of the bottom edge and
   fading to nothing before the top, so the name is legible over a light photograph without a band
   of flat colour across the tile. It is painted on the caption itself, which is what makes the
   gradient exactly as tall as the text it has to carry. */
.nico-tile-card__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  padding: 3.5rem 1.5rem 1.35rem;
  background-image: linear-gradient(to top, rgb(20 22 26 / 88%) 0%, rgb(20 22 26 / 0%) 100%);
}

.nico-tile-card__name {
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--nico-white);
}

.nico-tile-card__area {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 78%);
}

/* The production bakery's pill, in the accent rather than the light tint the hairline rows use:
   on a photograph the pale pill disappears. */
.nico-tile-card__badge {
  order: -1;
  padding: 0.15rem 0.65rem;
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-band-accent);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-white);
}

/* Porto's `.custom-thumb-info-style-1 .thumb-info-wrapper:after`: the accent, washed across the
   whole tile on hover and fading in from nothing. The reference inflates it 4px past every edge so
   no hairline of un-tinted photograph survives at the corners; `inset: -4px` is that, and the
   tile's own overflow clip keeps it inside the rounded corner.

   :focus-visible sits alongside :hover throughout, because a hover-only affordance does not exist
   on a phone or to a keyboard. It is `pointer-events: none` so it can never intercept the click
   that summoned it. */
.nico-tile-card::after {
  content: '';
  position: absolute;
  inset: -4px;
  z-index: 2;
  background-color: var(--nico-band-accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.nico-tile-card:hover::after,
.nico-tile-card:focus-visible::after {
  opacity: 0.72;
}

/* The caption has to stay readable through the accent wash, so it is lifted above it. Without
   this the name is the one thing the hover state hides. */
.nico-tile-card:hover .nico-tile-card__caption,
.nico-tile-card:focus-visible .nico-tile-card__caption {
  z-index: 3;
}

/* Numbered fact strip - Porto's counter row without the count-up plugin. */
.nico-facts {
  display: grid;
  gap: 1.5rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  margin: 0;
}

/* dd carries the number and dt the label, so the pair renders number-over-label
   while the document order stays term-then-description for a parser. */
.nico-facts div {
  display: flex;
  flex-direction: column;
  border-left: 3px solid var(--nico-band-accent);
  padding-left: 1.1rem;
}

.nico-facts dt {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
  order: 2;
}

.nico-facts dd {
  margin: 0;
  font-size: clamp(1.6rem, 3.5vw, 2.15rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.025em;
  color: var(--nico-ink);
}

/* Plain checked list for range/capability copy. */
.nico-ticks {
  display: grid;
  gap: 0.7rem;
  margin-top: 1.5rem;
}

.nico-ticks li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
}

.nico-ticks svg {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.28rem;
  color: var(--nico-band-accent);
}

/* ---------------------------------------------------------------- cta band */

.nico-cta {
  padding-block: clamp(2.75rem, 6vw, 4.25rem);
  background-color: var(--nico-band-accent);
  color: var(--nico-white);
  text-align: center;
}

.nico-cta h2 {
  color: var(--nico-white);
}

/* Full white rather than the 88% it was held back to. Over the accent this site ships, 88% white
   comes to 4.25:1 and 100% to 4.70 - and this is one sentence on a two-line band, so there is no
   hierarchy for the dimming to carry and nothing gained by paying for it in legibility. */
.nico-cta p {
  margin-top: 0.75rem;
  color: var(--nico-white);
}

.nico-cta .nico-btn-row {
  justify-content: center;
}

.nico-cta .nico-btn--primary {
  background-color: var(--nico-white);
  border-color: var(--nico-white);
  color: var(--nico-ink);
}

.nico-cta .nico-btn--primary:hover {
  background-color: var(--nico-ink);
  border-color: var(--nico-ink);
  color: var(--nico-white);
}

/* THE SECOND ACTION ON THIS BAND, and it needs its own rule now that `.nico-btn--outline` states
   the deep brown rather than taking the page's ink. Brown on the accent band is 1.88:1 - measured
   on /about and /faq, which are the two pages that put a secondary action in this band through
   partials/cta_section.php. White is 5.6:1 and matches the primary button standing beside it. */
.nico-cta .nico-btn--outline {
  border-color: var(--nico-white);
  color: var(--nico-white);
}

.nico-cta .nico-btn--outline:hover {
  background-color: var(--nico-ink);
  border-color: var(--nico-ink);
  color: var(--nico-white);
}

/* ---------------------------------------------------------------- footer */

/* EVERY COLOUR IN THIS BLOCK IS NOW A TOKEN. The footer carried five hand-written cool greys -
   #b9bec6, #d3d7dd, #949aa3, #8e949d and a bare rgba hairline - which were five values nothing
   else on the site shared and which all read as dirt against a warm brown ground. They resolve
   through --nico-on-dark, --nico-on-dark-muted and --nico-line-dark like every other element
   sitting on a dark surface, so repainting that surface repaints the footer with it. */
.nico-footer {
  padding-top: clamp(3rem, 7vw, 5.5rem);
  background-color: var(--nico-dark);
  color: var(--nico-on-dark-muted);
  font-size: 0.92rem;
}

/* The mark, centred over everything else. */
.nico-footer__mark {
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
  text-align: center;
}

/* THE WHITE VARIANT OF THE MARK, drawn as white on transparency and shipped as its own file. This
   rule used to invert the header's dark asset and blend it with `lighten`, which was the one way to
   get a white wordmark out of a file that carried an opaque white plate; both files are
   transparent now and each is already the colour its ground needs, so there is nothing left here
   to correct.

   `object-fit: contain` for the reason the header's own box gives: the two numbers are a stated
   box, and a mark whose proportion differs from it centres rather than stretching. */
.nico-footer__mark img {
  width: 209px;
  height: 80px;
  margin-inline: auto;
  object-fit: contain;
}

.nico-footer__mark p {
  max-width: 34rem;
  margin-inline: auto;
  margin-top: 1.25rem;
}

/* The locations, now ONE COLUMN of the grid rather than a full-width band above it. It keeps
   `.nico-footer__links`' spacing and adds only the second line each entry carries; the band's own
   rules - the centred text, the pair of hairlines, the auto-fit track - are gone with the band. */
/* The regions sentence above the location list. Set as body copy rather than as another link, so
   the column reads as a statement followed by places rather than as a longer list of links. */
.nico-footer__locations-lead {
  margin-bottom: 0.9rem;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--nico-on-dark-muted);
}

.nico-footer__location-area {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--nico-on-dark-muted);
}

/* The area is inside the location's own anchor, so it inherits the link's hover colour and the
   whole two-line entry lights up as one target rather than the name alone. */
.nico-footer__locations a:hover .nico-footer__location-area,
.nico-footer__locations a:focus-visible .nico-footer__location-area {
  color: inherit;
}

/*
 * FOUR EQUAL COLUMNS, and equal is the word that matters.
 *
 * This was `repeat(auto-fit, minmax(min(100%, 14rem), 1fr))`, which is a RESPONSIVE COLUMN COUNT -
 * it fits as many 14rem tracks as the width allows. That was right while the grid held two or
 * three blocks depending on the page, and it is wrong now that there are always four: auto-fit
 * would drop to three tracks at some widths and leave the fourth column alone on a row beneath
 * three others, which reads as a mistake rather than as a layout.
 *
 * So the count is declared. Four abreast on the desktop, two at tablet width, one on a phone -
 * three deliberate arrangements instead of however many happen to fit.
 */
.nico-footer__grid {
  display: grid;
  gap: 2rem clamp(1.5rem, 4vw, 3rem);
  grid-template-columns: minmax(0, 1fr);
  padding-bottom: clamp(2.5rem, 5vw, 3.5rem);
}

@media (min-width: 36em) {
  .nico-footer__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 62em) {
  .nico-footer__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* Bare circular hit areas rather than filled chips: the row should read as four small marks, not
   as four buttons competing with the footer's links. */
.nico-social {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
}

.nico-social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--nico-line-dark);
  border-radius: 50%;
  color: var(--nico-on-dark);
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.nico-social a:hover {
  background-color: var(--nico-band-accent);
  border-color: var(--nico-band-accent);
  color: var(--nico-white);
  text-decoration: none;
}

.nico-social svg {
  width: 1.05rem;
  height: 1.05rem;
}

/* Column headers, not headings: 600 and widely tracked, which is the opposite of the heading
   rule's heavy-and-tight and is why it overrides both properties. */
.nico-footer h2 {
  margin-bottom: 1rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nico-white);
}

.nico-footer a {
  color: var(--nico-on-dark);
  text-decoration: none;
}

/* The accent's DARK-GROUND value: --nico-band-accent on this brown is 2.2:1, which would make the
   hover state of every footer link the least readable text on the page. */
.nico-footer a:hover {
  color: var(--nico-accent-bright);
  text-decoration: underline;
}

.nico-footer__links li + li {
  margin-top: 0.5rem;
}

.nico-footer address {
  font-style: normal;
}

/* ---- the footer's bottom bar: legal wording, the language switcher, the social row

   Legal and language read from the left, the social marks sit hard right - the Google Store
   arrangement. `align-items: center` rather than the default stretch, so the social discs sit on
   the same optical line as text half their height. */
.nico-footer__bottom {
  padding-block: 1.25rem;
  border-top: 1px solid var(--nico-line-dark);
  font-size: 0.82rem;
  color: var(--nico-on-dark-muted);
}

.nico-footer__bottom .nico-shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
}

/* The left-hand group. It wraps as one unit, so on a narrow screen the language control follows
   the legal wording onto the next line instead of being stranded beside the social row. */
.nico-footer__bottom-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1.5rem;
}

.nico-footer__bottom p {
  margin: 0;
}

/* The terms and privacy links, sitting in the bottom bar's flex row as one more
   item beside the registered name and the regions line. A row rather than a
   stacked list, because at two entries a list reads as a pair of links and a
   column reads as a navigation block the bar has no room for. It wraps with
   everything else in that row on a narrow screen. */
.nico-footer__legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- the language switcher

   A DROPDOWN: the trigger shows the CURRENT language's flag and name, opening onto every language
   the site speaks - see layout/partials/footer.php for why the owner asked for this shape in place
   of the two inline links this control used to be. header.js governs the toggle exactly as it
   governs the header's own panels - a `[data-nico-toggle]` button naming a `[data-nico-panel]`
   through `aria-controls` - so nothing here duplicates that mechanism.

   WITH NO SCRIPT the panel below is simply a plain, reachable list: the rules that turn it into a
   hidden dropdown, and that show the trigger button at all, apply only once `[data-nico-js]` is on
   `<html>`. Without it a reader gets exactly the two anchors this control used to render inline.

   Flags are drawn locally as inline SVG - see that partial for why Porto's sprite sheet is not
   imported and why English flies the Union flag. */
.nico-footer__language-switcher {
  position: relative;
  display: flex;
  align-items: center;
}

/* Hidden until `[data-nico-js]` - the same gate every header toggle button uses (see
   `.nico-header__button`), and for the same reason: a button that opens nothing is worse than the
   plain links it would otherwise cover. */
.nico-footer__language-trigger {
  display: none;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.5rem;
  background: none;
  border: 0;
  border-radius: var(--nico-radius);
  color: var(--nico-on-dark);
  /* `font: inherit` wipes the family along with the button defaults, so the label face is restated
     here rather than taken from the grouped rule in the typography block - see the note there. */
  font: inherit;
  font-family: var(--nico-font-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  cursor: pointer;
}

[data-nico-js] .nico-footer__language-trigger {
  display: inline-flex;
}

.nico-footer__language-trigger:hover,
.nico-footer__language-trigger[aria-expanded='true'] {
  color: var(--nico-accent-bright);
}

.nico-footer__language-trigger:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 2px;
}

/* Collapsed to the flag alone at the width this bar starts to wrap - the same phone breakpoint the
   header's own button text collapses at (see `.nico-header__button-text`), so both controls agree
   on what "narrow" means. The name stays the button's accessible name regardless of whether it is
   on screen: that name comes from `aria-label` below, not from this text. */
@media (max-width: 47.99em) {
  .nico-footer__language-trigger-text {
    display: none;
  }
}

/* The panel. With no script it is the ORIGINAL inline row - `display: flex`, in normal flow, every
   entry a reachable link. `[data-nico-js]` is what turns it into a dropdown: hidden until its
   trigger's `aria-expanded` says otherwise, then lifted ABOVE the trigger rather than dropped below
   it, because this control sits at the very bottom of the page and a panel opening downward would
   run off the document. */
.nico-footer__languages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 1rem;
  margin: 0;
}

[data-nico-js] .nico-footer__languages {
  display: none;
  position: absolute;
  bottom: calc(100% + 0.6rem);
  left: 0;
  z-index: 30;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 0.5rem;
  min-width: 11rem;
  padding: 0.85rem 1rem;
  background-color: var(--nico-dark-deep);
  border: 1px solid var(--nico-line-dark);
  border-radius: var(--nico-radius);
  box-shadow: var(--nico-shadow-panel);
}

[data-nico-js] .nico-footer__language-trigger[aria-expanded='true'] ~ .nico-footer__languages {
  display: flex;
}

.nico-footer__flag {
  flex: 0 0 auto;
  border-radius: 1px;
}

.nico-footer__language {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

/* The colour has to be stated here rather than left to inherit: `.nico-footer a` already paints
   every footer link #d3d7dd, which is the body-link brightness and would make the one language on
   offer the brightest thing in a bar built to stay quiet. */
a.nico-footer__language {
  color: inherit;
}

a.nico-footer__language:hover {
  color: var(--nico-accent-bright);
  text-decoration: underline;
}

/* The language the reader is already in. Listed so the bar says which one that is - brighter than
   its sibling rather than dimmer, because here the current language is the statement and the other
   one is the offer. */
.nico-footer__language.is-current {
  color: var(--nico-on-dark);
  font-weight: 700;
}

/* ---------------------------------------------------------------- bare chrome: the order flow

   THE CHECKOUT AND THE REVIEW CARRY NO SITE CHROME. layout/base.php draws
   layout/partials/bare_head.php and bare_foot.php in place of the header, the buying-context band
   and the footer on a view that sets `$bareChrome`; see each partial's docblock for what it keeps
   and what it drops, and for the heights that made the owner ask.

   NOTHING HERE IS A NEW VALUE. The head takes the card's own surface with the header's hairline and
   block padding; the foot takes the three tokens `.nico-footer__bottom` resolves through, because it
   IS that bar with different contents - and the language control inside it is the footer's own
   markup, so it is painted by the footer's own rules directly above. */

/* The head. NOT STICKY, unlike `.nico-header`: this is two links, and 60px of permanently spent
   viewport is the opposite of what dropping the chrome was for. That choice is also why
   `.nico-page--bare` exists - shop.css's sticky summary panel offsets from the header's height, and
   on these two pages there is no header left for it to clear. */
.nico-barehead {
  background-color: var(--nico-surface);
  border-bottom: var(--nico-header-border) solid var(--nico-border);
}

.nico-barehead .nico-shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: var(--nico-header-pad-block);
}

.nico-barehead__logo {
  display: block;
  flex: 0 0 auto;
}

/* `object-fit: contain` for the reason `.nico-header__logo img` states: the box is two stated
   numbers, so a mark whose proportion differs from it centres rather than stretching. The markup
   declares the owner's own 230x88 frame, which is what reserves the space before the image decodes;
   this is the box it paints inside. */
.nico-barehead__logo img {
  width: var(--nico-barehead-logo-width);
  height: var(--nico-barehead-logo-height);
  object-fit: contain;
}

/* The basket, in the label face at the header controls' own size - so the one control on this bar
   reads as a control rather than as a line of body copy. The count is inside the text; see
   partials/bare_head.php for why there is no badge and no bag. */
.nico-barehead__basket {
  flex: 0 0 auto;
  color: var(--nico-ink);
  font-family: var(--nico-font-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}

.nico-barehead__basket:hover {
  color: var(--nico-band-accent);
  text-decoration: underline;
}

.nico-barehead__basket:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 2px;
}

/* The foot: the same dark ground, the same hairline and the same quiet ink as the site footer's
   bottom bar, flush with the bottom of the document so `.nico-page`'s rounded corners cut it
   exactly as they cut that one. */
.nico-barefoot {
  padding-block: 1rem;
  border-top: 1px solid var(--nico-line-dark);
  background-color: var(--nico-dark);
  color: var(--nico-on-dark-muted);
  font-size: 0.82rem;
}

.nico-barefoot .nico-shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

/* A row rather than a stacked column, at two entries, exactly as `.nico-footer__legal-links` is.
   Spelled here rather than borrowing that class, because the rule below has to paint a link colour:
   borrowing would have put this bar's anchors and the language entries beside them into a
   specificity argument over the colour `a.nico-footer__language` settles by inheriting. */
.nico-barefoot__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Stated rather than inherited: the global `a` colour is the accent, which reads at 2.1:1 on this
   ground - see the contact strip's note on that same pair. These two links are the quiet half of
   the bar, so they take the muted ink and brighten to the accent's dark-ground value on hover. */
.nico-barefoot__legal a {
  color: var(--nico-on-dark-muted);
  text-decoration: none;
}

.nico-barefoot__legal a:hover {
  color: var(--nico-accent-bright);
  text-decoration: underline;
}

.nico-barefoot__legal a:focus-visible {
  outline: 2px solid var(--nico-accent-bright);
  outline-offset: 2px;
}

/* The wordmark gives up width on a phone, to the 108x41 step the full header's own logo collapses
   to at its narrowest - the same scale, one stop further down, for the same reason: the shell has
   run out of room beside it. */
@media (max-width: 30em) {
  .nico-barehead {
    --nico-barehead-logo-width: 108px;
    --nico-barehead-logo-height: 41px;
  }
}

/* ---------------------------------------------------------------- error pages */

.nico-error {
  display: flex;
  align-items: center;
  min-height: 58vh;
  padding-block: clamp(3rem, 9vw, 6rem);
  text-align: center;
}

.nico-error__code {
  display: block;
  font-size: clamp(3.5rem, 12vw, 6rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--nico-band-accent);
}

.nico-error p {
  max-width: 34rem;
  margin-inline: auto;
  margin-top: 1rem;
  color: var(--nico-ink-muted);
}

.nico-error .nico-btn-row {
  justify-content: center;
}

/* The holding page served while the site is gated. It borrows .nico-error's centred column
   wholesale - one message, one action, vertically centred is exactly its shape - and adds only
   the wordmark, which stands in for the header the gated layout does not render. */
.nico-holding__logo {
  display: block;
  width: min(280px, 70vw);
  height: auto;
  margin: 0 auto 2rem;
}

/* ---------------------------------------------------------------- catalogue */

/* Breadcrumb, spelled with real links rather than a JS widget: it is the only
   thing on a deep page that tells a crawler where that page sits. */
.nico-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.55rem;
  margin-bottom: 1.5rem;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--nico-ink-muted);
}

.nico-breadcrumb span[aria-hidden='true'] {
  color: var(--nico-border);
}

.nico-catalogue__summary {
  margin-bottom: 2rem;
  font-size: 0.9rem;
  color: var(--nico-ink-muted);
}

.nico-family + .nico-family {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-top: clamp(2.5rem, 6vw, 4rem);
  border-top: 1px solid var(--nico-border);
}

.nico-family h2 {
  margin-bottom: 0.75rem;
}

.nico-family__intro {
  max-width: 46rem;
  margin-bottom: 1.75rem;
  color: var(--nico-ink-muted);
}

/* Category and product cards share the auto-fitting .nico-grid, so neither needs
   a column-class ladder of its own. */
.nico-category-card,
.nico-product-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 1.6rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background-color: var(--nico-white);
  /* The positioning context the whole-card click below is measured against. */
  position: relative;
}

/* THE WHOLE CARD IS THE LINK, and it is the HEADING's link that is stretched to fill it.

   No script is involved and none may be: on this site a script may toggle the state of markup the
   server already sent, and may never decide an action - a click handler that navigated would be a
   script choosing the destination. This is one real `<a>` with a real `href`, exactly as it was;
   only its clickable AREA grows. So it works with scripting off, it is one tab stop and not a new
   one, a middle click and "open in new tab" behave normally, and a crawler sees the same markup.

   The heading's link rather than one of the other two to the same product, because the heading
   carries the product's NAME - that name is what a screen reader announces for whatever the pointer
   lands on, and "Burger Bun Normal" is the only one of the three that describes where the click
   goes. The image link is `aria-hidden` and the footer link reads "Pack sizes and prices".

   The three links all point at the same URL, so the overlay covering the other two costs nothing.

   BOTH CARD KINDS, and the category card was missing from this list until the owner noticed. It has
   carried the positioning context since this rule was written - it shares the `position: relative`
   declaration above - so it LOOKED like a whole-card target to anyone reading the CSS, while only
   the heading text was actually clickable. A card that is plainly a card, with a border and a
   hover, teaches a reader that the whole of it is the target; the description and the count line
   did nothing. The category card holds exactly one link, so it has the same single destination the
   product card's overlay already assumes. */
.nico-product-card h2 a::after,
.nico-product-card h3 a::after,
.nico-category-card h3 a::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* Text inside the card can no longer be selected with a drag, because the overlay takes the press.
   That is the accepted cost of a whole-card target and it is the reason the card carries no control
   of its own - a second destination under the overlay would be unreachable, and anything added here
   later needs `position: relative` and a z-index above it, or it will silently stop working. */

.nico-category-card h3,
.nico-product-card h2 {
  margin-bottom: 0.6rem;
  font-size: 1.25rem;
}

.nico-category-card a,
.nico-product-card h2 a {
  color: inherit;
  text-decoration: none;
}

.nico-category-card a:hover,
.nico-product-card h2 a:hover {
  color: var(--nico-accent-dark);
  text-decoration: underline;
}

.nico-category-card p,
.nico-product-card__desc {
  font-size: 0.92rem;
  color: var(--nico-ink-muted);
}

/* The full description is in the markup - a crawler reads all of it - and only the
   card clamps it, so a long entry cannot stretch one card past its neighbours.
   Where line-clamp is unsupported the paragraph simply shows in full. */
.nico-product-card__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}

.nico-category-card__count {
  margin-top: auto;
  padding-top: 0.9rem;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nico-accent-dark);
}

.nico-product-card__price {
  margin-top: 1rem;
  font-size: 1.05rem;
  color: var(--nico-ink);
}

.nico-product-card__price span {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--nico-ink-muted);
}

.nico-product-card__packs {
  margin-top: 0.6rem;
  font-size: 0.82rem;
  color: var(--nico-ink-muted);
}

.nico-product-card__more {
  margin-top: auto;
  padding-top: 1rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.nico-back-link {
  margin-top: clamp(2rem, 5vw, 3rem);
  font-size: 0.9rem;
  font-weight: 600;
}

/* The ladder is the densest thing on the site, so it scrolls inside its own box
   rather than pushing the page sideways on a phone. */
.nico-table-wrap {
  overflow-x: auto;
  margin-top: 1.25rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background-color: var(--nico-white);
}

.nico-price-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}

.nico-price-table th,
.nico-price-table td {
  padding: 0.75rem 1rem;
  text-align: right;
  white-space: nowrap;
}

.nico-price-table th:first-child,
.nico-price-table td:first-child {
  text-align: left;
}

.nico-price-table thead th {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-ink);
  border-bottom: 1px solid var(--nico-border);
}

/* The tax basis under a money column's heading. Its own line, in sentence case, so it
   reads as a qualifier on the figures below rather than as part of the column name. */
.nico-price-table__tax {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: none;
}

.nico-price-table tbody tr + tr th,
.nico-price-table tbody tr + tr td {
  border-top: 1px solid var(--nico-border);
}

.nico-price-table tbody th {
  font-weight: 600;
}

/* The cheapest piece on the ladder. Marked with a tint and a left rule rather
   than colour alone, so it still reads as the highlighted row in monochrome. */
.nico-price-table tbody tr.is-best th,
.nico-price-table tbody tr.is-best td {
  background-color: var(--nico-accent-soft);
}

.nico-price-table tbody tr.is-best th {
  box-shadow: inset 3px 0 0 var(--nico-band-accent);
}

.nico-price-note {
  margin-top: 0.9rem;
  font-size: 0.88rem;
  color: var(--nico-ink-muted);
}

.nico-siblings {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-top: clamp(2rem, 5vw, 3rem);
  border-top: 1px solid var(--nico-border);
}

.nico-siblings h2 {
  margin-bottom: 1rem;
  font-size: 1.35rem;
}

.nico-siblings__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.nico-siblings__list a {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-white);
  font-size: 0.85rem;
  text-decoration: none;
}

.nico-siblings__list a:hover {
  border-color: var(--nico-band-accent);
  color: var(--nico-accent-dark);
}

/* ---------------------------------------------------------------- locations */

/* The cards reuse .nico-grid for their track sizing, exactly as the catalogue
   cards do, so the locations page adds no breakpoint of its own. */
.nico-location-card {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background-color: var(--nico-white);
}

.nico-location-card h2 {
  font-size: 1.25rem;
}

.nico-location-card h2 a {
  color: inherit;
  text-decoration: none;
}

.nico-location-card h2 a:hover {
  color: var(--nico-accent-dark);
  text-decoration: underline;
}

/* The production bakery, marked with a tinted pill rather than colour alone so it
   still reads as a distinct label in monochrome. */
.nico-location-card__badge {
  align-self: flex-start;
  margin-top: 0.6rem;
  padding: 0.15rem 0.65rem;
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-accent-soft);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-accent-dark);
}

.nico-location-card__desc {
  margin-top: 0.75rem;
  font-size: 0.92rem;
  color: var(--nico-ink-muted);
}

.nico-location-card__more {
  margin-top: auto;
  padding-top: 1rem;
  font-size: 0.85rem;
  font-weight: 600;
}

/* Opening hours: a description list, because each row genuinely is a term (the
   days) and its definition (the hours). Two columns that stay aligned without a
   table, and it reads in source order when the CSS never arrives. */
.nico-hours {
  display: grid;
  margin-top: 1.25rem;
  border-top: 1px solid var(--nico-border);
}

.nico-hours div {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.25rem 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--nico-border);
}

.nico-hours dt {
  font-weight: 600;
}

.nico-hours dd {
  font-variant-numeric: tabular-nums;
}

/* A closed day is stated, never omitted - the muted weight marks it without
   hiding it, since a missing day reads as "unknown" rather than "shut". */
.nico-hours .is-closed dt,
.nico-hours .is-closed dd {
  color: var(--nico-ink-muted);
}

.nico-hours--compact {
  margin-top: 1rem;
  font-size: 0.88rem;
}

.nico-hours__note {
  margin-top: 0.9rem;
  font-size: 0.88rem;
  color: var(--nico-ink-muted);
}

.nico-location__hero {
  width: 100%;
  height: auto;
  max-height: 26rem;
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
  border-radius: var(--nico-radius-lg);
  object-fit: cover;
}

.nico-location__body {
  align-items: start;
}

.nico-location__body h2 {
  margin-bottom: 1rem;
}

.nico-location__address {
  font-style: normal;
}

.nico-location__map {
  margin-top: 1rem;
  font-size: 0.9rem;
  font-weight: 600;
}

.nico-footer__cta {
  margin-top: 0.9rem;
  font-size: 0.9rem;
  font-weight: 600;
}

/* ---------------------------------------------------------------- content pages */

/* The FAQ list. A description list, because each row genuinely is a term (the
   question) and its definition (the answer) - the same reasoning .nico-hours is
   built on, and the same semantics the schema.org FAQPage node on that page
   states, so the markup and the structured data describe the page alike.

   NOTHING HERE COLLAPSES, and no rule below hides an answer. This site ships one
   script, which toggles the header and footer panels and nothing else; an answer
   behind a click is an answer a crawler that runs no JavaScript may never read,
   and that page exists to be read by exactly those agents. The question is
   stacked above its answer rather than set beside it, because an answer runs to
   a paragraph where an opening time runs to six characters. */
.nico-faq {
  display: grid;
  margin-top: 1.5rem;
  border-top: 1px solid var(--nico-border);
}

.nico-faq div {
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--nico-border);
}

.nico-faq dt {
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1.4;
}

.nico-faq dd {
  margin-top: 0.5rem;
  max-width: 60ch;
  color: var(--nico-ink-muted);
}

/* ---------------------------------------------------------------- legal pages */

/* The privacy notice and the terms of sale. Both are long documents read top to
   bottom rather than scanned, which is a different job from every other page on
   this site and the reason they get rules of their own rather than reusing the
   catalogue's shell.

   A READING COLUMN, NOT A FORM COLUMN. `.nico-shell--narrow` is 34rem, which is
   right for two stacked inputs and far too tight for a paragraph - it would set
   this text at roughly forty characters a line and double the page's length. The
   width below lands a paragraph near the 65-75 character measure that reads
   fastest, while still leaving the tables inside it room to breathe.

   The tables themselves need no width rule: `.nico-table-wrap` already scrolls
   them horizontally, which is the honest answer for a four-column table on a
   360px phone. */
.nico-shell--prose {
  max-width: 52rem;
}

/* Generous vertical rhythm and a clear heading hierarchy, because the only way a
   reader finds "how long do you keep my data" in a document this long is by
   scanning the headings. Each h2 carries an id, so every section of both pages
   has a fragment somebody can link to - which for a legal notice is the whole
   point of it being a page rather than a PDF. */
.nico-legal {
  margin-top: clamp(2rem, 5vw, 3rem);
}

.nico-legal h2 {
  margin-top: clamp(2.25rem, 5vw, 3rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--nico-border);
  font-size: 1.35rem;
  line-height: 1.3;
}

.nico-legal h3 {
  font-size: 1.05rem;
  line-height: 1.4;
}

.nico-legal p {
  margin-top: 1rem;
  max-width: 68ch;
}

.nico-legal .nico-btn-row,
.nico-legal .nico-table-wrap,
.nico-legal .nico-faq {
  margin-top: 1.5rem;
}

/* The version and effective date, under the lead. Quiet, because it is metadata
   about the document rather than part of it - but present, because "which
   wording was in force when I ordered" is a question these two pages have to be
   able to answer. */
.nico-legal__version {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--nico-ink-muted);
}

.nico-legal__version time {
  font-weight: 600;
}

.nico-legal__actions {
  margin-top: 1.25rem;
}

/* A numbered sequence read as a column, not as a card grid. The wholesale page
   sets its four steps in `.nico-grid--4` because they are a glanceable summary;
   these five are the ORDER OF EVENTS that decides when a customer is committed,
   so they read one after another in the same column as the prose around them and
   keep their numbers visible while doing it. */
.nico-steps {
  margin-top: 1.5rem;
  padding-left: 1.5rem;
  list-style: decimal;
}

.nico-steps > li {
  margin-top: 1.25rem;
  padding-left: 0.35rem;
}

.nico-steps > li::marker {
  font-weight: 700;
  color: var(--nico-ink-muted);
}

.nico-steps p {
  margin-top: 0.4rem;
  max-width: 66ch;
  color: var(--nico-ink-muted);
}

/* The processing, cookie and recipient tables. `.nico-price-table` is the wrong
   neighbour to reuse: it right-aligns every column but the first and sets
   `white-space: nowrap`, both of which are correct for money and pack counts and
   ruinous for a sentence explaining a lawful basis. So these share
   `.nico-table-wrap` - one scroll behaviour, one border, one radius - and take
   their own cell rules. */
.nico-legal-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.nico-legal-table th,
.nico-legal-table td {
  padding: 0.8rem 1rem;
  text-align: left;
  vertical-align: top;
  min-width: 11rem;
}

.nico-legal-table thead th {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
  border-bottom: 1px solid var(--nico-border);
  white-space: nowrap;
}

.nico-legal-table tbody tr + tr th,
.nico-legal-table tbody tr + tr td {
  border-top: 1px solid var(--nico-border);
}

.nico-legal-table tbody th {
  font-weight: 700;
}

/* A cookie's name is a literal a reader will look for in their browser's storage
   inspector, so it is set in the mono face that says "copy this exactly" rather
   than in the body face, where PHPSESSID reads as a word. */
.nico-legal-table code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
  word-break: break-word;
}

/* ---------------------------------------------------------------- account portal */

/* Every rule below is built from the tokens declared at the top of this file - no new colour, no
   new font, no new radius. The portal is the same site behind a sign-in, and a second palette is
   how two halves of one site start looking like two sites.

   There is no new stylesheet and no new asset: layout/base.php links exactly one CSS file, cache
   busted by ChangelogReader::assetVersion(), and a second link would be a second render-blocking
   request on a page that already has nothing to show. */

/* A reading-width column for forms. The catalogue shell is 1160px, which is right for a grid of
   cards and wrong for a stack of two inputs - a full-width text field is harder to scan, not
   easier. */
.nico-shell--narrow {
  max-width: 34rem;
}

/* A FORM measure, not a reading measure. --narrow is 34rem because that is a comfortable line
   length for prose; a form is not prose, and paired fields inside 34rem would be about 250px
   each - narrow enough that a province name wraps against its own border. Used by the delivery
   address form, which is the one public form with fields worth pairing. */
.nico-shell--form {
  max-width: 44rem;
}

/* Keeps "Set up your account" on one line inside `.nico-shell--narrow` (34rem, minus the shell's
   own gutter). The shared `h1` rule clamps up to 3.75rem, which wraps this heading to two lines
   the moment the viewport is wide enough for the clamp to leave its floor - the narrow column
   never grows to match. This class is scoped to the activation page's own h1 rather than touching
   `h1` or `.nico-section__head` (both shared by every other page that centres a heading, including
   several others inside this same `.nico-shell--narrow` column), so no other page's type changes.

   The max is capped low enough to fit this exact sentence at the column's fixed inner width, and
   the min matches the shared `h1` rule's own floor, so a narrow phone renders exactly as before -
   wrapping to two lines there was never the problem, only the desktop clamp overshooting was. */
.nico-heading-fit {
  font-size: clamp(2.15rem, 5vw, 2.6rem);
}

.nico-form {
  margin-top: 1.75rem;
}

/* Inline variant for a lone button (sign out), so it does not claim a whole form's spacing. */
.nico-form--inline {
  margin-top: 2rem;
}

.nico-field {
  margin-bottom: 1.25rem;
}

/* Block labels, always visible. A placeholder used as a label disappears the moment someone types,
   which is exactly when they most want to check what the field was for - and screen readers treat
   the two quite differently. Shared appearance with .nico-field__label below (the <span> variant
   for a value with no control to label). */
.nico-field label,
.nico-field__label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--nico-ink);
}

.nico-field input {
  display: block;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  background: var(--nico-white);
  color: var(--nico-ink);
  /* Inputs do not inherit the page font, so it is stated. 1rem, never smaller: iOS Safari zooms
     the viewport on focus for anything under 16px, which throws the layout sideways mid-typing. */
  font-family: var(--nico-font-body);
  font-size: 1rem;
}

.nico-field input:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 1px;
  border-color: var(--nico-band-accent);
}

.nico-field__hint {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  color: var(--nico-ink-muted);
}

/* An input with a control sitting ON it - the show/hide button on the two password boxes.

   The wrapper positions the button; the input reserves the room for it on its right, so a long
   password scrolls under the field's own edge rather than under the button. Both are stated here
   rather than on the password field specifically: any field that grows an inline control needs the
   same two rules, and the button's width is what decides the padding. */
.nico-field__control {
  position: relative;
}

.nico-field__control input {
  padding-right: 3rem;
}

/* WITHOUT A SCRIPT THERE IS NO BUTTON. `data-nico-js` is set on <html> before the first paint (see
   layout/base.php), so a reader with scripts blocked sees an ordinary password field rather than a
   control that does nothing - the same rule the header's toggles are drawn by.

   44px square: this is a control a thumb aims at on a phone, and it sits against the right edge of
   a field where a miss lands on the input and pops the keyboard. */
.nico-field__reveal {
  display: none;
}

[data-nico-js] .nico-field__reveal {
  position: absolute;
  top: 50%;
  right: 0.15rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  transform: translateY(-50%);
  border: 0;
  border-radius: var(--nico-radius);
  background: none;
  color: var(--nico-ink-muted);
  cursor: pointer;
}

[data-nico-js] .nico-field__reveal:hover {
  color: var(--nico-ink);
}

[data-nico-js] .nico-field__reveal:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: -2px;
  color: var(--nico-ink);
}

/* Both eyes are in the button and exactly one is shown, keyed on the state the script sets - so
   the picture cannot drift from `aria-pressed`, and pressing it swaps no markup. Pressed means the
   password is showing, which is when the struck-through eye is the true picture of what the button
   will do next. */
.nico-field__reveal-icon--hide,
.nico-field__reveal[aria-pressed='true'] .nico-field__reveal-icon--show {
  display: none;
}

.nico-field__reveal[aria-pressed='true'] .nico-field__reveal-icon--hide {
  display: block;
}

/* The password policy, one rule per line, under the field it governs - see the activation form and
   PasswordPolicyValidator::rules(). It is ordinary text until a script marks it, which is why it
   carries the rules themselves rather than bare ticks: with nothing running it still says what is
   required, exactly as the one-sentence version does on every other password form.

   The marker is a CHARACTER and not a colour: a dot while a rule is unanswered, a tick once it
   holds, a cross once it does not. A red-green colour-blind reader gets the same three states as
   everybody else, which is the ruling `.nico-notice--error` and `.nico-field__required` already
   make elsewhere in this file. */
.nico-rules {
  margin: 0.4rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  color: var(--nico-ink-muted);
}

.nico-rules__item {
  position: relative;
  padding-left: 1.25rem;
  line-height: 1.45;
}

.nico-rules__item + .nico-rules__item {
  margin-top: 0.2rem;
}

.nico-rules__item::before {
  position: absolute;
  left: 0;
  content: '\2022';
}

.nico-rules__item.is-nico-met {
  color: #2f6b46;
}

.nico-rules__item.is-nico-met::before {
  content: '\2713';
}

.nico-rules__item.is-nico-unmet {
  color: #8f3134;
}

.nico-rules__item.is-nico-unmet::before {
  content: '\2717';
}

/* Two fields per row, and ONE below 40em.

   Stacked is the default and the two-column rule is the addition, rather than the other way
   round: a browser that never applies the media query - and every one that is 320px wide -
   renders the fields exactly as it did before this rule existed. `minmax(0, 1fr)` rather than
   `1fr`, because a grid column's default minimum is its content's own width, and a long
   uneditable value would otherwise push the column past its share and the row past the page. */
.nico-form__row {
  display: grid;
  gap: 0 1rem;
}

@media (min-width: 40em) {
  .nico-form__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* The required marker. Colour is NOT the only thing carrying it - the asterisk is a character in
   the label text and reads as one with styling off entirely, which is the same ruling
   `.nico-notice--error` makes about a red-green colour-blind reader. */
.nico-field__required {
  color: var(--nico-band-accent);
  font-weight: 700;
}

/* What the asterisks mean, said once above the fields. */
.nico-form__legend {
  margin-bottom: 1.25rem;
  font-size: 0.85rem;
  color: var(--nico-ink-muted);
}

/* WHERE ONE PART OF A LONG FORM STOPS AND THE NEXT BEGINS - the "First outlet" block on the
   add-a-company form, which asks for a second THING rather than a second fact about the first one.

   It is worn ALONGSIDE `.nico-panel__title`, and the pair is the whole point. That class holds the
   heading to label size, which is right - a bare `h2` here is display type and sets the section
   label larger than the h1 above it. But at label size the heading then reads as one more field
   label, indistinguishable from "NPWP" over the box above it, so the heading alone no longer says a
   section starts. The rule and the space are what carry that.

   The line is the divider this stylesheet already uses between two blocks whose own spacing is not
   enough to separate them - see `.nico-company__outlet + .nico-company__outlet` in account.css,
   same values, same reason stated there: without it two blocks read as one.

   The top margin collapses against the preceding `.nico-field`'s 1.25rem rather than adding to it,
   so the gap above the line is 1.25rem and not 2.35rem. */
.nico-form__section {
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--nico-border);
}

/* The Places autocomplete widget, above the map on the delivery-address form.

   Google renders a custom element (gmp-place-autocomplete) whose internals are a shadow root this
   stylesheet cannot reach, so what is set here is only the box it sits in. Full width, and the
   space below it reserved, so the map does not jump up the page on a build where the widget never
   arrives. */
.nico-map__search {
  margin-bottom: 0.75rem;
}

.nico-map__search gmp-place-autocomplete {
  display: block;
  width: 100%;
}

/* The delivery-address map picker, on /account/profile/addresses/new and
   /account/profile/addresses/{id}.

   THE HEIGHT IS SET HERE RATHER THAN BY THE SCRIPT, and that is the whole reason this rule exists:
   a Google map renders into a container with no intrinsic size, so a div left to collapse to 0px
   draws nothing at all and looks exactly like a script that failed. Stating it in the stylesheet
   also means the space is reserved before the SDK arrives, so the fields below do not jump down
   the page when it does.

   The height is capped against the viewport as well as fixed in rem: on a phone in landscape a
   flat 22rem would push the Save button off the screen, and somebody who cannot see the button
   does not finish the form.

   A MINIMUM as well as a maximum, because the 60vh cap has a floor problem of its own: a phone
   held in landscape with the browser chrome showing can be under 350px tall, and 60vh of that is
   a strip too short to aim a pin in. Below the minimum the Save button moves down the page, which
   is recoverable by scrolling; a map too small to use is not. */
.nico-map {
  height: 22rem;
  min-height: 14rem;
  max-height: 60vh;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  background: var(--nico-light);
}

/* A value the form STATES rather than asks for - the address on the activation page, which is read
   from the account rather than typed, because the emailed token proves control of the account and
   not of anything submitted with it (see AccountController::activate()).

   A <span> and not a <label>: there is no control for a label to name, and a label pointing at
   nothing is a promise to a screen reader that the page cannot keep. Its appearance is shared with
   .nico-field label above, so the field reads as one of the set. */

/* Sized and spaced like an input so the row does not jump when a field becomes a fact, but plainly
   not one: no border, no white ground, nothing that invites a click. */
.nico-field__static {
  padding: 0.7rem 0;
  font-size: 1rem;
  overflow-wrap: anywhere;
}

/* The sign-up form's decoy field. Hidden HERE rather than by an inline style or a `type="hidden"`
   attribute, which is the whole mechanism: an automated submitter parses the form markup and fills
   what it finds, and a class whose rule lives in a separate stylesheet costs it a second fetch and
   a CSS parse it will not make. See AccountController::REGISTRATION_HONEYPOT_FIELD.

   Moved off-canvas rather than `display: none`, because that is the pair of properties an assistive
   technology honours the same way a browser does while leaving the input focusable enough to remain
   a real field. The markup carries `aria-hidden` and `tabindex="-1"` besides, so a screen-reader
   user never meets it - a real customer who filled this in would be refused in silence, which is a
   far worse outcome than a bot getting past it. */
.nico-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* One notice style, two intents. The error variant differs by a left rule and its colour, never by
   colour alone - a red-only difference is invisible to a red-green colour-blind reader, and the
   text of these messages is the whole content. */
.nico-notice {
  margin-top: 1.5rem;
  padding: 0.85rem 1rem;
  border-left: 3px solid var(--nico-band-accent);
  border-radius: var(--nico-radius);
  background: var(--nico-accent-soft);
  font-size: 0.94rem;
}

/* A REFUSAL HAS TO LOOK LIKE ONE, AND THE OWNER HAS CHOSEN HOW - 2026-10-06, from a reference
   sketch, with the alternative in front of him. This is his decision; it is not re-derived.

   THE BOX IS PALE AND THE TEXT IS RED. A solid red block with white text was built and measured at
   7.37:1 against the page cream, against this box's 1.09:1, and it does announce itself louder. He
   saw both and took this one, BECAUSE THE FIELD'S LABEL NOW GOES RED TOO (see the rule below the
   field border). What the loud banner was buying was "notice me"; what he actually wants noticed is
   the FIELD, and the label says which one. The banner only has to carry the sentence.
   So these are the classic validation values: #faeaea ground, #5e1e21 text, #e3bdbe border closing
   the box, #a4373a left rule. Measured on the live refusal, the 2-character number on the phone
   form, against the #faf7ed of the `.nico-section--light` it sits on: box 1.09:1, text 10.70:1
   against the box - quiet to find, excellent to read, which is the trade he picked.
   No token is added and no new red invented: #a4373a is the accent account.css's overdue tooltip
   names as shared with this variant.

   COLOUR IS NOT THE ONLY CARRIER, which is the ruling `.nico-field__required` and
   `.nico-rules__item` also state - and on a pale ground that ruling is HARDER to keep, because this
   box and its green `.nico-notice--success` pair are now two pale tints. Measured in greyscale
   (the luminance matrix CSS `grayscale(1)` applies) the two GROUNDS land on #ededed and #f1f1f1,
   1.04:1 apart, which is to say indistinguishable. The grounds carry NOTHING, and these do, in
   descending order of how much:
     1. THE MARK, and it is doing nearly all of it. A filled 20.8px disc here against a bare inline
        `✓` there: the disc is 7.11:1 against its own ground in greyscale and success's mark has no
        ground at all. That is a difference in SHAPE and in area of ink before either character is
        resolved - see the `::before` below, which is why it stayed a disc.
     2. THE LEFT RULE'S WIDTH - 4px against the base 3px. The two rules' greyscale SHADES are only
        1.24:1 apart, so it is the width that carries and not the colour; said plainly because the
        obvious reading of this declaration is the wrong one.
     3. THIS BOX IS CLOSED AND THAT ONE IS NOT - the 1px border runs the whole way round an error,
        where `.nico-notice--success` sets `border-left-color` only and computes to a 0px border on
        its other three sides. In greyscale that outline is 1.47:1 against the box: a hairline, real
        but the weakest of the three, and it is not relied on.
   So the honest summary is that ONE carrier does this job on a pale ground where two did it on a
   dark one. It is enough, and it is enough because the marked FIELD now also says which box is
   wrong - the red label and the 2px border below. The hue is for everybody else. */
.nico-notice--error {
  border: 1px solid #e3bdbe;
  border-left: 4px solid #a4373a;
  background: #faeaea;
  color: #5e1e21;
  font-weight: 500;
}

/* THE MARK STAYS A DISC and the disc INVERTS with the box. It was a white circle on red; on this
   pale ground a white circle is the ground, so it is now a solid #a4373a circle with a white `!`.
   Measured: the disc reads 5.66:1 against the pink behind it and the glyph 6.59:1 against the disc.
   KEEPING IT A DISC rather than reverting to a bare `!` is the point. It is a SHAPE and an area of
   solid ink, so it is the one part of this notice that still separates from `.nico-notice--success`
   when the hue is gone - the grounds no longer do, as that rule's comment records. A bare `!` beside
   a bare `✓` is a glyph difference and nothing more; a filled circle beside a bare glyph is visible
   before either character is resolved. It also reads as a badge rather than as the first letter of
   the sentence.
   The gap is a MARGIN and not a trailing space in the string, for the reason the success mark's own
   rule states: trailing whitespace in generated content is collapsed away.
   `line-height` equal to the box's own height is what centres the glyph vertically without a second
   layout mode; the negative `vertical-align` sits the disc on the text's baseline rather than on the
   line box, so a notice that wraps to two lines keeps the disc beside the FIRST one. */
.nico-notice--error::before {
  content: '!';
  display: inline-block;
  width: 1.3rem;
  height: 1.3rem;
  margin-right: 0.55rem;
  border-radius: 50%;
  background: #a4373a;
  color: var(--nico-white);
  font-weight: 700;
  line-height: 1.3rem;
  text-align: center;
  vertical-align: -0.25em;
}

/* THE FIELD A REFUSAL NAMED, and `aria-invalid` is both the hook and the announcement.
   Styling the attribute rather than a class of our own is what stops the two drifting: there is no
   way to paint a box red here without a screen reader being told it is invalid, and no way to tell a
   screen reader without the box going red. The mechanism the password field already uses for
   `aria-pressed` on its reveal button, applied to the one attribute that has a meaning.
   Colour is not alone again: the border goes to 2px, so the field reads as marked in a greyscale
   screenshot and to anybody who cannot separate the red from the ordinary grey. The sentence itself
   is above the form and tied to the control with `aria-describedby`, which is what a screen reader
   reads out on focus - the colour is for the eye that has already scrolled past the sentence. */
.nico-field input[aria-invalid='true'],
.nico-field select[aria-invalid='true'],
.nico-field textarea[aria-invalid='true'] {
  border: 2px solid #a4373a;
  background: #fdf7f7;
}

/* Focus still wins, and visibly. Without this the accent outline sits on a red border and the two
   fight; the field keeps its error border while focused so moving away and back does not change
   what the box is saying. */
.nico-field input[aria-invalid='true']:focus-visible,
.nico-field select[aria-invalid='true']:focus-visible,
.nico-field textarea[aria-invalid='true']:focus-visible {
  outline: 2px solid #a4373a;
  outline-offset: 1px;
  border-color: #a4373a;
}

/* THE LABEL OF THE MARKED FIELD GOES RED. The owner's choice of 2026-10-06, and the reason the
   notice above was allowed to go back to a pale box: the thing he wants a refusal to point at is
   the FIELD, and the label is what names it. "Phone number" in red over a red-bordered box says
   which box is wrong without a sentence being read, which is work the banner was being asked to do.

   THE SAME HOOK AS THE BORDER, deliberately: `[aria-invalid='true']`, the attribute the rule above
   paints from. There is no second class and the view gets nothing new to remember, so the label and
   the border cannot disagree about which field is wrong - they are two readings of one attribute,
   and `PortalForm::errorAttributes()` is still the only thing that can write it. A class here would
   be a second truth the emitter would have to keep in step with the first.

   `:has()` RATHER THAN A SIBLING COMBINATOR, because the label comes FIRST in the markup and `+`
   and `~` only look forward. The invalid control is matched as a DESCENDANT and not a child: on the
   activation form the input sits inside `.nico-field__control`, the wrapper that positions the
   reveal button, so `:has(> input)` would silently skip exactly the two password fields. What is
   PAINTED is the direct-child label, so a label belonging to something nested in this field - a
   `.nico-choice` box, say - is not reddened by its neighbour's refusal.

   A browser without `:has()` drops this rule and keeps the 2px border and the sentence, which is
   the same field marked less loudly rather than a field not marked.

   `.nico-field__required` inside the label keeps its own `color`, so the asterisk stays the band
   accent and does not quietly become a second shade of red beside the text. */
.nico-field:has(input[aria-invalid='true']) > label,
.nico-field:has(select[aria-invalid='true']) > label,
.nico-field:has(textarea[aria-invalid='true']) > label {
  color: #a4373a;
}

/* THE CONTROL A REFUSAL NAMED WHEN THAT CONTROL IS NOT A FIELD - a button, a link acting as one.
   The owner pressed "Choose a different payment method", the action was refused, and nothing on the
   page said which of the controls in front of him the sentence was about.
   WHY NOT `aria-invalid`. That attribute means "this form control's VALUE is invalid". A button has
   no value, so putting it there would be a false statement to a screen reader in order to reach a
   selector - and the rule above only gets to be honest BECAUSE the attribute means exactly one
   thing. Reaching for it here would spend that.
   SO THE HOOK IS A CLASS AND THE ANNOUNCEMENT IS `aria-describedby`, pointing at the `role="alert"`
   notice that already carries the sentence. What stops the two drifting is not the selector this
   time but the EMITTER: `PortalForm::controlErrorAttributes()` writes the whole `class` attribute
   and the pointer in one return value, so a view cannot paint this without also naming the sentence.
   Both or neither, the same contract `errorAttributes()` keeps, reached a different way.
   `.nico-btn` already carries `border: 2px solid transparent`, so the border costs no reflow and
   nothing moves when a control becomes marked. The ring is a second, non-colour carrier: the
   control changes SHAPE, which a greyscale screenshot and a red-green colour-blind reader both get.
   The hover and focus arms are not decoration - `.nico-btn--outline:hover` restates `border-color`
   at the same specificity, and without them the mark would vanish under the pointer. */
.is-nico-errored,
.is-nico-errored:hover,
.is-nico-errored:focus-visible {
  border-color: #a4373a;
  box-shadow: 0 0 0 3px #e3bdbe;
}

/* The success variant, opt-in from partials/flash.php - see its docblock for why the neutral style
   stays the default. Success sits next to the error variant above and the two now make a real
   red-green pair, which the SAME ruling covers: a colour-only difference is invisible to a
   red-green colour-blind reader. So this does not only change hue - it carries a leading checkmark
   too, the same non-colour signal `.nico-rules__item.is-nico-met` already uses for a met rule, and
   reuses that rule's own green (#2f6b46) rather than inventing a second one. */
.nico-notice--success {
  border-left-color: #2f6b46;
  background: #eaf3ec;
  color: #205032;
}

/* The gap is a MARGIN, not a trailing space inside the content string. A space at the end of
   generated content is collapsed away like any other trailing whitespace, which rendered the mark
   flush against the sentence - "checkYour account is ready". Seen in the browser, not reasoned. */
.nico-notice--success::before {
  content: '\2713';
  margin-right: 0.45rem;
  font-weight: 700;
}

/* Reserves room for the dismiss button below, on every flash regardless of whether a script is
   there to reveal it - the same choice `.nico-field__control input` makes for its reveal button. */
.nico-notice--dismissible {
  position: relative;
  padding-right: 3.5rem;
}

/* THE DISMISS CONTROL. Hidden unless `[data-nico-js]` is on `<html>` - the same gate
   `.nico-header__button` uses above - and for the same reason: header.js is what makes this button
   do anything, so a reader with scripts blocked must never meet one that does nothing. Positioned
   at the notice's own top-right corner, in a 44px square tap target, though the glyph it shows is
   small. */
.nico-notice__dismiss {
  display: none;
}

[data-nico-js] .nico-notice__dismiss {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

[data-nico-js] .nico-notice__dismiss:hover,
[data-nico-js] .nico-notice__dismiss:focus-visible {
  color: var(--nico-band-accent);
}

[data-nico-js] .nico-notice__dismiss:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: -2px;
}

.nico-form__aside {
  margin-top: 1.75rem;
  font-size: 0.88rem;
  color: var(--nico-ink-muted);
}

/* The same quiet note, sitting directly under the thing it is about rather than after a block of
 * controls. 1.75rem of air reads as "a new idea"; this one is a footnote to the table above it, so
 * it keeps the type and loses the gap. */
.nico-form__aside--tight {
  margin-top: 0.6rem;
}

.nico-account-facts {
  margin-top: 1.5rem;
}

.nico-account-facts div {
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--nico-border);
}

.nico-account-facts dt {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--nico-ink-muted);
}

.nico-account-facts dd {
  margin-top: 0.2rem;
  /* An email address has no spaces to wrap at, so a long one would push the page sideways on a
     phone without this. */
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- portal ordering */

/* The basket, checkout, review, confirmation and order history. Built from the same tokens as
   everything above - no new colour, no new font, no new radius - because the ordering pages are the
   same site behind the same sign-in as the account pages they sit beside.

   These pages carry NO SCRIPT, like the rest of src/Public, so every interactive thing below is a
   real form control. That constrains the CSS in one way worth stating: there is no class here that
   only makes sense once a script has toggled it, and no rule that hides a control the page still
   needs. A customer on Gili Air with a failing connection gets the same page everyone else does. */

/* Selects and textareas, matched to the input rule above rather than left to the browser.
   Unstyled, they inherit neither the page font nor the field border, so a form of one input and two
   dropdowns reads as three unrelated widgets. The properties are the input's, plus the two a select
   needs: `appearance: none` to drop the platform chrome the border then duplicates, and the padding
   room for the arrow drawn behind it.

   The buying-context band's switcher is named alongside them although it sits in no `.nico-field`:
   it is the same control doing the same job one page earlier, and a second copy of this rule for
   it would be a second answer to what a dropdown on this site looks like. What the band DOES
   restate is only the two properties a full-width form field wants and a band does not - see
   `.nico-buy-band__switch select`.

   The product page's size picker is named here for exactly that reason too. It is a dropdown on this
   site, so it is this dropdown; catalogue.css gives it only its PLACE on the eyebrow line and the
   width a control beside a heading wants, and states no border, font or arrow of its own. */
.nico-field select,
.nico-field textarea,
.nico-buy-band__switch select,
.nico-size-pick select {
  display: block;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  background: var(--nico-white);
  color: var(--nico-ink);
  font-family: var(--nico-font-body);
  /* 1rem, never smaller, for the same reason the input rule states it: iOS Safari zooms the
     viewport on focus for anything under 16px. */
  font-size: 1rem;
}

.nico-field select,
.nico-buy-band__switch select,
.nico-size-pick select {
  appearance: none;
  padding-right: 2.4rem;
  /* The disclosure arrow, as an inline SVG data URI rather than a pseudo-element: a `select` cannot
     carry ::after, and a background image needs no extra element. Its stroke is the muted ink token
     written out, because a data URI cannot read a custom property. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c6b7a' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  background-size: 1rem;
}

.nico-field textarea {
  resize: vertical;
  min-height: 5.5rem;
}

.nico-field select:focus-visible,
.nico-field textarea:focus-visible,
.nico-buy-band__switch select:focus-visible,
.nico-size-pick select:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 1px;
  border-color: var(--nico-band-accent);
}

/* A disabled control still has to be READABLE - it is explaining why it cannot be used. Greying it
   into illegibility turns a refusal into a mystery. */
.nico-field select:disabled,
.nico-field input:disabled {
  background: var(--nico-light);
  color: var(--nico-ink-muted);
  cursor: not-allowed;
}

/* A quiet button: the same shape and rhythm as the others, at the weight a secondary action in a
   table cell needs. Full `.nico-btn` sizing would make Remove as loud as Confirm, and this is the
   only modifier that ever sits inside a table row. */
.nico-btn--quiet {
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--nico-border);
  background: var(--nico-white);
  color: var(--nico-ink-muted);
  font-size: 0.78rem;
}

.nico-btn--quiet:hover,
.nico-btn--quiet:focus-visible {
  border-color: var(--nico-band-accent);
  background: var(--nico-accent-soft);
  color: var(--nico-ink);
}

/* ---- fieldsets and choices

   A `fieldset` carries a browser border and padding that fight every other box on the page, so both
   are reset and the grouping is carried by the legend and the spacing instead. The element is kept
   rather than swapped for a div because it is what makes a radio group ONE thing to a screen
   reader; a div of radios is announced as loose controls with no shared question. */
.nico-fieldset {
  margin: 0 0 1.5rem;
  padding: 0;
  border: 0;
}

.nico-fieldset legend {
  margin-bottom: 0.9rem;
  padding: 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--nico-ink);
}

/* The radio and its label on one line, with the label taking the remaining width so the whole
   phrase is a click target rather than just the 16px circle - which is the difference between a
   usable and an unusable form on a phone. */
.nico-choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}

.nico-choice input[type='radio'] {
  /* Big enough to hit. The browser default is around 13px, which is below every touch-target
     guideline and is the single most common reason a radio group is hard to use on a phone. */
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--nico-band-accent);
  flex: none;
}

.nico-choice label {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--nico-ink);
  cursor: pointer;
}

/* A lone checkbox inside a `.nico-field` (the terms box on the review page). `.nico-field input` makes
   every input a full-width block, which stacks the box above its label text. Here the box is taken
   out of the flow at the label's left edge and the label is indented to clear it, so the text and
   the link wrap beside the box with a hanging indent. */
.nico-field--check label {
  position: relative;
  padding-left: 1.75rem;
  cursor: pointer;
}

.nico-field--check input[type='checkbox'] {
  position: absolute;
  left: 0;
  top: 0.05rem;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  padding: 0;
  accent-color: var(--nico-band-accent);
}

/* The fields belonging to one choice, indented under it by a rule rather than by margin alone: the
   rule is what says "these belong to the option above" when both options are on screen at once,
   which they always are here - hiding one would need script. The one body that is ever hidden is the
   payment choice's redirect sentence, by the `:has()` rule in the `.nico-pay` block below, and it
   holds no field, so there is nothing in it for a customer to lose. */
.nico-choice__body {
  margin: 0 0 1.5rem 0.55rem;
  padding-left: 1.15rem;
  border-left: 2px solid var(--nico-border);
}

.nico-choice__body .nico-field:last-child {
  margin-bottom: 0;
}

/* ---- the payment choice on the review page

   ONE QUESTION, ONE BUTTON. Two radios decide how the order is paid for, and the single submit
   button beneath them carries two labels, one per branch, of which the page shows the one that
   matches the checked radio. The choice itself is never the stylesheet's: it is the radio's own
   checked state, which the browser posts whatever this file did or did not do, and the server
   accepts only the two words the radios carry. This file decides only what the page SAYS.

   THE INVARIANT, and it is the point of this whole block:

       NO BASE RULE MAY CARRY `display: none`. EVERY HIDE RULE LIVES INSIDE `:has()` AND MAY ONLY
       EVER HIDE THE BRANCH THAT IS NOT CHECKED.

   WHY THAT POLARITY. `:has()` is unsupported in older browsers, and a stylesheet can fail to load
   at all. Held to the polarity above, the page then degrades to showing EVERYTHING - both button
   labels and the redirect sentence - which is ugly and true. The opposite polarity (hide the online
   branch by default, reveal it on `:checked`) would show "Place order" to a customer who is about to
   be charged, in exactly the browsers that cannot run the reveal. That is the lie this rule exists to
   make impossible. A hide rule added here must name the branch it hides and the radio whose being
   checked makes that branch the unselected one; one that hides "unless" something is checked is the
   wrong shape, however it is spelled.

   `:has()` IS ALREADY THIS SITE'S IDIOM for a state that has to be read from a descendant: see the
   rule that reddens `.nico-field > label` when its control is `[aria-invalid='true']`, and the
   comment above it for why a sibling combinator was the wrong tool there. It is the wrong tool
   here for the same reason and one more - each radio sits inside its OWN `.nico-choice` div, so
   `input:checked ~` could never reach the button. There is no such rule in this stylesheet and this
   block does not introduce one.

   The hooks are classes naming the BRANCH (`__radio-transfer`, `__radio-online`), not attribute
   selectors on the posted `value`: the words PaymentMethod posts are the server's contract, and a
   second copy of them in a stylesheet is a copy nothing would keep in step. */

/* `min-width: 0` because a fieldset's default `min-inline-size: min-content` is what lets one long
   unbreakable run push a flex item wider than its column, and this one sits in a 21rem panel. The
   bottom margin `.nico-fieldset` leaves for a fieldset above another block is released: the panel's
   action column already spaces its children, and the button is INSIDE this fieldset. */
.nico-pay {
  min-width: 0;
  margin-bottom: 0;
}

/* A refusal sits between the legend and the first radio. `.nico-notice` opens with 1.5rem above
   itself and nothing below, which is right under a field and wrong here: the legend already leaves
   its own gap, and the notice must not touch the option it is about. */
.nico-pay > .nico-notice {
  margin: 0 0 0.75rem;
}

/* The sentence under the online option, in the shape `.nico-choice__body` already gives a note that
   belongs to the option above it (the rule down the left edge), tinted so it reads as a note and not
   as a field. `--nico-ground` and `--nico-ink-muted` together clear 4.5:1. It is also what the
   unavailable sentence wears, so the two states of that slot look alike; only the redirect sentence
   carries the hide hook below. */
.nico-pay__strip {
  margin-bottom: 1rem;
  padding-block: 0.6rem;
  padding-right: 0.85rem;
  background-color: var(--nico-ground);
  color: var(--nico-ink-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* A disabled option must not read as a live one: the browser greys the circle, and this greys the
   words, so the sentence beneath it is explaining something that already looks unavailable. */
.nico-pay .nico-choice input:disabled + label {
  color: var(--nico-ink-muted);
  cursor: default;
}

/* THE HIDE RULES - three, one per thing that belongs to a branch, each hiding that branch only when
   the OTHER radio is the checked one.

   Transfer checked: the online branch's redirect sentence and its button label go. */
.nico-pay:has(.nico-pay__radio-transfer:checked) .nico-pay__strip--redirect {
  display: none;
}

.nico-pay:has(.nico-pay__radio-transfer:checked) .nico-pay__label-pay {
  display: none;
}

/* Online checked: the transfer branch's button label goes. */
.nico-pay:has(.nico-pay__radio-online:checked) .nico-pay__label-place {
  display: none;
}

/* ---- the basket table */

/* The quantity control: a number box and its Update button on one line. `align-items: start` rather
   than center so the pair stays aligned to the top of the cell when a minimum-order hint wraps
   underneath it.

   `justify-content: flex-end` because `.nico-price-table` right-aligns every column but the first,
   and a flex container ignores the inherited `text-align` for its own children - so without this
   the control sat at the left of a cell whose heading was at the right, and the column header no
   longer pointed at the thing it named. */
.nico-cart__qty,
.nico-cart__add {
  display: flex;
  align-items: start;
  justify-content: flex-end;
  gap: 0.4rem;
}

/* THE BASKET ROW'S FORM HOLDS A `.nico-visually-hidden` LABEL, which is `position: absolute`, and
   the form was the one box between that label and the page that is not positioned. With no
   positioned ancestor the label resolves against the initial containing block, so it escapes
   `.nico-table-wrap`'s clip while still sitting at the x the form puts it at - the form's right
   edge, which on a phone is inside a table wider than the screen - and that 1px element alone made
   the whole basket page scroll sideways (scrollWidth 510 against a 375px window).

   Positioning the form makes IT the containing block, so the label is clipped by the table's own
   scroller like everything else in the row. The product page's twin form (`.nico-cart__add`) is
   anchored one level up instead, by `.nico-product-hero .nico-buy` in catalogue.css, for the
   tooltip reason stated there; that is why this rule names the basket's form alone. */
.nico-cart__qty {
  position: relative;
}

.nico-cart__qty input[type='number'],
.nico-cart__add input[type='number'] {
  width: 4.5rem;
  padding: 0.45rem 0.5rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  background: var(--nico-white);
  color: var(--nico-ink);
  font-family: var(--nico-font-body);
  /* 1rem for the iOS zoom reason stated on every other input in this file. A quantity box is the
     one a customer on a phone is most likely to focus, so it is the last place to shave a pixel. */
  font-size: 1rem;
  text-align: right;
}

.nico-cart__qty input:focus-visible,
.nico-cart__add input:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 1px;
  border-color: var(--nico-band-accent);
}

/* The cart's cells are taller than the catalogue ladder's, because two of them hold controls. Top
   alignment keeps the product name level with the row's first line instead of floating in the
   middle of it. */
.nico-cart td,
.nico-cart th {
  vertical-align: top;
}

.nico-cart__qty .nico-field__hint {
  margin-top: 0.35rem;
}

/* THE BASKET ROW'S UPDATE BUTTON, hidden only once buy-for-switch.js has said it is listening -
   the same two marks the checkout's own Update button is gated on, and for the same reason:
   `[data-nico-js]` like every script-gated rule here, and `[data-nico-autosubmit-ready]`, which
   that script writes on the form only when it has actually bound the change handler. A browser
   where the file is absent, blocked, still loading or missing `requestSubmit()` keeps the button,
   which is then the whole control - including the only way to remove a line, by typing 0 into the
   box beside it and pressing it. The default is the no-script state, never the other way round. */
[data-nico-js] form[data-nico-autosubmit-ready] .nico-cart__qty-apply {
  display: none;
}

/* A row the service refused. Tinted AND marked with an accent bar, never tint alone - the same
   ruling `.nico-notice--error` states: a colour-only difference is invisible to a red-green
   colour-blind reader, and the refusal underneath is the actual content. */
.nico-price-table tr.is-refused > th {
  box-shadow: inset 3px 0 0 #a4373a;
}

.nico-price-table tr.is-refused > th,
.nico-price-table tr.is-refused > td {
  background: #f8ecec;
}

/* The explanation row under a refused line. It carries no border of its own so it reads as part of
   the row above rather than as a row in its own right. */
.nico-cart__note > td {
  padding-top: 0;
}

.nico-cart__note .nico-notice {
  margin-top: 0.35rem;
}

/* ---- the money summary */

/* Right-aligned and narrow, sitting under the table it totals. Pushed to the right edge on wide
   screens because that is where the figures in the table's last column already are, and a total
   that does not line up with its column is a total people re-add by hand. */
.nico-summary {
  margin: 1.5rem 0 0 auto;
  max-width: 22rem;
}

.nico-summary div {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--nico-border);
}

.nico-summary dt {
  color: var(--nico-ink-muted);
  font-size: 0.9rem;
}

.nico-summary dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

/* The figure the customer is agreeing to. Larger, heavier and on a rule of its own, because it is
   the one number on the page that anybody actually reads. */
.nico-summary__total {
  border-bottom: 0;
  border-top: 2px solid var(--nico-ink);
  margin-top: 0.35rem;
  padding-top: 0.8rem;
}

.nico-summary__total dt {
  color: var(--nico-ink);
  font-weight: 600;
  font-size: 1rem;
}

.nico-summary__total dd {
  font-size: 1.2rem;
  font-weight: 700;
}

/* ---- the empty state

   A real state with a real next step, not an error, so it is styled as a quiet panel rather than as
   a notice. A red box telling someone their basket is empty is scolding them for arriving. */
/* The photo variant of the customer's mark, from CustomerAvatar::markup(). It shares
   `.nico-avatar` with the SVG disc so both are sized and placed by one rule; this only cuts it
   round and stops a non-square source stretching.

   `object-fit: cover` rather than trusting the file's shape: a photo is fitted WITHIN 512x512 on
   upload, which preserves its aspect ratio, so a portrait taken on a phone arrives tall and would
   letterbox in a round frame without this. */
.nico-avatar--photo {
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--nico-border);
  background: var(--nico-light);
}

/* The map before a customer has asked for it.

   HEIGHT ZERO RATHER THAN `display: none`, because Google's map measures its own container: a map
   built inside a `display: none` box lays out at 0x0 and stays wrong after the box reappears,
   where a zero-HEIGHT box is still laid out and re-measures cleanly when the height returns.
   `overflow: hidden` keeps the tiles from spilling while it is shut. The transition is what makes
   the reveal read as the map opening rather than the page jumping. */
.nico-map[data-collapsed] {
  height: 0;
  min-height: 0;
  border-width: 0;
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .nico-map {
    transition: height 0.2s ease;
  }
}

/* The second line of a checkbox label - what the option MEANS, under what it is called.

   Block, so it sits under the label text rather than running on from it, and quieter, so the two
   read as a name and its explanation rather than one long sentence. */
.nico-choice__hint {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--nico-ink-muted);
}

/* A row's actions as icons rather than a column of words.

   THREE BUTTONS OF PROSE PER ROW ("Edit", "Make main", "Remove") made the actions column wider
   than the number it was acting on, and on a phone it wrapped into a stack taller than the row.
   Icons carry the same three actions in a line. Each keeps a real accessible name - see the
   markup, which pairs an aria-hidden glyph with visually-hidden text - so nothing is lost to a
   screen reader, and `title` gives a pointer user the same word on hover. */
.nico-actions {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  justify-content: flex-end;
}

/* The forms wrapping a POST action must not add layout of their own between the icons. */
.nico-actions .nico-form {
  margin: 0;
}

.nico-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 32px, down from 40. The admin's table actions are `btn-sm`-sized and these sat noticeably
     larger than the number they act on; a row of three looked like the point of the row. Still a
     real target: 32px with the 0.35rem gap keeps the three separable under a thumb. */
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius);
  background: var(--nico-white);
  /* EACH ACTION CARRIES ITS OWN COLOUR, like the admin's, rather than three identical grey boxes
     that have to be read one at a time. The icon inherits it through `currentColor`. */
  color: var(--nico-ink-muted);
  line-height: 1;
  cursor: pointer;
}

.nico-action:hover,
.nico-action:focus-visible {
  border-color: currentColor;
}

/* Edit: the page's own accent, because it is the ordinary action of the three. */
.nico-action--edit {
  color: var(--nico-band-accent);
}

/* Favourite: gold, and FILLED once it is on - the fill is what replaced the "Main" badge, so it
   has to read as a state rather than as a button that happens to be yellow. */
.nico-action--favourite {
  color: #c9a227;
}

/* Full strength even though it is disabled: this one is disabled because it is ALREADY TRUE, which
   is the opposite of unavailable, and dimming it would say the wrong thing.

   THE SELECTOR REPEATS `[aria-disabled='true']` ON PURPOSE. The generic disabled rule further down
   this file matches the same element at the same specificity, and later wins - so a plain
   `.nico-action--favourite.is-on` lost the colour war and the star rendered grey. Matching the
   attribute here raises this rule above it rather than relying on source order, which the next
   person to move a block would break without noticing. */
.nico-action--favourite.is-on[aria-disabled='true'],
.nico-action--favourite.is-on[aria-disabled='true']:hover {
  opacity: 1;
  color: #c9a227;
  border-color: #c9a227;
  background: #fdf8e7;
}

/* The add action in a panel heading: quieter than a row action until pointed at, because it sits
   beside a heading rather than inside a row of controls. */
.nico-action--add {
  color: var(--nico-band-accent);
}

/* A DISABLED action stays in the row rather than disappearing from it.

   "Make main" on the row that already IS the main one is the case this exists for: removing the
   button would make the columns ragged and leave a person wondering where it went, while showing
   it live would offer an action that changes nothing. Dimmed and unclickable says "this is what
   that row is" - which is the actual answer. */
.nico-action[disabled],
.nico-action[aria-disabled='true'] {
  opacity: 0.4;
  cursor: default;
  border-color: var(--nico-border);
  color: var(--nico-ink-muted);
}

.nico-action[disabled]:hover,
.nico-action[aria-disabled='true']:hover {
  border-color: var(--nico-border);
  color: var(--nico-ink-muted);
}

/* Remove: red, but the MUTED red until pointed at. A permanently vivid delete button in every row
   turns a list into a row of warnings; muted-then-vivid keeps the colour meaning "careful" at the
   moment it matters. */
.nico-action--danger {
  color: #c08a86;
}

.nico-action--danger:hover,
.nico-action--danger:focus-visible {
  border-color: #b3261e;
  color: #b3261e;
  background: #fdf1f0;
}

/* A panel heading with an action at the far end of it.

   The heading keeps its own bottom margin from `.nico-panel > h2`, so this row only has to place
   the two horizontally - and `baseline` rather than `center`, so the icon sits on the heading's
   baseline instead of floating against its cap height. */
.nico-panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.nico-panel__head > h2 {
  margin-bottom: 0.75rem;
}

/* A number and its WhatsApp mark, kept on ONE line.

   Inline content wrapped in a narrow column and put the mark on a line of its own above the
   number, which reads as a stray icon rather than as a label. Flex keeps them together and
   `white-space: nowrap` keeps the digits from breaking mid-number - a telephone number split
   across two lines is genuinely harder to read back. */
.nico-phone-number {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
}

/* Sized by its own attributes; this only stops it being stretched by the flex line. */
.nico-icon--whatsapp {
  flex: none;
}

/*
 * THE ADDRESS CELL IS THE ONE CELL IN THESE TABLES THAT MUST WRAP.
 *
 * `.nico-price-table` sets `white-space: nowrap` on every th and td, which is right for money, a
 * pack count and a telephone number - all short, and all worse for being broken across lines. An
 * address is none of those. "Jl. Lebak Sari No. 8, Villa Kayu, Kerobokan Kelod" held on one line
 * makes the table wider than the panel, and `.nico-table-wrap` then scrolls it - which on a phone
 * hides the ACTIONS column, the whole reason the row became a table.
 *
 * So the address wraps and the row grows downward instead of sideways. `min-width` stops it
 * wrapping after every second word in the narrow right-hand column of the profile grid, and the
 * cell takes the free width because the other two columns size to their content.
 *
 * QUALIFIED WITH THE TABLE AND THE ELEMENT, and that is load-bearing rather than tidy. The rule it
 * has to beat is `.nico-price-table th, .nico-price-table td`, which is a class plus an element;
 * a bare `.nico-address-cell` is one class and LOSES, so the cell kept `nowrap`. Measured at 390px
 * with the bare selector: the address held one 675px line, the table scrolled to 889px inside a
 * 333px wrap, and the actions column sat at x=910 - entirely off-screen. The buttons were the whole
 * reason the row became a table, and they were the part a phone could not reach.
 */
.nico-price-table td.nico-address-cell {
  white-space: normal;
  min-width: 10rem;
  line-height: 1.5;
}

/*
 * ON A PHONE THE FLOOR COMES OFF ENTIRELY.
 *
 * 10rem is a reading floor, and at 390px it is still too much: measured with it in place, the table
 * came to 374px inside a 333px wrap and about 40px of the actions column stayed clipped. A floor
 * that protects the address's line length at the cost of the buttons has the priority backwards -
 * the address is READ and can afford to wrap badly, the buttons are PRESSED and cannot afford to be
 * off-screen.
 *
 * So below 48em the address wraps as tightly as it must and the three actions stay reachable.
 */
@media (max-width: 47.99em) {
  .nico-price-table td.nico-address-cell {
    min-width: 0;
  }

  /*
   * THE PROFILE'S TABLES GIVE UP HORIZONTAL PADDING BEFORE THEY GIVE UP THEIR ACTIONS.
   *
   * Found while checking the address table at 390px, in the PHONE table above it - which predates
   * this change and had the same defect the address work was sent to avoid: 408px of table inside
   * a 348px wrap, with the delete icon sitting 59px past the right edge, reachable only by
   * horizontally scrolling a region most people do not know scrolls.
   *
   * A telephone number is the one thing in that table that genuinely must not wrap, so the width
   * has to come from somewhere else: 1rem of side padding on each of three columns is 6rem of a
   * 348px box. Halved here, which is enough for both tables to fit whole.
   *
   * SCOPED TO `.nico-panel` on purpose. `.nico-price-table` is also the catalogue's price ladder
   * and the basket's line table, which are sized for their own content on their own pages; this is
   * about two three-column tables in a narrow account panel and must not reach them.
   */
  .nico-panel .nico-price-table th,
  .nico-panel .nico-price-table td {
    padding-inline: 0.5rem;
  }

  /*
   * AND THE NUMBER ITSELF WRAPS, once the padding has given all it can.
   *
   * Halving the padding took the phone table from 60px of overflow to 27px - better, and still not
   * whole. The remaining width is one unbreakable string: "+62 858-5735-5927" held the Number
   * column at 167px of a 333px box.
   *
   * The `nowrap` this lifts was aimed at a DIFFERENT problem - the WhatsApp mark breaking onto its
   * own line above the number, reading as a stray icon rather than as a label on it. That does not
   * come back here: the mark is a flex ITEM beside the text, not a character inside the text run,
   * so it stays welded to the number's first line while only the digits reflow.
   *
   * A number wrapping after a hyphen is untidy. A delete button 59px beyond the right edge of a
   * region most people never discover scrolls is broken, and between untidy and broken this is not
   * a close call.
   */
  .nico-phone-number {
    white-space: normal;
  }
}

/* Every icon inside an action is decorative and sized by the SVG's own attributes; this only stops
   a stray line-height from nudging it off centre. */
.nico-action .nico-icon {
  display: block;
}

/* The two-column layout of /account/profile.

   ONE COLUMN IS THE DEFAULT and the second is the addition, so a narrow screen renders the panels
   stacked in source order without the media query ever applying. 60em rather than the form grid's
   40em: these columns hold whole panels - a table with three columns of actions among them - not
   two short inputs, and splitting them earlier would make each too tight to read. */
.nico-profile-grid {
  display: grid;
  /* `minmax(0, 1fr)` on the SINGLE-column case too, not only on the two-column one. A grid track
     defaults to `auto`, which is sized by its content's own minimum - and the phone table's
     minimum is its full width, so the one-column track grew past a 390px viewport and scrolled
     the whole page sideways. `.nico-table-wrap` scrolls the table internally, but only once the
     track stops being asked to fit it. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0 3rem;
  align-items: start;
}

@media (min-width: 60em) {
  .nico-profile-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Each column is itself a stack, so the panels inside it keep an even rhythm independently of
   what the other column is doing - two columns of different heights is normal here and neither
   should stretch to match the other (hence `align-items: start` above). */
.nico-profile-grid__column {
  display: flex;
  flex-direction: column;
  /* THE SAME TRAP ONE LEVEL DOWN. A flex item's `min-width` is `auto`, meaning its content's
     minimum - so the phone table would push this column wider than its grid track even after the
     track was fixed, and the page would scroll sideways again for a different reason. Zero here
     lets `.nico-table-wrap` do the scrolling it already knows how to do. */
  min-width: 0;
}

/* One section of the profile page: a heading and whatever it governs.

   THE SPACING IS THE POINT. Before this existed, each section's submit button sat directly against
   the next section's heading - "Save your name" touching "Photo" - because a button row has no
   bottom margin and an h2 no top one. Stating both here fixes every panel at once, and scoping it
   to the panel leaves every other heading on the site alone. */
.nico-panel {
  margin-bottom: 2.5rem;
}

.nico-panel:last-child {
  margin-bottom: 0;
}

/* A panel that FOLLOWS a form needs the gap on its own top edge, and nothing else provides it: a
   form has no bottom margin and its submit button none either, so the invited-companies panel on
   the activation page sat directly against "Set up my account" - the button reading as part of the
   panel rather than as the end of the form above it.

   The same 2.5rem `.nico-panel` already puts BELOW itself, so a page of panels after a form keeps
   one rhythm rather than a larger first gap. Stated as a sibling rule rather than on the panel
   variant, because the problem is the pairing and not the panel: any panel after any form wants
   this, and a panel that opens a section still starts flush where its section begins. */
.nico-form + .nico-panel {
  margin-top: 2.5rem;
}

.nico-panel > h2 {
  margin-top: 0;
  margin-bottom: 0.75rem;
}

/* The form directly under a panel heading needs no top margin of its own - the heading has just
   provided the separation, and `.nico-form`'s own 1.75rem would double it. */
.nico-panel > .nico-form {
  margin-top: 0;
}

/* A panel that has to read as a distinct block WITHOUT a loud heading over it - the invited
   companies on the activation page. Its heading is a section label rather than display type (see
   `.nico-panel__title` below), so the grouping has to come from the ground instead of from the
   type. The white-on-light bordered treatment is `.nico-empty`'s, which is the shape this site
   already uses for a block standing apart from the flow around it. */
.nico-panel--soft {
  padding: 1.25rem 1.25rem 1rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background: var(--nico-white);
}

/* A SECTION LABEL, NOT A PAGE TITLE.

   `h2` is display type on this site - clamp(1.7rem, 4vw, 2.6rem) at weight 800 - and on a form
   that put the loudest type on the least important sentence: the line introducing a short list,
   set larger than every field under it and competing with the page's own h1.

   This drops it to a small uppercase label, smaller than the form's own `.nico-field label` text,
   so it ranks below the h1 where it belongs. Every property
   the heading defaults inherit is restated, because `h1, h2, h3, h4` sets weight, line-height,
   letter-spacing and colour, and leaving any of them to that rule would leave a display heading
   wearing a small font size. The element stays an h2: the document outline was never wrong, only
   its appearance was. */
.nico-panel__title {
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nico-ink-muted);
}

/* A plain readable list, for a SUMMARY of rows that are managed somewhere else - the addresses on
   the profile page. The stylesheet's reset strips list markers site-wide, so a bare <ul> would
   render as unspaced lines with nothing tying them together; this puts the rhythm back for the one
   case where the content really is a list rather than a table.

   ONLY THE RHYTHM. `list-style` and `padding-left` below are DEAD as written: the reset near the
   top of this file, `ul[class], ol[class] { padding: 0; list-style: none }`, is specificity (0,1,1)
   and outranks this bare class, so every .nico-list on the site has in fact always rendered with no
   marker and no indent - verified by computed style, not by reading. They are left in place rather
   than deleted because removing them is a visible change to the profile and companies pages, which
   is a decision about those pages and not one to make from here. Anything that genuinely needs the
   indent must raise its own specificity - see `ul.nico-list--nested` below for the shape. */
.nico-list {
  margin: 0 0 1.25rem;
  padding-left: 1.1rem;
  list-style: disc;
}

.nico-list li + li {
  margin-top: 0.4rem;
}

/* A .nico-list nested inside one of its own items - the outlets under one company.

   It drops the parent's 1.25rem bottom margin, which would open a gap INSIDE the group it belongs
   to and make each company's outlets look like the start of the next company.

   `ul.` RATHER THAN THE CLASS ALONE, and that is not a style choice. The reset near the top of
   this file - `ul[class], ol[class] { padding: 0 }` - is specificity (0,1,1) and outranks a bare
   class selector, so `padding-left` written as `.nico-list--nested` would be applied and then
   silently discarded. The element selector buys the tie, and being later in the file wins it.
   Without the indent an outlet sits at exactly the same left edge as the next company's name and
   the grouping disappears, which is the other half of what makes these rows readable. */
ul.nico-list--nested {
  margin: 0.35rem 0 0;
  padding-left: 1.1rem;
}

/* Stands exactly where a nested list would stand - the "no outlets yet" note under the company it
   is about. It takes the SAME 1.1rem indent as `ul.nico-list--nested`, because at flush left, with
   every real outlet around it now indented, it read as a remark about the whole list rather than
   as that one company's entry. A <p> and not a list item: there is no outlet to be an item for. */
.nico-list__empty {
  padding-left: 1.1rem;
}

/* WHAT this name is, said in front of the name itself.

   A single-site business names its outlet after itself, so the list genuinely renders the same
   text twice; with nothing but indentation between the two lines that is indistinguishable from a
   duplicate-rendering bug. The label is muted and set at body weight so the name it introduces
   still leads the line. */
.nico-list__label {
  font-weight: 400;
  color: var(--nico-ink-muted);
}

/* Denser rhythm for THIS panel only - the invited-companies list on the activation page. Scoped
   under `.nico-panel--soft` rather than tightening `.nico-list`/`.nico-list--nested` themselves,
   because those two also lay out the address summary on companies.php at their normal, more
   generous spacing; a change here must not loosen or tighten a list on any other page.

   A single-site company now collapses to one unlabelled line (see activate.php), but a company
   with several outlets, or a differently-named one, still carries multiple lines - so a
   fourteen-company invitation can still run long. Pulling in the line-height and the gap between
   rows keeps that list reading as one dense block rather than as a page of separate paragraphs,
   while staying comfortably readable rather than cramped. */
.nico-panel--soft .nico-list {
  line-height: 1.4;
  margin-bottom: 0;
}

.nico-panel--soft .nico-list li + li {
  margin-top: 0.3rem;
}

.nico-panel--soft ul.nico-list--nested {
  margin-top: 0.15rem;
}

.nico-panel--soft ul.nico-list--nested li + li {
  margin-top: 0.15rem;
}

.nico-empty {
  margin-top: 1.5rem;
  padding: 2.5rem 1.5rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background: var(--nico-white);
  text-align: center;
}

.nico-empty p {
  font-size: 1.1rem;
  font-weight: 600;
}

.nico-empty__hint {
  margin-top: 0.5rem;
  font-size: 0.94rem;
  font-weight: 400;
  color: var(--nico-ink-muted);
}

.nico-empty .nico-btn-row {
  justify-content: center;
}

/* ---- checkout and review */

/* A value shown where a control would be, when there is nothing to choose - a customer linked to no
   company has exactly one party they can order for. Set at input height so the row does not jump
   when the same page is rendered for someone who does have a choice. */
.nico-checkout__static {
  padding: 0.7rem 0;
  font-weight: 600;
}

/* THE PARTY SELECTOR'S UPDATE BUTTON AND ITS HINT, WHICH SWAP TOGETHER.

   buy-for-switch.js reloads the checkout when the party select changes, so the button it replaces
   is hidden and the sentence naming it is swapped for one that does not. Both are gated on the same
   two marks and nothing else: `[data-nico-js]` like every script-gated rule on this site, and
   `[data-nico-autosubmit-ready]`, which that script sets on the form only once it is actually
   listening. A browser where the file is absent, blocked, still loading or missing
   `requestSubmit()` therefore keeps the button AND the sentence that tells the reader to press it -
   which is the whole control there. The default below is the no-script state for the same reason:
   a rule that has not loaded yet must leave the working control on screen, never the other way
   round. The button keeps its own line, as it always has. */
.nico-checkout__hint--auto {
  display: none;
}

[data-nico-js] form[data-nico-autosubmit-ready] .nico-checkout__refresh,
[data-nico-js] form[data-nico-autosubmit-ready] .nico-checkout__hint--manual {
  display: none;
}

[data-nico-js] form[data-nico-autosubmit-ready] .nico-checkout__hint--auto {
  display: block;
}

.nico-review__facts {
  margin-top: 2rem;
}

/* ---- confirmation

   The order number is the one string a customer quotes back to us over WhatsApp, so it is the
   largest thing on its page. Letterspaced and tabular so a number read aloud from a phone screen
   cannot be misread - `1` against `7`, `0` against `O`. */
.nico-confirmation {
  margin-top: 1.5rem;
  padding: 1.75rem 1.25rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-lg);
  background: var(--nico-accent-soft);
  text-align: center;
}

.nico-confirmation__label {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--nico-ink-muted);
}

.nico-confirmation__number {
  margin-top: 0.4rem;
  font-size: clamp(1.5rem, 6vw, 2rem);
  font-weight: 800;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.nico-next-steps {
  margin-top: 2.5rem;
}

.nico-next-steps h2 {
  font-size: 1.1rem;
}

/* Numbered because the steps happen in this order and the numbers are the content. The default list
   padding is kept - a numbered list that has been un-indented reads as a paragraph with digits. */
.nico-next-steps ol {
  margin-top: 0.85rem;
  padding-left: 1.25rem;
  display: grid;
  gap: 0.6rem;
}

/* ---- order history */

.nico-orders th[scope='row'] {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The order's state as a word on a chip. Every state gets the same neutral chip by default and two
   of them are then tinted, which is the way round that matters: a new lifecycle value added later
   renders as a readable neutral chip rather than inheriting whichever colour happened to be last. */
.nico-state {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--nico-border);
  border-radius: var(--nico-radius-pill);
  background: var(--nico-light);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Awaiting payment - the state a prepaid portal order lands in. Accent rather than red: it is the
   normal outcome of ordering here, not a problem. */
.nico-state--1 {
  border-color: var(--nico-band-accent);
  background: var(--nico-accent-soft);
  color: var(--nico-accent-dark);
}

/* Cancelled. The only state worth marking as over, and marked by its border and its text as well as
   its ground, for the colour-blind reason stated above. */
.nico-state--4 {
  border-color: #a4373a;
  background: #f8ecec;
  color: #6f2427;
}

.nico-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 2rem;
}

/* The numbered run. Each number is a 2.75rem square target - the size a thumb needs - and the
   current page is the only filled one, so it is told apart by more than its colour: the fill, the
   bold weight and `aria-current` all say it. */
.nico-pager__pages {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nico-pager__page,
.nico-pager__gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  height: 2.75rem;
  font-variant-numeric: tabular-nums;
}

.nico-pager__page {
  padding-inline: 0.5rem;
  border: 2px solid var(--nico-border);
  border-radius: var(--nico-radius-pill);
  color: var(--nico-accent-dark);
  font-weight: 600;
  text-decoration: none;
}

.nico-pager__page:hover {
  border-color: var(--nico-accent-dark);
}

.nico-pager__page--current {
  border-color: var(--nico-accent-dark);
  background: var(--nico-accent-dark);
  color: var(--nico-white);
  font-weight: 800;
}

.nico-pager__gap {
  color: var(--nico-ink-muted);
}

.nico-pager__position {
  flex-basis: 100%;
  text-align: center;
  font-size: 0.88rem;
  color: var(--nico-ink-muted);
}

/* THE NUMBERS TAKE THE WHOLE FIRST ROW at every width and the two direction buttons share the one
   under it, so neither is squeezed by the other and the layout does not change shape between a
   desktop and a phone. */
.nico-pager__pages {
  flex-basis: 100%;
  order: -1;
}

.nico-pager__step {
  justify-content: center;
  padding-inline: 1.25rem;
  white-space: nowrap;
}

@media (max-width: 47.99em) {
  .nico-pager {
    gap: 0.75rem;
  }

  .nico-pager__step {
    flex: 1 1 0;
    padding-inline: 0.5rem;
    letter-spacing: 0.06em;
  }
}

/* ---- narrow screens

   62em is this site's primary breakpoint and the one every other layout rule uses. Below it the
   money summary stops being a right-hand column - there is no column to sit beside - and the button
   rows stack, so a Confirm button is never a thumb-stretch away from an Empty basket one.

   The tables themselves need no rule: `.nico-table-wrap` already scrolls horizontally, which is the
   honest answer for a five-column price table on a 360px phone. Collapsing it into stacked cards
   would lose the column alignment that makes the figures comparable. */
@media (max-width: 61.99em) {
  .nico-summary {
    max-width: none;
  }

  .nico-btn-row {
    gap: 0.6rem;
  }

  .nico-choice__body {
    margin-left: 0;
    padding-left: 0.9rem;
  }
}

/* ---------------------------------------------------------------- submit guard */

/* Baseline for ANY control /assets/public/js/submit-guard.js disables, whether or not it already
   carries a more specific disabled look. `.nico-action[disabled]` above is more specific and keeps
   its own treatment; this is the fallback for everything else - `.nico-btn` in every variant, the
   header's icon-only search submit, and anything future. `pointer-events: none` is what stops a
   disabled button's OWN :hover rules from painting over this while a stray pointer sits on it,
   without having to repeat this rule's selector in every `:hover` block on the page. */
button:disabled,
input[type="submit"]:disabled,
input[type="image"]:disabled {
  cursor: not-allowed;
  opacity: 0.6;
  pointer-events: none;
}

/* The one control actually submitting, while the request is in flight. Scoped to `.nico-btn`
   rather than every button the guard disables: the header's search submit and the row-level
   `.nico-action` icons are fixed-size squares with no room for a second glyph, and the dimming
   above already says "busy" for those. `currentcolor` is what lets one rule serve every `.nico-btn`
   variant - white on the accent-filled primary button, ink on the outline and ghost ones - without
   naming a colour per variant. */
.nico-btn.is-nico-busy::after {
  content: '';
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  border: 2px solid currentcolor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: nico-spin 0.6s linear infinite;
}

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

/* ---------------------------------------------------------------- buying context band */

/*
 * WHO THE CUSTOMER IS ORDERING FOR, under the menu on every page that can take an order. See
 * src/Public/View/partials/buying_context.php, which decides WHEN it appears; this decides only
 * what it looks like.
 *
 * FULL BLEED, with the shell inside it, so the band reads as a continuation of the header rather
 * than as a notice the page happens to open with. It is not sticky: `.nico-header` is, and a
 * second sticky strip under it would eat a third of a phone screen for a fact that does not
 * change while you scroll.
 *
 * THE TWO STATES DIFFER IN THREE WAYS AND ONE OF THEM IS THE PAINT. The badge word and the
 * sentence carry the rest - see the partial, which states why colour may not carry it alone.
 * NEITHER state uses the tailored-price blue: that hue means "this figure is yours" and nothing
 * else on the site may borrow it.
 */
.nico-buy-band {
  border-bottom: 1px solid var(--nico-border);
  background-color: var(--nico-accent-soft);
  color: var(--nico-ink);
}

/* The site's own navy, through the token every dark surface here resolves through - so the band
   needs no colour of its own and follows a reskin with everything else. On-dark text reads 11:1
   on it. */
.nico-buy-band--company {
  border-bottom-color: var(--nico-dark-deep);
  background-color: var(--nico-band-dark);
  color: var(--nico-on-dark);
}

.nico-buy-band__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 1.5rem;
  padding-block: 0.6rem;
}

/* THE CONTROL IS FOLDED AWAY, by the browser's own <details> and no script, at every width. It wraps
   the state line (the <summary>) and the form and nothing else, inside the shell above, so the band's
   ground colour and its full-width strip are untouched. It takes the whole row of the shell: the
   state is on the left of it and the opened form is below it, the same shape on a phone and on a
   desktop. */
.nico-buy-band__details {
  flex: 1 1 100%;
  min-width: 0;
}

.nico-buy-band__state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.7rem;
  font-size: 0.95rem;
}

/* FILLED in both states, in the two colours the owner's mockup draws - brick on the cream band,
   white on the navy one. The paint is no longer what tells the two apart: the BADGE WORD does
   ("Personal order" against "Company order"), which is the signal the partial says has to carry
   the difference for a reader who cannot see colour and for anything printed in monochrome. */
.nico-buy-band__badge {
  padding: 0.2rem 0.7rem;
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-accent);
  color: var(--nico-white);
  font-family: var(--nico-font-display);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nico-buy-band--company .nico-buy-band__badge {
  background-color: var(--nico-on-dark);
  color: var(--nico-dark);
}

.nico-buy-band__aside {
  font-size: 0.85rem;
  opacity: 0.85;
}

/* THE STATE LINE AS THE TOGGLE. A <summary> is the disclosure's whole target, so the row is a 44px
   tap target and the icon at its end is the affordance. `list-style` and the engine-specific marker
   are switched off for the engines that still draw the triangle: the icon says the row opens. */
.nico-buy-band__details > summary {
  min-height: 2.75rem;
  cursor: pointer;
  list-style: none;
}

.nico-buy-band__details > summary::-webkit-details-marker {
  display: none;
}

.nico-buy-band__details > summary:focus-visible {
  outline: 2px solid var(--nico-band-accent);
  outline-offset: 2px;
}

/* The brick ring is about 2.8:1 against the navy, under the 3:1 a focus ring needs, so the company
   band draws it in the on-dark token its own text uses. */
.nico-buy-band--company .nico-buy-band__details > summary:focus-visible {
  outline-color: var(--nico-on-dark);
}

/* THE ICON, at the end of the sentence. It is a ring round a two-way arrow, drawn in whatever
   colour the band's text is (`currentcolor`), so it needs no colour of its own in either state.
   INLINE, inside `__who`, so it follows the name when a long name wraps.

   `position: relative` is not decoration: the toggle holds a `.nico-visually-hidden` label, which
   is `position: absolute`, and without a positioned ancestor that label would resolve against the
   page instead of against the icon - the fault the basket's quantity form once had. */
.nico-buy-band__toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-left: 0.25rem;
  border: 1px solid currentcolor;
  border-radius: var(--nico-radius-pill);
  /* Measured, not `middle`: that centres the ring 4.5px below the name's own centre, and a ring
     sitting low beside a line of text reads as misplaced. */
  vertical-align: -0.3rem;
}

.nico-buy-band__icon {
  flex: 0 0 auto;
}

/* Filled while the pointer is on the row and while the control is open, in the badge's own pairing
   for the band: the ink on cream for the personal band, the on-dark token on navy for the company
   band. The same two states, so "open" and "about to open" read as one thing. */
.nico-buy-band__details > summary:hover .nico-buy-band__toggle,
.nico-buy-band__details[open] .nico-buy-band__toggle {
  background-color: var(--nico-ink);
  color: var(--nico-accent-soft);
}

.nico-buy-band--company .nico-buy-band__details > summary:hover .nico-buy-band__toggle,
.nico-buy-band--company .nico-buy-band__details[open] .nico-buy-band__toggle {
  background-color: var(--nico-on-dark);
  color: var(--nico-band-dark);
}

.nico-buy-band__switch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* The two properties a band's dropdown wants and a form field's does not: it sizes to its content
   instead of to the column, and it states the 44px floor rather than inheriting a field's taller
   padding. Everything else about it - the border, the font, the arrow, the focus ring - comes from
   the shared `.nico-field select` rules this selector is named in. */
.nico-buy-band__switch select {
  display: inline-block;
  width: auto;
  max-width: 100%;
  min-height: 44px;
  padding-block: 0.45rem;
}

/* The 44px floor again, stated rather than inherited. `.nico-btn`'s own padding happens to reach
   47px today, which is a fact about a font size that has moved before; a tap target is not
   something to leave depending on one. The inline padding is tightened from the button's 2rem so
   the pair still fits on one line on a 360px screen. */
.nico-buy-band__submit {
  min-height: 44px;
  padding-inline: 1.4rem;
}

/* THE SELECT AND THE SWITCH BUTTON ON ONE LINE, and only inside the band's disclosure: the
   dashboard draws the same form in its own corner and lays it out in account.css, so these rules
   are scoped to the `<details>` and cannot reach it.

   `nowrap`, and `min-width: 0` on the select, are the whole fix. A select's automatic minimum
   width is its LONGEST option, so with `min-width: auto` a party name of any length made the pair
   too wide for the row and the button dropped under the select. The select takes the free width
   and clips a long name instead (the sentence above it already states the party in full); the
   button keeps its natural width and the 44px floor above.
   `max-width` stops the select stretching across a 1400px band. */
.nico-buy-band__details > .nico-buy-band__switch {
  flex-wrap: nowrap;
  max-width: 34rem;
}

.nico-buy-band__details .nico-buy-band__switch select {
  flex: 1 1 0;
  min-width: 0;
}

.nico-buy-band__details .nico-buy-band__submit {
  flex: none;
}

/* ---------------------------------------------------------------- tailored prices */

/*
 * A PRICE AGREED WITH THIS BUYER, marked where it is printed. The markup is
 * src/Public/View/partials/tailored_price.php, which states why there are three signals and not
 * one; these rules draw the third.
 *
 * THE RULE IS ON THE CELL rather than on a box inside it, because "a border around the price" is
 * what it is. `.nico-price-table` collapses its borders, so this 2px edge simply wins against the
 * 1px row rule it meets - which is why the cell needs no z-index, no offset and no wrapper.
 *
 * THERE IS NO FILL ON THE CELL, deliberately. The cheapest rung of a ladder already carries
 * `.is-best`'s cream, and a second background here would either fight it or lose to it depending
 * on which selector happened to be longer - a mark whose appearance depends on an unrelated row
 * state is a mark that will be wrong one day. The edge clears 3:1 on both grounds (see
 * --nico-tailored-edge), so it does the whole job on either.
 */
.nico-price-table td.nico-tailored {
  border: 2px solid var(--nico-tailored-edge);
}

/*
 * THE ROW INSIDE THE CELL: the two figures, then the tooltip's trigger.
 *
 * ON A CHILD, NEVER ON THE `<td>`. A `display: flex` table cell stops taking part in the table's
 * column-sizing algorithm, so the column gets sized by the OTHER rows' short plain figures and
 * this wider cell then wraps inside it at every width. Measured on this ladder: it took the rows
 * from 58px to 98px and split the pair off from its trigger for no reason a reader could see.
 * With the flex one level down, the cell's max-content contribution is this row's full width and
 * the column widens to hold it.
 *
 * `flex-end` because every money column in this table is right-aligned and the cell must not stop
 * being so.
 */
.nico-tailored__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0 0.5rem;
}

/*
 * "WAS, NOW" ON ONE LINE - the order a person says it in, and the order the markup is in.
 *
 * A WRAPPING FLEX ROW, NOT TWO STACKED BLOCKS. The distinction is the whole requirement: the pair
 * stays on one line wherever the column can hold it, and drops to a second line only when it
 * genuinely cannot. Stacking them with `display: block` would take the second line ALWAYS, at
 * every width, which is what this replaced.
 *
 * AND IT FITS BECAUSE THE TRIGGER GIVES THE WIDTH BACK, not because wrapping is forbidden here.
 * Forbidding it was tried and measured: `flex-wrap: nowrap` does hold the figures together, but a
 * nowrap flex container's min-content width is the SUM of its items, so the column then demanded
 * more width than the hero layout has at 1280px and `.nico-table-wrap` began scrolling the whole
 * ladder sideways. That is the worse outcome - a second line is allowed when the column really is
 * too narrow; hiding the Add column behind a horizontal scrollbar on a desktop is not. The fix is
 * upstream of the cascade instead: the tooltip trigger takes 24px of LAYOUT width rather than 44
 * and buys its 44px tap target back with a pseudo-element, which frees the 20px the pair was
 * short of. See `.nico-tooltip__trigger`.
 *
 * The row gap is zero and the column gap is the space between the two figures, so a wrapped pair
 * sits tight rather than opening a gutter the unwrapped one never shows.
 */
.nico-tailored__pair {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0 0.5rem;
}

.nico-tailored__now {
  color: var(--nico-tailored);
  font-weight: 700;
}

/* The ordinary price, struck through. Muted rather than coloured: it is the figure being SET
   ASIDE, and painting it in the marker blue would give the same hue to both halves of a
   comparison. 5.5:1 on white - a withdrawn figure still has to be legible, because it is half of
   what the customer is being shown. */
.nico-tailored__was {
  color: var(--nico-ink-muted);
  font-size: 0.85rem;
}

/* THE SIGNAL THAT SURVIVES EVERYTHING - print, monochrome, a colour-blind reader, a stylesheet
   that failed to load. Its own pale ground rather than the page's, so the pill holds together on
   the white table, on the cream `.is-best` row and on a product card alike. */
.nico-tailored__tag {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border: 1px solid var(--nico-tailored-edge);
  border-radius: var(--nico-radius-pill);
  background-color: var(--nico-tailored-soft);
  color: var(--nico-tailored);
  font-family: var(--nico-font-display);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ---------------------------------------------------------------- the tailored-price tooltip */

/*
 * THE EXPLANATION, ON THE ROW IT IS ABOUT, AND REACHABLE BY EVERY INPUT METHOD.
 *
 * ---------------------------------------------------------------------------------------------
 * REVEALED BY `:hover` AND BY `:focus-within`, AND THE SECOND ONE IS THE IMPORTANT ONE. A
 * hover-only tooltip does not exist on a phone, and this trade orders on phones. The trigger is a
 * real `<button>`, so a tap focuses it and Tab reaches it; `:focus-within` on the wrapper catches
 * both, and also catches the pointer-click focus that `:focus-visible` deliberately does not
 * match. No script is involved in any of it.
 *
 * ---------------------------------------------------------------------------------------------
 * IT IS NOT CLIPPED, AND `position: fixed` IS NOT HOW. That was tried first and measured wrong.
 *
 * `.nico-table-wrap` is `overflow-x: auto`, and a box with one axis not `visible` computes the
 * OTHER axis to `auto` as well - confirmed in the browser, `overflow-y` reads `auto` on it - so
 * the ladder's wrapper clips on BOTH axes. A panel opening downward from the last row is
 * therefore cut off immediately. That is the classic failure here.
 *
 * `position: fixed` escapes the clip, because a fixed box's containing block is the viewport. It
 * is also WRONG: with auto insets the static position is computed in DOCUMENT coordinates and
 * then used as VIEWPORT coordinates, so the panel is correct only at scroll position zero. On a
 * page scrolled 195px it opened exactly 195px away from its trigger, off the bottom of the
 * screen - measured, at 360px, which is the one width where scrolling is guaranteed.
 *
 * So the panel is `position: absolute`, which tracks the page correctly, and the CLIP is what was
 * removed instead: its containing block is `.nico-product-hero .nico-buy`, an ancestor ABOVE the
 * scroller, and an overflow clip does not apply to a descendant whose containing block is an
 * ancestor of the clipping box. See `.nico-tooltip`, which is deliberately left unpositioned so
 * that resolution reaches past the wrapper, and catalogue.css, which moved the anchor there.
 *
 * The remaining cost is small and bounded: the panel does not move when the TABLE is scrolled
 * sideways underneath it, which needs a reader to be dragging the ladder with a tooltip already
 * open.
 *
 * ---------------------------------------------------------------------------------------------
 * IT NEVER MOVES THE ROW. Out of flow, so the cell's height is the same open or shut - the other
 * half of the brief, and the reason this is not a `<details>` disclosure, which would be natively
 * keyboard-operable and would push every row below it down each time it opened.
 */
.nico-tooltip {
  /* THE TRIGGER'S LAYOUT SIZE, NAMED ONCE. The panel's transform offsets it by exactly this, so
     the anchor and the control cannot drift apart when one of them is adjusted. It is NOT the tap
     target, which is 44px and comes from the pseudo-element below. */
  --nico-tooltip-trigger: 24px;

  /*
   * DELIBERATELY NOT `position: relative`, and this is the whole reason the panel is not clipped.
   *
   * The panel is `position: absolute` with every inset `auto`, so it lands at its static position
   * - right at the trigger - whatever its containing block turns out to be. Making this wrapper
   * the containing block would put that block INSIDE `.nico-table-wrap`, whose `overflow-x: auto`
   * clips on both axes, and the panel would be cut off at the table's edge. Left unpositioned,
   * the containing block is the nearest positioned ancestor ABOVE the scroller
   * (`.nico-product-hero .nico-buy`, see catalogue.css), and an overflow clip does not apply to a
   * descendant whose containing block is an ancestor of the clipper.
   */
  display: inline-flex;
  flex: none;
}

/*
 * 24px OF LAYOUT, 44px OF TAP TARGET, and the split is deliberate rather than a compromise.
 *
 * A 44px-wide button is 20px the price column does not have: at 1280px it pushed the two figures
 * onto separate lines, and forcing them back together made the whole ladder scroll sideways
 * instead. The pseudo-element below is the standard way out - pointer events on it hit the
 * button, so the target is a full 44px square while the box the table has to find room for is
 * 24px. The extra 10px on each side lands in the cell's own padding, where nothing else is
 * clickable.
 */
.nico-tooltip__trigger {
  position: relative;
  display: inline-flex;
  width: var(--nico-tooltip-trigger);
  height: var(--nico-tooltip-trigger);
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--nico-tailored);
  /* A button inherits neither the page font nor its size, and the shorthand is what the other
     buttons on this site that escape the platform defaults use. */
  font: inherit;
  cursor: pointer;
}

/* The tap target, and nothing else - no paint of its own. Centred on the visible mark so the
   target grows evenly rather than off to one side of what the reader is aiming at. */
.nico-tooltip__trigger::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.nico-tooltip__mark {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--nico-tailored-edge);
  border-radius: 50%;
  background-color: var(--nico-tailored-soft);
  font-family: var(--nico-font-display);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
}

.nico-tooltip__trigger:hover .nico-tooltip__mark,
.nico-tooltip__trigger:focus-visible .nico-tooltip__mark {
  background-color: var(--nico-tailored);
  border-color: var(--nico-tailored);
  color: var(--nico-white);
}

.nico-tooltip__panel {
  position: absolute;
  z-index: 60;
  display: none;
  width: max-content;
  /* Capped against the VIEWPORT as well as at a readable measure, so the panel can never be wider
     than the screen it has to fit on. */
  max-width: min(17rem, calc(100vw - 2rem));
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--nico-tailored-edge);
  border-radius: var(--nico-radius);
  background-color: var(--nico-tailored-soft);
  box-shadow: var(--nico-shadow-panel);
  color: var(--nico-ink-body);
  font-size: 0.82rem;
  /* The ladder sets `white-space: nowrap` on every cell, which would run this paragraph off the
     side of the screen on one line. It is prose and has to wrap. */
  white-space: normal;
  text-align: left;
  /* Right edge onto the trigger's right edge, top onto the trigger's bottom - measured from the
     static position, which is the trigger's top-left corner. Both offsets are the trigger's own
     layout size, read from the one custom property that states it. */
  transform: translate(calc(-100% + var(--nico-tooltip-trigger)), var(--nico-tooltip-trigger));
}

.nico-tooltip:hover .nico-tooltip__panel,
.nico-tooltip:focus-within .nico-tooltip__panel {
  display: block;
}

.nico-tooltip__text {
  display: block;
  margin-top: 0.4rem;
  line-height: 1.45;
}

@media (max-width: 40em) {
  /*
   * ON A PHONE THE PANEL STOPS BEING ANCHORED SIDEWAYS AND SPANS THE COLUMN.
   *
   * Right-aligning it to the trigger is right on a wide screen and wrong here: the ladder scrolls
   * sideways at this width, so the trigger can sit anywhere across the screen, and a panel pulled
   * its own width to the left of a trigger near the left edge would hang off it. Both insets are
   * pinned instead, and they resolve against the containing block - `.nico-buy`, the page column,
   * NOT the scrolling table - so the panel is as wide as the column allows and stays on screen
   * wherever the trigger has been scrolled to. `top` stays `auto`, so it still drops from the
   * trigger's own line.
   */
  .nico-tooltip__panel {
    left: 1rem;
    right: 1rem;
    max-width: none;
    width: auto;
    transform: translateY(var(--nico-tooltip-trigger));
  }
}

/* On a card the mark carries no figure - see _product_card.php for why a per-pack price may not
   be printed under a per-piece lead - so it is a tag and one muted sentence, sized to sit under
   the price without competing with it. */
.nico-product-card__tailored {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.5rem;
  margin-top: 0.45rem;
  color: var(--nico-ink-muted);
  font-size: 0.82rem;
}
