/* Public pages — the "Informed Editorial" design system.

   Ported by hand from the Stitch project of the same name
   (projects/7833871898285268206), which specifies a Source Serif 4 headline
   face over an Inter UI face, a warm off-white ground, one institutional blue
   and one editorial red. Stitch emits Tailwind-CDN utility classes and a
   Material Symbols icon font; neither ships here. This site is 2.176
   statically pre-rendered pages with no script and no build step at request
   time, so the design lives in tokens and plain selectors instead.

   Two rules from the corpus override the Stitch mockup, both for the same
   reason — the text is Vietnamese:

   1. No uppercase transforms. Vietnamese stacks tone marks into the cap line
      and `Ế Ộ Ữ` collide. The mockup sets the category heading and the
      category eyebrow in caps; here they get weight and tracking instead.
      This was already fixed once (63c8c6d) and is not being undone.
   2. No weights above 700. Heavier faces smear diacritics at label sizes.

   Deliberately NOT the admin design system under cms/docs/design/ — this
   stylesheet neither imports nor reuses it. */

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

:root {
  /* Surfaces. The ground is warm off-white, not pure white: the mockup's
     #fcf9f8 over a pure-white card is what gives the article its "printed
     page" separation without a shadow. */
  --surface: #fcf9f8;
  --paper: #ffffff;
  --panel: #f6f3f2;
  --panel-2: #f0eded;
  --panel-3: #eae7e7;
  --dim: #dcd9d9;

  /* Ink. Never #000 — the mockup's on-surface is a deep charcoal. */
  --ink: #1c1b1b;
  --muted: #424752;
  --outline: #727783;
  --line: #c2c6d4;

  /* One institutional blue, one editorial red. No third hue, no gradient. */
  --accent: #003e7f;
  --accent-bright: #0055aa;
  --red: #bc0100;

  /* Stitch: DEFAULT 0.125rem, lg 0.25rem, xl 0.5rem. Media stays near-sharp
     so a photograph reads as a photograph. */
  --r: 2px;
  --r-lg: 4px;
  --r-xl: 8px;

  /* 8px rhythm. */
  --s-sm: 8px;
  --s-md: 16px;
  --s-lg: 32px;
  --s-xl: 64px;
  --gutter: 24px;

  --serif: "Source Serif 4", "Noto Serif", Georgia, "Times New Roman", serif;
  --sans: Inter, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0 0 var(--s-xl);
  background: var(--surface);
  color: var(--ink);
  font: 400 16px/1.7 var(--sans);
  -webkit-font-smoothing: antialiased;
}

main { max-width: 760px; margin: 0 auto; padding: 0 var(--s-md); }

/* The headline face carries every heading on the site. This is the single
   most visible difference from the pre-redesign stylesheet, which set
   everything in system-ui. */
h1, h2, h3, h4 {
  font-family: var(--serif);
  color: var(--ink);
  text-wrap: balance;
}

h1 { font-size: 1.625rem; line-height: 1.3; letter-spacing: -0.01em; font-weight: 700; margin: 0 0 var(--s-md); }
h2 { font-size: 1.35rem; line-height: 1.35; font-weight: 600; margin: var(--s-lg) 0 var(--s-sm); }
h3 { font-size: 1.15rem; line-height: 1.4; font-weight: 600; margin: 1.5rem 0 0.5rem; }

@media (min-width: 760px) {
  h1 { font-size: 2.25rem; line-height: 1.2; letter-spacing: -0.02em; }
}

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

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

/* ------------------------------------------------------------- chrome */

/* Sticky, as the mockup has it: on a 4.237px-tall article the way back to
   the category should not be 4.000px up. */
header.site {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  flex-wrap: wrap;
  gap: 12px var(--s-md);
  align-items: center;
  padding: 10px var(--s-md);
  margin-bottom: var(--s-lg);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* The Techport wordmark. Serif, blue, italic — the mockup's own footer
   lockup, promoted to the masthead because this site has no logo file. */
header.site .brand {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  color: var(--accent-bright);
}

form.search {
  display: flex;
  gap: var(--s-sm);
  flex: 1 1 240px;
  /* A flex item's automatic minimum size is its min-content width, and this
     one's is the input's intrinsic 260px plus a 64px button — 324px, which
     overflows a 320px viewport sideways however small the flex-basis is. The
     page must not scroll horizontally at 320px; only the spec table may, and
     only inside its own box. */
  min-width: 0;
  max-width: 420px;
  margin-left: auto;
}

form.search input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: var(--s-sm) 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}
form.search input:focus-visible {
  outline: none;
  border-color: var(--accent-bright);
  box-shadow: 0 0 0 2px rgba(0, 85, 170, 0.18);
}

form.search button {
  min-height: 44px;
  min-width: 64px;
  padding: var(--s-sm) var(--s-md);
  border: 1px solid var(--accent);
  border-radius: var(--r-lg);
  background: var(--accent);
  color: #fff;
  font: 600 0.9375rem/1 var(--sans);
  cursor: pointer;
}
form.search button:hover { background: var(--accent-bright); border-color: var(--accent-bright); }

/* The 2px red rule above the footer is the mockup's one piece of pure
   ornament, and the only place the editorial red appears outside a heading
   accent. It is what stops the page ending on a whisper. */
/* Full-bleed band, centred contents. The mockup's footer runs edge to edge;
   the max-width lives in the padding so the band needs no wrapper element of
   its own — and `_footer` emits bare anchors rather than a list on purpose,
   so there is no element here to add one to. */
footer.site {
  margin: var(--s-xl) 0 0;
  padding: var(--s-lg) max(var(--s-md), calc((100% - 1200px) / 2));
  display: flex;
  flex-wrap: wrap;
  gap: 12px var(--gutter);
  align-items: center;
  background: var(--panel-3);
  border-top: 2px solid var(--red);
  color: var(--muted);
  font-size: 0.875rem;
}

footer.site .brand {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--accent-bright);
}

footer.site .links { display: flex; flex-wrap: wrap; gap: 4px 20px; }
footer.site a { color: var(--muted); text-decoration: none; }
footer.site a:hover { color: var(--accent); text-decoration: underline; }

/* A 44px minimum tap target on every navigational link, measured at 390px.

   `.crumbs a` is here rather than in either template's own block because
   `_breadcrumb` is shared: the article page rendered its anchors at 23.8px
   and the category page at 24.5px — the same defect twice, from one element
   the two templates size differently. */
header.site a,
footer.site a,
.crumbs a,
.records a,
.categories a,
.pagination a,
.contact a,
.notice a,
.meta a,
.not-found a,
.source a,
.onward a,
.catalogue dd a,
.categories a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* ------------------------------------------------------ shared crumbs */

/* The mockup runs the trail in a small tracked label face above the
   headline. Sentence case, not caps — see the header note. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 6px;
  margin: 0 0 12px;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.crumbs a { text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
/* Muted, like the rest of the trail. The category eyebrow directly below
   carries the same words in the accent blue, and when the crumb wore that
   treatment too the two read as one label printed twice. The trail is
   navigation; the eyebrow is the kicker. They are allowed to say the same
   thing only if they do not look the same saying it. */
.crumbs .cat { color: inherit; font-weight: 600; }
.crumbs .cat:hover { color: var(--accent-bright); }
.crumbs .chev { color: var(--outline); }
.crumbs [aria-current="page"] { color: var(--muted); font-weight: 500; }

/* ============================================================ ARTICLE */

/* ~40rem of prose. At 760px the measure ran to 47em, which is a wall of text
   rather than a column a reader tracks. The mockup's 800px article column is
   wider than that at 18px body text, so this keeps 40rem and lets the card
   padding carry the extra width instead. */
main.article {
  max-width: 42rem;
  margin: 0 auto;
  padding: 0 var(--s-md) var(--s-lg);
}

/* On a wide screen the article sits on a white card over the warm ground —
   the mockup's `md:border md:rounded-lg` treatment. Below that width the
   card frame is noise around a full-bleed column, so it drops away. */
@media (min-width: 760px) {
  main.article {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    padding: var(--s-lg) 2.5rem;
  }
}

/* The identity block. Shared byte-for-byte by both states — that is what
   makes a stub and a full record read as one design rather than two. */

main.article .eyebrow {
  margin: 0 0 var(--s-sm);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--accent-bright);
}

main.article h1 { margin: 0 0 var(--s-md); }

main.article .meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-sm) var(--s-md);
  align-items: center;
  margin: 0 0 var(--s-md);
  color: var(--muted);
  font-size: 0.8125rem;
}

/* The record type. A pill, as the catalogue brief has it — panel fill,
   hairline border, ink text. Not coloured by type: three types do not need
   three colours. */
main.article .meta .subtype {
  padding: 3px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--ink);
  font-weight: 600;
}
main.article .meta .rid { font-variant-numeric: tabular-nums; }
main.article .meta .supplier { font-weight: 500; }

main.article hr.close {
  height: 0;
  margin: 0 0 var(--s-lg);
  border: 0;
  border-top: 1px solid var(--line);
}

/* The lead. The mockup sets it one step up from body copy and semibold —
   it is the only paragraph on the page with that treatment, which is what
   makes it read as a standfirst rather than as a first paragraph. */
main.article .summary {
  margin: 0 0 var(--s-lg);
  font-size: 1.125rem;
  line-height: 1.65;
  font-weight: 600;
  color: var(--ink);
}

/* The supporting figure. 131 of 2.176 records carry an image; the mockup is
   image-led and this cannot be, so the figure sits below the identity block
   and emits nothing at all when absent. */
main.article .shot { margin: 0 0 var(--s-lg); }
main.article .shot img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 420px;
  object-fit: cover;
  border-radius: var(--r-xl);
  background: var(--panel-2);
}
main.article .shot figcaption {
  margin-top: var(--s-sm);
  text-align: center;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--muted);
}

/* --------------------------------------------------------- prose body */

.body { font-size: 1.0625rem; line-height: 1.75; }

/* The mockup's section heading: blue, with a 4px red rule down its left
   edge. This is the article template's signature element and the clearest
   single tell that the page follows the editorial system rather than the
   flat catalogue one.

   The corpus puts an h2 and an h3 on adjacent lines in every document, so
   the two sizes are set together and must not cross. */
.body h2 {
  font-size: 1.45rem;
  line-height: 1.3;
  font-weight: 700;
  color: var(--accent);
  margin: 2.5rem 0 0.75rem;
  padding-left: 12px;
  border-left: 4px solid var(--red);
}
.body h3 { font-size: 1.15rem; line-height: 1.35; font-weight: 600; margin: 1.75rem 0 0.5rem; }

.body p, .body ul, .body ol { margin: 0 0 1rem; }
.body li { margin: 0.35rem 0; }
.body ul { padding-left: 1.25rem; }
.body a { text-underline-offset: 2px; }

/* A wide spec table scrolls inside its own box; the page never does.
   The corpus's widest is seven columns (Máy cắt CNC kiểu giàn Magicut) and
   drags a 390px page 153px sideways without this, which costs every
   paragraph on the page its left margin. `display: block` is what makes
   overflow-x apply to a table at all; the rows still lay out in columns
   because thead and tbody keep their table display roles. */
.body table {
  display: block;
  overflow-x: auto;
  max-width: 100%;
  border-collapse: collapse;
  margin: 1.25rem 0;
  font-size: 0.9375rem;
}
/* Block, not table-caption: inside a display:block table a table-caption
   child gets its own anonymous table box and detaches from the rows. */
.body caption {
  display: block;
  text-align: left;
  font-weight: 600;
  margin-bottom: 0.5rem;
}
.body th, .body td {
  border: 1px solid var(--line);
  padding: 0.5rem 0.7rem;
  text-align: left;
  vertical-align: top;
}
.body thead th { background: var(--panel-2); white-space: nowrap; }
.body tbody th[colspan] { background: var(--panel-2); }

@media (max-width: 420px) {
  .body h2 { font-size: 1.25rem; }
}

/* -------------------------------------------------------- source line */

main.article .source {
  margin: var(--s-lg) 0 0;
  padding-top: var(--s-md);
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.875rem;
}

/* ------------------------------------------------------- contact card */

/* The mockup's supplier box: bordered card, heading over a hairline, a
   definition list of the facts the record actually carries. Every value is
   parsed from the record body — nothing here is invented. */
.contact {
  margin: var(--s-lg) 0 0;
  padding: var(--gutter);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}

.contact h2 {
  margin: 0 0 var(--s-md);
  padding: 0 0 var(--s-sm);
  border: 0;
  border-bottom: 1px solid var(--line);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ink);
}
.contact ul { list-style: none; margin: 0; padding: 0; font-size: 0.9375rem; color: var(--muted); }
.contact li { padding: 2px 0; }
.contact .org { margin: 0 0 var(--s-sm); font-weight: 700; color: var(--ink); font-size: 1rem; }

/* --------------------------------------------------- catalogue (stub) */

/* 2.045 of 2.176 pages. A stub is a catalogue entry, not a broken article:
   panel fill, hairline border, no warning colour, no alert icon, no reserved
   space where a body would have gone. */
.catalogue {
  margin: 0 0 var(--s-lg);
  padding: var(--gutter);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}
.catalogue h2 { margin: 0 0 var(--s-sm); border: 0; padding: 0; font-size: 1.15rem; color: var(--ink); }
.catalogue > p { margin: 0 0 var(--s-md); color: var(--muted); font-size: 0.9375rem; }

.catalogue .pairs { margin: 0 0 var(--s-md); }
.catalogue .pair {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--s-md);
  padding: var(--s-sm) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9375rem;
}
.catalogue .pairs > :last-child { border-bottom: 0; }
.catalogue dt { flex: 0 0 9rem; color: var(--muted); }
.catalogue dd { flex: 1 1 12rem; margin: 0; }

.catalogue .btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s-md);
  border: 1px solid var(--accent);
  border-radius: var(--r-lg);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 0.9375rem;
  text-decoration: none;
}
.catalogue .btn:hover { background: var(--accent-bright); border-color: var(--accent-bright); }

.onward {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--s-sm);
  font-size: 0.9375rem;
}
.onward .pipe { color: var(--outline); }

.notice {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-bright);
  border-radius: var(--r-lg);
  background: var(--panel);
  color: var(--muted);
}

/* =========================================================== CATEGORY */

.layout {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--s-md);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: var(--s-lg) 40px;
  align-items: start;
}

/* The global `main` rule is a reading column. A listing scans rather than
   reads, and it has a rail beside it, so this one page opts out. */
main.category { max-width: none; padding: 0; }

/* Chrome matches the widest content on the page. Where `:has()` is
   unsupported the chrome stays at its own width and the page is merely less
   aligned — nothing breaks. */


@media (max-width: 980px) {
  .layout { grid-template-columns: minmax(0, 1fr); gap: var(--s-lg); }
}

/* ----------------------------------------------------------- page head */

main.category .crumbs { margin: 0 0 var(--s-sm); }

/* The mockup sets the category heading in the blue. Sentence case, never
   uppercase — see the header note; this is a regression that has already
   been fixed once. */
main.category h1 {
  margin: 0 0 var(--s-sm);
  color: var(--accent-bright);
}

main.category .count {
  margin: 0 0 var(--s-lg);
  padding-bottom: var(--s-md);
  border-bottom: 2px solid var(--red);
  color: var(--muted);
  font-size: 0.9375rem;
}
main.category .count .split { color: var(--outline); }

/* ------------------------------------------------------ featured strip */

/* The mockup leads a category with one hero card and runs the rest as
   thumbnail rows. Ours draws only from records that carry an image — 131 of
   2.176 — so the hero is the first of those and never an empty box. The
   remainder of the listing has no thumbnail slot at all, because a slot
   blank on 94% of rows looks broken rather than sparse. */
.featured { margin: 0 0 var(--s-lg); }
.featured h2,
main.category h2.all {
  margin: 0 0 var(--s-md);
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--muted);
}
main.category h2.all { margin-top: var(--s-lg); }

.featured .cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
}

.featured .card {
  display: block;
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--s-md);
}
.featured .card:hover .t { color: var(--accent-bright); }
.featured .card:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 4px; }

/* The hero. First card spans the row, image runs 16:9 across the full
   width, headline steps up to article scale. This is the mockup's hero
   article, and it needs no extra markup — `_featured` already emits the
   image-bearing records in order. */
.featured .card:first-child {
  grid-column: 1 / -1;
  padding-bottom: var(--s-lg);
}
.featured .card:first-child img { aspect-ratio: 16 / 9; max-height: 380px; }
.featured .card:first-child .t {
  -webkit-line-clamp: 3;
  font-family: var(--serif);
  font-size: 1.45rem;
  line-height: 1.25;
  font-weight: 700;
  padding: 0;
  margin-top: var(--s-md);
}
.featured .card:first-child .sup { padding: 6px 0 0; font-size: 0.875rem; }

/* aspect-ratio holds the box before the image arrives, so the cards do not
   jump when a hotlinked file lands. The source is techport.vn's own 254x170
   thumbnail — see render._thumbnail. */
.featured img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 254 / 170;
  object-fit: cover;
  background: var(--panel-2);
  border-radius: var(--r-lg);
}

.featured .t {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  padding: 0;
  margin-top: 10px;
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.35;
  font-weight: 600;
}

.featured .sup {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  padding: 4px 0 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--muted);
}

@media (max-width: 760px) {
  .featured .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-md); }
  .featured .card:first-child .t { font-size: 1.25rem; }
}

/* --------------------------------------------------------- listing rows */

main.category .records { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
main.category .records li { padding: 0; border-bottom: 1px solid var(--line); }

/* Overrides the shared 44px chrome rule, which centres its anchor. A row
   whose title runs to two lines must keep the subtype label on the first,
   not floated to the middle of both. */
main.category .records a {
  display: flex;
  align-items: flex-start;
  gap: var(--s-md);
  width: 100%;
  padding: 14px var(--s-sm);
  text-decoration: none;
  color: var(--ink);
  transition: background-color 0.12s;
}
main.category .records a:hover { background: var(--panel); }
main.category .records a:hover .ttl { color: var(--accent-bright); }
main.category .records a:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: -2px; }

/* The one field every record has. Sentence case, never uppercase:
   Vietnamese capitals stack their tone marks into the cap line and
   `Ế Ộ Ữ` collide at this size. */
main.category .records .subtype {
  flex: 0 0 7rem;
  padding-top: 0.3em;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent-bright);
}

/* Serif titles are the listing's half of the redesign: the mockup runs its
   list items in the headline face, which is what makes a page of 100 rows
   read as an index of articles rather than as a database dump. */
main.category .records .ttl {
  flex: 1 1 auto;
  font-family: var(--serif);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
}

/* 240 of 2.176 records carry one, so it is a second line rather than a
   column that would be blank on 89% of rows. */
main.category .records .tags {
  display: block;
  margin-top: 3px;
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--muted);
}

/* Below this the 7rem label column would indent a long Vietnamese title into
   a narrow ribbon, so the label becomes a caption above it instead. */
@media (max-width: 700px) {
  main.category .records a { flex-direction: column; gap: 3px; padding: 14px 4px; }
  main.category .records .subtype { flex: none; padding-top: 0; }
}

/* ----------------------------------------------------------- empty state */

.empty { color: var(--muted); font-style: italic; }

/* Four of the 21 categories hold no records. This is a real page saying so,
   not a fallback string — it overrides the italic grey above. */
main.category .empty {
  font-style: normal;
  color: var(--ink);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-bright);
  border-radius: var(--r-lg);
  background: var(--panel);
  padding: 18px 20px;
}
main.category .empty p { margin: 0 0 12px; }
main.category .empty .next { font-weight: 700; margin-bottom: 4px; }

main.category .empty .alts { display: flex; flex-direction: column; margin: 0 0 14px; }
main.category .empty .alt {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}
main.category .empty .alts > :last-child { border-bottom: 0; }
main.category .empty .alt:hover { text-decoration: underline; }
main.category .empty .alt:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: -2px; }
main.category .empty .alt span {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

main.category .empty .more { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0; }
main.category .empty .more a { display: inline-flex; align-items: center; min-height: 44px; font-size: 0.92rem; }

/* ------------------------------------------------------------ pagination */

.pagination { display: flex; flex-direction: column; margin: var(--s-lg) 0 0; }

.pagination .of {
  margin: 0 0 var(--s-sm);
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.pagination .ctl { display: flex; flex-wrap: wrap; gap: 6px; }

/* The mockup's "XEM THÊM" control is a blue-outlined pill that fills on
   hover. This site paginates rather than appends — there is no script to
   append with — so every number wears that treatment instead. */
.pagination a {
  min-width: 44px;
  justify-content: center;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  color: var(--accent);
  font-weight: 600;
  font-size: 0.9375rem;
  text-decoration: none;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}
.pagination a:hover { border-color: var(--accent-bright); background: var(--accent-bright); color: #fff; }
.pagination a[aria-current="page"] {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}
.pagination a[aria-current="page"]:hover { background: var(--accent); border-color: var(--accent); }
.pagination a.step { padding: 0 var(--s-md); }
.pagination .gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  color: var(--muted);
}

/* ---------------------------------------------------------- category rail */

/* Every category and its count, from every category page. This is the site's
   only lateral navigation, and it is what keeps a two-record page and an
   empty one from reading as failures. */
.rail {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: var(--s-md) var(--gutter);
}
.rail h2 {
  margin: 0 0 var(--s-sm);
  padding-bottom: var(--s-sm);
  border-bottom: 1px solid var(--line);
  font-size: 1rem;
  font-weight: 700;
}
.rail .items { display: flex; flex-direction: column; }

.rail .row,
.rail .cur {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 2px 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  font-size: 0.875rem;
  line-height: 1.35;
}
.rail .items > :last-child { border-bottom: 0; }
.rail .row:hover { text-decoration: underline; }
.rail .row:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: -2px; }
.rail .cur { color: var(--ink); font-weight: 700; }

.rail b {
  flex: 0 0 auto;
  color: var(--muted);
  font-weight: 400;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

/* The four categories holding zero records stay listed. The taxonomy
   mirrors techport.vn's own, and dropping four entries would report it
   wrongly. */
.rail .zero { color: var(--muted); }
.rail .zero b { opacity: 0.55; }

/* ================================================ home, search, errors */

.home .lead { color: var(--muted); font-size: 1.0625rem; }

.categories { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.categories li { padding: 0; border-bottom: 1px solid var(--line); }
.categories a {
  width: 100%;
  padding: 6px 0;
  justify-content: space-between;
  text-decoration: none;
  font-family: var(--serif);
  font-size: 1.0625rem;
  font-weight: 600;
}
.categories a:hover { color: var(--accent-bright); }
.categories .n {
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.search-results h3 { font-size: 1rem; margin: var(--s-lg) 0 4px; }
.not-found .notice { margin-bottom: var(--s-md); }

.records { list-style: none; margin: 0; padding: 0; }
.records li { padding: 10px 0; border-bottom: 1px solid var(--line); }
.records a { text-decoration: none; }
.records a:hover { text-decoration: underline; }

@media (max-width: 420px) {
  body { font-size: 16px; }
  header.site { padding: var(--s-sm) 12px; }
  main.article { padding: 0 12px var(--s-lg); }
  .layout { padding: 0 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .featured .card,
  main.category .records a,
  .pagination a { transition: none; }
}
