/* Douhouse — generated by build.mjs. Edit STYLES there, not styles.css.

   Two themes from one set of tokens. Colour is only ever declared as a token,
   so the contrast checker in scripts/check-contrast.mjs can read the whole
   palette out of this file and prove every pairing before it ships. */
:root{
  color-scheme:dark;
  --bg:#0a0b0f;--elev:#13151c;--fg:#f1f2f6;--mu:#9aa1b1;
  --line:#31374a;--line2:#3f4657;--acc:#8ab4ff;--acc-ink:#0a0b0f;
  --ah:264;                        /* per-card accent hue (OKLCH), overridden inline;
                                      264 is the brand accent's own hue, so a card
                                      with no colour of its own reads as Douhouse blue */
  /* OKLCH, not HSL. HSL lightness is not perceptual: a yellow and a blue at the
     same HSL lightness differ by more than 1.5:1 in measured contrast, so a
     single per-hue formula in HSL cannot serve 20 icons. OKLCH lightness is
     perceptual, so one pair of numbers holds for every hue — see
     scripts/check-contrast.mjs, which is what caught this.
     Only the ingredients live here; --accent itself is mixed on .card, because
     a custom property resolves the var()s inside it against the element that
     DECLARES it. Declared here, var(--ah) would read this default hue and every
     card would come out the same colour no matter what its icon says. */
  --acc-l:80%;--acc-c:.13;
  --glow-l:70%;--glow-c:.16;--glow-a:.18;
  --shadow:0 1px 2px rgb(0 0 0/.4),0 12px 32px -12px rgb(0 0 0/.7);
  --shadow-lift:0 1px 2px rgb(0 0 0/.4),0 20px 44px -14px rgb(0 0 0/.85);
  --pop:0 16px 40px -12px rgb(0 0 0/.75);
  --wash1:hsl(222 80% 60% / .13);--wash2:hsl(268 80% 62% / .10);
  --ease:cubic-bezier(.22,.61,.36,1);
  --w:1080px;
  --barh:74px;                     /* the sticky bar's own height, kept here so
                                      scroll-margin can track it; see below */
}
@media (prefers-color-scheme:light){:root{
  color-scheme:light;
  --bg:#fbfaf8;--elev:#fff;--fg:#15171c;--mu:#585f6c;
  --line:#d3cdbf;--line2:#c6bfb0;--acc:#2a55c8;--acc-ink:#fff;
  --acc-l:50%;--acc-c:.13;
  --glow-l:70%;--glow-c:.15;--glow-a:.15;
  --shadow:0 1px 2px rgb(20 22 27/.05),0 10px 26px -14px rgb(20 22 27/.28);
  --shadow-lift:0 1px 2px rgb(20 22 27/.06),0 18px 38px -16px rgb(20 22 27/.34);
  --pop:0 14px 36px -10px rgb(20 22 27/.24);
  --wash1:hsl(222 84% 56% / .10);--wash2:hsl(268 74% 58% / .07);
}}

*{box-sizing:border-box}
/* No scroll-behavior:smooth. It silently breaks deep links: loading a URL that
   already carries a fragment leaves the page at scrollY 0 rather than at the
   heading, because the animation is started and then dropped during load.
   Measured on the same article, same width, one variable changed — smooth
   landed at scrollY 0, auto landed with the heading exactly at its
   scroll-margin. Article anchors are the whole point of having ids, so the
   flourish loses. */
html{-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;transition-duration:.01ms !important}
}
body{
  margin:0;background:var(--bg);color:var(--fg);
  font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans JP","Noto Sans KR","PingFang SC","Hiragino Sans",sans-serif;
  font-synthesis-weight:none;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
a{color:var(--acc);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:2px solid var(--acc);outline-offset:3px;border-radius:4px}
img{max-width:100%}

/* The wash belongs to the top of the document and scrolls away with it. As a
   fixed layer it stayed pinned to the viewport, so a coloured band followed the
   reader down the page and tinted body copy that was never meant to sit on it.
   Every alpha here is bounded and fed to the contrast checker as a composited
   background regardless. */
body{position:relative}
.wash{position:absolute;inset:0 0 auto;height:min(760px,72vh);z-index:-1;pointer-events:none;
  background:
    radial-gradient(58% 70% at 16% -6%,var(--wash1),transparent 68%),
    radial-gradient(50% 62% at 88% 2%,var(--wash2),transparent 66%);
}

main{max-width:var(--w);margin:0 auto;padding:0 24px 96px}
/* A sticky bar hides whatever an in-page link jumps to, so anything with an id
   keeps a landing strip the height of the bar plus a little air. The height has
   to be a variable, not a constant: the bar wraps to a second row on a phone,
   and a flat value tuned on a desktop left the target 54px underneath it. */
:target,[id]{scroll-margin-top:calc(var(--barh) + 14px)}

/* ---------------------------------------------------------------- header */
/* The tint and the hairline belong to a full-width element and the content to
   an inner one. Painting the bar itself and stretching it with 100vw would be
   fewer elements but wrong: 100vw counts the scrollbar, so the bar would run a
   scrollbar's width past the page and put a horizontal scrollbar under it. */
.bar{
  position:sticky;top:0;z-index:30;
  background:color-mix(in srgb,var(--bg) 78%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(14px);
  backdrop-filter:saturate(180%) blur(14px);
  border-bottom:1px solid var(--line);
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.bar{background:var(--bg)}}
.barin{
  display:flex;flex-wrap:wrap;gap:10px 22px;align-items:center;
  max-width:var(--w);margin:0 auto;padding:14px 24px;
}
.brand{display:flex;align-items:center;gap:10px;font-weight:640;letter-spacing:-.01em;color:var(--fg)}
.brand:hover{text-decoration:none}
.mark{
  display:inline-grid;place-items:center;width:28px;height:28px;
  border-radius:9px;background:var(--acc);color:var(--acc-ink);
  font-size:15px;font-weight:700;
  box-shadow:0 2px 10px -2px hsl(222 80% 55% / .55);
}
.links{display:flex;gap:6px;font-size:14px}
.links a,.links .here{padding:6px 10px;border-radius:8px;transition:background .18s var(--ease),color .18s var(--ease)}
.links a{color:var(--mu)}
.links a:hover{color:var(--fg);background:var(--elev);text-decoration:none}
.links .here{color:var(--fg)}

/* ---------------------------------------------------------------- language */
.langmenu{margin-left:auto;position:relative;font-size:14px}
.langmenu>summary{
  display:flex;align-items:center;gap:7px;padding:6px 12px;
  border:1px solid var(--line2);border-radius:999px;background:var(--elev);
  color:var(--mu);cursor:pointer;list-style:none;user-select:none;
  transition:color .18s var(--ease),border-color .18s var(--ease);
}
.langmenu>summary::-webkit-details-marker{display:none}
.langmenu>summary::after{content:"";width:5px;height:5px;margin-left:1px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform .2s var(--ease)}
.langmenu>summary:hover,.langmenu[open]>summary{color:var(--fg);border-color:var(--mu)}
.langmenu[open]>summary::after{transform:translateY(1px) rotate(225deg)}
.globe{flex:none}
.langpop{
  position:absolute;right:0;top:calc(100% + 8px);z-index:20;
  display:flex;flex-direction:column;min-width:186px;padding:6px;
  border:1px solid var(--line2);border-radius:14px;background:var(--elev);
  box-shadow:var(--pop);
}
.langpop a,.langpop .here{padding:8px 11px;border-radius:9px;white-space:nowrap}
.langpop a{color:var(--fg);transition:background .16s var(--ease)}
.langpop a:hover{background:color-mix(in srgb,var(--fg) 7%,transparent);text-decoration:none}
.langpop .here{color:var(--mu)}

/* ---------------------------------------------------------------- type */
h1{font-size:clamp(34px,6.2vw,62px);line-height:1.05;letter-spacing:-.032em;font-weight:680;margin:0 0 20px;text-wrap:balance}
h2{font-size:clamp(21px,2.4vw,27px);line-height:1.2;letter-spacing:-.02em;font-weight:640;margin:0 0 12px}
h3{font-size:17px;line-height:1.35;letter-spacing:-.011em;font-weight:620;margin:0 0 6px}
h4{font-size:17px;line-height:1.3;letter-spacing:-.011em;font-weight:640;margin:0}
.lead{color:var(--mu);font-size:clamp(17px,1.7vw,19px);line-height:1.6;max-width:60ch;margin:0}
.hero{padding:clamp(64px,11vw,124px) 0 clamp(40px,6vw,64px)}
.pagehead{padding:clamp(52px,8vw,92px) 0 clamp(20px,3vw,30px);max-width:68ch}
.pagehead h1{font-size:clamp(30px,4.6vw,46px)}
.pagehead .lead{margin-top:14px}
.article .pagehead{padding-bottom:6px}
.article .pagehead .crumb{margin-bottom:16px}
.article .pagehead .tags{margin-bottom:0}
.sec{margin-top:clamp(52px,7vw,88px)}
.sechead{display:flex;align-items:baseline;gap:16px;margin:0 0 26px}
.sechead h2{margin:0}
.sechead .rule{flex:1;height:1px;background:var(--line);transform:translateY(-3px)}

/* ---------------------------------------------------------------- stances */
.stancegrid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));counter-reset:stance}
.stance{
  position:relative;padding:22px 20px 20px;border-radius:16px;
  border:1px solid var(--line);background:var(--elev);box-shadow:var(--shadow);
}
.stance::before{
  counter-increment:stance;content:counter(stance,decimal-leading-zero);
  display:block;margin-bottom:12px;
  font-size:11px;font-weight:680;letter-spacing:.14em;color:var(--acc);
  font-variant-numeric:tabular-nums;
}
.stance p{margin:6px 0 0;color:var(--mu);font-size:14.5px;line-height:1.62}

/* ---------------------------------------------------------------- apps */
.group{margin-top:40px}
.group:first-of-type{margin-top:0}
.grouptitle{
  display:flex;align-items:center;gap:14px;
  font-size:12px;font-weight:660;text-transform:uppercase;letter-spacing:.15em;
  color:var(--mu);margin:0 0 16px;
}
.grouptitle::after{content:"";flex:1;height:1px;background:var(--line)}
.cards{list-style:none;margin:0;padding:0;display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(292px,1fr))}
.card{
  --accent:oklch(var(--acc-l) var(--acc-c) var(--ah));
  --glow:oklch(var(--glow-l) var(--glow-c) var(--ah) / var(--glow-a));
  position:relative;isolation:isolate;display:flex;flex-direction:column;
  padding:20px;border:1px solid var(--line);border-radius:18px;
  background:var(--elev);box-shadow:var(--shadow);
  transition:transform .22s var(--ease),border-color .22s var(--ease),box-shadow .22s var(--ease);
}
/* The accent glow is a pseudo-element that fades in, rather than a background
   that animates: a transition on background-image does not interpolate, so the
   colour would snap in instead of arriving.
   z-index:-1 puts it above the card's own background but below the in-flow
   text, which is what the painting order gives you for free. Lifting the
   children to z-index:1 instead would work too, and would silently box each of
   them into a stacking context. */
.card::before{
  content:"";position:absolute;inset:0;border-radius:inherit;z-index:-1;
  opacity:0;transition:opacity .26s var(--ease);pointer-events:none;
  background:radial-gradient(120% 90% at 8% 0%,var(--glow),transparent 62%);
}
.card:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--accent) 42%,var(--line));box-shadow:var(--shadow-lift)}
.card:hover::before{opacity:1}
.cardhead{display:flex;align-items:center;gap:13px;margin:0 0 12px}
.cardhead h4{text-wrap:balance}
.appicon{
  flex:none;width:46px;height:46px;border-radius:11px;
  box-shadow:0 0 0 1px color-mix(in srgb,var(--fg) 10%,transparent),0 4px 12px -4px rgb(0 0 0/.5);
  transition:transform .22s var(--ease);
}
.card:hover .appicon{transform:scale(1.05) rotate(-1.5deg)}
.card h4 a{color:var(--fg)}
.card p{margin:0 0 16px;color:var(--mu);font-size:14.5px;line-height:1.62}
/* Two real links per card — the name and the row below it — rather than one
   heading anchor stretched over the whole card. A stretched link tested as a
   lie here: .cardhead is a flex item in a stacking context of its own, so the
   overlay could not rise above the paragraph beneath it, and half the card was
   dead to a pointer. It would also have made the description unselectable. */
.go{
  margin-top:auto;font-size:13.5px;font-weight:560;color:var(--accent);
  display:inline-flex;align-items:center;gap:6px;align-self:flex-start;
}
.go svg{transition:transform .22s var(--ease)}
.card:hover .go svg{transform:translateX(3px)}

/* ---------------------------------------------------------------- prose */
/* Legal copy is a stack of h2 + one paragraph repeated a dozen times, which
   reads as an undifferentiated wall. The rule down the left edge gives each
   section a beginning without adding a single word. */
.prose{max-width:68ch;margin-top:30px;padding-left:20px;border-left:2px solid var(--line)}
.prose:first-of-type{margin-top:10px}
.prose h2{margin-bottom:8px;font-size:clamp(18px,2vw,21px)}
.prose p{color:var(--mu);margin:0;text-wrap:pretty}
.applinks{line-height:2.3}
.mailto{font-size:clamp(19px,2.4vw,24px);font-weight:640;letter-spacing:-.015em}
.tags{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 16px;padding:0}
.tags span{
  font-size:11.5px;letter-spacing:.04em;color:var(--mu);
  padding:3px 9px;border:1px solid var(--line);border-radius:999px;
}
.crumb{font-size:14px;margin:0 0 14px}
.backlink{margin:44px 0 0}
.article{max-width:72ch}
.article h1{margin-bottom:10px}
.article p{text-wrap:pretty}
.article h2,.article h3{scroll-margin-top:calc(var(--barh) + 14px)}
.anchor{
  margin-left:.4em;color:var(--mu);opacity:0;font-weight:400;
  transition:opacity .15s var(--ease);text-decoration:none;
}
h2:hover>.anchor,h3:hover>.anchor,.anchor:focus-visible{opacity:1}
.anchor:hover{color:var(--acc);text-decoration:none}
/* Touch has no hover, so the anchors would be permanently invisible and the
   headings would just carry an unexplained mark. Show them at rest instead. */
@media (hover:none){.anchor{opacity:.45}}
.article h2{margin-top:36px}
.article p,.article li{overflow-wrap:break-word}
.article ul,.article ol{padding-left:22px}
.article li{margin:6px 0}
.article hr{border:0;border-top:1px solid var(--line);margin:40px 0}
.article code{font:.88em/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:var(--elev);border:1px solid var(--line);border-radius:6px;padding:1.5px 5px;
  -webkit-box-decoration-break:clone;box-decoration-break:clone}
.article pre{background:var(--elev);border:1px solid var(--line);border-radius:14px;padding:16px 18px;overflow-x:auto;margin:20px 0;box-shadow:var(--shadow)}
.article pre code{background:none;border:0;padding:0;font-size:13px;line-height:1.6;white-space:pre;display:block}
.article pre{position:relative}
.article pre[data-lang]::before{
  content:attr(data-lang);position:absolute;top:8px;right:12px;
  font:600 10.5px/1 ui-monospace,SFMono-Regular,Menlo,monospace;
  letter-spacing:.1em;text-transform:uppercase;color:var(--mu);
}
.tablewrap{overflow-x:auto;margin:20px 0}
.article table{border-collapse:collapse;width:100%;font-size:15px}
.article th,.article td{border:1px solid var(--line);padding:9px 11px;text-align:left;vertical-align:top}
.article th{color:var(--mu);font-weight:620}

/* ---------------------------------------------------------------- footer */
.foot{border-top:1px solid var(--line);color:var(--mu);font-size:14px;margin-top:24px}
.footin{
  max-width:var(--w);margin:0 auto;padding:30px 24px 72px;
  display:flex;flex-wrap:wrap;gap:14px 28px;align-items:center;justify-content:space-between;
}
.footbrand{display:flex;align-items:center;gap:10px;margin:0}
.footbrand .mark{width:22px;height:22px;border-radius:7px;font-size:12px;box-shadow:none}
.footlinks{display:flex;flex-wrap:wrap;gap:8px 20px}
.footlinks a{color:var(--mu)}
.footlinks a:hover{color:var(--fg)}

@media (max-width:560px){
  :root{--barh:96px}
  main{padding:0 18px 72px}
  .barin{padding:10px 18px;gap:6px 12px}
  .footin{padding:26px 18px 60px}
  .langmenu{order:2}
  .links{order:3;flex-basis:100%;gap:2px;font-size:13.5px;margin-left:-7px}
  .links a,.links .here{padding:5px 7px}
  .cards{grid-template-columns:1fr}
  .prose{padding-left:16px}
}
