/* netkubelab.com — the public front door. One page, no application.
 *
 * Dark by default. The site is a shop window for hands-on technical courses and
 * it should look like the tools the work is done in; light stays available
 * through the toggle for anyone who reads better that way, and both are checked
 * for AA numerically by the test suite rather than by eye.
 *
 * Every colour pair here is verified: text, muted and accent against the page,
 * against a card, and against a raised surface, in both themes, plus the text
 * that sits on the accent itself. A token added without a pair is a token the
 * tests will not catch.
 */
:root{
  /* dark — the default */
  --bg:#0a1420; --surface:#101f2e; --surface-2:#16283a; --border:#1e3448;
  --text:#eaf2f8; --muted:#a8bccd;
  --accent:#5ad4e6; --accent-2:#5a9dff; --accent-soft:#122a3a;
  --on-accent:#06121c;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 8px 28px rgba(0,0,0,.35);
  --radius:14px; --maxw:1080px;
}
@media (prefers-color-scheme:light){
  :root:not([data-theme="dark"]){
    --bg:#f7f8fa; --surface:#ffffff; --surface-2:#eef1f5; --border:#d8dee6;
    --text:#1a2029; --muted:#5b6675;
    --accent:#0a63bf; --accent-2:#084a9e; --accent-soft:#e3eefb;
    --on-accent:#ffffff;
    --shadow:0 1px 2px rgba(16,24,40,.06),0 4px 12px rgba(16,24,40,.05);
  }
}
/* The light accent is a shade darker than it looks like it needs to be: at
   #0b6fd4 it reached 4.37 against --surface-2, which is under AA. The broader
   contrast test found it; the eye did not. */
:root[data-theme="light"]{
  --bg:#f7f8fa; --surface:#ffffff; --surface-2:#eef1f5; --border:#d8dee6;
  --text:#1a2029; --muted:#5b6675;
  --accent:#0a63bf; --accent-2:#084a9e; --accent-soft:#e3eefb;
  --on-accent:#ffffff;
  --shadow:0 1px 2px rgba(16,24,40,.06),0 4px 12px rgba(16,24,40,.05);
}
:root[data-theme="dark"]{
  --bg:#0a1420; --surface:#101f2e; --surface-2:#16283a; --border:#1e3448;
  --text:#eaf2f8; --muted:#a8bccd;
  --accent:#5ad4e6; --accent-2:#5a9dff; --accent-soft:#122a3a;
  --on-accent:#06121c;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 8px 28px rgba(0,0,0,.35);
}
*{box-sizing:border-box}

/* Thai needs the extra leading; English at the same value reads airy to the
   point of looking unfinished, so the mirror gets its own. */
body{
  margin:0; background:var(--bg); color:var(--text);
  font-family:"Noto Sans Thai","Sarabun",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  line-height:1.75; font-size:17px; -webkit-font-smoothing:antialiased;
}
html[lang="en"] body{line-height:1.65}
.wrap{max-width:var(--maxw); margin:0 auto; padding:0 22px}
a{color:var(--accent)}
:where(a,button):focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:6px}

/* header */
header.top{
  position:sticky; top:0; z-index:20;
  background:var(--bg);
  border-bottom:1px solid var(--border);
}
header.top .wrap{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px 14px;
  min-height:62px; padding-top:8px; padding-bottom:8px;
}
.brand{font-weight:700; text-decoration:none; color:var(--text); white-space:nowrap; font-size:1.06rem}
.brand span{color:var(--accent)}
.spacer{flex:1}
nav.top-nav{display:flex; flex-wrap:wrap; gap:4px 20px; font-size:.9rem}
nav.top-nav a{color:var(--muted); text-decoration:none}
nav.top-nav a:hover{color:var(--text)}
.btn{
  background:transparent; color:var(--muted); border:1px solid var(--border);
  border-radius:999px; padding:6px 14px; font:inherit; font-size:.82rem;
  cursor:pointer; text-decoration:none; display:inline-flex; align-items:center; gap:6px;
}
.btn:hover{border-color:var(--accent); color:var(--accent)}

/* hero — centred, with a faint grid and one soft light source. No photograph,
   no decorative panel: the light is doing the work an image would. */
.hero{
  position:relative; overflow:hidden; text-align:center;
  padding:96px 0 76px; border-bottom:1px solid var(--border);
}
.hero::before{
  content:""; position:absolute; inset:0; pointer-events:none; opacity:.55;
  background:
    repeating-linear-gradient(0deg, transparent 0 39px, var(--border) 39px 40px),
    repeating-linear-gradient(90deg, transparent 0 39px, var(--border) 39px 40px);
  -webkit-mask-image:radial-gradient(70% 62% at 50% 18%, #000 35%, transparent 100%);
  mask-image:radial-gradient(70% 62% at 50% 18%, #000 35%, transparent 100%);
}
.hero > .wrap{position:relative}
.hero h1{
  font-size:clamp(1.9rem, 4.8vw, 3.2rem); line-height:1.34; margin:0 auto 18px;
  letter-spacing:-.02em; max-width:22rem;
}
/* The gradient runs across the words, not the separators — the separators are
   punctuation and should read as punctuation. Every stop is checked against the
   page background by the tests, so the whole sweep stays above AA rather than
   only its ends. */
.hero h1{
  background:linear-gradient(96deg, var(--text) 30%, var(--accent) 72%, var(--accent-2));
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.hero h1 .beat{font-weight:400; opacity:.45}
/* Thai has no spaces, so a browser breaks it at syllable boundaries -- left to
   itself it split "ตั้งค่า" across two lines. Every word is a nowrap unit, and
   each separator belongs to the word after it so it never dangles at a line
   end. The cap is in rem, not ch: `ch` measures the width of "0", which has
   nothing to do with Thai glyphs and made the line far narrower than intended. */
.hero h1 .unit{white-space:nowrap}
.hero .lead{
  font-size:1.1rem; color:var(--muted); margin:0 auto 30px; max-width:44ch;
}
.actions{
  display:flex; flex-wrap:wrap; justify-content:center; gap:12px; margin:0 0 22px;
}
.cta-primary,.cta-ghost{
  display:inline-flex; align-items:center; gap:8px; text-decoration:none;
  font-weight:600; padding:13px 26px; border-radius:999px; font-size:.98rem;
}
.cta-primary{
  background:linear-gradient(92deg, var(--accent), var(--accent-2));
  color:var(--on-accent); border:1px solid transparent;
}
.cta-primary:hover{filter:brightness(1.07)}
.cta-ghost{border:1px solid var(--border); color:var(--text)}
.cta-ghost:hover{border-color:var(--accent); color:var(--accent)}
.chips{
  display:flex; flex-wrap:wrap; justify-content:center; gap:6px 26px;
  list-style:none; padding:0; margin:0; font-size:.88rem; color:var(--muted);
}

section{padding:64px 0; border-top:1px solid var(--border)}
.hero{border-top:0}
section > .wrap > h2{
  font-size:clamp(1.4rem,2.6vw,1.9rem); margin:0 0 8px;
  text-align:center; letter-spacing:-.01em;
}
section > .wrap > .sub{
  color:var(--muted); font-size:.96rem; margin:0 auto 34px;
  text-align:center; max-width:52ch;
}

/* card grid — one course today, a grid that does not look broken with one and
   does not need rewriting at nine */
.cards{
  display:grid; gap:22px; margin:0;
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
}
.course{
  position:relative; background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); box-shadow:var(--shadow);
  padding:30px 22px 22px; display:flex; flex-direction:column;
}
/* the course code rides the top edge, the way a category tag does */
.course .code{
  position:absolute; top:-11px; left:20px; margin:0;
  background:linear-gradient(92deg, var(--accent), var(--accent-2));
  color:var(--on-accent); border-radius:999px; padding:2px 13px;
  font-size:.74rem; letter-spacing:.06em; font-weight:700;
}
.course h3{margin:0 0 12px; font-size:1.2rem; line-height:1.5}
.course > p{margin:0 0 16px; color:var(--muted); font-size:.95rem}

/* level, as bars plus the words. The bars are decoration: the same thing is
   written out beside them, so nothing is lost when they are not rendered. */
.meta{display:flex; flex-wrap:wrap; align-items:center; gap:8px 16px; margin:0 0 16px}
.rank{display:inline-flex; align-items:center; gap:8px; font-size:.86rem; color:var(--muted)}
.bars{display:inline-flex; gap:3px}
.bars i{width:9px; height:9px; border-radius:2px; background:var(--border)}
.bars i.on{background:var(--accent)}
.facts{
  display:flex; flex-wrap:wrap; gap:6px 10px; font-size:.86rem;
  color:var(--muted); margin:0 0 16px;
}
.facts .dot{color:var(--border)}
.topics,.chips.inline{
  display:flex; flex-wrap:wrap; gap:7px; margin:0 0 16px; padding:0; list-style:none;
}
.topics li,.chips.inline li{
  background:var(--surface-2); border:1px solid var(--border); border-radius:999px;
  padding:3px 11px; font-size:.8rem; color:var(--muted);
}
/* .chips centres itself in the hero; inside a card it should sit against the
   left edge like the row of topics above it. */
.chips.inline{justify-content:flex-start}
.chips.inline li{border-style:dashed}
.cardfoot{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px 14px;
  margin-top:auto; padding-top:16px; border-top:1px solid var(--border);
}
.by{margin:0; font-size:.88rem; color:var(--muted)}
.by strong{color:var(--text); font-weight:600}
.cta{
  margin-left:auto; color:var(--accent); text-decoration:none;
  font-weight:600; font-size:.9rem; white-space:nowrap;
}
.cta:hover{text-decoration:underline}
.note{
  margin:14px 0 0; font-size:.84rem; color:var(--muted);
  border-left:2px solid var(--border); padding-left:12px;
}
.later{margin:26px 0 0; font-size:.92rem; color:var(--muted); text-align:center}

/* the About page: a narrower column for prose, a contact form that composes
   a message rather than sending one */
.wrap.narrow{max-width:44rem}
section > .wrap.narrow > h2{text-align:left}
.lede{color:var(--muted); font-size:1rem; margin:0 0 18px}
.points{margin:0; padding-left:1.3em; color:var(--muted)}
.points li{margin:0 0 12px}
.mailto{margin:0 0 26px; font-size:.98rem}
.mailto strong{color:var(--text); margin-right:8px}

.mailform{margin:0}
.mailform .row{margin:0 0 16px; display:flex; flex-direction:column; gap:6px}
.mailform label{font-size:.88rem; color:var(--muted)}
.mailform input,.mailform textarea{
  font:inherit; font-size:.96rem; color:var(--text);
  background:var(--surface); border:1px solid var(--border);
  border-radius:10px; padding:10px 13px; width:100%;
}
.mailform textarea{resize:vertical; min-height:8rem; line-height:1.7}
.mailform input:focus-visible,.mailform textarea:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px; border-color:transparent;
}
/* The rows stack label over field; the button should not be stretched by that
   to the width of a text input. */
.mailform .submit{margin:0 0 14px; align-items:flex-start}
.mailform button{border:0; cursor:pointer; font-family:inherit}
/* Not decoration: it says the button opens a mail app rather than sending, and
   a form that silently does nothing is worse than no form. */
.mailform .note{
  margin:0 0 8px; font-size:.85rem; color:var(--muted);
  border-left:2px solid var(--border); padding-left:12px;
}

/* articles and news */
.postlist{
  list-style:none; padding:0; margin:0; display:grid; gap:20px;
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
}
.postlist li{
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); padding:20px 22px; box-shadow:var(--shadow);
}
.postlist a{text-decoration:none}
.postlist strong{font-size:1.05rem; line-height:1.5; display:block; color:var(--text)}
.postlist a:hover strong{color:var(--accent)}
.postlist time,.newslist time{
  display:block; color:var(--muted); font-size:.8rem; margin:6px 0 0;
  font-variant-numeric:tabular-nums;
}
.postlist p{margin:10px 0 0; color:var(--muted); font-size:.93rem}

.newslist{list-style:none; padding:0; margin:0}
.newslist li{
  display:flex; flex-wrap:wrap; gap:4px 22px; padding:18px 0;
  border-bottom:1px solid var(--border);
}
.newslist li:last-child{border-bottom:0}
.newslist time{flex:none; width:7rem; margin:3px 0 0}
.newslist div{flex:1 1 18rem; min-width:0}
.newslist p{margin:3px 0 0; color:var(--muted); font-size:.93rem}

/* an article's own body */
.post{padding:44px 0 20px}
.post .wrap{max-width:44rem}
.post h2{font-size:1.32rem; margin:34px 0 12px; text-align:left}
.post h3{font-size:1.1rem; margin:26px 0 8px}
.post p{margin:0 0 17px}
.post ul,.post ol{margin:0 0 17px; padding-left:1.4em}
.post li{margin:5px 0}
.post blockquote{
  margin:0 0 17px; padding:2px 0 2px 16px;
  border-left:2px solid var(--accent); color:var(--muted);
}
.post pre{
  background:var(--surface); border:1px solid var(--border);
  border-radius:12px; padding:16px 18px; overflow-x:auto; margin:0 0 17px;
}
.post pre code{font-size:.86rem; line-height:1.7}
.post :not(pre) > code{
  background:var(--surface-2); border:1px solid var(--border);
  border-radius:5px; padding:1px 6px; font-size:.88em;
}
.post img{max-width:100%; height:auto; border-radius:12px}
.post hr{border:0; border-top:1px solid var(--border); margin:30px 0}

/* contact */
.contact{display:grid; gap:20px; grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr))}
.contact div{
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); padding:20px 22px;
}
.contact h3{margin:0 0 8px; font-size:1rem}
.contact p{margin:0; color:var(--muted); font-size:.93rem}

footer{
  border-top:1px solid var(--border); padding:34px 0 56px;
  color:var(--muted); font-size:.86rem; text-align:center;
}
footer p{margin:0 0 6px}

@media (min-width:900px){
  /* Wide enough for the four beats on one line, which is how the phrase reads.
     The .wrap still bounds it; this only lifts the narrow-screen cap. */
  .hero h1{max-width:none}
}

@media (max-width:560px){
  .hero{padding:64px 0 52px}
  .cta-primary,.cta-ghost{width:100%; justify-content:center}
  section{padding:48px 0}
  .chips{gap:6px 16px}
}
