/* ==========================================================================
   IBS Homepage 2026 — "Plate Map"

   Tokens, motion and prohibitions are specified in DESIGN.md at the project
   root. This file implements that contract and nothing beyond it.

   Everything is scoped to .ibs-hp26 (body) or #ibs-hp (main), so the theme's
   other 40-odd routes are untouched by loading order or specificity.
   ========================================================================== */

/* Tokens come from ibs-design-system.php, which is what carries light and dark
   across every route. The hex after each var() is the light-mode fallback, so
   this page still renders correctly on its own if that file is ever removed. */
.ibs-hp26 {
	--ground:      var(--ds-ground,     #f1f4f7);
	--frame:       var(--ds-frame,      #ffffff);
	--hairline:    var(--ds-hairline,   #d5dce4);
	--ink:         var(--ds-ink,        #10192a);
	--ink-2:       var(--ds-ink-2,      #46536a);
	--ink-3:       var(--ds-ink-3,      #5c6980);
	--assay:       var(--ds-assay,      #0b4fd1);
	--assay-deep:  var(--ds-assay-deep, #072f7d);
	--assay-wash:  var(--ds-assay-wash, #e4ecfb);
	--well-empty:  var(--ds-hairline,   #e2e7ee);

	--r:           2px;
	--rule:        1px solid var(--hairline);
	--ease:        cubic-bezier(0.16, 1, 0.3, 1);

	--measure:     68ch;
	--gutter:      clamp(20px, 4vw, 64px);
	--maxw:        1280px;
}

/* --------------------------------------------------------------------------
   Wrapper reset.

   header.php opens .page_content_wrap > .content_wrap > .content and footer.php
   closes them. Three selectors, this page only. Nothing else is overridden.
   -------------------------------------------------------------------------- */
.ibs-hp26 .page_content_wrap {
	background: var(--ground);
	padding: 0;
	margin: 0;
}
/* Held inside .page_content_wrap on purpose: the mobile menu panel also carries
   the class .content_wrap, and it lives outside the page's content wrapper.
   Unscoped, a reset meant for the page body strips the mobile menu's own inset
   as well, and the whole menu ends up flush against the edge of the screen. */
.ibs-hp26 .page_content_wrap .content_wrap,
.ibs-hp26 .page_content_wrap .content_wrap_fullscreen,
.ibs-hp26 .page_content_wrap .content {
	max-width: none;
	width: 100%;
	margin: 0;
	padding: 0;
}
.ibs-hp26 .sidebar,
.ibs-hp26 .content > article > .post_header,
.ibs-hp26 .page_title {
	display: none;
}

/* --------------------------------------------------------------------------
   Theme reconciliation.

   Four things the Lab theme does that this page has to answer for. Each one
   was measured on the rendered page, not guessed at.
   -------------------------------------------------------------------------- */

/* 1. The header is position:fixed, 125px tall, transparent, and carries
      scheme_dark with a white logo PNG. Over the old navy hero that worked.
      Over a polystyrene ground it is white-on-white.

      Rather than recolour a white logo and five nav links, the bar itself
      becomes the instrument fascia: deep assay, sitting above the bench. The
      theme's own scheme_dark markup is then correct as written, and the page
      gains a hard anchor at the top edge.

      ibs-mobile-optimization.php already owns this bar, and it deliberately
      makes it transparent at rest:

        html body.header_position_over:not(.ibs-scrolled) .top_panel
          { background: transparent !important }

      That is the correct call for the Elementor homepage, where the bar floats
      over a dark gradient hero. It is the wrong call here, where it floats over
      polystyrene. Beaten on specificity, not by shouting louder: three body
      classes to the incumbent's two, and the shorthand matched to shorthand
      because that plugin's own comments record losing a longhand fight here.

      Scrolled state is pinned to the same colour so the bar does not shift hue
      the moment the reader moves. */
html body.ibs-hp26.header_position_over:not(.ibs-scrolled) .top_panel,
html body.ibs-hp26.header_position_over:not(.ibs-scrolled) .top_panel.top_panel_custom,
html body.ibs-hp26 .top_panel,
html body.ibs-hp26 .top_panel.top_panel_custom {
	background: var(--assay-deep) !important;
	border-bottom-color: rgba(255, 255, 255, 0.14);
}

/* 2. That fixed header overlaps content. Reserve its height. */
.ibs-hp26 { --header-h: 125px; }
#ibs-hp { padding-top: var(--header-h); }

/* 4. The theme paints tbody th with a near-black background (rgb(1,0,3)),
      which renders the service names as ink on ink. */
#ibs-hp .ibs-table th,
#ibs-hp .ibs-table td {
	background: transparent;
	color: inherit;
}
#ibs-hp .ibs-table thead th { color: var(--ink-3); }
#ibs-hp .ibs-table tbody th { color: var(--ink); }

/* 5. The theme centres th. Left is the only alignment this table uses. */
#ibs-hp .ibs-table th,
#ibs-hp .ibs-table td,
#ibs-hp .ibs-table caption { text-align: left; }

/* --------------------------------------------------------------------------
   Ground and type
   -------------------------------------------------------------------------- */
#ibs-hp {
	background: var(--ground);
	color: var(--ink-2);
	font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
	font-size: 17px;
	line-height: 1.62;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

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

/* The theme sets font-family directly on h1-h6, p, a, td and th via its
   Typekit face (halyard-display). Inheritance from #ibs-hp loses to any
   matching element rule, always - so the headings were still rendering in the
   theme's face while the body rendered in ours. Naming the elements is the
   only thing that settles it. */
#ibs-hp,
#ibs-hp h1, #ibs-hp h2, #ibs-hp h3, #ibs-hp h4, #ibs-hp h5, #ibs-hp h6,
#ibs-hp p, #ibs-hp a, #ibs-hp li, #ibs-hp span, #ibs-hp div,
#ibs-hp th, #ibs-hp td, #ibs-hp caption,
#ibs-hp figcaption, #ibs-hp address, #ibs-hp time, #ibs-hp ol, #ibs-hp ul {
	font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* Re-assert the data face over that blanket. */
#ibs-hp .ibs-addr,
#ibs-hp .ibs-kicker,
#ibs-hp .ibs-table thead th,
#ibs-hp .ibs-news__meta,
#ibs-hp .ibs-close__tel a {
	font-family: 'Spline Sans Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

#ibs-hp p { margin: 0 0 1em; max-width: var(--measure); }
#ibs-hp p:last-child { margin-bottom: 0; }

/* The site's canonical content column, measured off the theme rather than
   invented: the header row and the footer's .ibs-footer-inner both resolve to a
   1290px content box, so at 1560px their content starts at x=128.

   This used to be max-width:1280 with the gutter INSIDE the box, which put our
   content at x=195 - a 67px step against the header above it and the footer
   below it on every single section. The gutter now only bites once the viewport
   is too narrow to hold the full column. */
.ibs-wrap {
	width: min(var(--maxw), 100% - (var(--gutter) * 2));
	margin-inline: auto;
	padding-inline: 0;
}

/* One spacing rhythm. More space above a heading than below it, everywhere. */
#ibs-hp > section { padding-block: clamp(64px, 9vw, 128px); }
#ibs-hp > section + section { border-top: var(--rule); }

.ibs-h1 {
	font-size: clamp(2.125rem, 3.1vw, 3rem);
	line-height: 1.08;
	letter-spacing: -0.03em;
	font-weight: 600;
	color: var(--ink);
	margin: 0 0 24px;
	text-wrap: balance;
}

.ibs-h2 {
	font-size: clamp(1.75rem, 2.6vw, 2.375rem);
	line-height: 1.14;
	letter-spacing: -0.026em;
	font-weight: 600;
	color: var(--ink);
	margin: 0 0 32px;
	text-wrap: balance;
}
.ibs-h2--invert { color: #fff; }

.ibs-h3 {
	font-size: 1.0625rem;
	line-height: 1.3;
	letter-spacing: -0.012em;
	font-weight: 600;
	color: var(--ink);
	margin: 0 0 6px;
}

.ibs-lede {
	font-size: clamp(1.0625rem, 1.35vw, 1.1875rem);
	line-height: 1.55;
	color: var(--ink-2);
	max-width: 46ch;
	margin: 0 0 40px;
}

.ibs-sub {
	color: var(--ink-2);
	max-width: 56ch;
	margin: -16px 0 40px;
}

/* Archivo for text, Spline Sans Mono for data.
   Geist was here first and was dropped on purpose: it is the face every new
   product page reaches for, and on a page whose whole argument is "we are not
   the template" that is exactly the wrong signature.
   Archivo is a working grotesque built for legibility at small sizes with real
   tabular figures - the register of a specification sheet, not a startup deck.

   Mono is used for plate addresses, slot numbers and tabular figures only —
   for data addresses and measurement, never as a costume for "technical". */
.ibs-addr,
.ibs-kicker {
	font-family: 'Spline Sans Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
	font-variant-numeric: tabular-nums;
	font-feature-settings: 'tnum' 1;
}

.ibs-kicker {
	font-size: 0.75rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	margin: 0 0 28px;
}

.ibs-addr {
	font-size: 0.6875rem;
	color: var(--ink-3);
	letter-spacing: 0.02em;
}
.ibs-addr--invert { color: rgba(255, 255, 255, 0.62); }

.ibs-sr {
	position: absolute; width: 1px; height: 1px;
	padding: 0; margin: -1px; overflow: hidden;
	clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Focus. One ring, everywhere, on the assay colour.
   -------------------------------------------------------------------------- */
#ibs-hp :focus-visible {
	outline: 2px solid var(--assay);
	outline-offset: 3px;
	border-radius: var(--r);
}

/* --------------------------------------------------------------------------
   Actions
   -------------------------------------------------------------------------- */
.ibs-actions { display: flex; flex-wrap: wrap; gap: 12px; }

.ibs-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0 24px;
	border-radius: var(--r);
	border: 1px solid transparent;
	font-size: 0.9375rem;
	font-weight: 500;
	letter-spacing: -0.006em;
	text-decoration: none;
	white-space: nowrap;          /* CTA labels never wrap */
	transition: background-color 160ms var(--ease),
	            border-color 160ms var(--ease),
	            color 160ms var(--ease),
	            transform 120ms var(--ease);
}
.ibs-btn:active { transform: scale(0.985); }

.ibs-btn--primary { background: var(--assay); color: #fff; }
.ibs-btn--primary:hover { background: var(--assay-deep); color: #fff; }

.ibs-btn--ghost { background: transparent; color: var(--ink); border-color: var(--hairline); }
.ibs-btn--ghost:hover { border-color: var(--ink); color: var(--ink); }

.ibs-btn--invert { background: #fff; color: var(--assay-deep); }
.ibs-btn--invert:hover { background: var(--assay-wash); color: var(--assay-deep); }

.ibs-btn--ghost-invert { background: transparent; color: #fff; border-color: rgba(255,255,255,0.42); }
.ibs-btn--ghost-invert:hover { border-color: #fff; color: #fff; }

.ibs-link {
	color: var(--assay);
	text-decoration: none;
	font-weight: 500;
	font-size: 0.9375rem;
	white-space: nowrap;
	border-bottom: 1px solid transparent;
	transition: border-color 160ms var(--ease);
	/* WCAG 2.2 target size. Measured at 22px before this. */
	display: inline-flex;
	align-items: center;
	min-height: 24px;
}
.ibs-link:hover { color: var(--assay); border-bottom-color: currentColor; }

/* ==========================================================================
   1. HERO — full-bleed photographic slider.

   The scrim is the difference between this and the reference site. A flat blue
   wash over the whole frame turns a photograph into a texture: every part of
   the image loses the same amount of contrast, including the parts carrying no
   type. Here the darkness is directional — heavy where the words sit, clearing
   to almost nothing over the subject — so the type is legible AND the picture
   still reads as a picture.
   ========================================================================== */
.ibs-hero {
	position: relative;
	padding: 0 !important;
	border-top: 0 !important;
	background: var(--assay-deep);
	overflow: hidden;
	isolation: isolate;
}

.ibs-hero__stage {
	position: relative;
	display: grid;
	min-height: clamp(520px, 78vh, 760px);
}

/* The change of frame, which is the part that has to feel expensive.

   Three things happen together and none of them alone would do it:

     1. the photographs cross-fade, slowly enough to read as a dissolve rather
        than a cut — 900ms, not the 300ms a UI transition would use;
     2. the incoming photograph keeps drifting after it arrives, a very slow
        scale from 1.06 to 1.00 across the whole dwell. A still image behind
        moving type looks like a slideshow; an image that is still travelling
        looks like film. This is the single biggest contributor;
     3. the words arrive after the picture, one line at a time, 90ms apart.

   The stagger is what makes it feel authored rather than automatic. */
.ibs-hero__slide {
	grid-area: 1 / 1;
	position: relative;
	display: flex;
	align-items: center;
	opacity: 0;
	visibility: hidden;
	transition: opacity 600ms var(--ease), visibility 0s linear 600ms;
}
.ibs-hero__slide.is-current {
	opacity: 1;
	visibility: visible;
	transition: opacity 600ms var(--ease), visibility 0s;
}

/* The arrival, on one timeline rather than two stacked animations.

   A plain cross-fade shows two photographs on top of each other for the whole
   duration, and the eye reads that as two pictures rather than one changing.
   Starting the incoming frame soft and slightly too large removes the second
   image from the equation: for the first fifth of the timeline there is nothing
   to compare against, only something resolving. Blur is what does that, and it
   is why this stops looking like a slideshow.

   Kept to 12px — blur is expensive to composite, Safari especially, and beyond
   about 20px it costs frames without looking any better. After the picture has
   resolved it keeps travelling to 1.00 for the rest of the dwell. */
/* The filter definition itself takes no space and paints nothing. */
.ibs-hero__fx { position: absolute; width: 0; height: 0; overflow: hidden; }

/* The displacement is applied to the media layer, and only while the ripple is
   running. A live feDisplacementMap over a full-bleed photograph is real work
   for the compositor, so it is attached for about 1.4 seconds per change and
   then taken off again — never left sitting on a static slide. */
.ibs-hero__slide.is-rippling .ibs-hero__media {
	filter: url(#ibs-ripple);
}

/* Blur drops from 12px to 5px now that the ripple carries the arrival: the two
   were doing the same job, and stacking a blur under a displacement map is
   twice the cost for less of an effect. */
.ibs-hero__slide.is-current .ibs-hero__img {
	animation: ibs-cinema var(--ibs-hero-dwell, 6500ms) cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes ibs-cinema {
	0%   { opacity: 0; transform: scale(1.13); filter: blur(5px); }
	14%  { opacity: 1; filter: blur(0); }
	100% { opacity: 1; transform: scale(1.00); filter: blur(0); }
}

.ibs-hero__slide.is-current .ibs-hero__eyebrow,
.ibs-hero__slide.is-current .ibs-hero__title,
.ibs-hero__slide.is-current .ibs-hero__sub,
.ibs-hero__slide.is-current .ibs-actions {
	animation: ibs-rise 760ms var(--ease) both;
}
.ibs-hero__slide.is-current .ibs-hero__eyebrow { animation-delay: 120ms; }
.ibs-hero__slide.is-current .ibs-hero__title   { animation-delay: 210ms; }
.ibs-hero__slide.is-current .ibs-hero__sub     { animation-delay: 300ms; }
.ibs-hero__slide.is-current .ibs-actions       { animation-delay: 390ms; }

/* Never from nothing: 14px and 0.92 opacity, so each line is already a shape
   before it settles. */
@keyframes ibs-rise {
	from { opacity: 0; transform: translate3d(0, 14px, 0); }
	to   { opacity: 1; transform: none; }
}

/* The parallax layer. Overscanned top and bottom so the frame never runs out
   of picture while it travels — without the 14% bleed you would see the edge
   of the photograph appear at the bottom of the hero on the way down. */
.ibs-hero__media {
	position: absolute;
	inset: -14% 0;
	will-change: transform;
}

.ibs-hero__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* object-position comes from the slide data: the subject stays in frame as
	   the viewport changes shape, instead of drifting out of it. */
}

/* ==========================================================================
   The split slide.  [TIDAK DIPAKAI SAAT INI]

   Tidak ada slide yang membawa 'layout' => 'split' lagi — slide kedua kembali
   full-bleed sejak fotonya diganti ke frame pameran (alasannya di
   ibs-homepage-2026.php). Aturan di bawah dibiarkan utuh dan tetap berfungsi:
   menambahkan kembali penanda itu pada sebuah slide langsung menghidupkannya.

   Not every message wants a photograph behind it. "410 products" is a claim
   about a catalogue, and the pictures that actually show product are close-ups
   shot upright — a pipette in two hands, not a room. Stretched across a
   full-bleed hero those go soft; stood upright beside the copy they stay sharp
   and read as the object they are.

   No extra markup. The media layer that the other slides use as a background is
   repositioned into a card, and the scrim it needs is switched off, so the
   slider, the dots, the entrance animation and the keyboard controls carry on
   working without knowing this slide is different.
   ========================================================================== */
.ibs-hero__slide--split .ibs-hero__scrim {
	display: none;
}

/* Scrim dimatikan berarti bidangnya jadi biru rata tanpa kedalaman sama
   sekali — dan pada slide ini bidang itu memenuhi dua pertiga bingkai. Satu
   gradien diagonal dari biru assay ke warna lantai memberinya arah, tanpa
   menambah warna baru ke dalam sistem. */
.ibs-hero__slide--split {
	background: linear-gradient(112deg,
		var(--assay-deep, #072f7d) 0%,
		#0a1a3f 58%,
		var(--ds-floor, #0a1020) 100%);
}

/* The parallax writes an inline transform to every .ibs-hero__media on scroll.
   On a full-bleed layer that is the effect; on a card it would slide the card
   out of its own composition, so it is refused here — and refusing it needs
   !important, because an inline style outranks everything else. */
.ibs-hero__slide--split .ibs-hero__media {
	position: absolute;
	inset: auto;
	right: max(var(--gutter, 24px), (100% - var(--maxw, 1280px)) / 2);
	top: 50%;
	/* Kartunya diperbesar. Pada ukuran sebelumnya (30% / 500px) ia mengambang
	   sebagai persegi kecil di kanan bidang biru yang luas — fotonya terbaca
	   sebagai tempelan, bukan sebagai separuh komposisi. */
	width: clamp(340px, 36%, 520px);
	height: min(84%, 620px);
	transform: translateY(-50%) !important;
	border: 1px solid rgba(255, 255, 255, 0.22);
	border-radius: var(--r, 2px);
	overflow: hidden;
	will-change: auto;
}

/* The arrival animation scales the image inside the card. Without this the
   1.13 start state would push the picture past the card's edge and the crop
   would visibly settle. */
.ibs-hero__slide--split .ibs-hero__img {
	transform-origin: center;
}

/* The copy column stops short of the card instead of running under it — and
   stays where the rest of the site starts.
 *
 * This element carries BOTH .ibs-wrap and .ibs-hero__copy. .ibs-wrap centres
 * itself with margin-inline:auto, which is right when it is the full measure.
 * Capping it at 640px without touching the margins left the auto margins to
 * split the remainder evenly: the copy was pushed to x=313 on a 1265px frame
 * while every other block on the page begins at x=51. The result is a third of
 * the hero standing empty on the left, and a headline that lines up with
 * nothing above or below it.
 *
 * The start edge is now stated explicitly, using the same expression .ibs-wrap
 * resolves to: the gutter while the viewport is narrower than the measure, and
 * half the leftover once it is wider. The end margin stays auto so the column
 * never reaches the card. */
.ibs-hero__slide--split .ibs-hero__copy {
	max-width: min(58%, 640px);
	margin-inline: max(var(--gutter, 24px), (100% - var(--maxw, 1280px)) / 2) auto;
}

/* Titik potong kartunya sedikit dinaikkan. Fotonya potret 1100x1280 dan
   kartunya 0,76 — yang terbuang hanya sekitar 6% di tiap sisi — tapi pada
   pusat 50% yang jatuh di tengah kartu adalah pergelangan tangan, bukan
   pipetnya. 42% menaikkan bingkai ke tangan dan ujung pipet. */
.ibs-hero__slide--split .ibs-hero__img {
	object-position: 50% 42% !important;
}

/* Below the split there is no room for a card beside anything, so the picture
   becomes a band across the foot of the frame.

   It deliberately stays position:absolute. Switching it to relative turns it
   into a flex item of .ibs-hero__slide, which is a flex container — and then
   width:100% is resolved against the space left over by the copy rather than
   the frame, which is how it first came out 204px wide instead of full width.
   Staying out of the flow keeps the width meaning what it says. */
@media (max-width: 860px) {
	.ibs-hero__slide--split .ibs-hero__media {
		inset: auto 0 0 0;
		top: auto;
		width: auto;
		height: 210px;
		transform: none !important;
		border-width: 1px 0 0 0;
		border-radius: 0;
	}
	/* The copy is centred in the frame; without this it would centre behind the
	   band rather than above it. */
	.ibs-hero__slide--split .ibs-hero__copy {
		max-width: none;
		margin-inline: auto;
		padding-bottom: 226px;
	}
}

/* Two layers, not one wash. The first is a hard directional gradient carrying
   the type; the second is a whisper of brand over the whole frame so three
   different photographs still feel like one site. */
.ibs-hero__scrim {
	position: absolute;
	inset: 0;
	background:
		linear-gradient(100deg,
			rgba(4, 15, 40, 0.92) 0%,
			rgba(4, 15, 40, 0.80) 28%,
			rgba(4, 15, 40, 0.42) 52%,
			rgba(4, 15, 40, 0.10) 78%,
			rgba(4, 15, 40, 0.02) 100%),
		linear-gradient(0deg, rgba(7, 47, 125, 0.20), rgba(7, 47, 125, 0.20));
}

.ibs-hero__copy {
	position: relative;
	z-index: 1;
	padding-block: clamp(96px, 12vh, 150px) clamp(120px, 15vh, 190px);
}

#ibs-hp .ibs-hero__eyebrow {
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-size: 0.75rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: #9dc0ff;
	margin: 0 0 20px;
	max-width: none;
}

/* These carry #ibs-hp because the design system sets colour on the bare
   elements (html.ibs-ds h1 at 0,1,1) and beat a lone class at 0,1,0 — which
   painted this headline in dark ink on top of a photograph. Type that sits on
   an image has to out-rank the page's default type, not tie with it. */
#ibs-hp .ibs-hero__title {
	font-size: clamp(2.25rem, 4.4vw, 4rem);
	line-height: 1.04;
	letter-spacing: -0.032em;
	font-weight: 600;
	color: #fff;
	margin: 0 0 22px;
	max-width: 17ch;
	text-wrap: balance;
}

#ibs-hp .ibs-hero__sub {
	font-size: clamp(1rem, 1.25vw, 1.1875rem);
	line-height: 1.55;
	color: rgba(255, 255, 255, 0.86);   /* 11.4:1 on the scrim at the type's edge */
	max-width: 46ch;
	margin: 0 0 36px;
}

/* A ghost button over a photograph needs its own ground, or it disappears the
   moment the picture behind it turns pale. */
#ibs-hp .ibs-btn--onphoto {
	background: rgba(255, 255, 255, 0.10);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	color: #fff;
	border-color: rgba(255, 255, 255, 0.45);
}
#ibs-hp .ibs-btn--onphoto:hover {
	background: rgba(255, 255, 255, 0.18);
	border-color: #fff;
	color: #fff;
}

/* Controls travel with the content column rather than sitting in the far
   corners of the screen, where the reference site puts them and where nobody
   is looking. */
.ibs-hero__controls {
	position: absolute;
	inset-inline: 0;
	bottom: clamp(28px, 4vh, 48px);
	z-index: 2;
	display: flex;
	align-items: center;
	gap: 14px;
}

.ibs-hero__nav,
.ibs-hero__pause {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	background: rgba(255, 255, 255, 0.10);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	border: 1px solid rgba(255, 255, 255, 0.34);
	border-radius: var(--r);
	color: #fff;
	cursor: pointer;
	transition: background-color 160ms var(--ease), border-color 160ms var(--ease);
}
.ibs-hero__nav:hover,
.ibs-hero__pause:hover { background: rgba(255, 255, 255, 0.20); border-color: #fff; }
.ibs-hero__nav:active,
.ibs-hero__pause:active { transform: scale(0.96); }
.ibs-hero__nav svg { width: 19px; height: 19px; }

/* Not margin-left:auto — that parked it in the bottom-right corner, under
   the floating WhatsApp bubble. It belongs with the other controls. */
.ibs-hero__pause { margin-inline-start: 6px; }
/* Both icons are one box shaped by clip-path. The play state used to be the
   classic zero-width border triangle, which works but reads to a linter — and
   to a reader of the file — as an accent border on a card. A shape should be
   declared as a shape. */
.ibs-hero__pause-ico {
	width: 13px; height: 13px;
	background: currentColor;
	clip-path: polygon(0 0, 34% 0, 34% 100%, 0 100%, 0 0, 66% 0, 100% 0, 100% 100%, 66% 100%, 66% 0);
	transition: clip-path 180ms var(--ease);
}
.ibs-hero__pause[aria-pressed="true"] .ibs-hero__pause-ico {
	clip-path: polygon(0 0, 100% 50%, 100% 50%, 0 100%, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0);
}

/* One frame means nothing to navigate between: a lone dot is a control that
   controls nothing. hero.js also returns early below two slides, so no timer
   and no ripple driver run either. */
.ibs-hero__controls:has(.ibs-hero__dots li:only-child) { display: none; }

.ibs-hero__dots {
	display: flex;
	align-items: center;
	gap: 12px;
	list-style: none;
	margin: 0;
	padding: 0;
}
/* Round, like the reference — but each dot is a 44px hit area with a small
   visible mark inside it, so it is thumb-sized without looking heavy. */
.ibs-hero__dots button {
	display: grid;
	place-items: center;
	width: 30px;
	height: 44px;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}
.ibs-hero__dots button::before {
	content: '';
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.42);
	transition: background-color 220ms var(--ease), transform 220ms var(--ease);
}
.ibs-hero__dots button:hover::before { background: rgba(255, 255, 255, 0.75); }
.ibs-hero__dots button[aria-selected="true"]::before {
	background: #fff;
	transform: scale(1.15);
}
/* The active dot draws its own dwell as a ring closing around it, so the reader
   can see the frame coming without a progress bar shouting about it. */
.ibs-hero__dot-fill {
	position: absolute;
	width: 22px;
	height: 22px;
	border-radius: 50%;
	border: 1.5px solid rgba(255, 255, 255, 0.85);
	clip-path: inset(0 0 0 50%);
	opacity: 0;
	pointer-events: none;
}
.ibs-hero__dots button { position: relative; }
.ibs-hero__dots button[aria-selected="true"] .ibs-hero__dot-fill {
	opacity: 1;
	animation: ibs-dwell var(--ibs-hero-dwell, 6500ms) linear forwards;
}
@keyframes ibs-dwell {
	from { transform: rotate(0deg); }
	to   { transform: rotate(360deg); }
}

.ibs-hero :focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

@media (max-width: 767px) {
	.ibs-hero__stage { min-height: clamp(460px, 72vh, 620px); }
	.ibs-hero__title { max-width: none; }
	.ibs-hero__scrim {
		background:
			linear-gradient(180deg,
				rgba(4, 15, 40, 0.62) 0%,
				rgba(4, 15, 40, 0.80) 45%,
				rgba(4, 15, 40, 0.94) 100%),
			linear-gradient(0deg, rgba(7, 47, 125, 0.20), rgba(7, 47, 125, 0.20));
	}
	.ibs-hero__controls { gap: 10px; }
	.ibs-hero__dots button { width: 26px; }
}

/* Reduced motion: no cross-fade, no auto-advance, no filling dot. The slider
   becomes a set of frames the reader moves through themselves. */
@media (prefers-reduced-motion: reduce) {
	.ibs-hero__slide { transition: none; }
	.ibs-hero__media { transform: none !important; inset: 0; }
	.ibs-hero__slide.is-rippling .ibs-hero__media { filter: none !important; }
	.ibs-hero__slide.is-current .ibs-hero__img,
	.ibs-hero__slide.is-current .ibs-hero__eyebrow,
	.ibs-hero__slide.is-current .ibs-hero__title,
	.ibs-hero__slide.is-current .ibs-hero__sub,
	.ibs-hero__slide.is-current .ibs-actions {
		animation: none;
		transform: none;
		opacity: 1;
	}
	.ibs-hero__dots button[aria-selected="true"] .ibs-hero__dot-fill { display: none; }
}

/* --------------------------------------------------------------------------
   The proof panel.

   This replaces a 96-well plate diagram that occupied the same slot. The plate
   was legible only to someone who already reads plate maps, and the site owner
   could not tell what the circles were for - which settles it. A visitor now
   reads a principal's mark and four dated facts, and needs no key to do it.
   -------------------------------------------------------------------------- */
.ibs-proof {
	min-width: 0;
	background: var(--frame);
	border: var(--rule);
	border-radius: var(--r);
	padding: clamp(24px, 3vw, 40px);
}

.ibs-proof__mark {
	padding-bottom: clamp(20px, 2.4vw, 28px);
	border-bottom: var(--rule);
}
.ibs-proof__mark img {
	display: block;
	width: auto;
	height: auto;
	max-width: 260px;
	max-height: 112px;
	object-fit: contain;
	object-position: left center;
}

.ibs-proof__list { margin: 0; padding: 0; }

.ibs-proof__row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 20px;
	padding: 15px 0;
	border-bottom: var(--rule);
}
.ibs-proof__row dt {
	margin: 0;
	color: var(--ink);
	font-weight: 500;
	font-size: 0.9375rem;
	letter-spacing: -0.008em;
}
.ibs-proof__row dd { margin: 0; flex: 0 0 auto; }

/* The sole-agency line is the reason this panel exists. It gets the weight. */
.ibs-proof__row:first-child dt {
	font-size: 1.0625rem;
	font-weight: 600;
	color: var(--assay);
}

.ibs-proof__foot {
	margin: 16px 0 0;
	font-size: 0.8125rem;
	color: var(--ink-3);
	max-width: none;
}

/* ==========================================================================
   2. STATS — four published figures on one rule.
   ========================================================================== */
.ibs-stats { padding-block: clamp(40px, 5vw, 64px) !important; }

.ibs-stats__list {
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 0;
}
.ibs-stats__item { padding-right: 24px; }
.ibs-stats__n {
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-size: clamp(1.75rem, 3vw, 2.75rem);
	line-height: 1;
	font-weight: 500;
	color: var(--ink);
	letter-spacing: -0.03em;
	margin: 0 0 8px;
}
.ibs-stats__label {
	margin: 0;
	font-size: 0.875rem;
	color: var(--ink-3);
}

/* ==========================================================================
   5. RECORD — the achievement timeline.
   ========================================================================== */
.ibs-record__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 0;
	border-top: 1px solid var(--ink);
}

.ibs-record__year { padding: 22px 24px 8px 0; position: relative; }
.ibs-record__year::before {
	content: '';
	position: absolute;
	inset-block-start: -1px;
	inset-inline-start: 0;
	width: 9px; height: 9px;
	background: var(--assay);
	border-radius: 50%;
	transform: translateY(-50%);
}
.ibs-record__n {
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-size: 1.125rem;
	font-weight: 500;
	color: var(--ink);
	margin: 0 0 12px;
	letter-spacing: -0.01em;
}
.ibs-record__items { list-style: none; margin: 0; padding: 0; }
.ibs-record__items li {
	font-size: 0.875rem;
	color: var(--ink-2);
	padding: 5px 0;
	max-width: 26ch;
}

/* ==========================================================================
   CATALOGUE — real product photography, asymmetric so it is not a card wall.

   Two lead tiles carry the two biggest categories at double width; the rest run
   six-up beneath them. Every shot is a product in that category, pulled live,
   so the section cannot go stale and contains no stock imagery.
   ========================================================================== */
.ibs-cat__head {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 32px;
	flex-wrap: wrap;
	margin-bottom: 40px;
}
.ibs-cat__head .ibs-h2 { margin-bottom: 12px; }
.ibs-cat__head .ibs-sub { margin: 0; }

.ibs-cat__grid {
	list-style: none;
	margin: 0 0 48px;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(6, minmax(0, 1fr));
	gap: clamp(12px, 1.4vw, 20px);
}

.ibs-cat__tile { min-width: 0; grid-column: span 1; }
.ibs-cat__tile.is-lead { grid-column: span 3; }

.ibs-cat__tile a {
	display: block;
	text-decoration: none;
	color: inherit;
	background: var(--frame);
	border: var(--rule);
	border-radius: var(--r);
	overflow: hidden;
	height: 100%;
	transition: border-color 180ms var(--ease);
}
.ibs-cat__tile a:hover { border-color: var(--assay); }

/* The IBS watermark, cropped out of view.
 *
 * Every catalogue photograph carries the IBS logo baked into a white band above
 * the instrument. On the shop it was measured across 98 files from nine pages:
 * the band always ends between 12.8% and 15.3% of image height, and the nearest
 * product content starts at 22.3%. Dropping the top 17% clears the logo by 1.7
 * points and stops 5.3 points short of the product.
 *
 * Berkas kategori beranda diukur terpisah — 300x300 dan 768x768, bukan 470x588
 * milik toko — dan logonya berakhir di 15,3%-15,4%, produk terdekat mulai 24%.
 * Potongannya dinaikkan ke 18,5%, tepat di tengah antara piksel logo terakhir
 * dan piksel produk pertama. Angka pertama 17% benar secara hitungan tapi hanya
 * menyisakan 1,6 poin di atas logo; satu berkas yang logonya sedikit lebih
 * rendah akan menyisakan sisiknya di tepi atas.
 *
 * The ratio now lives on the box and the image is sized by height and anchored
 * to the bottom, so the 17% falls outside the box and is clipped, and the
 * remaining 83% grows to fill the space it left — the instrument ends up bigger
 * than it was. The 10px bottom inset is carried inside the height calculation
 * (100% - 10px) / 0.83, so the crop stays exactly 17% and never eats product.
 *
 * Width follows the image's own ratio, so nothing is ever cut off at the sides:
 * at 4/3 the image resolves to ~72% of the tile width, at 16/9 to ~54%.
 *
 * The file on the server is untouched. This only changes what is displayed. */
.ibs-cat__shot {
	display: block;
	position: relative;
	overflow: hidden;
	background: #fff;
	aspect-ratio: 4 / 3;
}
.ibs-cat__tile.is-lead .ibs-cat__shot { aspect-ratio: 16 / 9; }

.ibs-cat__shot img {
	display: block;
	position: absolute;
	inset-inline-start: 50%;
	inset-block-end: 10px;
	width: auto;
	max-width: none;
	height: calc((100% - 10px) / 0.815);
	padding: 0;
	transform: translateX(-50%);
	transition: transform 320ms var(--ease);
}
.ibs-cat__tile.is-lead .ibs-cat__shot img { inset-block-end: 20px; height: calc((100% - 20px) / 0.815); }
.ibs-cat__tile a:hover .ibs-cat__shot img { transform: translateX(-50%) scale(1.03); }

.ibs-cat__meta {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 12px;
	padding: 12px 14px 14px;
	border-top: var(--rule);
}
.ibs-cat__name {
	color: var(--ink);
	font-weight: 500;
	font-size: 0.875rem;
	letter-spacing: -0.008em;
}
.ibs-cat__tile.is-lead .ibs-cat__name { font-size: 1.0625rem; font-weight: 600; }
.ibs-cat__meta .ibs-addr { flex: 0 0 auto; white-space: nowrap; }

.ibs-cat__rest {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px 8px;
	margin: 0;
	padding-top: 22px;
	border-top: var(--rule);
	max-width: none;
	color: var(--ink-2);
	font-size: 0.9375rem;
}
.ibs-cat__rest .ibs-addr { text-transform: uppercase; letter-spacing: 0.06em; }

/* Each name used to be one run-on text node, separated only by a "·"
   character with no room around it to click or tap. Now every field with a
   real category behind it (see ibs_hp26_field_link()) is its own link, with
   enough padding to be a comfortable target — WCAG 2.2 target size, same
   24px measure .ibs-link uses above — and the "·" moves to a pseudo-element
   so it never has to double as clickable text. Fields with no matching
   category stay plain text, same size and spacing, so the list still reads
   as one continuous set either way. */
.ibs-cat__rest-item {
	display: inline-flex;
	align-items: center;
	padding: 4px 6px;
	margin: -4px -6px;
	min-height: 24px;
}
a.ibs-cat__rest-item {
	color: var(--ink-2);
	text-decoration: none;
	border-radius: 3px;
	transition: color 160ms var(--ease), background-color 160ms var(--ease);
}
a.ibs-cat__rest-item:hover,
a.ibs-cat__rest-item:focus-visible {
	color: var(--assay);
	background: var(--assay-wash);
}
.ibs-cat__rest-item:not(:last-child)::after {
	content: "\00b7";
	margin-inline-start: 8px;
	color: var(--ink-3);
}

/* ==========================================================================
   2. FIELDS — the plate spelled out. Addressed list, not cards.
   ========================================================================== */
.ibs-fields__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 0;
	border-top: var(--rule);
}

.ibs-fields__item {
	display: flex;
	align-items: baseline;
	gap: 14px;
	padding: 15px 4px;
	border-bottom: var(--rule);
	transition: color 140ms var(--ease);
}
.ibs-fields__item .ibs-addr { flex: 0 0 auto; min-width: 2.2em; }
.ibs-fields__name { color: var(--ink); font-weight: 500; letter-spacing: -0.008em; }

.ibs-fields__item.is-read { color: var(--assay); }
.ibs-fields__item.is-read .ibs-addr,
.ibs-fields__item.is-read .ibs-fields__name { color: var(--assay); }

/* ==========================================================================
   3. SERVICES — a specification table. Three icon cards are prohibited here.
   ========================================================================== */
.ibs-table {
	width: 100%;
	border-collapse: collapse;
	text-align: left;
	font-size: 0.9375rem;
}

.ibs-table thead th {
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-size: 0.6875rem;
	font-weight: 400;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ink-3);
	padding: 0 20px 12px 0;
	border-bottom: 1px solid var(--ink);
	vertical-align: bottom;
}

.ibs-table tbody th,
.ibs-table tbody td {
	padding: 22px 20px 22px 0;
	border-bottom: var(--rule);
	vertical-align: top;
}

.ibs-table tbody th {
	font-weight: 600;
	color: var(--ink);
	font-size: 1.0625rem;
	letter-spacing: -0.012em;
	width: 22%;
	min-width: 170px;
}
.ibs-table tbody th .ibs-addr { display: block; margin-bottom: 5px; font-weight: 400; }

.ibs-table tbody td { color: var(--ink-2); max-width: 46ch; }
.ibs-table__via {
	white-space: normal;
	color: var(--ink-3) !important;
	font-size: 0.875rem;
	width: 26%;
	min-width: 200px;
}
.ibs-table__act {
	width: 1%;
	white-space: nowrap;
	padding-left: 28px !important;
	padding-right: 0 !important;
}

/* ==========================================================================
   4. PRINCIPALS — an addressed rack. Optically normalised, no redundant caption:
   the mark already says the name, and the alt text carries it for everyone else.
   ========================================================================== */
/* The Eppendorf band. A 3.2:1 strip is the wrong shape for a hero and the right
   shape here: it reads as a rule with a picture in it, opening the row of
   principal marks rather than competing with them. */
.ibs-rack__band {
	margin: 0 0 clamp(28px, 3.5vw, 48px);
	border: var(--rule);
	border-radius: var(--r);
	overflow: hidden;
	background: var(--frame);
}
.ibs-rack__band img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 32 / 10;
	object-fit: cover;
	object-position: 50% 50%;
}
.ibs-rack__band figcaption {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 8px 16px;
	padding: 10px 14px;
	border-top: var(--rule);
}
/* Sumbernya disebut, dan disebut lebih pelan daripada keterangannya. */
.ibs-rack__band figcaption span { color: var(--ink-3); opacity: 0.8; }

@media (max-width: 720px) {
	/* Pada layar sempit pita 3.2:1 tinggal 110px — potretnya hilang. Dipotong
	   jadi lebih tegak, dengan titik fokus di tangan dan pipetnya. */
	.ibs-rack__band img { aspect-ratio: 16 / 10; object-position: 62% 50%; }
}

.ibs-rack__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: minmax(124px, 1fr);
	gap: 0;
	border-top: var(--rule);
	border-left: var(--rule);
	overflow-x: auto;
	overscroll-behavior-inline: contain;
	scroll-snap-type: inline proximity;
	scrollbar-width: thin;
}
.ibs-rack__slot { scroll-snap-align: start; }

.ibs-rack__slot {
	position: relative;
	border-right: var(--rule);
	border-bottom: var(--rule);
	background: var(--frame);
	padding: 14px 10px 20px;
	display: grid;
	grid-template-rows: auto 1fr;
	gap: 8px;
	min-height: 148px;
}
.ibs-rack__slot .ibs-addr { opacity: 0.5; }

.ibs-rack__mark { display: grid; place-items: center; }

.ibs-rack__mark img {
	display: block;
	width: auto;
	height: auto;
	max-width: calc(92% * var(--ibs-mark-scale, 1));
	max-height: calc(76px * var(--ibs-mark-scale, 1));
	object-fit: contain;
	/* These were desaturated to 0.16 at 0.78 opacity so the eleven marks would
	   read as one set. They read as one set and none of them read as a brand:
	   Lab Bubble and Boxun disappeared outright. Evening out the weights is the
	   per-brand scale factor's job, which it already does. The marks are shown
	   as their owners drew them. */
	opacity: 1;
	transition: transform 200ms var(--ease);
}
.ibs-rack__slot:hover img { transform: scale(1.04); }

/* ==========================================================================
   5. CHAIN — what happens after the invoice clears. A ruled sequence.
   ========================================================================== */
.ibs-chain__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
	gap: 0;
	border-top: 1px solid var(--ink);
}

.ibs-chain__step {
	padding: 24px 28px 8px 0;
	position: relative;
}
.ibs-chain__step .ibs-addr { display: block; margin-bottom: 14px; }
.ibs-chain__step p { font-size: 0.9375rem; color: var(--ink-2); max-width: 34ch; }

/* The chain reads as a chain: a rule runs through it, ticked at each step. */
.ibs-chain__step::before {
	content: '';
	position: absolute;
	inset-block-start: -1px;
	inset-inline-start: 0;
	width: 9px;
	height: 9px;
	background: var(--assay);
	border-radius: 50%;
	transform: translateY(-50%);
}

/* ==========================================================================
   6. NEWS — editorial lead + list. Never three equal cards.
   ========================================================================== */
.ibs-news__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 24px;
	flex-wrap: wrap;
}
.ibs-news__head .ibs-h2 { margin-bottom: 32px; }

.ibs-news__grid {
	display: grid;
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
	gap: 0 clamp(28px, 4vw, 56px);
	border-top: 1px solid var(--ink);
}

.ibs-news__item { min-width: 0; }
.ibs-news__item.is-lead { grid-row: span 2; border-bottom: none; padding-top: 28px; }
.ibs-news__item:not(.is-lead) { border-bottom: var(--rule); padding-block: 22px; }
.ibs-news__item:not(.is-lead):first-of-type { padding-top: 28px; }

.ibs-news__link {
	display: block;
	text-decoration: none;
	color: inherit;
}

.ibs-news__thumb {
	display: block;
	margin-bottom: 18px;
	border: var(--rule);
	border-radius: var(--r);
	overflow: hidden;
	background: var(--frame);
}
.ibs-news__thumb img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	transition: transform 400ms var(--ease);
}
.ibs-news__link:hover .ibs-news__thumb img { transform: scale(1.02); }

.ibs-news__item:not(.is-lead) .ibs-news__thumb { display: none; }

.ibs-news__meta {
	display: block;
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-size: 0.6875rem;
	color: var(--ink-3);
	margin-bottom: 8px;
}

.ibs-news__title {
	font-size: 1.0625rem;
	line-height: 1.34;
	letter-spacing: -0.014em;
	font-weight: 600;
	color: var(--ink);
	margin: 0;
	text-wrap: pretty;
}
.ibs-news__item.is-lead .ibs-news__title {
	font-size: clamp(1.25rem, 1.9vw, 1.625rem);
	line-height: 1.22;
	letter-spacing: -0.022em;
}
.ibs-news__link:hover .ibs-news__title { color: var(--assay); }

/* ==========================================================================
   7. CLOSE — the one drenched panel. The plate, fully read.
   ========================================================================== */
.ibs-close {
	background: var(--assay-deep);
	color: rgba(255, 255, 255, 0.82);
	border-top: none !important;
}

.ibs-close__grid {
	display: grid;
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
	gap: clamp(36px, 5vw, 80px);
	align-items: start;
}

.ibs-close__lede {
	font-size: 1.0625rem;
	color: rgba(255, 255, 255, 0.80);   /* 8.9:1 on --assay-deep, measured */
	max-width: 44ch;
	margin: 0 0 36px;
}

.ibs-close__card {
	font-style: normal;
	border: 1px solid rgba(255, 255, 255, 0.20);
	border-radius: var(--r);
	padding: 24px;
}
.ibs-close__card .ibs-addr { display: block; margin-bottom: 14px; }

.ibs-close__addr { color: rgba(255, 255, 255, 0.80); font-size: 0.9375rem; margin-bottom: 16px; }

.ibs-close__tel a {
	font-family: 'Spline Sans Mono', ui-monospace, monospace;
	font-variant-numeric: tabular-nums;
	color: #fff;
	font-size: 1.0625rem;
	text-decoration: none;
	border-bottom: 1px solid rgba(255, 255, 255, 0.34);
}
.ibs-close__tel a:hover { border-bottom-color: #fff; }

.ibs-close :focus-visible { outline-color: #fff; }

/* ==========================================================================
   Responsive. Every multi-column layout declares its own collapse.
   ========================================================================== */
@media (max-width: 1080px) {
	.ibs-hero__grid { grid-template-columns: 1fr; gap: 44px; }
	.ibs-news__grid { grid-template-columns: 1fr; }
	.ibs-news__item.is-lead { grid-row: auto; border-bottom: var(--rule); padding-bottom: 26px; }
	.ibs-news__item:not(.is-lead):first-of-type { padding-top: 22px; }
	.ibs-close__grid { grid-template-columns: 1fr; }
	.ibs-fields__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.ibs-record__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.ibs-record__year { padding-bottom: 20px; }
	.ibs-cat__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
	.ibs-cat__tile.is-lead { grid-column: span 2; }
}

@media (max-width: 860px) {
	/* The spec table becomes stacked records. Headers are repeated per cell so
	   the meaning survives the collapse. */
	.ibs-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
	.ibs-table tbody th,
	.ibs-table tbody td { display: block; width: auto; max-width: none; padding: 0 0 10px; border: none; }
	.ibs-table tbody tr {
		display: block;
		padding: 22px 0;
		border-bottom: var(--rule);
	}
	.ibs-table tbody tr:first-child { border-top: var(--rule); }
	.ibs-table__via { white-space: normal; }
	.ibs-table__act { padding-top: 6px !important; }
}

@media (max-width: 1080px) {
	.ibs-hp26 { --header-h: 88px; }
}

@media (max-width: 720px) {
	#ibs-hp { font-size: 16px; }
	.ibs-h1 { font-size: clamp(2rem, 8.4vw, 2.625rem); }
	.ibs-actions .ibs-btn { flex: 1 1 auto; }

	/* Every well stays a 44px-class target on touch by dropping to the 12
	   columns the plate actually needs and letting the rows breathe. */
	.ibs-fields__list { grid-template-columns: 1fr; }
	.ibs-cat__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.ibs-cat__tile.is-lead { grid-column: span 2; }
	.ibs-cat__tile.is-lead .ibs-cat__shot img { aspect-ratio: 16 / 10; }
	.ibs-rack__slot { min-height: 104px; padding: 10px 8px 14px; }
	.ibs-stats__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 0; }
	.ibs-record__list { grid-template-columns: 1fr; border-top: none; }
	.ibs-record__year { padding: 20px 0 10px 20px; border-top: 1px solid var(--ink); }
	.ibs-proof__mark img { max-height: 88px; }
}

/* Touch devices resolve hover on tap. Gate the decorative half. */
@media (hover: none) {
	.ibs-rack__slot:hover img { transform: none; }
}

/* --------------------------------------------------------------------------
   Reduced motion. Fewer and gentler, not zero: the plate is simply already
   read, and colour still responds so the crosshair keeps working.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	.ibs-news__thumb img,
	.ibs-btn {
		transition-duration: 1ms;
	}
	.ibs-news__link:hover .ibs-news__thumb img { transform: none; }
	.ibs-btn:active { transform: none; }
}

/* --------------------------------------------------------------------------
   Print: the plate is a diagram worth keeping on a tender printout.
   -------------------------------------------------------------------------- */
@media print {
	#ibs-hp { background: #fff; font-size: 11pt; }
	.ibs-close { background: #fff; color: #000; }
	.ibs-h2--invert, .ibs-close__lede, .ibs-close__addr { color: #000; }
	.ibs-actions, .ibs-news__thumb { display: none; }
}

/* --------------------------------------------------------------------------
   Dark mode. The composition does not change — only the tokens it reads. Two
   places need a note: the close panel is already a dark object in light mode,
   so in dark mode it needs a visible edge to separate it from the ground, and
   the principal marks were desaturated for a light ground.
   -------------------------------------------------------------------------- */
html[data-theme="dark"] .ibs-close,
html.ibs-ds:not([data-theme="light"]) .ibs-close {
	border-top: 1px solid var(--hairline) !important;
}

@media (prefers-color-scheme: dark) {
	html.ibs-ds:not([data-theme="light"]) .ibs-rack__mark img,
	html.ibs-ds:not([data-theme="light"]) .ibs-proof__mark img {
		filter: saturate(0.2) contrast(1.02) brightness(1.28);
	}
	html.ibs-ds:not([data-theme="light"]) .ibs-rack__slot:hover img {
		filter: brightness(1.15);
	}

	html.ibs-ds:not([data-theme="light"]) .ibs-btn--primary { color: var(--ds-ground); }
	html.ibs-ds:not([data-theme="light"]) .ibs-btn--primary:hover {
		background: var(--assay);
		color: var(--ds-ground);
		filter: brightness(1.12);
	}
}
/* The primary action in dark mode. The assay colour lifts to #6fa3ff there so
   it can be read on a dark ground — and a white label on top of it measures
   2.51:1. What changes is the ink, not the colour: ground-dark ink on that blue
   measures 6.5:1. Hover cannot use assay-deep either, which in dark mode is
   almost black, so the button would vanish under the pointer. */
html[data-theme="dark"] .ibs-btn--primary { color: var(--ds-ground); }
html[data-theme="dark"] .ibs-btn--primary:hover {
	background: var(--assay);
	color: var(--ds-ground);
	filter: brightness(1.12);
}

html[data-theme="dark"] .ibs-rack__mark img,
html[data-theme="dark"] .ibs-proof__mark img {
	filter: saturate(0.2) contrast(1.02) brightness(1.28);
}
html[data-theme="dark"] .ibs-rack__slot:hover img { filter: brightness(1.15); }
