/*
  r8-restyle.css: blog.noethera.com r8 system port (t_0cf35cf0, web-dev, 2026-11-10).
  Loads AFTER theme.css (wins on source order, no !important needed). Matches landing
  branch web-dev/t_1d8d4e23-homepage-r8-combined @ 75a0b2a2f13671734d4f8155acf4b40ba95dedd7.
  Design source of truth: Hermes/workspaces/design/noethera/2026-11-10-blog-concept/
  (CONCEPT.md r6/r7b, shared/blog.css, shared/tokens.css) + the landing's own
  app/theme-tokens.css, app/home-r8.css, app/globals.css (nav/footer/heading chrome).
*/

/* ===================================================================
   0. Kill the old cream-theme atmosphere + the dark/light toggle.
   Cal.com / the landing have a plain white canvas, no grain/dot/glow
   overlay and no theme switch -- token-neutralising theme.css's own
   --glow/--dot/--grain-opacity/--lamp-* already de-fangs 90% of this;
   these two rules remove the remaining markup-level artifacts.
   =================================================================== */
.fx.glow, .fx.grain { display: none; }
.mobile-menu-theme { display: none !important; } /* t_7eeffec7: .theme-toggle re-enabled
   below (DESIGN-SPEC.md §1) -- only this dead/unused class (no markup references it any
   more) stays force-hidden. theme.css's own base .theme-toggle{display:inline-flex}
   (line 386) plus the existing @media(max-width:700px){.nav-r>.theme-toggle{display:none}}
   compact-nav rule (theme.css line ~292) already produce exactly the "visible desktop /
   hidden+moves-to-drawer mobile" behaviour the spec asks for -- no new selector needed. */

/* t_ff23c70c (web-dev, 2026-11-11), OVERRIDE 1 (Wira via Noe, 2026-11-11, on top of the
   draft DESIGN-SPEC): re-enable `.fx.dots`, but with noethera.com main's OWN literal
   colour/opacity values, NOT the blog's own --dot token (rgba(25,24,27,.12), too faint,
   Wira rejected the concept's version on sight). Values pulled from main's live computed
   style (confirmed 2026-11-11, both breakpoints, noethera-landing/app/globals.css
   `.fx.dots` ~L167-172 + its `@media(max-width:680px)` override ~L5950-5955): same 18px
   grid / 1px dot / mask-image fade geometry main already uses, just main's own hex+alpha
   hardcoded here instead of routed through --dot (keeps the blog's own --dot token
   unchanged for any other future use, only this one rule diverges). */
.fx.dots {
  display: block;
  background-image: radial-gradient(rgba(201, 154, 102, 0.14) 1px, transparent 1px);
  background-size: 18px 18px;
  mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 0%, transparent 75%);
}
@media (max-width: 680px) {
  .fx.dots {
    background-image: radial-gradient(rgba(139, 105, 87, 0.13) 1px, transparent 1px);
    mask-image: radial-gradient(ellipse 100% 60% at 50% 20%, #000 0%, transparent 70%);
    -webkit-mask-image: radial-gradient(ellipse 100% 60% at 50% 20%, #000 0%, transparent 70%);
  }
}

/* ===================================================================
   1. Radius system: --r (16px, soft card/panel/image radius, theme.css)
   stays as-is; everything that is a PILL in the real cal.com/r8 system
   (buttons, tags, chips, pager numbers, lang switch, mega-menu rows,
   share/social icon circles) gets the new --r-pill (999px) instead.
   =================================================================== */
.btn,
.tag,
.lang-switch,
.blog-lang-switch,
[data-nav] .nav-links > a,
.nav-services .nav-trigger-label,
.nav-trigger-chev-btn,
.nav-services-menu .mega-item,
.nav-services-menu .mega-more a,
.topic-row a,
.category-pill,
.insight-article-back,
.insight-tag,
.insight-share-inline a,
.insight-share-inline button,
.share a,
.share button,
.pager a.page-numbers,
.pager span.page-numbers,
.site-footer .foot-soc a,
.site-footer .foot-soc-disabled,
.blog-topic,
.blog-pager a,
.blog-pager span {
  border-radius: var(--r-pill);
}
/* Cards/panels get the slightly larger --r-card (24px) instead of the generic 16px. */
.insight-featured-card,
.blog-cta-inner,
.insight-rel-card-cover,
.blog-card,
.blog-featured,
.blog-empty {
  border-radius: var(--r-card);
}
.nav-services-menu { border-radius: 20px; }
.mobile-menu-panel { border-radius: 0; }

/* ===================================================================
   2. Heading accent + V3 style (landing app/globals.css ~4226, home-r8.css
   ~255; pages-r8.css ~201 for the V3 weight/letter-spacing/line-height).
   Brown is ONLY ever this <em>/<i> inside an h1/h2 -- never a link, a
   button, an active state, or body copy.
   =================================================================== */
h1 em, h1 i, h2 em, h2 i {
  color: var(--accent-italic);
  font-family: var(--font-italic);
  font-style: italic;
  font-synthesis: none;
  font-weight: 700;
}
/* V3 (t_03ab676d, Wira approved 2026-11-10 ~17:15, "v3"): calmer weight (800->700),
   tighter letter-spacing, denser line-height, text-wrap:balance -- same values the
   landing's inner-page .pg-hero-h1 uses, applied here to the blog's own hero/page h1s. */
.nh-h1,
.nh-title,
.insight-article-header h1 {
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.06;
  text-wrap: balance;
}
.nh-h1 i, .nh-title i { font-weight: 700; }

/* ===================================================================
   3. Nav: identical shape/behavior to the landing's own top bar (t_7fbd83d4,
   Wira 2026-11-11 verbatim: "Chip floating top barnya gak sama dengan yg di
   main web ya"). Source of truth: noethera-landing main@44cd54f
   app/styles/chrome-r8.css "Nav retheme" block + hooks/useNavScrolled.ts
   (same IntersectionObserver-at-32px-scroll idea, here theme.js's own
   `scrollY>32` toggle of [data-nav].scrolled -- no JS change needed).
   Flat, fully transparent bar at top on every viewport (no box at all,
   same as the landing); only [data-nav].scrolled gets the inset floating
   glass pill (desktop ~820px centred, mobile 12px-inset near-full-width).
   The old `[data-nav]::before`-painted bar (always a rounded, inset box,
   just invisible at top) is retired: `.nav-in` itself now carries the
   fill/shape directly, exactly like the landing's own `.nav-in`. Values
   are a 1:1 port (same --bg/--txt/--line-2/--r-pill tokens, already
   matched to the landing by the t_0cf35cf0 port). */
[data-nav] {
  background: transparent;
  border-bottom: 0;
  padding: 22px 0 0;
  transition: padding-top .35s ease;
}
[data-nav]::before { content: none; }
.nav-in {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0;
  box-shadow: none;
  gap: 18px;
  min-height: 56px;
  padding: 0 10px 0 22px;
  transition:
    background .35s ease, border-color .35s ease, border-radius .35s ease,
    box-shadow .35s ease, padding .35s ease, width .12s ease-out;
}
[data-nav].scrolled { padding-top: 11px; }
[data-nav].scrolled .nav-in {
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  border-color: rgba(255, 255, 255, 0.6);
  border-radius: var(--r-pill);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 8px 24px rgba(0, 0, 0, 0.08);
  padding: 6px 10px 6px 22px;
  width: min(calc(100% - 24px), 820px);
}
/* Hotfix 1.6.7 (cream-bar bug, Noe live report 03:07, t_0eea30df): the rule above never had a
   dark pairing, so in dark mode the scrolled pill rendered as this same near-opaque WHITE fill
   -- the white logo/toggle icon/Start pill all went invisible on it. Values ported 1:1, not by
   eye, from noethera-landing main@ab6d849 app/styles/dark-mode.css lines 274-279
   ([data-theme="dark"] .home-r8-scope nav.nav-scrolled .nav-in): dark translucent glass,
   hairline border, inset top highlight only -- no external drop shadow (per that file's own
   "shadow/highlight stay none in dark" note). Only background/border-color/box-shadow are
   overridden; blur/radius/padding/width above are untouched (not theme-dependent). Higher
   specificity (html + 4 classes/attrs vs. 3) wins over both this desktop rule and the
   @media(max-width:700px) mobile rule below with no @media needed on this override, same
   pattern as the landing's own unconditional dark-mode.css rule. Light scrolled stays exactly
   as above -- untouched. */
html[data-theme="dark"] [data-nav].scrolled .nav-in {
  background: rgba(10, 10, 10, 0.92);
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
@media (prefers-reduced-motion: reduce) {
  .nav-in { transition: none; }
}

/* Grouped hairline track, centred (the landing's own `.nav-links`): replaces
   the old full-bleed uppercase/mono underline links with a sentence-case,
   sans-font pill track. `.nav-services` must give up its own containing
   block (position:static) so the absolutely-centred track doesn't anchor
   the services dropdown to the trigger instead of the nav bar -- same fix
   as the landing's own chrome-r8.css (Round-17).
   Scoped under `[data-nav]` (qa-dev finding on t_ea857025/5321bab): WP's own
   get_the_posts_pagination() emits an unrelated `<div class="nav-links">` with
   bare `<a class="page-numbers">` children inside noethera_render_pager()
   (home.php/category.php/search.php). A bare `.nav-links`/`.nav-links > a`
   selector here would also match that pager markup (position:absolute +
   the pill sizing would rip Prev/1/2/Next out of flow into a floating
   fragment) -- `[data-nav] .nav-links` only ever matches the header nav's
   own track. */
[data-nav] .nav-services { position: static; }
[data-nav] .nav-links {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  box-sizing: border-box;
  gap: 2px;
  height: 44px;
  left: 50%;
  margin-left: 0;
  padding: 0 5px;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
}
[data-nav] .nav-links > a,
.nav-services .nav-trigger-label {
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  color: var(--txt);
  font-family: var(--f);
  font-size: 14px;
  font-weight: 600;
  height: 36px;
  min-height: 36px;
  letter-spacing: normal;
  padding: 0 16px;
  text-transform: none;
  transition: background-color .15s ease-out, border-color .15s ease-out, color .15s ease-out;
}
[data-nav] .nav-links > a::after, .nav-services .nav-trigger-label::after,
[data-nav] .nav-links > a::before, .nav-services .nav-trigger-label::before { content: none; }
.nav-services .nav-trigger-label { padding-right: 4px; }
[data-nav] .nav-links > a:hover, .nav-services .nav-trigger-label:hover {
  background: var(--panel);
  color: var(--txt);
}
[data-nav] .nav-links > a.on, [data-nav] .nav-links > a[aria-current="page"],
.nav-services .nav-trigger-label.on {
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line-2);
  color: var(--txt);
  font-weight: 700;
}
[data-nav].scrolled .nav-links > a,
[data-nav].scrolled .nav-services .nav-trigger-label,
[data-nav].scrolled .lang-switch { color: var(--txt); }
/* Services dropdown re-centred under the nav bar (not the trigger), same
   landing fix (Round-17, t_c61a5e7a): nav-services-menu was left:8px against
   a now position:static ancestor, so it would otherwise hug the viewport's
   left edge instead of centring under the pill. */
.nav-services-menu {
  left: 50%;
  padding: 26px;
  top: calc(100% + 14px);
  transform: translateX(-50%);
  width: min(var(--mega-w), calc(100vw - 48px));
}

/* Logo: 52px top, 34px once scrolled (the landing's own .nw sizing). */
.nav-in .brand-wordmark { height: 52px; width: 52px; }
[data-nav].scrolled .nav-in .brand-wordmark { height: 34px; width: 34px; }

/* nav-r right-alignment (BLOCKER fix, Noe round-1 eye-check on 5321bab): since
   .nav-links now sits position:absolute (taken out of the flex flow above),
   .nav-in's only remaining flex children are .brand and .nav-r/.hamburger --
   margin-left:auto (theme.css's own original mechanism, restored here, NOT
   margin-left:0) is what pushes nav-r (and the mobile hamburger) to the far
   right edge of the bar/pill, same as the landing. */
.nav-r { gap: 10px; margin-left: auto; }

/* EN/ID switch: small neutral hairline pill, the landing's own (currently
   unrendered there -- the landing is EN-only in its own nav) .nav-r
   .lang-switch token values, so the blog's switch reads like a native part
   of this nav system instead of a leftover "ID circle" chip. Evidence:
   .evidence/t_7fbd83d4/ -- proposed placement is nav-r, left of the CTA,
   same spot the switch already held before this change. */
.nav-r > .lang-switch {
  background: var(--bg);
  border: 0;
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 1px var(--line-2);
  color: var(--muted);
  font-family: var(--m);
  font-size: 12px;
  font-weight: 600;
  justify-content: center;
  letter-spacing: .06em;
  min-height: 36px;
  min-width: 0;
  padding: 0 12px;
  text-transform: uppercase;
}
.nav-r > .lang-switch:hover { color: var(--txt); }

/* CTA: small black pill, "Start"/"Mulai" only (header.php copy change), no arrow. */
.nav-r .btn-acc {
  font-size: 14px;
  min-height: 36px;
  padding: 0 18px;
}
.nav-r .btn-acc span { display: none; }

/* Services chevron gap (Noe round-2 eye-check, finding #2): 1:1 port of the landing's own
   Round-15/18/19/21b fixes (chrome-r8.css ~424-458) -- theme.css's shared, non-r8
   `.nav-trigger-row` (gap:8px) + `.nav-trigger-chev-btn` (centred icon in a 44px WCAG box)
   stacks into ~29px of visual space; the landing scopes a tighter desktop-only variant under
   its own `.home-r8-scope`. Equivalent scope here is `[data-nav] .nav-trigger-row` -- the
   mobile drawer uses its own separate `.mobile-services-trigger` markup, never this selector,
   so dropping the gap/centring here can't touch the 44px mobile tap target. */
[data-nav] .nav-trigger-row { gap: 0; }
[data-nav] .nav-trigger-chev-btn {
  justify-content: flex-start;
  min-height: 36px;
  min-width: 22px;
  padding-left: 0;
  width: 22px;
}

@media (max-width: 700px) {
  [data-nav] {
    background: transparent;
    border-bottom: 0;
    padding-top: calc(14px + env(safe-area-inset-top, 0px));
    /* t_43cac283 item 8 (Noe "~"): `.nav-in` right below is `position:fixed`
       here, so it contributes NOTHING to `[data-nav]`'s own flow height --
       measured live, that leaves `[data-nav]` only 14px tall (padding-top
       alone), which is also all `.nh-head`'s own margin-top:-navH/padding-
       top:navH+var(--sec-y) trick (11c below) has to push the hero content
       down from, landing the eyebrow 6px BEHIND the (unrelated, still
       fixed-position) nav logo at 390/375 instead of clear of it. One
       declaration restores a sane flow height without moving the fixed bar
       itself (its own top/height are hardcoded, untouched by this). */
    padding-bottom: 36px;
    /* Round-2 fix (gap fix follow-on): raise the WHOLE <nav> (not just its
       .nav-in child) above `.mobile-menu` (z-index:150) now that the sheet's
       wrapper spans the full viewport and visually overlaps the bar's own
       0-66px band. <nav> itself was still the theme's base z-index:40
       (position:sticky) -- a lower sibling stacking context always loses to
       a higher one regardless of what z-index its own descendants (.nav-in
       at 200, .hamburger at 301) claim internally, so without this the
       opaque sheet painted over the logo/close icon instead of under them. */
    z-index: 200;
  }
  [data-nav]::before { content: none; }
  .nav-in {
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    box-shadow: none;
    height: 52px;
    left: 0;
    padding: 0 16px;
    position: fixed;
    right: 0;
    top: calc(14px + env(safe-area-inset-top, 0px));
    transition:
      background .35s ease, border-color .35s ease, box-shadow .35s ease,
      left .35s ease, right .35s ease, top .35s ease, height .35s ease, padding .35s ease;
    width: auto;
    z-index: 200;
  }
  [data-nav].scrolled .nav-in {
    background: rgba(255, 255, 255, 0.94);
    -webkit-backdrop-filter: blur(36px) saturate(1.8);
    backdrop-filter: blur(36px) saturate(1.8);
    border-color: rgba(25, 24, 27, 0.08);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.6),
      0 1px 2px rgba(0, 0, 0, 0.04),
      0 8px 24px rgba(0, 0, 0, 0.08);
    height: 56px;
    left: 12px;
    padding: 0 8px 0 16px;
    right: 12px;
    top: calc(10px + env(safe-area-inset-top, 0px));
  }
  .nav-in .brand-wordmark { height: 52px; width: 52px; }
  [data-nav].scrolled .nav-in .brand-wordmark { height: 34px; width: 34px; }
  /* Sheet-open nav reset (Noe round-3 eye-check finding #1): the landing's own open sheet
     (`.r13mm-sheet`, z-index 310, ABOVE its nav's 200) paints a solid `.r13mm-sheet-head` bare
     logo + close row with no pill/shadow and the top (unscrolled) logo size, whatever the
     page's scroll position was when the sheet opened. This theme keeps the fixed nav bar
     itself on top of the sheet instead (z-index 200 vs `.mobile-menu`'s 150, so the hamburger/
     close icon stays clickable) -- so when `.scrolled` is also true, the bar's floating glass
     pill (fill + shadow + the smaller 34px logo) stayed visible behind the open sheet instead
     of resetting to a bare row. Fixed CSS-only via `:has()` (no theme.js change: the sheet
     toggle already just adds/removes `.open` on `.mobile-menu`, the DOM sibling right after
     `<nav data-nav>`) -- same specificity as the `.scrolled` rules above (attr+pseudo-class+
     class), wins on source order since it's declared after them. */
  [data-nav]:has(+ .mobile-menu.open) .nav-in {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    height: 52px;
    left: 0;
    padding: 0 16px;
    right: 0;
    top: calc(14px + env(safe-area-inset-top, 0px));
    /* `.scrolled .nav-in`'s own `width: min(calc(100% - 24px), 820px)` is declared OUTSIDE
       this media query (desktop pill cap) and this block never re-declares `width`, so without
       this it leaks through uncontested (same specificity, but no competing `width` decl here
       to win against) -- the bar rendered at the scrolled pill's 366px inset width instead of
       the bare row's full viewport width. */
    width: auto;
  }
  [data-nav]:has(+ .mobile-menu.open) .nav-in .brand-wordmark { height: 52px; width: 52px; }
  /* Drawer top offset: both nav states pin the same bottom edge (66px --
     14+52 unscrolled == 10+56 scrolled), so one fixed value tracks either
     state, replacing the old flush-top (0+56) measurement.
     Round-2 fix (Noe eye-check on 5321bab, finding #3): the wrapper itself
     now spans the FULL viewport (top:0, like the landing's own inset:0
     sheet) instead of starting at 66px -- `.mobile-menu-panel`'s own
     top padding is pushed down by the same 66px instead, so the panel's
     solid background paints all the way up behind the fixed nav icons.
     Before, the 0-66px band had no element painting it (the nav bar is
     intentionally transparent at page top, D1 above), so the page's own
     content showed through in that band while the sheet was open. Visual
     position of the first menu row is unchanged (66+16 before == 66+16 now,
     just contributed by padding instead of the wrapper's offset); icons
     stay on top via the existing z-index gap (nav 200 > .mobile-menu 150). */
  .mobile-menu { top: 0; }
  .mobile-menu-panel { padding-top: calc(66px + 16px + env(safe-area-inset-top, 0px)); }
}

/* ===================================================================
   4. Footer: dark r8-footer (rounded top, inverted tokens) + the r7b
   sheet construction (`.r8-sheet-last` / `.r8-footer-flat`) FOLDED INTO
   SHARED CSS so index/category/search/404 all match, not just the post
   template (the brief's named KNOWN GAP). The section immediately
   before </main> (home/category/single/404/search's final CTA section)
   gets `.r8-sheet-last` in the PHP; the footer itself always carries
   `.r8-footer-flat` (both added here, applies everywhere <footer> renders).
   =================================================================== */
:root { --r8-radius-sheet: 28px; }
@media (max-width: 680px) { :root { --r8-radius-sheet: 20px; } }
.r8-sheet-last {
  position: relative; z-index: 2; background: var(--bg);
  border-radius: 0 0 var(--r8-radius-sheet) var(--r8-radius-sheet);
  overflow: hidden;
}
/* t_48032d1a (cmo go-live visual check): main's own sheet->footer seam
   (`section.r8-sec.r8-alt`, measured live) is a FULL-BLEED white section with
   its 28px bottom corners sitting at the viewport edges -- no dark footer
   shows beside it. Here `.r8-sheet-last` was a plain block child of
   `.nh-shell` (max-width:1400, margin:0 auto), so at >1400px its rounded
   bottom sat at the 1400px shell edge with the dark footer visible on both
   sides. Same full-bleed breakout `.nh-head-dots` already uses (11b below):
   width:100vw + left:50% + margin-left:-50vw cancels out to the viewport
   edges at any width because `.nh-shell` is itself centred (margin:0 auto) --
   at <=1400px .nh-shell already spans the full viewport so the maths reduces
   to a 0 shift there. Its own content (`template-parts/cta.php`'s `.w`,
   max-width:1280) is untouched, still centred inside this now full-width
   section. */
.r8-sheet-last { width: 100vw; left: 50%; margin-left: -50vw; }
.site-footer {
  /* t_7eeffec7 (web-dev, 2026-11-13), DESIGN-SPEC.md §3: was `background: var(--txt);`,
     a backdoor "always-dark" hack that only worked while --txt stayed dark in light
     mode -- real dark mode flips --txt to near-white for body text and the footer
     inherited that flip, rendering as a light bar. Pinned to the literal value
     instead (same hex --txt already held in light), so the footer stays the one
     constant-dark surface in both themes, same as main. */
  background: #19181b;
  border-top: 0;
  border-radius: 0;
  margin-top: calc(-1 * var(--r8-radius-sheet));
  padding-top: calc(var(--s9) + var(--r8-radius-sheet));
  position: relative; z-index: 1;
}
.site-footer::before, .site-footer::after { display: none; } /* kill the old warm glow */
.site-footer .foot-wordmark { color: #fff; }
.site-footer .foot-brand > p,
.site-footer .foot-col a,
.site-footer .foot-addr,
.site-footer .foot-legal p,
.site-footer .foot-bot { color: rgba(255, 255, 255, 0.66); }
.site-footer .foot-col h5, .site-footer .foot-legal h5 { color: rgba(255, 255, 255, 0.46); }
.site-footer .foot-col a:hover { color: #fff; }
.site-footer .foot-legal, .site-footer .foot-bot { border-top-color: rgba(255, 255, 255, 0.14); }
.site-footer .foot-soc a, .site-footer .foot-soc-disabled { border-color: rgba(255, 255, 255, 0.22); color: rgba(255, 255, 255, 0.66); }
.site-footer .foot-soc a:hover { border-color: #fff; color: #fff; }
.site-footer .foot-langs a { color: rgba(255, 255, 255, 0.5); }
.site-footer .foot-langs a.on { color: #fff; }

/* t_43cac283 (web-dev, 2026-11-11): footer parity vs main (noethera.com), all
   numbers measured live via getBoundingClientRect()/getComputedStyle() at
   1440 and 390 -- never guessed. Scoped to .site-footer only (never the
   shared .w/.foot-* classes used elsewhere on the page). */

/* 1) Horizontal rhythm: theme.css's shared `.w` (max-width:1280, inset 48px,
   same value at every width) is wrong for this footer -- main's OWN footer
   wrapper is not that shared box at all. Measured live on main at
   320-2560px: padding-left/right is a flat 28px under <=680px and a flat
   88px from 681px up, capped once the content hits 1264px wide (checked at
   700/768/900/1024/1200/1440/1920/2200/2560 -- all exact). 1440 is exactly
   the point the cap engages, which is why "88px at 1440" and "1264px cap"
   are the same fact. */
.site-footer .w { max-width: 1264px; width: min(100% - 56px, 1264px); }
@media (min-width: 681px) {
  .site-footer .w { width: min(100% - 176px, 1264px); }
}

/* 2) Column x positions (>=681, incl. 1440): main's own grid-template-columns
   is "356.359px 254.547px 254.547px 254.547px" with gap:48px at 1440 --
   exactly 1.4fr 1fr 1fr 1fr of the (now-fixed) 1264px content box. Replaces
   the old 1.5fr .85fr .85fr max-content guess. Gap (48px) already matched. */
.site-footer .foot-top { grid-template-columns: 1.4fr 1fr 1fr 1fr; }

/* 3) Link rhythm (>=681): main's own footer links are inline, not a 44px
   block tap-target -- measured li height 24px + ul gap 10px = 34px
   link-to-link, no padding-block at all. Below 681px we deliberately KEEP
   the >=44px tap target (padding-block:10px + line-height:24px = 44px):
   that is an intentional accessibility deviation from main for this one
   breakpoint (Noe/t_43cac283 acceptance item 2), not a parity miss. */
.site-footer .foot-col a { padding-block: 10px; line-height: 24px; }
.site-footer .foot-addr { line-height: 24px; }
@media (min-width: 681px) {
  .site-footer .foot-col { gap: 10px; }
  .site-footer .foot-col a { display: inline-block; padding-block: 0; }
  /* theme.css's own .foot-langs a carries a min-height:44px tap-target bump
     (same shape as the link-rhythm bug above) that inflates the bottom bar
     taller than main's own plain-text language links -- measured live,
     main's own .foot-bot is 42.25px tall at 1440 vs this file's 69px before
     this override. Keep the 44px tap target on mobile (<=680, untouched,
     same accessibility reasoning as item 3 above); drop it at desktop
     widths to match main's own footer-height. */
  .site-footer .foot-langs a { min-height: 0; }
}

/* 4) Mobile (<=680) stacking order: main's own mobile grid is NOT a plain
   single-column stack -- measured live at 390: brand spans the full row,
   then "What we build" + "Studio" sit side by side in a 2-col row, then
   "Reach us" spans the full row again (rowGap/columnGap both 24px). DOM
   order here is already brand -> build -> studio -> reach, so a 2-col
   track with brand/reach forced to span both columns reproduces the same
   auto-placement main gets. Overrides theme.css's own <=700px single-column
   rule (this file loads after theme.css, same specificity, so it wins). */
@media (max-width: 680px) {
  .site-footer .foot-top { grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .site-footer .foot-brand, .site-footer .foot-col--reach { grid-column: 1 / -1; }
}

/* 5) Legal-entity block: main renders this as one plain-text block, same
   small grey text throughout (color:rgba(255,255,255,.45), 12px, weight
   400) -- no label+rule treatment, no bold/white first line, no border-top
   divider, measured live. Blog's markup keeps its own <h5>+<p> structure
   (footer.php unchanged, out of scope here) but is restyled to look the
   same: the label falls back to plain running text, and the border/margin
   that used to separate it from the paragraph above become the same 24px
   gap main has between its description paragraph and the legal block. */
.site-footer .foot-legal { border-top: 0; margin-top: 24px; padding-top: 0; }
.site-footer .foot-legal h5 {
  font-family: var(--f);
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  margin: 0 0 2px;
}
/* theme.css ships its own `.site-footer .foot-legal p:first-of-type{color:var(--txt);
   font-weight:600}` (pre-existing, higher specificity than a plain `.foot-legal p`) --
   must be restated here with the same pseudo-class or it silently wins back the bold/
   dark first line regardless of load order. Both rules now carry the same 0.45/400
   main uses throughout the block. */
.site-footer .foot-legal p { color: rgba(255, 255, 255, 0.45); font-weight: 400; }
.site-footer .foot-legal p:first-of-type { color: rgba(255, 255, 255, 0.45); font-weight: 400; margin-bottom: 2px; }

/* 6) Footer-height remainder (>=681): after items 1-5 the grid/padding-left
   match main exactly (measured: grid 372px tall both sides), but the bottom
   bar (copyright + language switch) still didn't -- three small theme.css
   defaults (`.foot-bot` margin-top:60px, `.site-footer` padding-bottom:34px,
   plus the now-shorter `.foot-bot` row height after item 3's min-height
   fix) left the whole footer 22px short of main's 651.25px at 1440. Bumped
   to main's own measured values (margin-top:64px, padding-bottom:48px,
   ~3px back on the row itself) -- closes the footer-height gap to 0 at
   1440 without reintroducing the oversized tap-target bug item 3 fixed. */
@media (min-width: 681px) {
  .site-footer { padding-bottom: 48px; }
  .site-footer .foot-bot { margin-top: 64px; padding-bottom: 3px; }
}

/* ===================================================================
   5. Cards: cal.com image-top card (r6 lock) -- grey cover mat, white
   text area, ONE continuous hairline border around cover+text together
   (--line-3, strong enough to read on both white and the grid-texture
   section tone). Used by template-parts/item.php (grid), card-featured.php
   (featured), related-card.php (related) -- one shared component.
   =================================================================== */
.blog-card {
  display: flex; flex-direction: column; gap: 12px; position: relative;
  background: var(--bg); overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-3);
  padding: 0 20px 20px;
  text-decoration: none; color: inherit;
}
.blog-card .cover {
  margin: 0 -20px; width: calc(100% + 40px);
  border-radius: 0; overflow: hidden; aspect-ratio: 16/10;
  background: var(--panel); padding: 10px; box-sizing: border-box;
}
.blog-card .cover img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 9px; transition: transform .4s var(--ease-out); }
.blog-card:hover .cover img { transform: scale(1.03); }
.blog-card .cover-ph { width: 100%; height: 100%; display: grid; place-items: center; color: var(--faint); font-family: var(--m); font-size: 11px; border-radius: 9px; }
.blog-card .meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 4px; font-family: var(--m); font-size: 11px; color: var(--faint); letter-spacing: .02em; text-transform: uppercase; margin-top: 4px; }
.blog-card .meta > * { white-space: nowrap; }
.blog-card .meta .cat { color: var(--txt); font-weight: 700; }
.blog-card .meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); flex: none; }
.blog-card h2, .blog-card h3 { margin: 0; font-size: 17px; line-height: 1.35; font-weight: 700; letter-spacing: -.005em; }
.blog-card h2 a, .blog-card h3 a { text-decoration: none; color: inherit; }
.blog-card h2 a::after, .blog-card h3 a::after { content: ''; position: absolute; inset: 0; }
.blog-card .excerpt { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.blog-card .foot { font-size: 11.5px; color: var(--faint); font-family: var(--m); margin-top: 2px; }
@media (max-width: 480px) {
  .blog-card { padding: 0 16px 16px; border-radius: 20px; }
  .blog-card .cover { margin: 0 -16px; width: calc(100% + 32px); }
}

.blog-featured {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--s7); align-items: center;
  box-shadow: inset 0 0 0 1px var(--line-3); padding: var(--s6);
  text-decoration: none; color: inherit;
}
/* Noe 2026-11-10 18:20 (t_63afc0eb): featured cover had a grey mat nested inside the
   card's own outer hairline border ("2 boxes") -- landing/r8 grammar has no nested
   containers. Image now goes straight in the card, flush, with only the outer
   .blog-featured hairline (box-shadow inset above) as the single border. The cover-ph
   TEXT PLACEHOLDER (no-featured-image fallback, not a real photo) keeps its own
   background since there is no photo there to go flush with. */
.blog-featured .cover { border-radius: 20px; overflow: hidden; aspect-ratio: 16/11; order: 2; }
.blog-featured .cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blog-featured .cover-ph { width: 100%; height: 100%; display: grid; place-items: center; color: var(--faint); font-family: var(--m); background: var(--panel); }
.blog-featured .body { display: flex; flex-direction: column; gap: 10px; order: 1; }
.blog-featured .meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; row-gap: 4px; font-family: var(--m); font-size: 11.5px; color: var(--faint); letter-spacing: .03em; text-transform: uppercase; }
.blog-featured .meta > * { white-space: nowrap; }
.blog-featured .meta .cat { color: var(--txt); font-weight: 700; }
.blog-featured .meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); flex: none; }
.blog-featured h2 { margin: 2px 0 0; font-size: clamp(22px, 2.3vw, 30px); line-height: 1.2; font-weight: 800; letter-spacing: -.01em; }
.blog-featured h2 a { text-decoration: none; color: inherit; }
.blog-featured h2 a::after { content: ''; position: absolute; inset: 0; }
.blog-featured p.excerpt { margin: 0; font-size: 15px; line-height: 1.6; color: var(--muted); max-width: 52ch; }
.blog-featured .cta { margin-top: 6px; font-size: 13.5px; font-weight: 700; display: inline-flex; gap: 6px; align-items: center; }
@media (max-width: 860px) {
  .blog-featured { grid-template-columns: 1fr; padding: var(--s5); gap: var(--s5); }
  .blog-featured .cover { order: 1; } .blog-featured .body { order: 2; }
}

/* ===================================================================
   6. Search + empty state (new search.php, category zero-posts, 404):
   one shared .blog-empty component, cal.com-flavoured icon tile.
   =================================================================== */
.blog-empty { text-align: center; padding: var(--s9) var(--s5); max-width: 480px; margin: var(--s7) auto 0; }
.blog-empty .mark { width: 64px; height: 64px; border-radius: 20px; background: var(--bg-2); margin: 0 auto var(--s5); display: flex; align-items: center; justify-content: center; color: var(--faint); }
.blog-empty .mark svg { width: 28px; height: 28px; }
.blog-empty h1, .blog-empty h2 { margin: 0 0 8px; font-size: 22px; font-weight: 800; }
.blog-empty p { margin: 0 0 var(--s6); font-size: 14.5px; color: var(--muted); line-height: 1.6; }
.blog-empty .sugg { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: var(--s5); }
.blog-search { position: relative; max-width: 420px; width: 100%; margin: var(--s5) auto 0; }
.blog-search input {
  width: 100%; box-sizing: border-box; font-family: var(--f); font-size: 14px; color: var(--txt);
  background: var(--bg); border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 12px 16px 12px 42px; min-height: 44px;
}
.blog-search input::placeholder { color: var(--faint); }
.blog-search input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.blog-search svg { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--faint); pointer-events: none; }
.blog-search-results-head { font-family: var(--m); font-size: 12px; color: var(--faint); margin-top: var(--s5); text-align: center; }

/* ===================================================================
   7. Grid container for .blog-card (index/category/search/related) --
   plain CSS grid, no outer border/joined-line background (each card
   already carries its own hairline border, one border per card, never
   a "card inside a bordered grid" double-border look).
   =================================================================== */
.blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s5); }
@media (max-width: 980px) { .blog-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .blog-grid { grid-template-columns: 1fr; gap: var(--s6); } }
.blog-related-grid .nh-grid-filler { display: none; }
.nh-featured-grid { margin-top: var(--s7); }

/* ===================================================================
   8. t_f15e2d92 (web-dev, 2026-11-10): listing hero + featured-read merge
   (Noe's fix #2 "digabung"). .nh-featured-wrap now lives INSIDE the same
   .nh-section.nh-head as the title/lead/chips (home.php), so it only needs
   its own internal-group gap here -- the section's own --sec-y top/bottom
   padding budget (no longer duplicated by a second sibling <section>) is
   what used to compound into the dead space before AND after the card.
   --s8 (64px) sits below --sec-y desktop (72px): a real, visible break
   between the hero copy and the featured card, but still smaller than the
   gap to the next section, same "group gap < section gap" rule every other
   joined section on this theme already follows.
   =================================================================== */
.nh-featured-wrap { margin-top: var(--s8); }

/* ===================================================================
   9. Mobile menu sheet parity (Noe round-2 eye-check, finding #1): the drawer still used
   the theme's OLD pre-r8 dark/numbered markup (gray bg, mono 01-05 badges, a line under every
   row, CTA pinned to the screen's bottom edge by `.mobile-menu-links{flex:1}`), not the
   landing's own r13mm- sheet (components/home/Nav.tsx + app/globals.css ~6016-6425, main@44cd54f).
   Markup itself stays the theme's `.mobile-menu*` classes (header.php) -- only ported the
   landing's VALUES onto this theme's own selectors, since renaming classes risks breaking
   theme.js's querySelectorAll hooks (data-mobile-menu, data-mobile-services-toggle, etc).
   =================================================================== */
/* 9a. White sheet, not the grey --bg-2 panel (landing: `background: var(--r8-bg, var(--bg))`). */
.mobile-menu-panel { background: var(--bg); }

/* 9b. The landing's sheet has no separate pinned-bottom section: `.r13mm-body` and
   `.r13mm-foot` are plain flow children of the one scrolling `.r13mm-sheet` (`overflow-y: auto`),
   so the CTA sits right after the list, not glued to the viewport's bottom edge. Un-flex
   `.mobile-menu-links` (theme.css gives it `flex:1`, which is what stretches it to fill the
   panel and pushes the CTA down) and let the whole panel scroll as one unit instead. */
.mobile-menu-panel { overflow-y: auto; overflow-x: hidden; }
.mobile-menu-links { flex: none; overflow-y: visible; }

/* 9c. Items: landing `.r13mm-row` is ink-black, font:600 19px/1.2, no number badges (the
   mono 01-05 `<span>`s are removed in header.php this round). */
.mobile-menu-link {
  color: var(--txt);
  font: 600 19px/1.2 var(--f);
  letter-spacing: normal;
  min-height: 44px;
  padding: 16px 0;
}
.mobile-menu-link.on { color: var(--txt); }
@media (hover: hover) {
  .mobile-menu-link:not(.on):hover { color: var(--txt); }
}

/* 9d. Dividers: landing's `.r13mm-row:last-of-type{border-bottom:none}` relies on the Services
   trigger being a different element TYPE (<button>) than the plain links (<a>) -- a lone
   <button> among the <a> links auto-matches `:last-of-type` for its own type, same as the last
   <a> (Career) does for its type, so only Work/Insights/About keep the line under them. This
   theme's Services row is `.mobile-services-row` (a sibling <div>, not a `.mobile-menu-link` at
   all), so the natural `:last-of-type` trick doesn't apply the same way -- state it explicitly
   instead: no line under the Services row, no line under the last `.mobile-menu-link` (Career). */
.mobile-services-row { border-bottom: none; }
.mobile-menu-links > a.mobile-menu-link:last-of-type { border-bottom: none; }

/* 9e. Services row/trigger: drop the warm accent colour (var(--acc)/var(--acc-2)) the old
   dark drawer used -- "ink is the only emphasis colour" (FOUNDATIONS.md), same neutral tone
   already applied to the desktop `.nav-trigger-chev-btn` above. */
.mobile-services-link { color: var(--txt); font: 600 19px/1.2 var(--f); letter-spacing: normal; }
.mobile-services-trigger { color: var(--faint); }
.mobile-services-chev { color: var(--faint); }

/* 9f. CTA block: landing's `.r13mm-foot` is `border-top` + `margin-top:32px` +
   `padding-top:48px` sitting in the upper half right after the list (now possible since 9b
   stopped forcing it to the bottom), plus a centred grey tagline under the button
   (`.r13mm-foot-sub`, new `.mobile-menu-cta-sub` <p> added in header.php this round, same
   EN/ID copy as Nav.tsx's own r13mm-foot-sub strings). */
.mobile-menu-cta {
  border-top: 1px solid var(--line-2);
  flex-shrink: 0;
  margin-top: var(--s6);
  padding-top: var(--s7);
  /* Noe round-3 finding #2: the landing's `.r13mm-foot` sits OUTSIDE `.r13mm-sheet`'s own body
     padding, so its border-top runs edge to edge. `.mobile-menu-panel` (theme.css) insets
     everything via its own `max(28px, env(safe-area-inset-*))` left/right padding -- bleed
     this block past that padding with matching negative margins, then re-apply the identical
     inset as this block's own padding so the button/tagline stay aligned with the list above. */
  margin-left: calc(-1 * max(28px, env(safe-area-inset-left, 0px)));
  margin-right: calc(-1 * max(28px, env(safe-area-inset-right, 0px)));
  padding-left: max(28px, env(safe-area-inset-left, 0px));
  padding-right: max(28px, env(safe-area-inset-right, 0px));
}
.mobile-menu-cta-sub {
  color: var(--muted);
  font-size: 13px;
  margin: var(--s3) 0 0;
  text-align: center;
}

/* ===================================================================
   10. t_ff23c70c (web-dev, 2026-11-11), DESIGN-SPEC t_0664c7eb: hero becomes
   ONE 2-col `.nh-hero-grid` (title+intro left / featured card right),
   `.nh-topics` moves out as a sibling AFTER the grid.

   10b. t_257b0403 (web-dev, 2026-11-11), CONCEPT 2026-11-11-blog-hero-horizontal-
   card-v3 (Wira round 1 "ya setuju dengan design blog terbaru" + round 2 mobile-
   only fix "covernya kgedean"): the featured card flips from a vertical/stacked
   layout (`.nh-featured-v`, too tall vs the left block -- live measured
   692.59px vs 264.09px, the original complaint) to a HORIZONTAL split: image
   flush left (square, fills the card's own height), text column right --
   `.nh-featured-h` replaces `.nh-featured-v` (template-parts/card-featured-h.php).
   Card height is pinned to the left block's own measured height (264.09px,
   re-confirmed this session on the live DOM/seed at true 1440px -- exact match
   to the concept's own number) via --card-h, tops+bottoms aligned (align-items:
   start, unchanged). This only holds exactly from ~1400px up (h1's clamp()
   below maxes out there); narrower desktop widths (1141-1399px) see the left
   block shrink a little as the clamp eases off -- not exercised by the
   acceptance widths (1440/1024/390/360), flagging for whoever revisits this.
   A NEW tablet band (1140-861px) is added here: a horizontal card at 1024
   both outgrows the (clamp-shrunk) left block AND leaves too little width for
   a readable 2-line title next to a sensible image column, so this band
   stacks the hero to one column instead (own call, same pattern mobile
   already used) -- the existing <=860px breakpoint already did this for
   mobile, this just extends the same stacking behaviour one band wider. */
.nh-hero-grid {
  display: grid;
  grid-template-columns: minmax(300px, 440px) 1fr;
  gap: var(--s8);
  align-items: start;
}
@media (max-width: 1140px) and (min-width: 861px) {
  .nh-hero-grid { grid-template-columns: minmax(0, 1fr); gap: 28px; }
}
@media (max-width: 860px) {
  .nh-hero-grid { grid-template-columns: minmax(0, 1fr); }
}
.nh-hero-left { min-width: 0; }
.nh-topics { margin-top: var(--s7); }

/* .nh-featured-h: horizontal variant of .blog-featured (section 5) -- same meta/h2/
   excerpt/cta sub-tokens as .nh-featured-v did, image column flush left (fills the
   card's own height, no padding) instead of a top-stacked cover. box-shadow inset
   (not a real border) keeps the single-hairline-frame convention the rest of the
   theme already uses (.blog-featured, the old .nh-featured-v).
   14. t_fae71203 (web-dev, 2026-11-13), 1.6.10: Wira (live 1.6.9 screenshot,
   verbatim) -- "background card featured post kok transparant?" -- this card used
   to rely on "no background of its own" so it inherited whatever sat behind it;
   that was fine against a flat --bg page, but 1.6.9's full-bleed .nh-head-wave
   canvas (section 13) now paints behind it with the SAME --bg page colour, and a
   transparent card lets that canvas's dot-wave texture show straight through the
   text panel. Fix: background-color: var(--bg) explicit -- the exact token
   .blog-card already uses for its own opaque surface (r8-restyle.css ~L594), so
   light = white (#fff) and dark = the dark surface (#131316), same pairing as
   every other article card. Border (box-shadow inset), radius and the image side
   are untouched; only the previously-transparent fill is now opaque. */
.nh-featured-h {
  --card-h: 264.09px;
  display: flex; flex-direction: row; align-items: stretch;
  height: var(--card-h);
  position: relative; overflow: hidden;
  background-color: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line-3); border-radius: var(--r-card);
  text-decoration: none; color: inherit;
}
.nh-featured-h .cover { flex: none; width: var(--card-h); height: 100%; overflow: hidden; display: block; text-decoration: none; }
.nh-featured-h .cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nh-featured-h .cover-ph { width: 100%; height: 100%; display: grid; place-items: center; color: var(--faint); font-family: var(--m); background: var(--panel); }
.nh-featured-h .body { flex: 1; min-width: 0; padding: 0 var(--s6); display: flex; flex-direction: column; justify-content: center; gap: var(--s2); }
.nh-featured-h .meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; row-gap: 4px; font-family: var(--m); font-size: 11.5px; color: var(--faint); letter-spacing: .03em; text-transform: uppercase; }
.nh-featured-h .meta > * { white-space: nowrap; }
.nh-featured-h .meta .cat { color: var(--txt); font-weight: 700; }
.nh-featured-h .meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); flex: none; }
.nh-featured-h h2 {
  margin: 0; font-size: 21px; line-height: 1.3; font-weight: 800; letter-spacing: -.01em; position: relative;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.nh-featured-h h2 a { text-decoration: none; color: inherit; }
.nh-featured-h h2 a::after { content: ''; position: absolute; inset: 0; }
.nh-featured-h p.excerpt {
  margin: 0; font-size: 14px; line-height: 1.55; color: var(--muted); max-width: 52ch;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.nh-featured-h .cta { margin-top: 2px; font-size: 13px; font-weight: 700; display: inline-flex; gap: 6px; align-items: center; }

/* 10c. Tablet band (861-1140px): stack to one column, banner-style image on top
   (wider than mobile's ratio since there's more width to spend) -- see the
   section-10b comment above for why this band isn't forced into 2 columns. */
@media (max-width: 1140px) and (min-width: 861px) {
  .nh-featured-h { --card-h: auto; height: auto; flex-direction: column; }
  .nh-featured-h .cover { width: 100%; height: auto; aspect-ratio: 21/9; }
  .nh-featured-h .body { padding: 24px; justify-content: flex-start; }
  .nh-featured-h h2 { font-size: 19px; }
  .nh-featured-h p.excerpt { font-size: 13.5px; max-width: 62ch; }
}

/* 10d. Mobile (<=860px), stacked layout + Wira's round-2 "covernya kgedean" fix:
   the ROOT CAUSE (CONCEPT.md "Round 2"): a stacked-only `height:100%` rule here
   previously would have inherited silently from the row-axis default above
   (same selector/specificity, property simply not redeclared) and stretched the
   cover with the flex column -- explicit `height:auto` kills that before the
   ratio applies, so `aspect-ratio:16/9` (matches the source photo's own native
   1024x576 dimensions exactly) renders with zero crop on the image's own
   baked-in corner label. `object-position:left top` is a defensive pin, not
   load-bearing once container and source share the same ratio. */
@media (max-width: 860px) {
  .nh-featured-h { --card-h: auto; height: auto; flex-direction: column; }
  .nh-featured-h .cover { width: 100%; height: auto; aspect-ratio: 16/9; }
  .nh-featured-h .cover img { object-position: left top; }
  .nh-featured-h .body { padding: 20px; justify-content: flex-start; }
  .nh-featured-h h2 { font-size: 18px; }
  .nh-featured-h p.excerpt { font-size: 13.5px; max-width: 62ch; }
}

/* ===================================================================
   11. t_5e78c15a (web-dev, 2026-11-11), cmo visual-check round-4 fixes on
   top of 2dd5e2c's shell-parity build (on 059ed59). Wira's original ask
   ("hapus pembatas frame dan background harusnya putih ada efek kotak2
   kayak main") was only half-applied: the 1px border-inline, the 1px
   .nh-rail lines, and the 45deg dither gutters (>=1400px) all still
   painted -- those ARE the "pembatas frame" + grey checker, never
   intentionally kept. Main's own visible "kotak2" (what Wira is actually
   pointing at) is `.hero-r8-dots`, a DIFFERENT, lighter radial dot field
   than `.fx.dots` -- override 1 above matches `.fx.dots` to a different
   home-r8.css rule that sits BEHIND main's white hero and is never what a
   visitor actually sees there.
   =================================================================== */
/* 11a. Frame removal, every viewport. theme.css only painted the border +
   checker gutters >=1400px (@media(min-width:1400px)); `.nh-rail` painted
   its 1px line at EVERY width unconditionally. Same selectors, loaded
   after theme.css so this wins on source order with no !important --
   .nh-shell's own max-width/centring/background is untouched, only the
   visible border/line/checker paint goes. */
.nh-shell { border-inline: 0; }
.nh-rail, .nh-gutter { display: none; }

/* 11b. REMOVED (t_10b962ef, web-dev, 2026-11-13): this used to be main's hero
   dot field (`.hero-r8-dots` port, t_5e78c15a 2026-11-11) -- a CSS-drawn
   halftone dot grid (`.nh-head-dots`), full-bleed via `overflow:visible` +
   a vw breakout. Once section 12 below (t_7eeffec7, 1.6.6) added the
   `.nh-head::before` hero wave band -- which already bakes a dot-wave
   texture into its own image (same technique as noethera.com's About page,
   itself a frozen frame of the Home dot-wave renderer) -- this separate CSS
   dot grid duplicated that baked-in dot pattern, the "two overlapping dot
   patterns" Wira reported. Removed outright (markup `<div class="nh-head-
   dots">` dropped from home.php) rather than hidden, so `.nh-head` is back
   to its plain theme.css `overflow:hidden` with no dead full-bleed override
   left behind. */

/* 11c. t_48032d1a (cmo go-live visual check): the dots rect above still only
   started at y~=80 (main measured [0,0,1920,1000], i.e. rect.top=0). Root
   cause, same bug class as noethera-landing's own t_eb5aac85
   (hooks/useNavHeightVar.ts): `.nh-head-dots` is `inset:0` on `.nh-head`
   (position:relative, theme.css), and `.nh-head` is a normal in-flow block
   that only starts AFTER the sticky nav's own real flow height -- the nav
   reserves that space, so the dots layer (flush to .nh-head's own box) never
   painted behind it, a hard seam at the nav's bottom edge.
   Fix, same mechanism as main's `.hero-r8` (app/home-r8.css): pull `.nh-head`
   up by the nav's own rendered height (negative margin-top collapses through
   the un-padded, un-bordered `<main>` and through the adjoining nav/main
   sibling margins -- see theme.js's measureNavHeight() for how --nh-nav-h is
   kept live, mirroring useNavHeightVar.ts) so the box's own top edge lands at
   true page y:0, then add the SAME amount back as padding-top so the real
   hero content (`.w`) keeps its old visual position -- margin-top and
   padding-top read the same variable, so they always cancel exactly
   regardless of the nav's current height. `.nh-head-dots`'s `inset:0` then
   naturally covers the extra padding-top area too (abs-positioned children
   align to the padding box), so the dots run from y:0 with no further change
   to the mask/gradient values already ported above. Scoped to the SAME
   selector 11b already uses (home's own un-flat head only) -- archive/
   article/search/404 heads (`.nh-head--flat`) are untouched. */
:root { --nh-nav-h: 78px; }
@media (max-width: 700px) { :root { --nh-nav-h: 56px; } }
.nh-head:not(.nh-head--flat) {
  margin-top: calc(-1 * var(--nh-nav-h));
  padding-top: calc(var(--nh-nav-h) + var(--sec-y));
}

/* 11e. Hero H1 fits its own `.nh-hero-left` column (2dd5e2c's
   `.nh-hero-grid`: minmax(300px,440px) 1fr, gap var(--s8)). `--fs-display`
   (clamp(42px,8vw,116px), theme.css) is the SHARED display scale every
   other large heading on this theme also uses -- at 1440px that resolved
   to ~115px, wide enough that the single word "Noethera" alone (~115px *
   ~0.58 avg char-width * 8 chars =~ 535px) overflowed the 440px column
   outright (cmo's live measurement: 0px gap to the featured card). Scoped
   to `.nh-hero-grid .nh-h1` only -- the shared token and every other h1/
   `.nh-title` on the theme (article heads, etc.) are untouched. Only
   active >860px (the SAME breakpoint `.nh-hero-grid` itself already
   switches to 2 columns at, section 10 above): below that the grid stacks
   to 1 column with the full content width available, so "390/360 stacking
   unchanged" per the brief -- the original `--fs-display` clamp still
   applies there exactly as before this round. */
@media (min-width: 861px) {
  .nh-hero-grid .nh-h1 { font-size: clamp(30px, 4vw, 56px); }
}

/* ===================================================================
   13. t_a2e5bc7f (web-dev, 2026-11-13), 1.6.9: hero wave as code, full-bleed, no
   image. Wira (live 1.6.8 screenshot, verbatim): "malah tambah aneh sekarang, gak
   full backgroundnya, ini gambar ya? harusnya bisa code kayak about page kan."
   Section 12 above (1.6.6-1.6.8, now REMOVED) painted the hero band as a baked
   webp `background-image` inside `.nh-head::before` -- not full-bleed (it never
   broke out of `.nh-head`'s own box, which on the live site does not reach the
   viewport edge the way `.nh-shell`'s own 1400px-max math assumes) and an image,
   against Wira's standing rule ("harusnya code, jangan gambar biar ringan").
   Fix: the exact same technique noethera.com's About/inner-page hero now uses
   (AboutHeroWave.tsx, t_a277a246) -- the Home HeroDotWave WebGL renderer (same
   math, same HERO_TUNE), vendored verbatim into this theme
   (assets/js/dot-wave-renderer.js, stripped of TS types only) and drawn into a
   <canvas> that hero-wave.js inserts as `.nh-head`'s first child at runtime (no
   markup change needed in any of the 5 templates that render a `.nh-head`: home/
   category/single/search/404). ONE static frame (render(3) once, redrawn only on
   resize or a [data-theme] flip) -- no requestAnimationFrame loop, so there is no
   per-frame cost after the first paint; prefers-reduced-motion is irrelevant
   (nothing animates). setPlaneTop(null), not 0 -- the same qa-dev round-2
   must-fix #1 the About page hit (0 clamps to planeTop 0.19, denser/darker than
   the neutral 0.34 default). Exactly one dot pattern (1.6.8's dedupe, section 11b,
   stays removed). */
.nh-head { overflow: visible; }
.nh-head-wave {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  height: 100%;
  margin-left: -50vw;
  z-index: 0;
  display: block;
  pointer-events: none;
  /* Same global dim as /about (about.css ~L560, qa-dev round 2 must-fix #1 part
     2): the raw renderer output is tuned bold for the Home hero: 0.4 keeps it a
     subtle texture that never competes with the text, not a new guess. */
  opacity: 0.4;
  /* Full-bleed via the SAME vw breakout `.r8-sheet-last` and the old (removed)
     `.nh-head-dots` already used on this theme (body already has
     overflow-x:hidden, theme.css): width:100vw + left:50% + margin-left:-50vw
     cancels out to the viewport edges at any width, independent of whatever
     `.nh-head`'s own box width happens to be -- the root cause of the "stops at
     the content shell" bug, since the old ::before was only ever `inset:0` on
     that box. */
  --nhw-mask: linear-gradient(180deg,
    transparent 0%, rgba(0,0,0,.3) 4%, rgba(0,0,0,.55) 10%,
    rgba(0,0,0,.55) 60%,
    /* Soft fade into section 2: >=160px, no seam (same ask as the /about fix,
       which fades out over its own last 120px -- this theme's hero runs taller,
       so the band is widened to the literal 160px floor the brief sets). */
    rgba(0,0,0,.4) calc(100% - 160px),
    rgba(0,0,0,.12) calc(100% - 48px),
    transparent 100%);
  mask-image: var(--nhw-mask);
  -webkit-mask-image: var(--nhw-mask);
}
/* Layering: wave (z:0, .nh-head-wave) < content (z:1, .w default from
   theme.css). t_10b962ef (web-dev, 2026-11-13) removed the `.nh-head-dots`
   middle layer along with its markup and its 11b/round-3-addendum rules above --
   `.w`'s own existing theme.css z-index:1 already beats the wave's z-index:0 with
   no override needed, so no z-index rule is required here either. */

/* Components -- mobile drawer theme row (markup: header.php), directly above
   .mobile-menu-cta. Same shape as main's own drawer-foot pattern: label left,
   44px circular toggle right, divider above, sits right above Start. A separate
   component from the desktop .theme-toggle (different size/shape), sharing only
   the .theme-toggle-icon sun/moon swap CSS (theme.css) via its own class. */
.theme-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}
.theme-row-lbl {
  font-size: 14px;
  font-weight: 600;
  color: var(--txt);
  display: flex;
  align-items: center;
  gap: 10px;
}
.theme-toggle-mobile {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line-ui);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--txt);
  cursor: pointer;
}
/* .theme-row supplies its own top divider -- suppress .mobile-menu-cta's so the
   drawer doesn't show two dividers back to back (same adjacent-sibling pattern
   already used for the old .mobile-menu-theme class, theme.css line ~394). */
.theme-row + .mobile-menu-cta { border-top: 0; margin-top: 0; padding-top: 12px; }
