
@font-face{font-family:"OneEnd Sans";src:url(/assets/fonts/sans-78349950e446) format("woff2");font-weight:45 920;font-display:swap}
@font-face{font-family:"OneEnd Sans JP";src:url(/assets/fonts/sans-jp-67c948e43594) format("woff2");font-weight:45 930;font-display:swap;unicode-range:U+3000-303F,U+3040-30FF,U+31F0-31FF,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FF01-FF60,U+FFE0-FFE6}
@font-face{font-family:"JetBrains Mono";src:url(/assets/fonts/mono-18be452724bf) format("woff2");font-weight:100 800;font-display:swap}
/* =========================================================================
   Tokens.
   Every value a page can use is named here and nowhere else. A colour written
   into a rule below is a colour that cannot be changed for dark mode, cannot
   be checked for contrast, and will be typed slightly differently the next
   time — which is how a product ends up with nine greys.

   The scales are deliberately short. A spacing scale with twelve steps is a
   scale nobody can hold in their head, so every layout invents its own value
   and the scale stops meaning anything.
   ========================================================================= */
:root{
  /* --- surfaces, back to front ------------------------------------------
     raised is separate from card because a dropdown opens on top of a
     card. If the two are the same colour the edge disappears and the menu
     looks like part of what it covers. */
  --bg:#fafafa;
  --panel:#f4f4f5;
  --card:#ffffff;
  --raised:#ffffff;
  --hover:#f0f0f2;
  --scrim:rgba(0,0,0,.45);

  /* --- text: what is said, what supports it, and what is switched off ----
     All three clear 4.5:1 on bg and card. faint was 3.6:1 once; a colour
     for text that cannot be read is not a colour for text. */
  --ink:#0a0a0b;
  --muted:#55555c;
  --faint:#6e6e76;

  /* --- lines: separates, encloses, invites -------------------------------
     rule divides and is allowed to be quiet. A control's edge is the only
     thing telling someone where to click, so it gets its own token held to
     3:1 (WCAG 1.4.11) — the divider colour used to do both jobs and did
     the second at 1.4:1. */
  --rule:#e4e4e7;
  --rule-soft:#eeeef0;
  --rule-strong:#d0d0d5;
  --border-control:#8b8b93;

  /* --- the one accent ---------------------------------------------------
     5.2:1 on the page background as text, and 5.2:1 under white as a fill,
     so one value serves links and buttons and no lighter variant is needed.
     A second accent would leave the screen unable to say which thing
     matters more. */
  /* The primary button is the ink colour, not the accent: one solid pill,
     black on a light page and white on a dark one, as on the home page. The
     accent stays for links, focus and the one live thing on a screen. */
  --ink-hover:#2e2e33;
  /* Icons drawn by the stylesheet, for markup that carries none: the notices.
     Used as masks, so they take the text's colour like every other icon. */
  --icon-info:url("data:image/svg+xml,%3Csvg xmlns='http://www%2Ew3%2Eorg/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 11v5'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  --icon-alert:url("data:image/svg+xml,%3Csvg xmlns='http://www%2Ew3%2Eorg/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 8v5'/%3E%3Cpath d='M12 16h.01'/%3E%3C/svg%3E");
  --icon-warn:url("data:image/svg+xml,%3Csvg xmlns='http://www%2Ew3%2Eorg/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l10 18H2z'/%3E%3Cpath d='M12 10v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
  --brand:#c2410c;
  --brand-hover:#9a3410;
  --brand-ink:#ffffff;
  --brand-soft:#fdf1e8;
  --focus-ring:#c2410c;

  /* --- meaning, never decoration ---------------------------------------- */
  --ok:#1a7f37;      --ok-bg:#e8f5ec;
  --warn:#8a5a0b;    --warn-bg:#fbf4e3;
  --danger:#b42318;  --danger-bg:#fdeceb;

  /* --- chart series ------------------------------------------------------
     Never the meaning colours: a green line reads as "fine" and the graph
     starts lying. The first series is the accent, so a single-series chart
     matches the rest of the page. */
  --c1:#c2410c;
  --c2:#4a6a94;
  --c3:#6f5694;
  --c4:#8a7354;
  --c5:#7a7a82;

  /* --- spacing: a 4px grid, named by multiples of four ------------------
     space-6 is 24px because it is six fours. The old six-step scale jumped
     from 24 straight to 48, and every layout that needed 32 invented it.
     Skipped numbers are skipped on purpose; a value between two steps is
     outside the system. */
  --space-05:.125rem;
  --space-1:.25rem;
  --space-2:.5rem;
  --space-3:.75rem;
  --space-4:1rem;
  --space-5:1.25rem;
  --space-6:1.5rem;
  --space-8:2rem;
  --space-10:2.5rem;
  --space-12:3rem;
  --space-16:4rem;

  /* --- corners: where, more than how much ------------------------------- */
  --radius-xs:.25rem;
  --radius-sm:.375rem;
  --radius-md:.5rem;
  --radius-lg:.75rem;
  --radius-xl:1rem;
  --radius-full:999px;

  --line-1:1px;
  --line-2:2px;

  /* --- controls ----------------------------------------------------------
     One height for everything that sits in a row — a button beside a search
     field used to be 32px beside 40px. md is the default; sm is for inside a
     table; lg is for the one action a marketing page exists for, and for
     every control on a touch screen, where 44 is the finger. target is the
     floor below which nothing clickable shrinks (WCAG 2.5.8). */
  --control-sm:1.75rem;
  --control-md:2.25rem;
  --control-lg:2.75rem;
  --target-min:1.5rem;

  /* --- icons ------------------------------------------------------------
     Sized by the text they sit beside, never on their own: 12 by a caption,
     16 by body and small, 20 by a heading, 24 by a page title. One stroke
     for the whole set so two icons side by side are the same drawing. */
  --icon-xs:.75rem;
  --icon-sm:1rem;
  --icon-md:1.25rem;
  --icon-lg:1.5rem;
  --icon-xl:2.5rem;
  --icon-stroke:1.5;

  /* --- type -------------------------------------------------------------
     Every size comes with its own line height, on the 4px grid, so a
     line of 14px text is 22px tall wherever it is. Hangul is never set
     below 12px, and caption is for labels and figures — a sentence at
     12px belongs in small. */
  --t-caption:.75rem;     --lh-caption:1.125rem;
  --t-small:.8125rem;     --lh-small:1.25rem;
  --t-body:.875rem;       --lh-body:1.375rem;
  --t-body-lg:1rem;       --lh-body-lg:1.625rem;
  --t-heading:1rem;       --lh-heading:1.5rem;
  --t-title-3:1.125rem;   --lh-title-3:1.625rem;
  --t-title-2:1.5rem;     --lh-title-2:2rem;
  --t-title-1:1.875rem;   --lh-title-1:2.375rem;
  --t-display:clamp(2rem,4vw + 1rem,3rem); --lh-display:1.15;
  --t-code:.8125rem;      --lh-code:1.25rem;

  /* Nothing above 600: in a console, heavy text reads as a warning. */
  --w-normal:400;
  --w-medium:500;
  --w-bold:600;
  /* Only the home page's big figures: thin numerals read as numbers, not as shouting. */
  --w-light:300;

  /* No positive tracking on Hangul — the jamo are already far enough
     apart. Only headings tighten, and only a little. */
  --ls-tight:-.01em;
  --ls-display:-.025em;
  --ls-normal:0;
  --ls-wide:.04em;

  --shadow-1:0 1px 2px rgba(0,0,0,.05);
  --shadow-2:0 4px 12px rgba(0,0,0,.08);
  --shadow-3:0 16px 40px rgba(0,0,0,.14);

  --d-1:120ms;
  --d-2:200ms;
  --d-spin:600ms;
  --d-blink:1.1s;
  --ease:cubic-bezier(.2,0,0,1);

  /* Above its own section's background, below everything else. */
  --z-lift:1;
  --z-nav:30;
  --z-drop:40;
  --z-modal:50;
  --z-toast:60;

  --font:"OneEnd Sans","OneEnd Sans JP","Pretendard Variable",Pretendard,-apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",system-ui,sans-serif;
  /* The same stack without the Japanese file, for the one word 日本語 in the
     language menus of a page that is not Japanese: three glyphs are not worth
     1.4 MB, and the system draws them well enough. */
  --font-no-jp:"OneEnd Sans","Pretendard Variable",Pretendard,-apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",system-ui,sans-serif;
  /* Hangul in a code line falls through to the sans rather than to a
     system font that sets it a size larger. */
  --mono:"JetBrains Mono","OneEnd Sans","OneEnd Sans JP",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;

  /* 34rem because a line of SQL has to fit. prose is about forty Hangul
     syllables to a line at 16px. */
  --measure:34rem;
  --prose:42rem;
  --content:60rem;
  /* The console's working area: as wide as the screen gives, up to 100rem.
     60rem left a third of a wide screen empty beside the menus. */
  --content-app:100rem;
  --topbar:3rem;
  --sidebar:15rem;
  --sidebar-mini:3.5rem;
  --subbar:13rem;
  /* Widths that are not spacing: a column, a label, a line of help. Named so
     that the rules below carry no lengths of their own. */
  --width-label:8.5rem;
  --width-chip:14rem;
  --width-col:15rem;
  --width-col-sm:12rem;
  --width-note:30rem;
  --width-auth:56rem;
  --width-auth-modal:30rem;
  /* The switchers' panel, and how tall its list grows before it scrolls. */
  --width-switcher:20rem;
  --height-switcher:20rem;
  /* The operator's flow map: a section's tile, and how tall its chart grows. */
  --width-flow-tile:14rem;
  --height-flow:36rem;
  --width-agent:28rem;
  --width-drawer:44rem;
  /* Media queries cannot read a custom property, so these are the record of
     the three widths the rules below write out by hand. Change one, change
     the query with it. */
  --bp-sm:48rem;
  --bp-md:60rem;
  --bp-lg:80rem;

  /* --- the public pages --------------------------------------------------
     The home page is dark whatever the theme: it is a poster, not a
     workspace, and the brick wall it opens on is light on black. Its own
     names, so the console's palette and its contrast checks stay as they are.
     brick is the one orange in the logo, and on the home page the one thing
     alive on each screen. */
  --home-bg:#08090a;
  --home-tile:#0b0b0c;
  --home-tile-2:#111113;
  --home-line:#1d1d20;
  --home-line-2:#2a2a2e;
  --home-line-3:#3d3d42;
  --home-ink:#fafafa;
  --home-ink-2:#8e8e93;
  --home-ink-3:#808088;
  --home-num:#1c1c1f;
  --home-ok:#3ddc84;
  --home-ok-glow:rgba(61,220,132,.15);
  --home-ok-bg:#0f2418;
  --home-warn:#fbbf24;
  --home-warn-bg:#2a2110;
  --home-danger:#f87171;
  --home-danger-bg:#2a1414;
  --home-scrim:rgba(0,0,0,.92);
  /* The shading over the home page's two canvases: text has to read over the moving wall. */
  --home-shade-top:rgba(0,0,0,.65);
  --home-shade-side:rgba(0,0,0,.7);
  --home-shade-mid:rgba(0,0,0,.35);
  --home-shade-edge:rgba(0,0,0,.2);
  --brick:#ff5a1f;
  --brick-2:#ff8a5c;
  --brick-soft:#2b1206;
  --brick-ink:#ffd9c9;
  --brick-glow:rgba(255,90,31,.12);
  --brick-edge:rgba(255,90,31,.4);
  --home-hatch:repeating-linear-gradient(135deg,rgba(255,255,255,.028) 0 1px,transparent 1px 5px);
  --home-dots:radial-gradient(rgba(255,255,255,.06) .8px,transparent .8px) 0 0/5px 5px;
  --home-sparks:radial-gradient(rgba(255,90,31,.55) .9px,transparent 1px) 0 0/6px 6px;
  --home-rules:repeating-linear-gradient(90deg,var(--home-line) 0 1px,transparent 1px 2rem);
  /* Sizes on the scale's far side: a poster's type and spacing. */
  --space-20:5rem;
  --space-24:6rem;
  --space-32:8rem;
  --control-xl:3rem;
  --home-width:90rem;
  --home-gutter:2rem;
  --home-gutter-sm:1.25rem;
  --home-head:22rem;
  --home-measure:56rem;
  --home-nav:4rem;
  --home-notice:2.25rem;
  --home-hero:min(51.25rem,100svh);
  --home-hero-min:40rem;
  --home-side:18.75rem;
  --home-chat:23.75rem;
  --home-chat-min:38.75rem;
  --home-tile-h:18rem;
  --home-panel:27.5rem;
  --home-end:32.5rem;
  --home-spark-w:16.25rem;
  --home-edge:2.875rem;
  --home-node:1.875rem;
  --home-dot:.375rem;
  --home-caret-w:.4375rem;
  --home-caret-h:.9375rem;
  --t-hero:clamp(2.25rem,4.9vw,4.625rem);   --lh-hero:1.14;
  --t-lede:clamp(1.75rem,3.1vw,2.875rem);   --lh-lede:1.2;
  --t-num:clamp(6rem,11vw,10.5rem);          --lh-num:.86;
  --t-num-sm:5.25rem;
  --t-stat:clamp(3rem,5vw,4.75rem);          --lh-stat:1;
  --t-block:clamp(2.125rem,4.2vw,3.875rem); --lh-block:1.12;
  --t-end:clamp(2.5rem,5.2vw,5rem);          --lh-end:1.08;
  --t-wordmark:1.1875rem;                    --lh-wordmark:1.5rem;
  --ls-hero:-.04em;
  --ls-lede:-.035em;
  --ls-num:-.06em;
  --ls-label:.06em;

  /* Set by markup, one specimen at a time, so the guide can render a swatch
     for each colour without inventing any. Declared here so they are never
     undefined, which is the one way a var() fails silently. */
  --sw:var(--panel);
  --len:var(--space-3);
  --icon:var(--icon-sm);

  color-scheme:light;
}

/* Dark is different values for the same names — never different names, and
   never a rule that exists in only one theme. Both themes are neutral greys
   around the same orange, so switching looks like the same product with the
   lights on or off.

   Written twice on purpose. The media query is the system setting; the
   attribute is a choice the person made, and it has to win in both
   directions, so "dark" must beat a light system and "light" must beat a
   dark one. */
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --bg:#08090a;
  --panel:#0b0b0c;
  --card:#0e0e10;
  --raised:#161618;
  --hover:#161618;
  --scrim:rgba(0,0,0,.7);
  --ink:#fafafa;
  --muted:#a1a1a6;
  --faint:#8a8a90;
  --rule:#1d1d20;
  --rule-soft:#161618;
  --rule-strong:#2a2a2e;
  --border-control:#6b6b72;
  --ink-hover:#d4d4d8;
  --brand:#ff8a5c;
  --brand-hover:#ffa27d;
  --brand-ink:#08090a;
  --brand-soft:#2b1206;
  --focus-ring:#ff8a5c;
  --ok:#3ddc84;      --ok-bg:#0f2418;
  --warn:#fbbf24;    --warn-bg:#2a2110;
  --danger:#f87171;  --danger-bg:#2a1414;
  --c1:#ff8a5c;
  --c2:#7d93b5;
  --c3:#a08fc4;
  --c4:#b5a184;
  --c5:#6b6b72;
  /* A card on a dark page gets no shadow — height comes from the surface
     getting lighter. What floats over the page still casts one, darker,
     or a menu sits flat on the card it opened from. */
  --shadow-1:none;
  --shadow-2:0 4px 12px rgba(0,0,0,.5);
  --shadow-3:0 16px 40px rgba(0,0,0,.6);
  color-scheme:dark;
}}

:root[data-theme="dark"]{
  --bg:#08090a;
  --panel:#0b0b0c;
  --card:#0e0e10;
  --raised:#161618;
  --hover:#161618;
  --scrim:rgba(0,0,0,.7);
  --ink:#fafafa;
  --muted:#a1a1a6;
  --faint:#8a8a90;
  --rule:#1d1d20;
  --rule-soft:#161618;
  --rule-strong:#2a2a2e;
  --border-control:#6b6b72;
  --ink-hover:#d4d4d8;
  --brand:#ff8a5c;
  --brand-hover:#ffa27d;
  --brand-ink:#08090a;
  --brand-soft:#2b1206;
  --focus-ring:#ff8a5c;
  --ok:#3ddc84;      --ok-bg:#0f2418;
  --warn:#fbbf24;    --warn-bg:#2a2110;
  --danger:#f87171;  --danger-bg:#2a1414;
  --c1:#ff8a5c;
  --c2:#7d93b5;
  --c3:#a08fc4;
  --c4:#b5a184;
  --c5:#6b6b72;
  /* A card on a dark page gets no shadow — height comes from the surface
     getting lighter. What floats over the page still casts one, darker,
     or a menu sits flat on the card it opened from. */
  --shadow-1:none;
  --shadow-2:0 4px 12px rgba(0,0,0,.5);
  --shadow-3:0 16px 40px rgba(0,0,0,.6);
  color-scheme:dark;
}

/* On a phone, body text is 16px: 14px is a desk size read at arm's length,
   and iOS zooms the page into any field set below 16px. Everything sized as
   body — sentences, fields, buttons, table cells — moves together. */
@media (max-width:48rem){:root{--t-body:1rem;--lh-body:1.5rem}}

*{box-sizing:border-box}
html,body{height:100%}
/* Scrollbars are the palette's, not the browser's grey slab: thin, the
   thumb a strong rule on a clear track, the ink-faint colour under the
   pointer. The two menus show theirs only while the pointer is over them,
   as Supabase's and Vercel's menus do. */
:root{scrollbar-width:thin;scrollbar-color:var(--rule-strong) transparent}
*{scrollbar-width:thin;scrollbar-color:var(--rule-strong) transparent}
::-webkit-scrollbar{width:var(--space-2);height:var(--space-2)}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--rule-strong);border-radius:var(--radius-full);border:var(--line-2) solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background-color:var(--faint)}
::-webkit-scrollbar-corner{background:transparent}
.shell > .side,.sub-side{scrollbar-color:transparent transparent}
.shell > .side:hover,.sub-side:hover{scrollbar-color:var(--rule-strong) transparent}
.shell > .side::-webkit-scrollbar-thumb,.sub-side::-webkit-scrollbar-thumb{background-color:transparent}
.shell > .side:hover::-webkit-scrollbar-thumb,.sub-side:hover::-webkit-scrollbar-thumb{background-color:var(--rule-strong)}
body{margin:0;background:var(--bg);color:var(--ink);
  font:var(--w-normal) var(--t-body)/var(--lh-body) var(--font);
  -webkit-font-smoothing:antialiased}
a{color:var(--brand);text-decoration:none}
a:hover{text-decoration:underline}
/* A heading that breaks leaves one word alone on its second line unless
   told not to; a paragraph that breaks leaves one syllable. */
h1,h2,h3{margin:0;letter-spacing:var(--ls-tight);font-weight:var(--w-bold);text-wrap:balance}
/* A heading no component sizes is still on the scale. Without this an h2 in
   a form fell back to the browser's 1.5em (21px) and an h3 to 1.17em
   (16.38px). Zero specificity, so every component's own size wins. */
:where(h2,h3){font-size:var(--t-heading);line-height:var(--lh-heading)}
p{margin:0;text-wrap:pretty}
/* A list or a block of code no component spaces sits on the scale, not the
   browser's 1em (14px, 13px in code). */
:where(ul,ol,pre){margin-block:var(--space-3)}
dl,dd{margin:0}
code,.mono{font-family:var(--mono)}

/* --- top bar --------------------------------------------------------- */
.top{position:sticky;top:0;z-index:var(--z-nav);display:flex;align-items:center;gap:var(--space-3);
  height:var(--topbar);padding:0 var(--space-4);background:var(--bg);border-bottom:var(--line-1) solid var(--rule-soft)}
.brand{display:flex;align-items:center;gap:var(--space-2);min-height:var(--control-md);min-width:var(--control-md);font-weight:var(--w-bold);color:var(--ink);font-size:var(--t-heading);line-height:var(--lh-heading)}
.brand:hover{text-decoration:none}
.mark{width:var(--icon-md);height:var(--icon-md);flex:none;display:block}
/* The mark takes the page's colours, so it is dark on a light page and light
   on a dark one; only the middle brick is always the same orange. */
.mark-row{fill:var(--ink)}
.mark-key{fill:var(--brick);transition:transform var(--d-2) var(--ease)}
.brand:hover .mark-key{transform:translateX(var(--line-2))}
.crumb{display:flex;align-items:center;gap:var(--space-1);min-width:0;color:var(--muted)}
.crumb .sep{opacity:.45}
/* The switchers: the organisation's name and the project's name are the
   buttons that list the others, as on every console this one is measured
   against. A <details>, so they open without script; the script adds typing
   to filter and arrow keys. */
.switcher{min-width:0;max-width:100%}
.switcher > summary{list-style:none}
.switcher > summary::-webkit-details-marker{display:none}
.switcher-trigger{display:inline-flex;align-items:center;gap:var(--space-2);height:var(--control-md);padding:0 var(--space-2);
  border-radius:var(--radius-md);color:var(--ink);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);
  white-space:nowrap;cursor:pointer;user-select:none}
.switcher-trigger:hover,.switcher[open] > .switcher-trigger{background:var(--hover)}
.switcher-trigger > .ic{flex:none;color:var(--faint)}
.switcher-name{min-width:0;max-width:var(--width-col-sm);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.switcher-plan{flex:none}
.avatar{display:inline-grid;place-items:center;flex:none;width:var(--icon-md);height:var(--icon-md);border-radius:var(--radius-xs);
  background:var(--ink);color:var(--bg);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-bold)}
.avatar.lg{width:var(--control-md);height:var(--control-md);border-radius:var(--radius-md);font-size:var(--t-heading);line-height:var(--lh-heading)}
/* An organisation's chosen colour behind its initial (0240), from the chart
   palette so both themes hold it; an uploaded image fills the same box. */
.avatar.tone-ink{background:var(--ink);color:var(--bg)}
.avatar.tone-c1{background:var(--c1);color:var(--bg)}
.avatar.tone-c2{background:var(--c2);color:var(--bg)}
.avatar.tone-c3{background:var(--c3);color:var(--bg)}
.avatar.tone-c4{background:var(--c4);color:var(--bg)}
.avatar.tone-c5{background:var(--c5);color:var(--bg)}
img.avatar{object-fit:cover}
/* A favourite project's star is filled (0242). */
button[data-star][aria-pressed="true"] .ic{fill:currentColor}
/* The person's accent and density (0243): the links, focus and live colour
   from the chart palette, and tighter cards and tables for long lists. */
body[data-accent="c2"]{--brand:var(--c2);--brand-hover:var(--c2);--focus-ring:var(--c2)}
body[data-accent="c3"]{--brand:var(--c3);--brand-hover:var(--c3);--focus-ring:var(--c3)}
body[data-accent="c4"]{--brand:var(--c4);--brand-hover:var(--c4);--focus-ring:var(--c4)}
body[data-density="compact"] .card-body{padding:var(--space-3)}
body[data-density="compact"] td{padding:var(--space-1) var(--space-3)}
body[data-density="compact"] .section{margin:0 0 var(--space-5)}
/* A project's state as one dot: green up, amber paused, red failed, and a
   hollow ring while it is still being built. */
.state-dot{flex:none;width:var(--space-2);height:var(--space-2);border-radius:var(--radius-full);background:var(--faint)}
.state-dot.ok{background:var(--ok)}
.state-dot.warn{background:var(--warn)}
.state-dot.err{background:var(--danger)}
.state-dot.soon{background:none;border:var(--line-1) solid var(--faint)}
.switcher-panel{position:absolute;z-index:var(--z-drop);top:calc(100% + var(--space-1));left:0;width:var(--width-switcher);max-width:calc(100vw - var(--space-4));
  padding:var(--space-1);background:var(--raised);border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-3);animation:rise var(--d-1) var(--ease)}
/* The search results (the palette and /search) are the same rows as the
   switchers. Outside a .switcher-panel they were bullets, arrows and
   filled primary pills. */
:is(.switcher-panel,[data-search-results]) ul{margin:0;padding:0;list-style:none}
.switcher-panel hr{margin:var(--space-1) 0;border:0;border-top:var(--line-1) solid var(--rule)}
.switcher-list{margin:0}
.switcher-list ul{max-height:var(--height-switcher);overflow-y:auto}
.switcher-label{padding:var(--space-2) var(--space-3) var(--space-1);color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium)}
:is(.switcher-panel,[data-search-results]) :is(a,button){display:flex;align-items:center;justify-content:flex-start;gap:var(--space-2);width:100%;min-height:var(--control-md);padding:0 var(--space-3);
  border:0;border-radius:var(--radius-md);background:none;color:var(--ink);font-family:inherit;font-size:var(--t-body);line-height:var(--lh-body);
  font-weight:var(--w-normal);text-align:left;white-space:nowrap;cursor:pointer}
:is(.switcher-panel,[data-search-results]) :is(a,button):is(:hover,:focus-visible){background:var(--hover);text-decoration:none}
.switcher-panel button[aria-current]{font-weight:var(--w-medium)}
.switcher-list button{justify-content:flex-start}
.switcher-list .switcher-name{flex:1 1 auto;max-width:none}
/* The tick's place is kept on every row, so the plans line up down the list. */
.switcher-tick{flex:none;width:var(--icon-sm)}
.switcher-list button > .ic{flex:none;color:var(--brand)}
.switcher-links .ic{color:var(--muted)}
.switcher-more{color:var(--muted)}
.switcher-find{display:flex;align-items:center;gap:var(--space-2);margin:0 0 var(--space-1);padding:0 var(--space-3);
  border-bottom:var(--line-1) solid var(--rule-soft);color:var(--faint)}
.switcher-find[hidden]{display:none}
.switcher-find input[type=search]{height:var(--control-md);padding:0;border:0;border-radius:0;background:none}
.switcher-find input[type=search]:focus-visible{outline:none}
.switcher-none{padding:var(--space-2) var(--space-3);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.chip{display:inline-flex;align-items:center;gap:var(--space-1);max-width:var(--width-chip);height:var(--control-sm);padding:0 var(--space-2);
  border:var(--line-1) solid var(--rule);border-radius:var(--radius-sm);background:var(--card);color:var(--ink);
  font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.top .spacer{flex:1}
.top nav{display:flex;align-items:center;gap:var(--space-1)}
.top nav a{display:inline-flex;align-items:center;min-height:var(--control-md);padding:0 var(--space-3);border-radius:var(--radius-md);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.top nav a:hover{background:var(--panel);color:var(--ink);text-decoration:none}

/* --- app shell ------------------------------------------------------- */
.shell{display:grid;grid-template-columns:var(--sidebar) minmax(0,1fr);min-height:calc(100dvh - var(--topbar))}
.side{border-right:var(--line-1) solid var(--rule-soft);padding:var(--space-4) var(--space-2);background:var(--bg)}
/* The menu and the page scroll apart: the menu stays under the bar with a
   scroll of its own, and reaching its end never scrolls the page. */
.shell > .side{position:sticky;top:var(--topbar);align-self:start;height:calc(100dvh - var(--topbar));overflow-y:auto;overscroll-behavior:contain;
  scrollbar-gutter:stable}
.side .group{margin:0 0 var(--space-6)}
/* Korean takes no positive tracking — the jamo are spaced widely already — and
   there is no uppercase to reach for, so the group title separates itself by
   size and colour instead. */
.side .group-title{padding:0 var(--space-3) var(--space-1);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);
  color:var(--muted)}
.side a{display:flex;align-items:center;justify-content:flex-start;gap:var(--space-3);
  min-height:var(--control-md);padding:0 var(--space-3);border-radius:var(--radius-md);color:var(--ink);font-size:var(--t-small);line-height:var(--lh-small)}
.top .side-open,.side-close{display:none}
.side a:hover{background:var(--hover);text-decoration:none}
/* The icon is quieter than the word, and lights with it where you are. */
.side a .ic{color:var(--muted)}
.side a:hover .ic,.side a[aria-current] .ic{color:var(--ink)}
.side a .soon{margin-left:auto}
/* Where you are is a filled surface and nothing else. A left bar would read as
   the row marker the tables already use it for. */
/* Where you are: the raised row and the one orange edge, the brick from the mark. */
.side a[aria-current]{background:var(--raised);font-weight:var(--w-medium);box-shadow:inset var(--line-2) 0 0 var(--brand)}
/* A section's own pages: a second column beside the menu, under the
   section's name, as Supabase lays out its product menus. The page's tabs
   say the same thing, so on a screen wide enough for the column they go. */
.side a[aria-current="true"]{box-shadow:none}
.shell.has-sub{grid-template-columns:var(--sidebar) var(--subbar) minmax(0,1fr)}
.sub-side{position:sticky;top:var(--topbar);align-self:start;height:calc(100dvh - var(--topbar));overflow-y:auto;overscroll-behavior:contain;
  display:flex;flex-direction:column;gap:var(--space-05);padding:var(--space-4) var(--space-2);border-right:var(--line-1) solid var(--rule-soft);background:var(--bg)}
.sub-title{margin:0 0 var(--space-2);padding:0 var(--space-3) var(--space-3);border-bottom:var(--line-1) solid var(--rule);
  font-size:var(--t-body-lg);line-height:var(--lh-body-lg);font-weight:var(--w-bold);white-space:nowrap}
/* A heading is set apart three ways: a rule above it, its own colour and
   weight, and space. The pages under it are the ink; the heading is not. */
.sub-group{margin-top:var(--space-3);padding:var(--space-4) var(--space-3) var(--space-1);border-top:var(--line-1) solid var(--rule);
  font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-bold);color:var(--faint);white-space:nowrap}
.sub-title + .sub-group{margin-top:0;padding-top:var(--space-2);border-top:0}
.sub-side a{display:flex;align-items:center;min-height:var(--control-md);padding:0 var(--space-3);border-radius:var(--radius-md);
  color:var(--ink);font-size:var(--t-small);line-height:var(--lh-small);white-space:nowrap}
.sub-side a:hover{background:var(--hover);text-decoration:none}
.sub-side a[aria-current]{background:var(--raised);color:var(--ink);font-weight:var(--w-medium)}
@media (min-width:60.0625rem){.has-sub .tabs{display:none}}
@media (max-width:60rem){.sub-side{display:none}}
/* The menu folded to its icons (sideToggle): 3.5rem, the names in each row's
   title, the group names and a section's pages out of the way. Wide screens
   only; below --bp-md the menu is a drawer and never folds. */
.side-foot{position:sticky;bottom:calc(-1 * var(--space-4));margin:var(--space-2) calc(-1 * var(--space-2)) calc(-1 * var(--space-4));
  padding:var(--space-2);background:var(--bg);border-top:var(--line-1) solid var(--rule-soft)}
.side-toggle{display:flex;align-items:center;justify-content:flex-start;gap:var(--space-3);width:100%;min-height:var(--control-md);padding:0 var(--space-3);
  color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-normal);white-space:nowrap}
.side-toggle:hover{color:var(--ink)}
@media (max-width:60rem){.side-foot{display:none}}
@media (min-width:60.0625rem){
  :root[data-side="mini"] .shell{grid-template-columns:var(--sidebar-mini) minmax(0,1fr)}
  :root[data-side="mini"] .shell.has-sub{grid-template-columns:var(--sidebar-mini) var(--subbar) minmax(0,1fr)}
  :root[data-side="mini"] .side{padding-left:var(--space-1);padding-right:var(--space-1)}
  :root[data-side="mini"] .side .group{margin-bottom:var(--space-3)}
  :root[data-side="mini"] .side .group + .group{padding-top:var(--space-3);border-top:var(--line-1) solid var(--rule-soft)}
  :root[data-side="mini"] .side .group-title,:root[data-side="mini"] .side a span,:root[data-side="mini"] .side .soon,
  :root[data-side="mini"] .side-toggle span{display:none}
  :root[data-side="mini"] .side a,:root[data-side="mini"] .side-toggle{justify-content:center;padding:0}
  :root[data-side="mini"] .side-foot{margin-left:calc(-1 * var(--space-1));margin-right:calc(-1 * var(--space-1));padding:var(--space-2) var(--space-1)}
  :root[data-side="mini"] .side-toggle .ic{transform:scaleX(-1)}
}
/* The organisation's and the account's pages, behind the two names in the top bar. */
.top-menu > summary{cursor:pointer}
.top-account{margin-left:var(--space-2)}
.top-account > summary{gap:var(--space-2);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-normal)}
.top-account .menu-list{left:auto;right:0}
.top-account form,.top-account form button{width:100%}
/* The theme and language buttons again, inside the account menu, for a phone:
   with six buttons in the bar the project name was cut to one letter. */
.top-account .top-more{display:none}
@media (max-width:30rem){.top > .theme-toggle,.top > .lang-menu{display:none}.top-account .top-more{display:list-item}
  /* The mark too: the menu button beside it leads to the same pages, and its
     36px went to the project name. */
  .top > .brand{display:none}
  /* And the organisation, which the account menu reaches as 조직: at 320px
     its square and two slashes left the project two letters. */
  .crumb > .sep,.crumb > details:first-of-type{display:none}}
/* The bar's own controls beside the switchers (topbar.ts): the usage badge
   after the project, then 연결, feedback, help and the SQL editor on the
   right. Below --bp-md the last three leave the bar for the account menu
   (top-wide / top-narrow), and 연결 and the badge keep only their icons. */
a.usage-badge{flex:none;text-decoration:none}
.top-connect{flex:none}
.top-help .menu-list{left:auto;right:0}
.top-account .top-narrow{display:none}
.switcher-branch .switcher-trigger > .ic:first-child{color:var(--muted)}
@media (max-width:60rem){.top > .top-wide{display:none}.top-account .top-narrow{display:list-item}
  .top-connect span,a.usage-badge span,.switcher-branch .switcher-plan{display:none}
  .switcher-branch .switcher-name{max-width:var(--width-label)}}
/* 다른 서버에서 가져오기 (import-views.ts): the five steps as a row of
   names with this one marked, the source cards, our own drawn source mark,
   and the drawing of where a value is on the source's screens. */
.import-steps{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:0 0 var(--space-4);padding:0;list-style:none;
  counter-reset:step;font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.import-steps li{counter-increment:step;display:flex;align-items:center;gap:var(--space-1)}
.import-steps li::before{content:counter(step);display:inline-grid;place-items:center;width:var(--icon-sm);height:var(--icon-sm);
  border:1px solid var(--rule);border-radius:var(--radius-full)}
.import-steps li[aria-current="step"]{color:var(--ink);font-weight:var(--w-bold)}
.import-steps li[data-done]::before{border-color:var(--ink)}
.import-sources{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--width-col-sm),1fr));gap:var(--space-3);margin:0;padding:0;list-style:none}
.import-source h3{margin:0}
.import-mark{flex:none;color:var(--muted)}
.import-howto{margin:0;padding-left:var(--space-4);display:grid;gap:var(--space-1)}
/* The connect dialog: wider than a confirmation, because it holds code. */
/* 연결: a drawer from the right, the assistant's shape. One scroll, the
   drawer body's: the head stays put, and nothing inside it scrolls on its
   own (a key is one line that ends in …, its 복사 copies the whole of it). */
dialog.drawer{position:fixed;inset:0 0 0 auto;margin:0;width:min(100%,var(--width-drawer));max-width:100%;height:100dvh;max-height:100dvh;
  padding:0;border:0;border-left:var(--line-1) solid var(--rule);background:var(--raised);color:var(--ink);box-shadow:var(--shadow-3);overflow:hidden}
dialog.drawer[open]{display:flex;flex-direction:column;animation:drawer-in var(--d-2) var(--ease)}
dialog.drawer::backdrop{background:var(--scrim)}
@keyframes drawer-in{from{transform:translateX(var(--space-6));opacity:0}}
.drawer-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);flex:none;
  padding:var(--space-3) var(--space-3) var(--space-3) var(--space-6);border-bottom:var(--line-1) solid var(--rule-soft)}
.drawer-head h2{margin:0;font-size:var(--t-title-3);line-height:var(--lh-title-3)}
.drawer-body{flex:1;min-height:0;min-width:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:var(--space-5) var(--space-6) var(--space-8)}
.drawer-body p{color:var(--muted)}
.connect-panel{display:grid;gap:var(--space-4);min-width:0}
.connect-panel > *,.connect-body > *,.connect-step > *{min-width:0}
.connect-keys{display:grid;gap:var(--space-3)}
.connect-keys label{display:grid;gap:var(--space-1);min-width:0}
.connect-keys .field .value{max-height:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;user-select:all}
.connect-body pre.code{overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere}
.connect-tabs.tabs{margin:0;gap:var(--space-4)}
.connect-body .connect-tabs.tabs{flex-wrap:wrap;row-gap:var(--space-2);overflow:visible}
.connect-body > p:first-child{margin:0}
.connect-pick > button{height:var(--control-md)}
.connect-body{display:grid;gap:var(--space-3);min-width:0}
.connect-pick{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3);margin:0}
.connect-step{display:grid;gap:var(--space-2);min-width:0}
.connect-step h3{display:flex;align-items:center;gap:var(--space-2);margin:0;font-size:var(--t-heading);line-height:var(--lh-heading)}
.connect-step-n{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--icon-lg);height:var(--icon-lg);
  border-radius:var(--radius-full);background:var(--panel);color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium)}
.connect-files{display:flex;flex-wrap:wrap;gap:var(--space-1) var(--space-3);font-size:var(--t-small);line-height:var(--lh-small)}
.connect-prompt{display:grid;gap:var(--space-1);padding:var(--space-3);border:var(--line-1) solid var(--rule);border-radius:var(--radius-md);background:var(--panel)}
.connect-prompt .space-row{justify-content:space-between;margin:0}
.connect-prompt pre{white-space:pre-wrap;overflow-wrap:anywhere}
.side .soon{font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--muted);border:var(--line-1) solid var(--rule);
  border-radius:var(--radius-xs);padding:0 var(--space-1);font-weight:var(--w-medium)}
.main{min-width:0;padding:var(--space-8) var(--space-8) var(--space-16)}
.main-inner{max-width:var(--content-app);margin:0 auto}
/* A month, a bar a day. Sizes are SVG attributes; only colour is here. */
.chart{display:block;width:100%;height:auto}
.chart .bar{fill:var(--muted)}
.chart .bar.over{fill:var(--warn)}
.chart .base{fill:var(--rule)}
.flow-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--width-flow-tile),1fr));gap:var(--space-3)}
.flow-tile{display:block;padding:var(--space-3);border:1px solid var(--rule);border-radius:var(--radius-md);background:var(--panel);color:inherit;text-decoration:none}
.flow-tile:hover{border-color:var(--muted)}
.flow-tile.bad{border-color:var(--danger);background:var(--danger-bg)}
.flow-tile strong{display:block;overflow-wrap:anywhere}
.flow-tile .hint{margin:var(--space-1) 0 0}
.flow-svg{display:block;width:100%;height:auto;max-height:var(--height-flow)}
.flow-svg .box{fill:var(--panel);stroke:var(--rule)}
.flow-svg .box.here{fill:var(--brand-soft);stroke:var(--brand)}
.flow-svg .box.bad{fill:var(--danger-bg);stroke:var(--danger)}
.flow-svg .line{fill:none;stroke:var(--muted);stroke-width:1.5}
.flow-svg .line.bad{stroke:var(--danger);stroke-dasharray:5 4}
.flow-svg text{fill:var(--ink);font-size:var(--t-small);line-height:var(--lh-small)}
.flow-svg text.sub{fill:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption)}
.flow-svg text.bad{fill:var(--danger)}

/* A statement is printed — or saved as PDF from the print dialog — without
   the console around it. */
@media print{
  .top,.side,.sub-side,.no-print{display:none}
  .shell{display:block}
  .main{padding:0}
}
/* --bp-md */
@media (max-width:60rem){
  /* The section bar sits on top and keeps its own height: on a short page
     the grid used to stretch it to fill the screen. */
  .shell{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .side{align-self:start}
  .shell > .side{position:static;height:auto;overflow:visible}
  /* The menu is a drawer, not a strip to swipe: the bar's menu button opens
     the whole list, grouped and upright as on a desktop. It is the aside's
     own :target, so it opens without script and every link in it closes it
     by leaving the page. */
  .top .side-open{display:inline-flex}
  .shell > .side{display:none}
  .shell > .side:target{display:block;position:fixed;top:var(--topbar);left:0;right:0;bottom:0;z-index:var(--z-drop);
    overflow-y:auto;padding:var(--space-4) var(--space-3) var(--space-12);background:var(--bg);border:0;animation:rise var(--d-2) var(--ease)}
  .side-close{display:flex;justify-content:flex-end;margin:0 0 var(--space-2)}
  /* The account address is in the account page; on a phone the bar keeps
     the mark, the project and the way out. */
  .top nav{display:none}
  .top{gap:var(--space-1);padding:0 var(--space-3)}
  .chip{max-width:var(--width-col-sm)}
  /* On a phone the organisation is its square and the project keeps its
     name; either list opens across the screen under the bar. */
  .switcher-org .switcher-name,.switcher-org .switcher-plan{display:none}
  .switcher-trigger{max-width:100%;padding:0 var(--space-1)}
  .crumb{flex:1 1 auto}
  .top .spacer{display:none}
  .top-account > summary span{display:none}
  .switcher-trigger .switcher-name{max-width:var(--width-col-sm)}
  /* The project's name is the one word on the bar a person reads. With
     both chevrons, the branch's name and eight-pixel gaps it was left one
     syllable (기…). The branch keeps its icon; each trigger still opens its list. */
  .crumb .switcher-trigger > .ic:last-child,.switcher-branch .switcher-trigger .switcher-name{display:none}
  .switcher-panel{position:fixed;top:var(--topbar);left:var(--space-2);right:var(--space-2);width:auto;max-width:none}
  /* On a phone the way out is its icon; the name stays for screen readers. */
  button.logout span{display:none}
  button.logout{width:var(--control-md);padding:0}
  .side a{min-height:var(--control-lg)}
  .main{padding:var(--space-6) var(--space-4) var(--space-12)}
}

/* --- polish (#15, #32, #165) -------------------------------------------
   The bar along a phone's bottom edge: the four places opened most and the
   whole menu, where a thumb reaches. Below --bp-sm only; the drawer it opens
   covers it. */
.bottom-bar{display:none}
@media (max-width:48rem){
  .bottom-bar{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));position:fixed;left:0;right:0;bottom:0;z-index:var(--z-nav);
    padding:0 0 env(safe-area-inset-bottom);background:var(--bg);border-top:var(--line-1) solid var(--rule-soft)}
  .bottom-bar a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-05);min-height:var(--control-lg);
    padding:var(--space-1) 0;color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption)}
  .bottom-bar a:hover{color:var(--ink);text-decoration:none}
  .bottom-bar a[aria-current]{color:var(--ink);font-weight:var(--w-medium)}
  .main{padding-bottom:calc(var(--space-16) + var(--control-lg))}
}
@media print{.bottom-bar{display:none}}
/* A grid column's width, picked from four rather than typed: the content
   security policy refuses a style attribute. A pinned column stays at the
   left while the rest scroll under it; the script sets the offset of the
   second and third from the widths before them. */
.col-s{width:calc(var(--space-16) * 2);min-width:calc(var(--space-16) * 2);max-width:calc(var(--space-16) * 2)}
.col-m{width:calc(var(--space-16) * 3);min-width:calc(var(--space-16) * 3);max-width:calc(var(--space-16) * 3)}
.col-l{width:calc(var(--space-16) * 5);min-width:calc(var(--space-16) * 5);max-width:calc(var(--space-16) * 5)}
.col-xl{width:calc(var(--space-16) * 8);min-width:calc(var(--space-16) * 8);max-width:calc(var(--space-16) * 8)}
.col-s,.col-m,.col-l,.col-xl{overflow:hidden;text-overflow:ellipsis}
.col-pin{position:sticky;left:0;z-index:var(--z-lift);background:var(--card);box-shadow:inset calc(var(--line-1) * -1) 0 0 var(--rule-soft)}
/* A deployment's first screen, as the reconciler photographed it at 1280 by 800. */
.shot{display:block;width:100%;height:auto;aspect-ratio:1280 / 800;object-fit:cover;object-position:top;
  border:var(--line-1) solid var(--rule-soft);border-radius:var(--radius-md);background:var(--panel)}
.shot-thumb{width:calc(var(--space-16) * 2)}

/* --- tabs -------------------------------------------------------------
   Two faces of one subject. A different job is a different page, so there
   are only ever a handful of these and they never carry a filled
   background — the underline is the whole signal. */
/* The rule under the tabs is a shadow rather than a border so the row can
   scroll sideways on a phone without clipping the current tab's underline. */
.tabs{display:flex;gap:var(--space-6);margin:0 0 var(--space-6);
  box-shadow:inset 0 calc(var(--line-1) * -1) 0 var(--rule);overflow-x:auto;scrollbar-width:none}
.tabs a{flex:none;white-space:nowrap;padding:0 0 var(--space-2);color:var(--muted);
  font-size:var(--t-body);line-height:var(--lh-body);font-weight:var(--w-medium);border-bottom:var(--line-2) solid transparent}
.tabs a:hover{color:var(--ink);text-decoration:none}
.tabs a[aria-current]{color:var(--ink);border-bottom-color:var(--brand)}

/* --- pagination --------------------------------------------------------
   Belongs to a table. Put a table in without one and the screen falls apart
   the moment the list outgrows a page — quietly, because the rows that are
   missing are the ones nobody can see. */
.pager{display:flex;align-items:center;gap:var(--space-2);margin-top:var(--space-3);
  font-size:var(--t-small);line-height:var(--lh-small)}
.pager .btn[aria-disabled]{opacity:.45;pointer-events:none}
.pager .num{white-space:nowrap}
.pager .switch{display:flex;align-items:center;gap:var(--space-2)}
.search{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-3)}
.search input{flex:1 1 var(--width-col-sm);min-width:0}
/* A labelled field in a filter row keeps its name beside it, so the row stays
   one height (부터 above its date made that pair taller than the rest). */
.search > label{display:flex;align-items:center;gap:var(--space-2);flex:1 1 var(--width-col-sm);white-space:nowrap}
.search > label > :is(input,select){flex:1 1 auto;min-width:0}
/* A build setting's box with its Override switch beside it (views.buildSettingsFields). */
.override-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
.override-row > input{flex:1 1 var(--width-col-sm);min-width:0}
.override-row > .check{align-items:center}
input[type=search]{
  height:var(--control-md);padding:0 var(--space-3);border:var(--line-1) solid var(--border-control);border-radius:var(--radius-md);background:var(--card);
  color:var(--ink);font:inherit;font-size:var(--t-body);line-height:var(--lh-body);width:100%}

/* --- page furniture --------------------------------------------------- */
.page-head{margin:0 0 var(--space-8)}
/* A page title written without its head block still keeps the head's space
   below it: a notice or card under a bare h1 sat flush against the title. */
.main-inner > h1{margin:0 0 var(--space-6)}
.page-head h1{font-size:var(--t-title-1);line-height:var(--lh-title-1);letter-spacing:var(--ls-display)}
.page-head p{margin:var(--space-1) 0 0;color:var(--muted)}
/* A title with the page's one action beside it, on the right, as Vercel and Supabase put "Add". */
.page-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4);margin:0 0 var(--space-8)}
.page-head-row > .page-head{margin:0;min-width:0}
.page-head-row > .btn{flex:none;white-space:nowrap}
.section{margin:0 0 var(--space-8)}
/* A card straight on the page is a section of its own and keeps a section's distance. */
.main-inner > .card{margin-bottom:var(--space-8)}
.section > h2{font-size:var(--t-heading);line-height:var(--lh-heading);margin:0 0 var(--space-1)}
.section > .hint{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);margin:0 0 var(--space-3)}
/* A section's blocks never touch. A title with no hint under it sat 4px
   above its card; a hint, a list or a second title after a card sat on its
   border. Twelve between blocks, twenty-four above a sub-title. */
.section > h2:not(:has(+ .hint)){margin-bottom:var(--space-3)}
/* One class of weight, so a component's own pair rule (.snippet + .snippet,
   .grid > .card + .card) still decides its own gap. */
.section > :where(.card,.field,.empty,.snippet,.scroll-x,.rules,.signin-list,.result-tabs,.notice) + *,
.section > * + :is(.card,.field,.empty,.snippet,.signin-list,.notice){margin-top:var(--space-3)}
/* …including a hint or a block that asked for a smaller mt-* gap: after a box, twelve. */
.section > :where(.card,.field,.empty,.snippet,.scroll-x,.rules,.signin-list,.result-tabs,.notice) + :is(.hint,[class*="mt-"]){margin-top:var(--space-3)}
.hint + .notice{margin-top:var(--space-3)}
.section > * + h3{margin-top:var(--space-6)}
.section > h3 + *{margin-top:var(--space-2)}
/* Words under a button are its note, not its label: they never sit on it. */
.section > :where(form,.actions) + :is(p,.help,.hint,.card,.empty,div){margin-top:var(--space-2)}
.card-body > :is(p,.help,.hint) + form{margin-top:var(--space-3)}
/* A form straight on the page ends where a section would. */
.main-inner > :is(form,form.stack){margin-bottom:var(--space-8)}
/* A title inside a form starts a new group: more room above it than below. */
.stack > :is(h2,h3):not(:first-child){margin-top:var(--space-5)}

.card{border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);background:var(--card);box-shadow:var(--shadow-1)}
/* Stacked cards need a gap; cards in a grid already have one, and a margin
   there pushed every card after the first a step lower than its row. */
.card + .card{margin-top:var(--space-3)}
.grid > .card + .card{margin-top:0}
/* The import screen lays its source cards out in a grid too. Without this the
   second and third card sat twelve lower than the first, so the first looked
   taller than its row (2026-10-23). */
.import-sources > .card + .card{margin-top:0}
.card-body{padding:var(--space-5)}
/* A name over a value inside a card: a label, not a heading, with room above the next one. */
.card .card-body h3:has(+ .field){margin-bottom:0;font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);color:var(--muted)}
.card-body h3 + .field{margin-top:var(--space-2)}
.card .card-body .field + h3,.card .card-body dl + h3{margin-top:var(--space-5)}
.card-foot{padding:var(--space-3) var(--space-5);border-top:var(--line-1) solid var(--rule-soft);background:var(--panel);
  border-radius:0 0 var(--radius-lg) var(--radius-lg);font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.card h3{font-size:var(--t-heading);line-height:var(--lh-heading);margin:0 0 var(--space-1)}
/* A card that is itself the link — a list of documents, of projects. The
   whole card is the target, its title reads as a title rather than a link,
   and the edge darkens on hover so it is plain that it can be pressed. */
.card-link{display:block;color:var(--ink);text-decoration:none;transition:border-color var(--d-1) var(--ease)}
.card-link:hover{border-color:var(--border-control);text-decoration:none}
.card-title{font-size:var(--t-heading);line-height:var(--lh-heading);font-weight:var(--w-bold);color:var(--ink)}
.card-link:hover .card-title{color:var(--brand)}
/* Buttons that belong together, side by side, wrapping on a narrow screen. */
.actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
/* A get form that switches what a page shows (a month, a page size): one row. */
.switch{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
/* A form laid out as a row: labelled fields and the button sit on one baseline. */
.actions:has(> label){align-items:flex-end}
.actions > label.check{min-height:var(--control-md);align-items:center}
/* A field in a row of buttons is as wide as a column, not the whole row. */
.actions > input[type=text],.actions > input:not([type]),.actions > select{width:auto;flex:0 1 var(--width-col)}

.grid{display:grid;gap:var(--space-3)}
.grid.two{grid-template-columns:repeat(auto-fit,minmax(var(--width-col),1fr))}
.grid.three{grid-template-columns:repeat(auto-fit,minmax(max(var(--width-col-sm),calc((100% - 2 * var(--space-3)) / 3)),1fr))}

dl.facts{display:grid;grid-template-columns:minmax(var(--width-label),max-content) minmax(0,1fr);gap:var(--space-2) var(--space-4);font-size:var(--t-small);line-height:var(--lh-small)}
dl.facts dt{color:var(--muted)}
dl.facts dd{min-width:0;overflow-wrap:anywhere}

/* One caption line and two pixels above and below it: 22px. */
.badge{display:inline-flex;align-items:center;gap:var(--space-1);height:calc(var(--lh-caption) + var(--space-1));padding:0 var(--space-2);
  border-radius:var(--radius-full);
  font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);border:var(--line-1) solid transparent;
  white-space:nowrap;flex:none}
.badge.ok{color:var(--ok);background:var(--ok-bg);border-color:color-mix(in srgb,var(--ok) 30%,transparent)}
.badge.soon{color:var(--muted);background:var(--panel);border-color:var(--rule)}
.badge.warn{color:var(--warn);background:var(--warn-bg);border-color:color-mix(in srgb,var(--warn) 30%,transparent)}
.badge.err{color:var(--danger);background:var(--danger-bg);border-color:color-mix(in srgb,var(--danger) 30%,transparent)}
.dot{width:var(--space-2);height:var(--space-2);border-radius:var(--radius-full);background:currentColor}

/* --- utilities ---------------------------------------------------------
   A short, closed set. Every one of these replaced an inline style that was
   written slightly differently each time it was needed; a page that reaches
   for a style attribute is a page outside the system, and the suite refuses one.
   ----------------------------------------------------------------------- */
.m-0{margin:0}
.mt-1{margin-top:var(--space-1)}
.mt-2{margin-top:var(--space-2)}
.mt-3{margin-top:var(--space-3)}
.mb-2{margin-bottom:var(--space-2)}
.muted{color:var(--muted)}
.fine{font-size:var(--t-caption);line-height:var(--lh-caption)}
.small{font-size:var(--t-small);line-height:var(--lh-small)}
.title{font-size:var(--t-title-2);line-height:var(--lh-title-2)}
.stat{font-size:var(--t-title-2);line-height:var(--lh-title-2);font-weight:var(--w-bold);letter-spacing:var(--ls-tight);margin-top:var(--space-1)}
/* A figure's name is a quiet label above it: the number is what the eye looks for. */
.card .card-body h3:has(+ .stat){font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);color:var(--muted);margin:0}
.mid{vertical-align:middle}
.anywhere{overflow-wrap:anywhere}
.lead{color:var(--muted);margin-top:var(--space-1)}
.note{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);margin:var(--space-1) 0 var(--space-2)}
pre.code{margin:0;overflow-x:auto;font-family:var(--mono);font-size:var(--t-code);line-height:var(--lh-code)}
/* A box beside a label that may run to two lines: aligned to the first line,
   not the middle of the paragraph. */
.check{display:flex;flex-direction:row;align-items:flex-start;gap:var(--space-2);font-weight:var(--w-normal)}
.check input{width:auto}
/* --bp-sm: a choice's note goes under its name, lined up with it, instead of
   both squeezing into half a phone each (조직 / 멤버만 beside three lines). */
@media (max-width:48rem){
  .check{flex-wrap:wrap;row-gap:var(--space-05)}
  .check > .help{flex:1 0 100%;padding-left:calc(var(--icon-sm) + var(--space-2))}
}
@media (max-width:48rem) and (pointer:coarse){.check > .help{padding-left:calc(var(--icon-md) + var(--space-2))}}
/* The browser draws these, and each browser draws them differently — one
   size and the accent make them the same control everywhere. */
input[type=checkbox],input[type=radio]{flex:none;width:var(--icon-sm);height:var(--icon-sm);
  margin:calc((var(--lh-small) - var(--icon-sm)) / 2) 0 0;accent-color:var(--brand);cursor:pointer}
input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}
@media (pointer:coarse){
  input[type=checkbox],input[type=radio]{width:var(--icon-md);height:var(--icon-md);margin-top:0}
  .check{min-height:var(--control-lg);align-items:center}
}
select,textarea{padding:var(--space-2) var(--space-3);border:var(--line-1) solid var(--border-control);border-radius:var(--radius-md);
  background:var(--card);color:var(--ink);font:inherit;font-size:var(--t-body);line-height:var(--lh-body)}
/* Fields are the body size wherever they sit. Inheriting made one inside a
   <label> 13px and one beside it 14px in the same row, and anything under
   16px makes a phone zoom in when it is tapped. */
select{height:var(--control-md);max-width:100%;padding-block:0}
select:focus-visible,textarea:focus-visible{outline:var(--line-2) solid var(--focus-ring);outline-offset:-1px;border-color:var(--focus-ring)}
textarea.sql{width:100%;font-family:var(--mono);font-size:var(--t-code);line-height:var(--lh-code);resize:vertical}

/* --- specimens ---------------------------------------------------------
   Only the design guide uses these, and they exist so that it can too: the
   rule is that markup never carries a style, and a guide that broke it would
   be arguing against itself. The two that vary take a token through a custom
   property — markup may name a token, it may not invent a value.
   ----------------------------------------------------------------------- */
.swatch{display:flex;align-items:center;gap:var(--space-2)}
.swatch .chip-sw{width:var(--space-8);height:var(--space-8);border-radius:var(--radius-md);flex:none;
  border:var(--line-1) solid var(--rule);background:var(--sw)}
.space-row{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2)}
.space-row code{width:var(--width-label)}
.space-bar{height:var(--space-3);width:var(--len);background:var(--brand);border-radius:var(--radius-xs)}
.type-hero{font-size:var(--t-display);line-height:var(--lh-display);font-weight:var(--w-bold);letter-spacing:var(--ls-display);margin:0 0 var(--space-3)}
.type-title-1{font-size:var(--t-title-1);line-height:var(--lh-title-1);font-weight:var(--w-bold);letter-spacing:var(--ls-display);margin:0 0 var(--space-2)}
.type-head{font-size:var(--t-title-2);line-height:var(--lh-title-2);font-weight:var(--w-bold);letter-spacing:var(--ls-tight);margin:0 0 var(--space-2)}
.type-title-3{font-size:var(--t-title-3);line-height:var(--lh-title-3);font-weight:var(--w-bold);letter-spacing:var(--ls-tight);margin:0 0 var(--space-2)}
.type-heading{font-size:var(--t-heading);line-height:var(--lh-heading);font-weight:var(--w-bold);margin:0 0 var(--space-2)}
.type-body-lg{font-size:var(--t-body-lg);line-height:var(--lh-body-lg);margin:0 0 var(--space-2)}
.type-body{font-size:var(--t-body);line-height:var(--lh-body);margin:0 0 var(--space-1)}
.type-small{font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted);margin:0 0 var(--space-1)}
.type-fine{font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--muted);margin:0 0 var(--space-1)}
.type-code{font-family:var(--mono);font-size:var(--t-code);line-height:var(--lh-code);margin:0}
.icon-row{display:flex;align-items:flex-end;gap:var(--space-8);flex-wrap:wrap}
.icon-cell{display:grid;justify-items:center;gap:var(--space-2)}
.icon-box{width:var(--icon);height:var(--icon);border:var(--line-1) dashed var(--rule-strong);border-radius:var(--radius-xs)}
.guide{padding-top:var(--space-3)}

/* --- forms ------------------------------------------------------------ */
.auth{max-width:var(--measure);margin:0 auto;padding:var(--space-4) var(--space-4) var(--space-12)}
.auth.with-aside{max-width:var(--width-auth);display:grid;grid-template-columns:var(--measure) minmax(0,1fr);gap:var(--space-4);align-items:start}
/* --bp-sm */
@media (max-width:48rem){.auth.with-aside{grid-template-columns:1fr;gap:var(--space-6);max-width:var(--measure)}}
/* A stack is the same column wherever it is written: a form, a card's body,
   a dialog's body. Only form.stack was styled, so sixty-odd div.stack (cards,
   the connect dialog) laid their fields, keys and headings edge to edge.
   A fieldset keeps its own tighter grid. */
:is(form,div).stack{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-3)}
form.stack{margin:var(--space-4) 0 0}
/* …except as the first thing in a box, whose padding is already its margin. */
:is(.card-body,.modal-body,.stack) > form.stack:first-child{margin-top:0}
/* A form's own button is as wide as its words; .block asks for the full width. */
:is(form,div).stack > button:not(.block),:is(form,div).stack > .actions{justify-self:start}
/* A group of choices under one name: a plain group, never a box. It sits in
   a card almost everywhere, and a grey panel inside the card read as a box
   in a box; the legend is what names the group. */
fieldset{min-width:0;margin:0;padding:0;border:0;display:grid;gap:var(--space-3)}
/* A form shown but not usable on this plan (free-views.ts lockedForm): its fields keep the form's own layout. */
fieldset.locked{display:contents}
legend{padding:0;margin:0 0 var(--space-1);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);color:var(--muted)}
label{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:var(--space-1);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium)}
label .opt{font-weight:var(--w-normal);color:var(--muted)}
/* A text field is a text field whatever its type says — or whether it says
   one at all: an input with no type is text, and sixteen of them went
   unstyled because the selector only listed the types someone remembered. */
input:not([type]),input[type=email],input[type=password],input[type=text],input[type=url],
input[type=number],input[type=tel],input[type=date],input[type=time],input[type=datetime-local]{
  height:var(--control-md);padding:0 var(--space-3);border:var(--line-1) solid var(--border-control);border-radius:var(--radius-md);background:var(--card);
  color:var(--ink);font:inherit;font-size:var(--t-body);line-height:var(--lh-body);width:100%}
input::placeholder,textarea::placeholder{color:var(--faint)}
/* A file field was the browser's grey "Choose File" box in the middle of
   pill buttons. Its button is now a small secondary pill, its words a note. */
input[type=file]{max-width:100%;font:inherit;font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
input[type=file]::file-selector-button{min-height:var(--control-sm);margin-right:var(--space-3);padding:0 var(--space-3);
  border:var(--line-1) solid var(--border-control);border-radius:var(--radius-full);background:transparent;color:var(--ink);
  font:inherit;font-weight:var(--w-bold);cursor:pointer}
input:focus-visible{outline:var(--line-2) solid var(--focus-ring);outline-offset:-1px;border-color:var(--focus-ring)}
/* A sentence, so small rather than caption: 12px Hangul is for labels. */
.help{font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted);font-weight:var(--w-normal)}
/* A field's own error or confirmation, under it (principles 6-B B6). Words first, colour second. */
.help.field-error{color:var(--danger)}
.help.field-ok{color:var(--ok)}
/* Hover is a token, not a filter: brightness() lightened the orange in the
   light theme and did the same in the dark one, where the accent is
   already light — so the two themes disagreed about what hover means. */
/* Every button is a pill, as on the home page. The primary is the ink
   colour: one solid shape per screen, and the accent is left free to mark
   links, focus and what is live. */
button,.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  min-height:var(--control-md);padding:0 var(--space-4);border:var(--line-1) solid transparent;border-radius:var(--radius-full);
  background:var(--ink);color:var(--bg);font:inherit;font-size:var(--t-body);line-height:var(--lh-body);font-weight:var(--w-bold);
  cursor:pointer;white-space:nowrap;transition:background-color var(--d-1) var(--ease),border-color var(--d-1) var(--ease)}
.btn:hover{text-decoration:none;background:var(--ink-hover)}
button:hover{background:var(--ink-hover)}
button.secondary,.btn.secondary{background:transparent;color:var(--ink);border-color:var(--border-control)}
button.secondary:hover,.btn.secondary:hover{background:var(--hover)}
/* .btn.quiet as well as button.quiet: ten "삭제" and "연결 해제" links were
   written as <a class="quiet btn">, matched only .btn, and rendered as the
   filled primary — the loudest thing on the page for the one action the
   system says should never be the first thing the hand goes to. */
button.quiet,.btn.quiet{background:none;color:var(--muted);border-color:transparent;font-weight:var(--w-medium);padding:0 var(--space-3)}
button.quiet:hover,.btn.quiet:hover{background:var(--hover);color:var(--ink)}
button.small,.btn.small{min-height:var(--control-sm);padding:0 var(--space-3);
  font-size:var(--t-small);line-height:var(--lh-small)}
button.large,.btn.large{min-height:var(--control-lg);padding:0 var(--space-5);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
/* An icon inside anything takes the text's colour and the size its
   neighbour calls for. */
.ic{width:var(--icon-sm);height:var(--icon-sm);flex:none;fill:none;stroke:currentColor;
  stroke-width:var(--icon-stroke);stroke-linecap:round;stroke-linejoin:round;vertical-align:-.125em}
.ic.md{width:var(--icon-md);height:var(--icon-md)}
.ic.lg{width:var(--icon-lg);height:var(--icon-lg)}
/* A button that is only an icon is still a button-sized target. */
button.icon-only,.btn.icon-only{width:var(--control-md);padding:0}
/* The one that cannot be undone. Outlined rather than filled on purpose:
   it has to be findable without being the thing the hand goes to first. */
button.danger,.btn.danger{background:transparent;color:var(--danger);border-color:var(--danger)}
button.danger:hover,.btn.danger:hover{background:var(--danger-bg)}
button.block{width:100%}
.alt{margin:var(--space-4) 0 0;font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.notice{position:relative;padding:var(--space-3) var(--space-4) var(--space-3) calc(var(--space-4) + var(--icon-sm) + var(--space-2));border-radius:var(--radius-md);font-size:var(--t-small);line-height:var(--lh-small);margin:0 0 var(--space-4);
  border:var(--line-1) solid var(--rule)}
.notice::before{content:"";position:absolute;left:var(--space-4);top:calc(var(--space-3) + (var(--lh-small) - var(--icon-sm)) / 2);
  width:var(--icon-sm);height:var(--icon-sm);background:currentColor;mask:var(--icon-info) center / contain no-repeat}
.notice.error::before{mask-image:var(--icon-alert)}
.notice.warn::before{mask-image:var(--icon-warn)}
.notice.error{color:var(--danger);background:var(--danger-bg);
  border-color:color-mix(in srgb,var(--danger) 35%,transparent)}
.notice.warn{color:var(--warn);background:var(--warn-bg);
  border-color:color-mix(in srgb,var(--warn) 35%,transparent)}
/* A way out inside a notice is a word on the notice's line, not a 36px
   button: the button made the line taller than the icon beside it, which
   stayed at the top. */
/* Not when the notice has a row of buttons (.actions): a link beside a button
   would be a word next to a 36px pill, two sizes in one row. */
.notice .btn:not(.actions .btn){min-height:0;padding:0 var(--space-1);vertical-align:baseline;font-size:inherit;line-height:inherit;color:inherit;
  text-decoration:underline;text-underline-offset:var(--line-2)}
.notice .btn:not(.actions .btn):hover{background:transparent;color:inherit}
.notice .actions .btn:hover{text-decoration:none}

/* --- secret fields ---------------------------------------------------- */
.field{display:flex;align-items:stretch;gap:0;border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);
  background:var(--panel);overflow:hidden}
.field .value{flex:1;min-width:0;padding:var(--space-2) var(--space-3);font-size:var(--t-code);line-height:var(--lh-code);
  font-family:var(--mono);overflow-wrap:anywhere;
  max-height:calc(var(--lh-code) * 4 + var(--space-2) * 2);overflow:auto;user-select:all}
.field + .notice{margin-top:var(--space-2)}
.field .value[data-masked="1"]{filter:blur(5px);user-select:none;overflow:hidden;max-height:var(--space-10)}
.field .acts{display:flex;border-left:var(--line-1) solid var(--rule)}
.field .acts button{border-radius:0;background:var(--card);color:var(--muted);border:0;
  border-left:var(--line-1) solid var(--rule);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);
  min-height:var(--control-sm);padding:0 var(--space-3)}
.field .acts button:first-child{border-left:0}
.field .acts button:hover{background:var(--hover);color:var(--ink)}

/* A block of code to paste, named, with its install line and a copy button on the bar above it. */
.snippet{border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);background:var(--panel);overflow:hidden}
.snippet + .snippet{margin-top:var(--space-4)}
.snippet-head{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border-bottom:var(--line-1) solid var(--rule);background:var(--card)}
.snippet-head h3{flex:none;font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-bold)}
.snippet-note{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);
  font-size:var(--t-caption);line-height:var(--lh-caption)}
.snippet pre.code{padding:var(--space-4)}

/* --- empty / coming soon ---------------------------------------------- */
.empty > .ic{display:block;margin:0 auto var(--space-3);color:var(--faint)}
/* A note under an empty state belongs to the section, not to the box: it gets room. */
.empty + p{margin-top:var(--space-3)}
.empty{border:var(--line-1) dashed var(--rule-strong);border-radius:var(--radius-lg);padding:var(--space-12) var(--space-6);text-align:center;
  background:var(--panel)}
.empty h3{font-size:var(--t-heading);line-height:var(--lh-heading);margin:0 0 var(--space-1)}
.empty p{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);max-width:var(--width-note);margin:0 auto}
.empty .steps + p{margin-top:var(--space-4)}
/* An empty state's next step sits under its sentence, centred with it. */
.empty .actions{justify-content:center;margin-top:var(--space-4)}
.empty > form{margin-top:var(--space-4)}
.empty .steps{display:inline-grid;gap:var(--space-1);margin:var(--space-4) 0 0;text-align:left;font-size:var(--t-small);line-height:var(--lh-small);
  color:var(--muted)}

/* --- tables ------------------------------------------------------------ */
table{width:100%;border-collapse:collapse;font-size:var(--t-small);line-height:var(--lh-small)}
th{text-align:left;font-weight:var(--w-bold);color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption);padding:var(--space-2) var(--space-4);
  border-bottom:var(--line-1) solid var(--rule-soft)}
td{padding:var(--space-3) var(--space-4);border-bottom:var(--line-1) solid var(--rule-soft);vertical-align:top}
/* A cell breaks a word only when the word alone is wider than the column.
   overflow-wrap:anywhere (from body) also let the table squeeze a column to one
   syllable a line (로/그/인) to make room for a long neighbour. */
:where(td,th),:where(td,th) :where(code,.mono){overflow-wrap:break-word}
tr:last-child td{border-bottom:0}
/* A column title broken between syllables reads as two words. */
th{white-space:nowrap}
/* A status, a commit, a time or a count is one word: it never breaks.
   A long name or URL breaks instead, and only at its own punctuation. */
.nowrap,td time,td .badge{white-space:nowrap}
/* A checkbox in a cell is a control with its word: it does not wrap under itself. */
td > label.check{white-space:nowrap}
/* The overview while it is being arranged: one line above each section with
   its name, a handle and two arrows; outside arranging there is none. */
.arrange-head{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-3);margin:0 0 var(--space-4)}
.arrange-item{margin:0 0 var(--space-4);padding:var(--space-2) var(--space-3) var(--space-3);border:var(--line-1) dashed var(--rule);border-radius:var(--radius-lg)}
.arrange-item > .section{margin:var(--space-2) 0 var(--space-4)}
.arrange-item > .section:last-child{margin-bottom:0}
.arrange-bar{display:flex;align-items:center;gap:var(--space-1);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);cursor:grab}
.arrange-name{flex:1;min-width:0;font-weight:var(--w-medium);color:var(--ink)}
/* --bp-sm */
@media (max-width:48rem){th,td{padding-left:var(--space-3);padding-right:var(--space-3)}}
.scroll-x{overflow-x:auto}

/* --- the database screens ----------------------------------------------
   A list beside its subject, as in every database tool: the tables, or the
   saved queries and recent runs, on the left, and the one chosen on the
   right. One column on a narrow screen, the list first. */
.db-split{display:grid;grid-template-columns:var(--width-col-sm) minmax(0,1fr);gap:var(--space-6);align-items:start}
/* --bp-md */
@media (max-width:60rem){.db-split{grid-template-columns:minmax(0,1fr)}}
.db-side{display:grid;gap:var(--space-2);min-width:0}
.db-side h3{margin:var(--space-3) 0 0;color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-bold)}
.db-side .search{margin:var(--space-2) 0 0}
.db-side ul{display:grid;gap:var(--space-05);margin:0;padding:0;list-style:none;max-height:calc(100dvh - var(--space-16) * 3);overflow-y:auto}
.db-side li{display:flex;align-items:center;gap:var(--space-1);min-width:0}
.db-side li > a{flex:1;display:flex;align-items:center;gap:var(--space-2);min-width:0;min-height:var(--control-sm);padding:0 var(--space-2);
  border-radius:var(--radius-sm);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.db-side li > a span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.db-side li > a .ic{flex:none;color:var(--faint)}
.db-side li > a:hover{background:var(--hover);color:var(--ink);text-decoration:none}
.db-side li > a[aria-current]{background:var(--brand-soft);color:var(--ink);font-weight:var(--w-medium)}
.db-side li > a .dot{flex:none;color:var(--ok)}
.db-side li > a .dot.error{color:var(--danger)}
/* The saved-query menu holds a rename form, which wants the room of a field. */
.db-side .menu-list{min-width:var(--width-col)}
.db-side .menu-list form{padding:var(--space-1)}

/* A table's rows as a grid: one line each, a long value cut at the edge and
   read whole on the row's own page. A cell that can be changed in place says
   so with its cursor; one being changed holds its input, NULL and a message. */
.data-grid th,.data-grid td{padding-top:var(--space-2);padding-bottom:var(--space-2)}
.data-grid td{max-width:var(--width-col);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.data-grid th a{color:inherit}
.data-grid th[aria-sort] a{color:var(--ink)}
.data-grid td[data-col]{cursor:cell}
.data-grid td[data-col]:hover{background:var(--hover)}
.data-grid td[data-editing]{max-width:none;overflow:visible;padding:var(--space-1);background:var(--card);white-space:normal}
.data-grid [data-cell-editor]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);min-width:var(--width-col)}
.data-grid [data-cell-editor] > input:not([type=checkbox]),.data-grid [data-cell-editor] > select{flex:1;width:auto;min-width:0;height:var(--control-sm)}
.data-grid [data-cell-editor] > textarea{flex:1 1 100%;font-family:var(--mono);font-size:var(--t-code);line-height:var(--lh-code)}
.data-grid [data-cell-error]{flex:1 1 100%;color:var(--danger);font-size:var(--t-caption);line-height:var(--lh-caption)}
.data-grid td[data-state=saving]{opacity:.6}
.data-grid tr[data-selected] td{background:var(--brand-soft)}
/* NULL is not a value, so it is not drawn like one. */
.is-null{color:var(--faint);font-style:italic}

/* Several fields that make one thing, on one line: a filter, a column. */
.field-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-2)}
.field-row + .field-row{margin-top:var(--space-2)}
.field-row > label:not(.check),.field-row > select,.field-row > input{flex:1 1 var(--width-label);min-width:0;width:auto}
.field-row > .check{flex:none;align-self:center}

/* One tab per statement when a run had several. Pills rather than the page's
   underlined tabs: these switch a result in place, not the page. */
.result-tabs{display:flex;flex-wrap:wrap;gap:var(--space-1);margin:var(--space-3) 0 0}
.result-tabs a{display:inline-flex;align-items:center;min-height:var(--control-sm);padding:0 var(--space-3);border:var(--line-1) solid var(--rule);
  border-radius:var(--radius-full);color:var(--muted);font-family:var(--mono);font-size:var(--t-small);line-height:var(--lh-small)}
.result-tabs a:hover{background:var(--hover);color:var(--ink);text-decoration:none}
.result-tabs a[aria-current]{background:var(--ink);border-color:var(--ink);color:var(--bg)}
.actions .spacer{flex:1}
/* A field among buttons (the SQL box's "저장할 이름") is as wide as a column, not the row. */
.actions > input[type=text]{flex:0 1 var(--width-col-sm);width:auto;min-width:0}

/* --- marketing --------------------------------------------------------- */
.hero{max-width:var(--content);margin:0 auto;padding:var(--space-16) var(--space-4) var(--space-12);text-align:center}
.hero h1{font-size:var(--t-display);line-height:var(--lh-display);letter-spacing:var(--ls-display)}
.hero .lede{margin:var(--space-5) auto 0;max-width:var(--measure);color:var(--muted);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.hero .cta{display:flex;gap:var(--space-3);justify-content:center;margin:var(--space-8) 0 0;flex-wrap:wrap}
.hero .cta .btn{min-height:var(--control-lg);padding:0 var(--space-5);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.hero .under{margin:var(--space-4) 0 0;font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.strip{max-width:var(--content);margin:0 auto;padding:0 var(--space-4) var(--space-8)}
.strip .grid{gap:var(--space-4)}
.strip .card-body h3{font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted);font-weight:var(--w-bold)}
.strip .card-body .big{font-size:var(--t-title-3);line-height:var(--lh-title-3);font-weight:var(--w-bold);margin:var(--space-1) 0}
.strip .card-body p{font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.status-list{display:grid;gap:var(--space-2)}
.status-list .line{display:flex;align-items:baseline;gap:var(--space-2);font-size:var(--t-small);line-height:var(--lh-small)}
.status-list .line .what{flex:1}
.foot{border-top:var(--line-1) solid var(--rule-soft);margin-top:var(--space-8)}
.foot .inner{max-width:var(--content);margin:0 auto;padding:var(--space-4);display:flex;flex-wrap:wrap;gap:var(--space-3) var(--space-6);
  align-items:center;font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.foot .inner .spacer{flex:1}
/* The business identification 전자상거래법 제10조 requires on the pages a
   customer actually sees. Its own row, because a registration number wrapped
   into a line of navigation is technically present and practically invisible,
   and "쉽게 알 수 있도록" is the standard the article sets. */
.foot .who{max-width:var(--content);margin:0 auto;padding:0 var(--space-4) var(--space-4);
  font-size:var(--t-caption);color:var(--muted);line-height:var(--lh-caption)}
.foot .who b{font-weight:var(--w-bold);color:var(--muted)}
.foot .who .sep{opacity:.45;padding:0 var(--space-2)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* --- the public top bar -------------------------------------------------
   One bar for every public page: the mark and the menu on the left where the
   eye starts, the two ways in on the right. It takes the page's colours, so
   it is light over the pricing table and black over the home page. */
.site-notice{position:relative;display:flex;align-items:center;justify-content:center;gap:var(--space-2);height:var(--home-notice);
  padding:0 var(--space-4);overflow:hidden;border-bottom:var(--line-1) solid var(--rule-soft);color:var(--muted);
  font-size:var(--t-small);line-height:var(--lh-small);white-space:nowrap}
.site-notice::before,.site-notice::after{content:"";position:absolute;top:0;bottom:0;width:var(--home-spark-w);background:var(--home-sparks);opacity:.55}
.site-notice::before{left:0;mask-image:linear-gradient(90deg,var(--home-bg),transparent)}
.site-notice::after{right:0;mask-image:linear-gradient(-90deg,var(--home-bg),transparent)}
.site-notice a{position:relative;z-index:var(--z-lift);display:inline-flex;align-items:center;gap:var(--space-1);color:var(--ink);font-weight:var(--w-medium)}
.site-top{position:sticky;top:0;z-index:var(--z-nav);background:color-mix(in srgb,var(--bg) 92%,transparent);
  backdrop-filter:saturate(1.4) blur(5px);border-bottom:var(--line-1) solid var(--rule-soft)}
.site-top-in{display:flex;align-items:center;gap:var(--space-12);max-width:var(--home-width);height:var(--home-nav);
  margin:0 auto;padding:0 var(--home-gutter)}
.site-top .brand{font-size:var(--t-wordmark);line-height:var(--lh-wordmark);letter-spacing:var(--ls-lede);white-space:nowrap;flex:none}
.site-top .mark{width:var(--icon-lg);height:var(--icon-lg)}
.site-nav{display:flex;align-items:center;gap:var(--space-1)}
.site-nav a{display:inline-flex;align-items:center;min-height:var(--control-md);padding:0 var(--space-3);border-radius:var(--radius-full);
  color:var(--muted);font-size:var(--t-body);line-height:var(--lh-body);font-weight:var(--w-medium);transition:color var(--d-1) var(--ease)}
.site-nav a:hover{color:var(--ink);text-decoration:none}
.site-actions{display:flex;align-items:center;gap:var(--space-2);margin-left:auto}
.site-pill{display:inline-flex;align-items:center;gap:var(--space-2);min-height:var(--control-md);padding:0 var(--space-4);
  border-radius:var(--radius-full);font-size:var(--t-body);line-height:var(--lh-body);font-weight:var(--w-bold);white-space:nowrap;
  transition:background-color var(--d-1) var(--ease),border-color var(--d-1) var(--ease)}
.site-pill:hover{text-decoration:none}
.site-pill .ic{transition:transform var(--d-2) var(--ease)}
.site-pill:hover .ic{transform:translateX(var(--space-05))}
.site-pill-ghost{color:var(--ink);border:var(--line-1) solid var(--rule-strong)}
.site-pill-ghost:hover{background:var(--hover)}
.site-pill-fill{color:var(--bg);background:var(--ink);border:var(--line-1) solid var(--ink)}
.site-pill-fill:hover{background:var(--muted);border-color:var(--muted)}
.site-pill-lg{min-height:var(--control-xl);padding:0 var(--space-6);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
/* On a narrow screen the menu folds into one button beside "start". */
.site-actions details.site-more{display:none}
.site-more > summary{padding:0 var(--space-3);cursor:pointer}
.site-more .menu-list{left:auto;right:0}
/* --bp-md */
@media (max-width:60rem){.site-nav,.site-actions > .site-login{display:none}.site-actions details.site-more{display:inline-block}
  .site-top-in{gap:var(--space-4);padding:0 var(--home-gutter-sm)}}
/* The languages sit in the folded menu only on a phone, where the bar has no
   room for their button: at 360px the bar was 374px wide and the page scrolled
   sideways. */
.site-more .site-more-lang{display:none}
@media (max-width:30rem){.site-actions > .lang-menu{display:none}.site-more .site-more-lang{display:list-item}}

/* --- the home page ------------------------------------------------------
   Black ground, two greys for two-tone headlines, and orange for the one
   thing that is moving on each screen. The console's own names point at the
   home palette here, so the top bar, the footer and every shared component
   follow without a rule of their own. */
.home{--bg:var(--home-bg);--panel:var(--home-tile);--card:var(--home-tile);--raised:var(--home-tile-2);--hover:var(--home-tile-2);
  --ink:var(--home-ink);--muted:var(--home-ink-2);--faint:var(--home-ink-3);
  --rule:var(--home-line);--rule-soft:var(--home-line);--rule-strong:var(--home-line-2);--border-control:var(--home-line-3);
  --brand:var(--brick-2);--brand-hover:var(--brick);--brand-ink:var(--home-bg);--brand-soft:var(--brick-soft);--focus-ring:var(--brick-2);
  --ok:var(--home-ok);--ok-bg:var(--home-ok-bg);--warn:var(--home-warn);--warn-bg:var(--home-warn-bg);
  --danger:var(--home-danger);--danger-bg:var(--home-danger-bg);--scrim:var(--home-scrim);--shadow-1:none;
  color-scheme:dark;background:var(--home-bg);word-break:keep-all}
.home ::selection{background:var(--brick);color:var(--home-bg)}
.home-w{max-width:var(--home-width);margin:0 auto;padding:0 var(--home-gutter)}
.home-tag{display:flex;align-items:center;gap:var(--space-3);font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);
  font-weight:var(--w-medium);letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--ink)}
.home-tag::before{content:"";border-left:var(--space-2) solid var(--brick);border-top:var(--space-1) solid transparent;border-bottom:var(--space-1) solid transparent}

.home-hero{position:relative;height:var(--home-hero);min-height:var(--home-hero-min);overflow:hidden;border-bottom:var(--line-1) solid var(--home-line)}
.home-wall{position:absolute;inset:0;width:100%;height:100%;display:block}
.home-hero::after,.home-end::after{content:"";position:absolute;inset:0;pointer-events:none}
.home-hero::after{background:linear-gradient(180deg,var(--home-shade-top) 0%,transparent 22%,transparent 45%,var(--home-scrim) 78%,var(--home-bg) 100%),
  linear-gradient(90deg,var(--home-shade-side) 0%,transparent 55%)}
.home-hero-in{position:relative;z-index:var(--z-lift);height:100%;display:flex;flex-direction:column;justify-content:flex-end;padding-bottom:var(--space-16)}
.home-h1{margin-top:var(--space-5);font-size:var(--t-hero);line-height:var(--lh-hero);letter-spacing:var(--ls-hero);font-weight:var(--w-bold)}
.home-h1 span{display:block;color:var(--muted)}
.home-geo{margin-top:var(--space-4);font-size:var(--t-body);line-height:var(--lh-body);color:var(--muted)}
.home-cta{display:flex;flex-wrap:wrap;gap:var(--space-3);margin-top:var(--space-10)}
.home-detect{display:flex;align-items:center;gap:var(--space-12);margin-top:var(--space-16);padding-top:var(--space-6);
  border-top:var(--line-1) solid var(--home-line);overflow:hidden}
.home-detect-k{font-size:var(--t-small);line-height:var(--lh-small);color:var(--faint);white-space:nowrap}
.home-detect-row{display:flex;gap:var(--space-12);margin:0;padding:0;list-style:none;white-space:nowrap;
  font-size:var(--t-wordmark);line-height:var(--lh-wordmark);font-weight:var(--w-bold);letter-spacing:var(--ls-display);color:var(--muted);
  mask-image:linear-gradient(90deg,var(--home-bg) 80%,transparent)}

.home-sec{padding-top:var(--space-32);scroll-margin-top:var(--home-nav)}
.home-head{display:grid;grid-template-columns:var(--home-head) minmax(0,1fr)}
.home-num{margin-top:var(--space-5);font-family:var(--mono);font-size:var(--t-num);line-height:var(--lh-num);font-weight:var(--w-light);
  letter-spacing:var(--ls-num);color:var(--home-num)}
.home-lede{font-size:var(--t-lede);line-height:var(--lh-lede);letter-spacing:var(--ls-lede);font-weight:var(--w-medium)}
.home-lede span{color:var(--muted)}

/* 01: the tools an assistant gets, and what it does with them */
.home-mcp{display:grid;grid-template-columns:var(--home-side) minmax(0,1fr) var(--home-chat);margin-top:var(--space-24);min-height:var(--home-chat-min)}
.home-feats{display:grid;align-content:start;gap:var(--space-10);padding:var(--space-10) var(--space-10) 0 0}
.home-feat h3{font-size:var(--t-title-3);line-height:var(--lh-title-3);letter-spacing:var(--ls-display)}
.home-feat p{margin-top:var(--space-2);color:var(--muted);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.home-connect{margin-top:var(--space-2)}
.home-connect code{display:inline-block;margin-top:var(--space-2);padding:var(--space-1) var(--space-2);border:var(--line-1) solid var(--home-line-2);
  border-radius:var(--radius-sm);background:var(--home-tile);color:var(--brick-2);font-size:var(--t-code);line-height:var(--lh-code)}
.home-lines{position:relative;border-left:var(--line-1) solid var(--home-line);background:var(--home-rules);
  mask-image:linear-gradient(180deg,var(--home-bg) 70%,transparent)}
.home-flow{position:absolute;left:50%;top:var(--space-10);transform:translateX(-50%);display:grid;justify-items:center;margin:0;padding:0;list-style:none}
.home-node{display:flex;align-items:center;gap:var(--space-2);height:var(--home-node);padding:0 var(--space-3);border-radius:var(--radius-full);
  font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);
  background:var(--home-tile-2);border:var(--line-1) solid var(--home-line-2);color:var(--ink);white-space:nowrap}
.home-node-root{background:var(--home-ink);border-color:var(--home-ink);color:var(--home-bg)}
.home-node-ok{border-color:var(--brick-edge);color:var(--brick-2)}
.home-node-ok::after,.home-live::before,.home-tree-ok::after{content:"";width:var(--home-dot);height:var(--home-dot);border-radius:var(--radius-full);
  background:var(--home-ok);box-shadow:0 0 0 var(--line-2) var(--home-ok-glow)}
.home-edge{position:relative;width:var(--line-1);height:var(--home-edge);background:linear-gradient(180deg,var(--home-line-2),var(--brick) 50%,var(--home-line-2))}
.home-edge::after{content:"";position:absolute;left:50%;top:50%;width:var(--home-dot);height:var(--home-dot);border:var(--line-1) solid var(--brick);
  border-radius:var(--radius-full);background:var(--home-bg);transform:translate(-50%,-50%)}
.home-chat{border-radius:var(--radius-xl);overflow:hidden;display:flex;flex-direction:column;border:var(--line-1) solid var(--home-line-2);background:var(--home-tile)}
.home-chat-top{display:flex;align-items:center;justify-content:space-between;height:var(--control-lg);padding:0 var(--space-4);
  border-bottom:var(--line-1) solid var(--home-line);font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.home-chat-top b{display:flex;align-items:center;gap:var(--space-2);color:var(--ink);font-weight:var(--w-medium)}
.home-chat-top .mark{width:var(--icon-sm);height:var(--icon-sm)}
.home-chat-top span{font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption)}
.home-chat-body{flex:1;display:grid;align-content:start;gap:var(--space-4);padding:var(--space-5) var(--space-4)}
.home-bubble{justify-self:end;max-width:86%;padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);background:var(--home-tile-2);
  font-size:var(--t-body);line-height:var(--lh-body)}
.home-steps{display:grid;gap:var(--space-3);margin:0;padding:0;list-style:none}
.home-step{display:flex;align-items:center;gap:var(--space-3);font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--muted)}
.home-step b{color:var(--ink);font-weight:var(--w-medium)}
.home-step .ic{width:var(--icon-sm);height:var(--icon-sm);color:var(--home-ok)}
.home-reply{font-size:var(--t-body);line-height:var(--lh-body);color:var(--ink)}
.home-chat-in{margin:var(--space-4);padding:var(--space-3);border:var(--line-1) solid var(--home-line-2);border-radius:var(--radius-lg);
  font-size:var(--t-small);line-height:var(--lh-small);color:var(--faint)}
.home-caret{display:inline-block;width:var(--home-caret-w);height:var(--home-caret-h);margin-left:var(--space-05);background:var(--brick);
  vertical-align:-.125em;animation:blink var(--d-blink) steps(1) infinite}
@keyframes blink{50%{opacity:0}}
/* --bp-lg */
@media (max-width:80rem){.home-mcp{grid-template-columns:1fr;min-height:0}.home-lines{display:none}
  .home-feats{grid-template-columns:repeat(3,minmax(0,1fr));padding:0 0 var(--space-10)}}

/* 02: one fragment of the real product under each part of it */
.home-stair{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:var(--space-4);margin-top:var(--space-20);align-items:start}
.home-stair > :nth-child(2){margin-top:var(--space-16)}
.home-stair > :nth-child(3){margin-top:var(--space-32)}
.home-stair > :nth-child(4){margin-top:var(--space-6)}
.home-stair > :nth-child(5){margin-top:var(--space-24)}
.home-cap{min-height:var(--space-16);font-size:var(--t-title-3);line-height:var(--lh-title-3);letter-spacing:var(--ls-display);color:var(--muted)}
.home-cap b{color:var(--ink);font-weight:var(--w-bold)}
.home-frag{border-radius:var(--radius-xl);position:relative;height:var(--home-tile-h);margin-top:var(--space-6);overflow:hidden;
  border:var(--line-1) solid var(--home-line);background:var(--home-hatch),var(--home-tile)}
.home-frag::after{content:"";position:absolute;left:0;right:0;bottom:0;height:var(--space-12);pointer-events:none;
  background:linear-gradient(180deg,transparent,var(--home-tile))}
.home-tree{position:absolute;inset:var(--space-6) 0 0;display:grid;justify-items:center;align-content:start}
.home-tree-v{width:var(--line-1);height:var(--space-6);background:linear-gradient(180deg,var(--home-line-2),var(--home-ok))}
.home-px{position:absolute;left:var(--space-4);right:var(--space-4);bottom:var(--space-10);display:grid;
  grid-template-columns:repeat(22,minmax(0,1fr));grid-template-rows:repeat(9,minmax(0,1fr));grid-auto-flow:column;gap:var(--space-05);height:var(--space-32)}
.home-px i{background:var(--home-line)}
.home-px .home-px-on{background:var(--brick)}
.home-px .home-px-mid{background:var(--brick-soft)}
.home-legend{position:absolute;z-index:var(--z-lift);left:var(--space-4);bottom:var(--space-3);display:flex;gap:var(--space-4);
  font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--muted)}
.home-legend span::before{content:"";display:inline-block;width:var(--space-2);height:var(--space-2);margin-right:var(--space-2);background:var(--brick)}
.home-legend span + span::before{background:var(--home-line-2)}
.home-win{position:absolute;inset:var(--space-4) var(--space-4) auto;overflow:hidden;border:var(--line-1) solid var(--home-line-2);
  border-radius:var(--radius-md);background:linear-gradient(180deg,var(--home-tile-2),var(--home-tile))}
.home-win-h{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);height:var(--space-8);padding:0 var(--space-3);
  border-bottom:var(--line-1) solid var(--home-line);font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);
  letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--faint)}
.home-live{display:flex;align-items:center;gap:var(--space-2);color:var(--home-ok)}
.home-win-b{padding:var(--space-3)}
.home-si-t{font-size:var(--t-body);line-height:var(--lh-body);font-weight:var(--w-bold)}
.home-si-l{display:block;margin-top:var(--space-2);font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--muted)}
.home-si-i{display:flex;align-items:center;height:var(--control-md);margin-top:var(--space-1);padding:0 var(--space-3);overflow:hidden;
  border:var(--line-1) solid var(--home-line-2);border-radius:var(--radius-sm);background:var(--home-bg);color:var(--faint);
  font-size:var(--t-small);line-height:var(--lh-small);white-space:nowrap}
.home-si-or{display:flex;align-items:center;gap:var(--space-2);margin:var(--space-2) 0;font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--faint)}
.home-si-or::before,.home-si-or::after{content:"";flex:1;height:var(--line-1);background:var(--home-line)}
.home-sso{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)}
.home-sso span{display:flex;align-items:center;justify-content:center;gap:var(--space-2);height:var(--control-md);
  border:var(--line-1) solid var(--home-line-2);border-radius:var(--radius-sm);background:var(--home-tile-2);
  font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-bold);white-space:nowrap}
.home-sso svg{width:var(--icon-xs);height:var(--icon-xs);flex:none;fill:currentColor}
.home-file{position:relative;display:grid;grid-template-columns:var(--icon-sm) minmax(0,1fr) auto;align-items:center;gap:var(--space-3);
  height:var(--control-lg);padding:0 var(--space-3);border-bottom:var(--line-1) solid var(--home-line);
  font-size:var(--t-caption);line-height:var(--lh-caption)}
.home-file:last-child{border-bottom:0}
.home-file .ic{color:var(--muted)}
.home-file-n{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.home-file em{font-style:normal;color:var(--faint);white-space:nowrap}
.home-file-up em{color:var(--brick-2)}
.home-file-up::after{content:"";position:absolute;left:0;bottom:-1px;width:64%;height:var(--line-2);background:linear-gradient(90deg,transparent,var(--brick))}
.home-job{display:grid;gap:var(--space-2);padding:var(--space-3);border-bottom:var(--line-1) solid var(--home-line)}
.home-job:last-child{border-bottom:0}
.home-job-r{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);white-space:nowrap}
.home-job b{font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-bold)}
.home-job code{padding:0 var(--space-1);border:var(--line-1) solid var(--home-line-2);border-radius:var(--radius-xs);color:var(--muted);
  font-size:var(--t-caption);line-height:var(--lh-caption)}
.home-job small{font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--faint)}
.home-ok{display:inline-flex;align-items:center;gap:var(--space-1);font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--home-ok)}
.home-ok::before{content:"";width:var(--space-1);height:var(--space-1);border-radius:var(--radius-full);background:currentColor}
/* --bp-lg */
@media (max-width:80rem){.home-stair{grid-template-columns:repeat(2,minmax(0,1fr))}.home-stair > *{margin-top:0}}

/* 03: what a translation reaches, and who you can sign in with */
.home-lang{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-8);margin-top:var(--space-20);align-items:stretch}
.home-hl{display:grid;align-content:start;gap:var(--space-3);margin:0;padding:0;list-style:none}
.home-hl li{width:fit-content;padding:var(--space-05) var(--space-5) var(--space-1);background:var(--brick-soft);color:var(--brick-ink);
  font-size:var(--t-block);line-height:var(--lh-block);font-weight:var(--w-bold);letter-spacing:var(--ls-lede)}
.home-hl-note{margin-top:var(--space-4);color:var(--muted);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.home-idp{border-radius:var(--radius-xl);overflow:hidden;display:grid;grid-template-rows:auto 1fr;border:var(--line-1) solid var(--home-line);background:var(--home-dots),var(--home-bg)}
.home-idp .home-win-h{height:var(--space-10);padding:0 var(--space-4)}
.home-idp-g{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:1fr;margin:0;padding:0;list-style:none}
.home-idp-g li{display:flex;flex-direction:column;justify-content:space-between;gap:var(--space-6);min-height:var(--space-24);padding:var(--space-4);
  border-top:var(--line-1) solid var(--home-line);border-left:var(--line-1) solid var(--home-line);
  font-size:var(--t-body-lg);line-height:var(--lh-body-lg);font-weight:var(--w-medium);transition:background var(--d-2) var(--ease)}
.home-idp-g li:nth-child(3n+1){border-left:0}
.home-idp-g li::before{content:"";width:var(--space-2);height:var(--space-2);border-radius:var(--radius-xs);background:var(--home-line-2)}
.home-idp-g li:hover,.home-idp-g .home-idp-on{background:linear-gradient(180deg,var(--brick-glow),transparent 70%)}
.home-idp-g li:hover::before,.home-idp-g .home-idp-on::before{background:var(--brick)}
.home-stats{border-radius:var(--radius-xl);overflow:hidden;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:var(--space-8) 0 0;border:var(--line-1) solid var(--home-line)}
/* The number reads first; the label stays first in the markup, where a list of terms puts it. */
.home-stat{display:flex;flex-direction:column-reverse;justify-content:flex-end;padding:var(--space-8) var(--space-6) var(--space-6);border-left:var(--line-1) solid var(--home-line);background:var(--home-dots)}
.home-stat:first-child{border-left:0}
.home-stat dd{font-size:var(--t-stat);line-height:var(--lh-stat);font-weight:var(--w-light);letter-spacing:var(--ls-hero)}
.home-stat dt{margin-top:var(--space-4);color:var(--muted);font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.home-stat dt span{display:block}
/* --bp-md */
@media (max-width:60rem){.home-lang{grid-template-columns:1fr}.home-stats{grid-template-columns:repeat(2,minmax(0,1fr))}
  .home-stat:nth-child(3){border-left:0}.home-stat:nth-child(n+3){border-top:var(--line-1) solid var(--home-line)}}

/* 04: the published plans, read from the rows the invoice uses */
.home-price{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:var(--space-4);margin-top:var(--space-20)}
.home-plan{border-radius:var(--radius-xl);display:flex;flex-direction:column;padding:var(--space-8);border:var(--line-1) solid var(--home-line);background:var(--home-hatch),var(--home-tile)}
.home-plan-paid{border-color:var(--brick-edge);
  background:radial-gradient(70% 90% at 100% 0%,var(--brick-glow),transparent 60%),var(--home-hatch),var(--home-tile)}
.home-plan h3{font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);
  letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--muted)}
.home-plan-paid h3{color:var(--brick-2)}
.home-plan-a{margin-top:var(--space-4);font-size:var(--t-title-1);line-height:var(--lh-title-1);font-weight:var(--w-bold);letter-spacing:var(--ls-display)}
.home-plan-a small{margin-left:var(--space-1);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium);color:var(--muted)}
.home-plan-go{justify-content:center;margin-top:var(--space-6)}
.home-plan-note{margin-top:var(--space-4);color:var(--muted)}
.home-plan dl{display:grid;grid-template-columns:minmax(0,1fr) auto;margin-top:var(--space-8);border-top:var(--line-1) solid var(--home-line)}
.home-plan dt,.home-plan dd{padding:var(--space-3) 0;border-bottom:var(--line-1) solid var(--home-line);font-size:var(--t-body);line-height:var(--lh-body)}
.home-plan dt{color:var(--muted)}
.home-plan dd{text-align:right;font-weight:var(--w-medium)}
.home-more{display:inline-flex;margin-top:var(--space-6);font-weight:var(--w-medium)}
/* --bp-md */
@media (max-width:60rem){.home-price{grid-auto-flow:row;grid-template-columns:1fr}}

.home-end{position:relative;height:var(--home-end);margin-top:var(--space-32);overflow:hidden;
  border-top:var(--line-1) solid var(--home-line);border-bottom:var(--line-1) solid var(--home-line)}
.home-end::after{background:radial-gradient(60% 80% at 30% 60%,var(--home-scrim),var(--home-shade-mid) 70%,var(--home-shade-edge))}
.home-end-in{position:relative;z-index:var(--z-lift);height:100%;display:flex;flex-direction:column;justify-content:center}
.home-end h2{font-size:var(--t-end);line-height:var(--lh-end);letter-spacing:var(--ls-hero)}
.home-end h2 span{color:var(--muted)}
.home .foot{margin-top:0}
.home .foot .inner a{color:var(--muted)}
.home .foot .inner a:hover{color:var(--ink);text-decoration:none}
.home .foot .inner,.home .foot .who{max-width:var(--home-width);padding-left:var(--home-gutter);padding-right:var(--home-gutter)}
.home .foot .inner{padding-top:var(--space-12)}

/* --bp-md */
@media (max-width:60rem){.home-w{padding:0 var(--home-gutter-sm)}.home-head{grid-template-columns:1fr}
  .home-num{margin:var(--space-4) 0 var(--space-5);font-size:var(--t-num-sm);line-height:var(--lh-num)}.home-sec{padding-top:var(--space-24)}
  .home-feats{grid-template-columns:1fr;gap:var(--space-6)}.home-detect{gap:var(--space-6)}.home-cap{min-height:0}}
/* --bp-sm */
@media (max-width:48rem){.home-stair{grid-template-columns:1fr}}
@media (prefers-reduced-motion:reduce){.home-caret{animation:none}}


/* --- the other public pages, in the home page's clothes -------------------
   Docs and prices open with the same tag and two-tone title as a home
   section, then the content in tiles and tables on the same dark ground. */
.home-page{padding-top:var(--space-20);padding-bottom:var(--space-24)}
/* A page's own title stacks under its tag instead of beside it: the title
   is the page, not one section of it. */
.home-page .home-head{grid-template-columns:minmax(0,1fr);gap:var(--space-5)}
.home-page .home-lede{max-width:var(--home-measure)}
.home-group{margin:var(--space-16) 0 var(--space-6)}
.home-group h2{font-size:var(--t-title-2);line-height:var(--lh-title-2);letter-spacing:var(--ls-display)}
.home-group p{margin-top:var(--space-1);color:var(--muted)}
.home-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4)}
.home-tile{border-radius:var(--radius-xl);position:relative;display:flex;flex-direction:column;gap:var(--space-2);min-height:var(--space-32);padding:var(--space-6);
  border:var(--line-1) solid var(--home-line);background:var(--home-hatch),var(--home-tile);color:var(--ink);
  transition:border-color var(--d-2) var(--ease),background var(--d-2) var(--ease)}
.home-tile:hover{text-decoration:none;border-color:var(--brick-edge);
  background:radial-gradient(80% 100% at 100% 0%,var(--brick-glow),transparent 60%),var(--home-hatch),var(--home-tile)}
.home-tile-n{font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);color:var(--faint)}
.home-tile h3{font-size:var(--t-title-3);line-height:var(--lh-title-3);letter-spacing:var(--ls-display)}
.home-tile p{color:var(--muted);font-size:var(--t-body);line-height:var(--lh-body)}
.home-tile-go{position:absolute;right:var(--space-5);top:var(--space-5);color:var(--faint);transition:color var(--d-2) var(--ease),transform var(--d-2) var(--ease)}
.home-tile:hover .home-tile-go{color:var(--brick-2);transform:translateX(var(--space-05))}
.home-connect-band{border-radius:var(--radius-xl);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-3);margin-top:var(--space-12);
  padding:var(--space-4) var(--space-5);border:var(--line-1) solid var(--home-line);background:var(--home-dots),var(--home-tile);color:var(--muted)}
.home-connect-band code{color:var(--brick-2);font-size:var(--t-code);line-height:var(--lh-code)}
/* --bp-md */
@media (max-width:60rem){.home-tiles{grid-template-columns:1fr}.home-page{padding-top:var(--space-12)}}

/* One document: the list of all of them in the margin, the text at reading width. */
.home-docs{display:grid;grid-template-columns:var(--width-col) minmax(0,1fr);gap:var(--space-16);padding-top:var(--space-12);padding-bottom:var(--space-16)}
.home-docs-solo{grid-template-columns:minmax(0,1fr)}
.home-docs-side{position:sticky;top:calc(var(--home-nav) + var(--space-6));align-self:start;max-height:calc(100dvh - var(--home-nav) - var(--space-12));overflow:auto}
.home-docs-side h2{margin:var(--space-6) 0 var(--space-2);font-size:var(--t-small);line-height:var(--lh-small);
  font-weight:var(--w-bold);color:var(--ink)}
.home-docs-side h2:first-child{margin-top:0}
.home-docs-side ul{margin:0;padding:0;list-style:none}
.home-docs-side a{display:block;padding:var(--space-1) var(--space-3);border-left:var(--line-1) solid var(--home-line);color:var(--muted);
  font-size:var(--t-small);line-height:var(--lh-small)}
.home-docs-side a:hover{color:var(--ink);text-decoration:none}
.home-docs-side a[aria-current]{color:var(--ink);border-left-color:var(--brick)}
.home-docs .home-docs-main{min-width:0;margin:0;padding:0;max-width:var(--prose)}
.home-docs-main .home-tag a{color:inherit}
.home-docs-h1{margin-top:var(--space-4);font-size:var(--t-lede);line-height:var(--lh-lede);letter-spacing:var(--ls-lede)}
.home-docs-lead{margin:var(--space-3) 0 var(--space-10);color:var(--muted)}
/* --bp-md */
@media (max-width:60rem){.home-docs{grid-template-columns:1fr;gap:var(--space-8)}.home-docs-side{display:none}}

/* Prices: the note under the plans, and the comparisons in plain tables. */
.home-status{display:flex;align-items:center;gap:var(--space-5);margin-top:var(--space-12);padding:var(--space-8);
  border:var(--line-1) solid var(--home-line);border-radius:var(--radius-xl);background:var(--home-hatch),var(--home-tile)}
.home-status h2{font-size:var(--t-title-2);line-height:var(--lh-title-2);letter-spacing:var(--ls-display)}
.home-status p{margin-top:var(--space-1);color:var(--muted)}
.home-status-dot{flex:none;width:var(--space-4);height:var(--space-4);border-radius:var(--radius-full);background:var(--faint);
  box-shadow:0 0 0 var(--space-2) var(--home-tile-2)}
.home-status-ok .home-status-dot{background:var(--ok);box-shadow:0 0 0 var(--space-2) var(--ok-bg)}
.home-status-warn{border-color:var(--brick-edge)}
.home-status-warn .home-status-dot{background:var(--warn);box-shadow:0 0 0 var(--space-2) var(--warn-bg)}
.home-status-list{margin-top:var(--space-4);padding:0;list-style:none;border:var(--line-1) solid var(--home-line);border-radius:var(--radius-xl);background:var(--home-tile);overflow:hidden}
.home-status-list li{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;align-items:center;gap:var(--space-8);
  padding:var(--space-4) var(--space-6);border-top:var(--line-1) solid var(--home-line)}
.home-status-list li:first-child{border-top:0}
.home-status-name{font-weight:var(--w-medium)}
.home-status-fig{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.home-status-fig b{color:var(--ink);font-weight:var(--w-medium)}
@media (max-width:60rem){
  .home-status{padding:var(--space-6)}
  .home-status-list li{grid-template-columns:minmax(0,1fr) auto;gap:var(--space-2) var(--space-4);padding:var(--space-4)}
}
.home-note{border-radius:var(--radius-xl);margin-top:var(--space-6);padding:var(--space-6);border:var(--line-1) solid var(--home-line);background:var(--home-tile)}
.home-note h2{font-size:var(--t-title-3);line-height:var(--lh-title-3)}
.home-note p{margin-top:var(--space-2);color:var(--muted)}
.home-note .home-note-sub{color:var(--faint);font-size:var(--t-small);line-height:var(--lh-small)}
.home-compares{display:grid;gap:var(--space-6);margin-top:var(--space-16)}
.home-compare{border-radius:var(--radius-xl);min-width:0;padding:var(--space-6);border:var(--line-1) solid var(--home-line);background:var(--home-tile)}
.home-compare h3{font-size:var(--t-title-3);line-height:var(--lh-title-3)}
.home-compare > p{margin:var(--space-1) 0 var(--space-4);color:var(--muted)}
.home-compare table{width:100%}
.home-compare .home-compare-src{margin:var(--space-4) 0 0;color:var(--faint);font-size:var(--t-caption);line-height:var(--lh-caption)}


/* --- signing in, on the dark surface --------------------------------------
   The same forms as a page or over one: log in and sign up open as dialogs
   from the top bar, and /login and /signup stay as pages for a link, a
   bookmark, an error and a browser without script. */
.home .auth{padding-top:var(--space-16);padding-bottom:var(--space-20)}
.home .auth .title{font-size:var(--t-title-1);line-height:var(--lh-title-1);letter-spacing:var(--ls-display)}
/* A single form on a public page sits on a card of its own, as wide as the
   dialog that opens it from the top bar, so the two read as one thing. */
.home .auth:not(.with-aside),.auth-card{padding:var(--space-8);border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);background:var(--panel)}
.home .auth:not(.with-aside){max-width:var(--width-auth-modal);margin:var(--space-16) auto var(--space-20)}
/* --bp-sm */
@media (max-width:48rem){.home .auth:not(.with-aside),.auth-card{padding:var(--space-5)}
  .home .auth:not(.with-aside){margin:var(--space-8) var(--space-4) var(--space-12)}}
/* Google and GitHub above the email form: full width, the provider's own mark. */
.auth-social{display:grid;gap:var(--space-2);margin:var(--space-5) 0 0}
.auth-provider{width:100%}
/* Passkey, SSO and code sign-in under the main button: one group, spaced like the social buttons. */
.auth-signup{margin:var(--space-5) 0 0}
.auth-signup .alt{margin:0 0 var(--space-2)}
.auth-extras{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--space-1) var(--space-3);margin:var(--space-4) 0 0}
.auth-extras .btn,.auth-extras button{white-space:nowrap}
.auth-extras [hidden]{display:none}
.auth-mark{width:var(--icon-md);height:var(--icon-md);flex:none}
.auth-or{display:flex;align-items:center;gap:var(--space-3);margin:var(--space-4) 0 0;color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.auth-or::before,.auth-or::after{content:"";flex:1;border-top:var(--line-1) solid var(--rule)}
.auth-or + form.stack{margin-top:var(--space-3)}
.auth-aside{padding:var(--space-6);border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);
  background:var(--home-hatch),var(--card)}
.auth-aside h2{font-size:var(--t-heading);line-height:var(--lh-heading)}
.auth-aside ul{display:grid;gap:var(--space-3);margin:var(--space-4) 0;padding:0;list-style:none}
.auth-aside li{display:flex;align-items:flex-start;gap:var(--space-2);font-size:var(--t-body);line-height:var(--lh-body)}
.auth-aside li .ic{margin-top:var(--space-05);color:var(--ok)}
.auth-aside p{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
dialog.auth-modal{width:min(100% - var(--space-8),var(--width-auth-modal))}
.auth-modal .modal-body h2{font-size:var(--t-title-2);line-height:var(--lh-title-2)}
.auth-modal .alt{margin-top:var(--space-3);font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.auth-close{position:absolute;top:var(--space-3);right:var(--space-3);margin:0}

/* --- the six states ---------------------------------------------------
   Everything that can be pressed has all six. A component missing one is
   unfinished, so they are written once here rather than per component. */

/* Focus is never removed, for any reason. For somebody working from the
   keyboard it is the cursor. Outside the element rather than inside, so it
   is visible on a filled button as well as an outlined one. */
:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
  outline:var(--line-2) solid var(--focus-ring);
  outline-offset:var(--line-2);
  border-radius:var(--radius-xs)}

/* Hover only where there is a pointer. On a touch screen it sticks after a
   tap and the last thing touched looks selected. */
@media (hover:hover){
  :where(button.secondary,button.quiet,.btn.secondary,.btn.quiet,.side a,.top nav a,tbody tr):hover{background:var(--hover)}
}

/* A finger is not a pointer. On a touch screen every control is the large
   one, whatever the page asked for — 44 is what a thumb hits. */
/* Plain selectors, not :where() — this has to beat the size each component
   sets for itself, and a zero-specificity rule beats nothing. */
@media (pointer:coarse){
  button,.btn,button.small,.btn.small,select,input:not([type]),input[type=email],input[type=password],
  input[type=text],input[type=url],input[type=number],input[type=tel],input[type=search],
  input[type=date],input[type=time],input[type=datetime-local],
  .side a,.top nav a,.field .acts button,.menu-list a,.menu-list button{min-height:var(--control-lg)}
}

:where(button,.btn):active{transform:scale(.97);transition:transform var(--d-1) var(--ease)}

:where(button,.btn,input,select,textarea)[disabled],
:where(button,.btn)[aria-disabled=true]{
  opacity:.45;pointer-events:none;cursor:not-allowed}

/* Waiting keeps its size. A control that shrinks into a spinner shoves
   whatever is beside it. */
:where(button,.btn)[aria-busy=true]{color:transparent;position:relative}
:where(button,.btn)[aria-busy=true]::after{
  content:"";position:absolute;inset:50% auto auto 50%;
  width:var(--icon-sm);height:var(--icon-sm);margin:calc(var(--icon-sm) / -2) 0 0 calc(var(--icon-sm) / -2);
  border:var(--line-2) solid currentColor;border-top-color:transparent;
  border-radius:var(--radius-full);color:var(--ink);
  animation:spin var(--d-spin) linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* --- numbers ----------------------------------------------------------
   Columns of figures only line up if the digits are the same width. */
:where(td,th,.stat,.num,time,.mono,code){font-variant-numeric:tabular-nums}
/* The row's name stays on one line when a wide table scrolls, rather
   than being squeezed one syllable to a line. */
.scroll-x td:first-child,.scroll-x td.mono{white-space:nowrap}
/* --bp-sm: on a phone every cell keeps its line and the table scrolls sideways,
   instead of a short word standing one syllable per line. */
@media (max-width:48rem){.scroll-x td{white-space:nowrap}}
/* A number broken across lines reads as two numbers. */
:where(td,th).num,:where(td.num) *{white-space:nowrap}

/* Hangul breaks on word boundaries, not between jamo. */
body{word-break:keep-all;overflow-wrap:break-word}
/* break-word, not anywhere: anywhere also lets a box shrink to one letter
   (a radio's title stood 비밀 / 번호, a table column 로 / 그 / 인). A word
   still breaks when it alone is wider than its line. What is long by
   nature (code, IDs, keys) may break anywhere. */
:where(code,kbd,samp,.mono){overflow-wrap:anywhere}

/* --- motion -----------------------------------------------------------
   Two durations exist. A third would be somebody's taste rather than a
   decision, and a long animation makes a tool feel slow. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:1ms!important;animation-iteration-count:1!important;
    transition-duration:1ms!important;scroll-behavior:auto!important}
}

/* A public page's part shown on the design guide: the home page's own surface
   and colours, inside the console's guide. */
.guide-stage{overflow:hidden;border:var(--line-1) solid var(--rule);border-radius:var(--radius-xl);background:var(--bg);color:var(--ink)}

/* --- theme switch ------------------------------------------------------ */
.theme-switch{display:inline-flex;gap:var(--space-1);padding:var(--space-1);
  background:var(--panel);border-radius:var(--radius-full)}
.theme-switch button{border:0;background:transparent;color:var(--muted);cursor:pointer;
  padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);
  font:var(--w-medium) var(--t-caption)/var(--lh-caption) var(--font);
  transition:background var(--d-1) var(--ease),color var(--d-1) var(--ease)}
.theme-switch button[aria-pressed=true]{background:var(--card);color:var(--ink);
  box-shadow:var(--shadow-1)}


.rules{margin:0;padding-left:var(--space-4);display:grid;gap:var(--space-2);color:var(--ink)}
/* The one-press switch in the console's top bar: shows the sun while it is
   dark (press for light) and the moon while it is light. Which one shows is
   the stylesheet's to decide, so the first frame is already right. */
.theme-toggle .when-light{display:none}
:root[data-theme="light"] .theme-toggle .when-dark{display:none}
:root[data-theme="light"] .theme-toggle .when-light{display:inline-flex}
@media (prefers-color-scheme:light){
  :root:not([data-theme]) .theme-toggle .when-dark{display:none}
  :root:not([data-theme]) .theme-toggle .when-light{display:inline-flex}
}
.rules li{padding-left:var(--space-1)}
.checks{margin:0;padding:0;list-style:none;display:grid;gap:var(--space-1);color:var(--ink)}
.checks li{position:relative;padding-left:var(--space-6)}
.checks li::before{content:"";position:absolute;left:var(--space-1);top:calc((var(--lh-body) - var(--space-2)) / 2);
  width:var(--space-2);height:var(--space-2);border-radius:var(--radius-xs);
  border:var(--line-1) solid var(--rule-strong)}


/* --- consent, and the documents it points at ---------------------------
   Each consent is its own box with its own notice, because 별도 동의 means
   separately given and a single "agree to everything" is exactly what that
   rule forbids. The notice folds away: four lines per box unfolded is a wall
   nobody reads, and a wall nobody reads is not informed consent either.
   ----------------------------------------------------------------------- */
.consents{border:var(--line-1) solid var(--rule);border-radius:var(--radius-md);padding:var(--space-3);margin:0;
  display:grid;gap:var(--space-2);background:var(--panel)}
.consents legend{padding:0 var(--space-1);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-bold);color:var(--muted)}
.consent{display:grid;gap:var(--space-1)}
.consent details{color:var(--muted)}
.consent summary{cursor:pointer;padding:var(--space-1) 0}
.consent summary:focus-visible{outline:var(--line-2) solid var(--focus-ring);outline-offset:var(--line-2);border-radius:var(--radius-xs)}

/* A document read start to finish, rather than a screen scanned. Measured
   line length and space between sections; everything else is the app's. */
/* line-height here once read a token with a stray 5 after it — "1.7 5",
   which is not a line height, so every legal document on the site was set
   at the body's. */
.prose{max-width:var(--prose);margin:0 auto;padding:var(--space-8) var(--space-4) var(--space-16);
  font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
.prose h2{font-size:var(--t-title-2);line-height:var(--lh-title-2);margin:0 0 var(--space-3)}
.prose p{margin:0 0 var(--space-4)}
.prose ul{margin:0 0 var(--space-4);padding-left:var(--space-6)}
.prose li{margin:0 0 var(--space-1)}
.prose table{margin:0 0 var(--space-2)}
.prose .scroll-x{margin:0 0 var(--space-4)}
.prose h3{font-size:var(--t-title-3);line-height:var(--lh-title-3);margin:var(--space-8) 0 var(--space-2)}
.prose pre.code{background:var(--panel);border:var(--line-1) solid var(--rule);border-radius:var(--radius-md);
  padding:var(--space-3);margin:0 0 var(--space-2)}
.prose code{background:var(--panel);border:var(--line-1) solid var(--rule-soft);border-radius:var(--radius-xs);
  padding:var(--space-05) var(--space-1);font-size:var(--t-code);line-height:inherit}
.prose pre.code code{background:none;border:0;padding:0}

/* --- things that float over the page ----------------------------------
   Four, each for one job: a modal stops the page to ask one question; a menu
   holds actions that do not fit beside the thing they act on; a toast says
   that something finished and goes away; a tooltip names an icon. All four
   sit on --raised with a shadow, because something above the page that
   looks like part of it is read as part of it. */
@keyframes rise{from{opacity:0;transform:translateY(var(--space-2))}}

/* A native <dialog>: the browser traps focus, closes on Escape and gives
   focus back, which a hand-made overlay gets wrong in some way every time. */
dialog.modal{width:min(100% - var(--space-8),var(--measure));max-height:calc(100dvh - var(--space-16));
  padding:0;border:var(--line-1) solid var(--rule);border-radius:var(--radius-xl);
  background:var(--raised);color:var(--ink);box-shadow:var(--shadow-3)}
dialog.modal[open]{animation:rise var(--d-2) var(--ease)}
dialog.modal::backdrop{background:var(--scrim)}
.modal-body{padding:var(--space-6)}
.modal-body h2{font-size:var(--t-title-3);line-height:var(--lh-title-3);margin:0 0 var(--space-2)}
.modal-body.stack > h2{margin:0}
.modal-body p{color:var(--muted)}
/* A command in a dialog wraps rather than running off its edge on a phone. */
.modal-body pre.code{white-space:pre-wrap;overflow-wrap:anywhere}
.modal-body strong + :is(pre,p){margin-top:var(--space-1)}
.modal-foot{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--space-2);margin:0;
  padding:var(--space-4) var(--space-6);border-top:var(--line-1) solid var(--rule-soft);background:var(--panel)}

/* A disclosure, not an ARIA menu: a list of ordinary links and buttons that
   Tab moves through. role=menu promises arrow keys, and a promise the
   keyboard cannot keep is worse than not making it. */
details.menu{position:relative;display:inline-block}
details.menu > summary{list-style:none}
details.menu > summary::-webkit-details-marker{display:none}
.menu-list{position:absolute;z-index:var(--z-drop);top:calc(100% + var(--space-1));left:0;
  min-width:var(--width-col-sm);margin:0;padding:var(--space-1);list-style:none;
  background:var(--raised);border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-2);animation:rise var(--d-1) var(--ease)}
.menu-list a,.menu-list button{display:flex;align-items:center;justify-content:flex-start;gap:var(--space-2);
  width:100%;min-height:var(--control-md);padding:0 var(--space-3);border:0;border-radius:var(--radius-md);
  background:none;color:var(--ink);font-size:var(--t-body);line-height:var(--lh-body);
  font-weight:var(--w-normal);text-align:left;white-space:nowrap}
.menu-list a:hover,.menu-list button:hover{background:var(--hover);text-decoration:none}
.menu-list button.danger,.menu-list a.danger{color:var(--danger)}
/* A row's ⋯ menu opens toward the page, not off its right edge. */
.row-menu .menu-list{left:auto;right:0}
/* A table scrolls sideways, which clips what hangs below it: the last row's menu opens upward. */
.scroll-x tr:last-child .row-menu .menu-list{top:auto;bottom:calc(100% + var(--space-1))}
.menu-list button.danger:hover{background:var(--danger-bg)}

/* --- the assistant -------------------------------------------------------
   A drawer from the right on a wide screen and the whole screen on a phone,
   opened from the icon in the top bar. A native <dialog>, for the reasons
   the modal is one. Replies, tool calls and the call waiting for 실행 are
   one column, oldest first, the way a conversation is read. */
.agent-open .ic{color:var(--brand)}
dialog.agent{position:fixed;inset:0 0 0 auto;margin:0;width:min(100%,var(--width-agent));max-width:100%;height:100dvh;max-height:100dvh;
  padding:0;border:0;border-left:var(--line-1) solid var(--rule);background:var(--raised);color:var(--ink);box-shadow:var(--shadow-3)}
dialog.agent[open]{display:flex;flex-direction:column;animation:rise var(--d-2) var(--ease)}
dialog.agent::backdrop{background:var(--scrim)}
.agent-head{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3) var(--space-2) var(--space-4);border-bottom:var(--line-1) solid var(--rule-soft)}
.agent-head h2{display:flex;align-items:center;gap:var(--space-2);margin:0;font-size:var(--t-heading);line-height:var(--lh-heading)}
.agent-head h2 .ic{color:var(--brand)}
.agent-actions{display:flex;align-items:center;gap:var(--space-1);margin:0 0 0 auto}
.agent-log{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--space-3);padding:var(--space-4)}
.agent-empty,.agent-busy{color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.agent-msg{max-width:100%;font-size:var(--t-body);line-height:var(--lh-body);overflow-wrap:anywhere}
.agent-msg p + p,.agent-msg ul,.agent-msg ol,.agent-msg pre{margin:var(--space-2) 0 0}
.agent-msg ul,.agent-msg ol{padding-left:var(--space-5)}
.agent-msg pre{overflow-x:auto;padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);background:var(--panel);
  font-size:var(--t-code);line-height:var(--lh-code)}
.agent-msg code{font-size:var(--t-code);line-height:inherit}
.agent-user{align-self:flex-end;max-width:85%;padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);background:var(--panel);white-space:pre-wrap}
.agent-bot{align-self:stretch}
.agent-tool{border:var(--line-1) solid var(--rule);border-radius:var(--radius-md);background:var(--card);font-size:var(--t-small);line-height:var(--lh-small)}
.agent-tool > summary{display:flex;align-items:center;gap:var(--space-2);min-height:var(--control-md);padding:0 var(--space-3);cursor:pointer;list-style:none}
.agent-tool > summary::-webkit-details-marker{display:none}
.agent-tool .badge{flex:none;margin-left:auto}
.agent-args{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-family:var(--mono);
  font-size:var(--t-caption);line-height:var(--lh-caption)}
.agent-result{margin:0;max-height:var(--width-col);overflow:auto;padding:var(--space-2) var(--space-3);border-top:var(--line-1) solid var(--rule-soft);
  font-family:var(--mono);font-size:var(--t-caption);line-height:var(--lh-caption);white-space:pre-wrap;overflow-wrap:anywhere}
/* The call waiting for the person: the danger edge, because everything
   that waits is something that cannot be taken back. */
.agent-confirm{display:grid;gap:var(--space-2);padding:var(--space-3);border:var(--line-1) solid var(--danger);border-radius:var(--radius-md);
  background:var(--danger-bg);font-size:var(--t-small);line-height:var(--lh-small)}
.agent-confirm .agent-args{white-space:normal;overflow-wrap:anywhere}
.agent-decide{display:flex;justify-content:flex-end;gap:var(--space-2);margin:0}
.agent-secret{padding:var(--space-3);border:var(--line-1) solid var(--warn);border-radius:var(--radius-md);background:var(--warn-bg);
  font-size:var(--t-small);line-height:var(--lh-small)}
.agent-secret dl{display:grid;gap:var(--space-1);margin-top:var(--space-2)}
.agent-secret dt{color:var(--muted)}
.agent-secret code{overflow-wrap:anywhere;user-select:all}
.agent-form{margin:0;padding:var(--space-1) var(--space-4) var(--space-3)}
/* The send button sits inside the input, bottom right, as in a chat app. */
.agent-input{position:relative}
.agent-input textarea{display:block;width:100%;max-height:var(--width-col-sm);resize:none;padding-right:calc(var(--control-md) + var(--space-3))}
.agent-send{position:absolute;right:var(--space-2);bottom:var(--space-2);border-radius:var(--radius-full)}
/* What is left of the month: numbers only, small, above the input on the right. */
.agent-quota{margin:0;padding:var(--space-2) var(--space-4) 0;border-top:var(--line-1) solid var(--rule-soft);text-align:right;color:var(--faint);
  font-size:var(--t-caption);line-height:var(--lh-caption);font-variant-numeric:tabular-nums;white-space:nowrap}
.agent-quota:empty{display:none}
.agent-page{display:flex;flex-direction:column;gap:var(--space-3);max-width:var(--prose)}
.agent-page .agent-log{padding:0;overflow:visible}
.agent-page .agent-form{padding:var(--space-3) 0 0}
.agent-page .agent-quota{padding:0}
/* On a phone the top bar keeps the menu, the mark, where you are, the
   assistant and the account: the address gives way to its icon, and the
   breadcrumb shrinks before anything is pushed off the edge. */
@media (max-width:60rem){
  dialog.agent{width:100%;border-left:0}
  .crumb{flex:0 1 auto;overflow:hidden}
  .agent-open,.top-account{flex:none}
  .top-account{margin-left:0}
  .top-account > summary span{display:none}
  .top-account > summary{width:var(--control-md);padding:0}
}
.menu-list hr{margin:var(--space-1) 0;border:0;border-top:var(--line-1) solid var(--rule-soft)}
/* The language menu, top right on every page. It opens towards the page,
   and on a phone the button keeps only its globe; the name stays in the
   button's label for screen readers. */
.lang-menu > summary{cursor:pointer}
.lang-menu .menu-list{left:auto;right:0}
html:not([lang="ja"]) a[hreflang="ja"]{font-family:var(--font-no-jp)}
.lang-menu .menu-list a[aria-current],.site-more-lang a[aria-current],.top-more a[aria-current]{font-weight:var(--w-bold)}
/* --bp-md */
@media (max-width:60rem){.lang-toggle span{display:none}.lang-toggle{padding:0 var(--space-3)}}

/* --- a list of projects ------------------------------------------------
   Search and the primary action on one row, then a card per project: name
   and address, its state, the last deployment, and where it lives. The whole
   card opens it: the 열기 button's ::after covers the card, so there is one
   control to focus and the card is still a single target. */
.list-tools{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-4)}
.list-find{flex:1 1 var(--width-col);margin:0}
.list-find select{flex:none;width:auto}
.view-toggle{display:inline-flex;gap:var(--space-05);padding:var(--space-05);border:var(--line-1) solid var(--rule);border-radius:var(--radius-md);background:var(--card)}
.view-toggle .btn{min-height:var(--control-sm);width:var(--control-sm);color:var(--muted)}
.view-toggle .btn[aria-current]{background:var(--hover);color:var(--ink)}
.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--width-col),1fr));gap:var(--space-4);margin:0;padding:0;list-style:none}
.project-grid > .card + .card{margin-top:0}
.project-card{position:relative;display:grid;align-content:start;gap:var(--space-3);padding:var(--space-4);transition:border-color var(--d-1) var(--ease)}
.project-card:hover,.project-card:focus-within{border-color:var(--border-control)}
.project-card.is-here{border-color:var(--rule-strong)}
.project-card-head{display:flex;align-items:flex-start;gap:var(--space-3);min-width:0}
.project-card-name{flex:1;min-width:0}
.project-card-name h3{font-size:var(--t-body);line-height:var(--lh-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.project-card-name h3 .badge{margin-left:var(--space-1);vertical-align:middle}
.project-card-name p{color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.project-card-deploy{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.project-row-deploy{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
/* The list view: one line a project, the address cut rather than wrapped. */
.project-table td{white-space:nowrap}
.project-row-host{display:inline-block;max-width:var(--width-col);overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}
.project-card-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-1) var(--space-3);padding-top:var(--space-3);
  border-top:var(--line-1) solid var(--rule-soft);color:var(--muted);font-size:var(--t-caption);line-height:var(--lh-caption)}
.project-card-meta form{margin-left:auto}
/* The whole card opens the project: the button is no size of its own and its
   ::after covers the card (the star sits above it). It points, the card
   lights on hover, and keyboard focus rings the card. */
.project-open{display:block;width:0;min-width:0;height:0;min-height:0;padding:0;margin:0;border:0;background:none;box-shadow:none;overflow:visible}
.project-open::after{content:"";position:absolute;inset:0;border-radius:var(--radius-lg);cursor:pointer}
/* The pressed-button shrink is a transform, and a transform makes this 0x0
   button the box its ::after is sized against: pressing the card shrank the
   cover to nothing before the click landed, so no card opened. */
.project-open:active{transform:none}
.project-open:focus-visible{outline:none}
.project-open:focus-visible::after{outline:var(--line-2) solid var(--focus-ring);outline-offset:var(--line-2)}
.project-card:has(.project-open):hover{background:var(--hover)}
/* What else on the card is its own control sits above that cover: the star
   was under it, so pressing it opened the project instead. */
.project-card [data-star],.project-card a{position:relative;z-index:var(--z-lift)}
.project-row-name{display:inline-flex;align-items:center;gap:var(--space-2)}

/* Bottom right, newest last, never more than the screen can read in the
   time it stays. A toast confirms; it never carries the only copy of an
   error — that stays on the page as a notice, where it waits to be read. */
.toasts{position:fixed;z-index:var(--z-toast);right:var(--space-4);bottom:var(--space-4);
  display:grid;gap:var(--space-2);width:min(100% - var(--space-8),var(--measure));margin:0;padding:0;list-style:none}
.toasts.in-place{position:static;width:auto;max-width:var(--measure)}
.toast{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);background:var(--raised);color:var(--ink);
  box-shadow:var(--shadow-3);font-size:var(--t-small);line-height:var(--lh-small);animation:rise var(--d-2) var(--ease)}
.toast .ic{margin-top:var(--space-05)}
.toast.ok .ic{color:var(--ok)}
.toast.error .ic{color:var(--danger)}
.toast-text{flex:1;min-width:0}
.toast button.quiet{min-height:var(--control-sm);margin:calc(var(--space-1) * -1) 0}

/* Names an icon, nothing more. It appears on hover and on keyboard focus,
   so anything it says must also be reachable some other way on a touch
   screen, where there is no hover. */
.tip{position:relative;display:inline-flex}
.tip-body{position:absolute;z-index:var(--z-drop);bottom:calc(100% + var(--space-2));left:50%;
  transform:translateX(-50%);width:max-content;max-width:var(--width-col);padding:var(--space-1) var(--space-2);
  border-radius:var(--radius-sm);background:var(--ink);color:var(--bg);
  font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium);
  pointer-events:none;opacity:0;visibility:hidden;transition:opacity var(--d-1) var(--ease)}
.tip:hover .tip-body,.tip:focus-within .tip-body{opacity:1;visibility:visible}
/* Escape hides it without moving focus (WCAG 1.4.13). */
.tip[data-dismissed] .tip-body{opacity:0;visibility:hidden}

/* Hidden means hidden, even on an element whose component sets a display:
   the live pages hide what has not arrived yet with the attribute, and a
   label.check or a .btn would otherwise show through it. */
[hidden]{display:none!important}

/* The first stop for a keyboard: past the top bar and the menu, straight to
   the page. Off screen until it has focus. */
.skip{position:absolute;z-index:var(--z-toast);left:var(--space-2);top:var(--space-2);padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-md);background:var(--brand);color:var(--brand-ink);font-size:var(--t-small);line-height:var(--lh-small);
  font-weight:var(--w-medium);transform:translateY(calc(-100% - var(--space-4)))}
.skip:focus{transform:none;text-decoration:none}
.main:focus{outline:none}

/* Where in a bucket you are: each folder above this one is a link, the last
   is the page itself. */
.trail ol{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-1);margin:0 0 var(--space-4);padding:0;list-style:none;
  font-size:var(--t-small);line-height:var(--lh-small);color:var(--muted)}
.trail li{display:flex;align-items:center;gap:var(--space-1);min-width:0;overflow-wrap:anywhere}
.trail li + li::before{content:'/';color:var(--faint)}
.trail [aria-current]{color:var(--ink);font-weight:var(--w-medium)}
a.entry{display:inline-flex;align-items:center;gap:var(--space-2)}
a.entry .ic{color:var(--faint)}
tr:has(> td > a.entry) > td{vertical-align:middle}

/* An upload form that is also a place to drop files. Without the script it
   is the file input and the button; with it, dragging over it says so. */
.dropzone{display:grid;justify-items:center;gap:var(--space-2);padding:var(--space-8) var(--space-6);text-align:center;
  border:var(--line-2) dashed var(--border-control);border-radius:var(--radius-lg);background:var(--panel);
  transition:border-color var(--d-1) var(--ease),background var(--d-1) var(--ease)}
.dropzone > .ic{color:var(--faint)}
.dropzone-title{margin:0;font-weight:var(--w-medium)}
.dropzone .help{margin:0;max-width:var(--width-note)}
.dropzone[data-dragging]{border-color:var(--brand);background:var(--brand-soft)}
.upload-queue{list-style:none;margin:0;padding:0;width:100%;display:grid;gap:var(--space-3);text-align:left}
.upload-queue:empty{display:none}
.upload-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:var(--space-1) var(--space-3);align-items:center}
.upload-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.upload-note{white-space:nowrap}
.upload-row progress{grid-column:1 / -1;width:100%;accent-color:var(--brand)}
.upload-row[data-state="failed"] .upload-note{color:var(--danger)}
.upload-summary{margin:0}
/* 파일 올려 배포하기 (uploadDeployForm): the drop box with its two pickers
   and what was chosen; under it, left aligned, where and how; the button at
   the foot. A picker is a button whose file input is for screen readers and
   the keyboard, ringed where the input has focus. */
.deploy-drop{padding:var(--space-6)}
form[data-dragging] .deploy-drop{border-color:var(--brand);background:var(--brand-soft)}
.deploy-drop .actions{justify-content:center}
.file-pick{position:relative;cursor:pointer}
.file-pick:focus-within{outline:var(--line-2) solid var(--focus-ring);outline-offset:var(--line-2)}
.choice-row{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-6)}
.deploy-upload > .card-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-5)}
.deploy-upload-target{display:grid;gap:var(--space-2);padding:0;border:0;border-radius:0;background:none}
.deploy-upload-target legend{padding:0 0 var(--space-2);color:var(--ink)}
.deploy-upload-branch{max-width:var(--measure)}
/* The preview's name only asks for itself when the deployment is a preview. */
.deploy-upload:has(input[name="target"][value="production"]:checked) .deploy-upload-branch{display:none}
.deploy-upload .card-foot.space-row{justify-content:space-between;gap:var(--space-4);margin:0;flex-wrap:wrap}
/* Git 연결 before a repository: what it does, and the button beside it. */
.git-connect{justify-content:space-between;gap:var(--space-4);margin:0;flex-wrap:wrap}

/* A file shown as it is: an image at most a screen tall, the start of a text. */
.preview{display:flex;justify-content:center;padding:var(--space-4);border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);
  background:var(--panel)}
.preview img{max-width:100%;max-height:calc(var(--width-note) * 1.2);height:auto}
pre.preview{display:block;max-height:calc(var(--width-note) * 1.2);overflow:auto;border:0;white-space:pre-wrap;overflow-wrap:anywhere}

/* Output that may still be growing. Held to a height and scrolled, and kept
   at the bottom while new lines arrive unless the reader has scrolled up. */
pre.log{max-height:calc(var(--width-note) * 1.2);overflow:auto;padding:var(--space-4);white-space:pre-wrap;overflow-wrap:anywhere}
.live-badge .dot{animation:blink calc(var(--d-2) * 6) var(--ease) infinite}
tbody[data-live] tr[data-arrived]{animation:rise var(--d-2) var(--ease)}
form[aria-busy="true"]{cursor:progress}

/* --- touch, last ------------------------------------------------------
   The finger's 44px, again, after every component. The touch block further
   up loses to any later rule of the same weight, and on a phone the menu
   items, the switchers, the view toggle and the public top bar measured 36px
   and 28px under it. Here nothing comes after it. */
@media (pointer:coarse){
  .menu-list a,.menu-list button,.switcher-panel a,.switcher-panel button,[data-search-results] a,[data-search-results] button,.switcher-trigger,
  .site-pill,.site-nav a,.view-toggle .btn,.check,.actions > label.check,.toast button.quiet{min-height:var(--control-lg)}
  .toast button.quiet{margin:0}
  .switcher-trigger{height:auto}
  .view-toggle .btn,button.logout{width:var(--control-lg)}
  /* Square, and kept square: in the crowded top bar of a phone flex shrank
     these to 31 and 36px wide. */
  .top > .btn.icon-only,.top-account > summary{flex:none;min-width:var(--control-lg)}
  /* Padding, not flex: a flex summary loses its disclosure triangle. */
  details.fine > summary{padding-block:calc((var(--control-lg) - var(--lh-caption)) / 2)}
  details:not(.fine) > summary:not([class]){padding-block:calc((var(--control-lg) - var(--lh-body)) / 2)}
  /* A field below 16px makes iOS zoom the page when it takes focus. */
  :is(input,select,textarea):not([type=checkbox],[type=radio]){font-size:var(--t-body-lg);line-height:var(--lh-body-lg)}
  /* Links that stand on their own — not inside a sentence — are a finger's
     width tall too: the footer, the notice bar, "all prices", the wordmark,
     and the way on under a sign-in form. */
  .foot .inner a,.home-more,.site-top .brand,.alt a{display:inline-flex;align-items:center;min-height:var(--control-lg)}
  .alt a{vertical-align:middle}
  .foot .inner{row-gap:0}
  .site-notice{height:calc(var(--control-lg) + var(--line-1))}
  .site-notice a{align-self:stretch}
}


.signin-tabs{margin:0 0 var(--space-6)}
.signin-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4);padding:var(--space-4) 0;border-bottom:var(--line-1) solid var(--rule-soft);font-weight:var(--w-normal)}
.signin-row:last-of-type{border-bottom:0}
.signin-row strong{display:block}
.signin-row .help{display:block;margin-top:var(--space-1)}
.signin-switch{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:calc(var(--icon-md) * 1.8);height:var(--icon-md);margin:0;border-radius:var(--radius-full);
  background:var(--rule);border:0;cursor:pointer;transition:background var(--d-1) var(--ease)}
.signin-switch::after{content:"";position:absolute;top:var(--space-05);left:var(--space-05);width:calc(var(--icon-md) - var(--space-1));height:calc(var(--icon-md) - var(--space-1));border-radius:var(--radius-full);background:var(--raised);
  box-shadow:var(--shadow-1);transition:transform var(--d-1) var(--ease)}
.signin-switch:checked{background:var(--ok)}
.signin-switch:checked::after{transform:translateX(calc(var(--icon-md) * 0.8))}
.signin-switch:disabled{opacity:.5;cursor:not-allowed}
.signin-switch:focus-visible{outline:var(--line-2) solid var(--brand);outline-offset:var(--space-05)}
.signin-foot{display:flex;justify-content:flex-end;align-items:center;gap:var(--space-2);margin-top:var(--space-4)}
.signin-foot[hidden]{display:none}
.signin-list{margin:0;padding:0;list-style:none;border:var(--line-1) solid var(--rule);border-radius:var(--radius-lg);overflow:hidden;background:var(--raised)}
.signin-list li + li{border-top:var(--line-1) solid var(--rule-soft)}
.signin-item{display:flex;align-items:center;gap:var(--space-3);min-height:var(--control-lg);padding:var(--space-2) var(--space-4);color:var(--ink)}
.signin-item:hover{background:var(--hover);text-decoration:none}
.signin-item .ic{margin-left:auto;color:var(--faint)}
.signin-name{font-weight:var(--w-medium)}
.signin-group{margin:var(--space-4) 0 var(--space-2);color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.signin-scrim{position:fixed;inset:0;z-index:var(--z-modal);background:var(--scrim)}
.signin-scrim-close{position:absolute;inset:0}
.signin-sheet{position:fixed;inset:0 0 0 auto;z-index:var(--z-modal);display:flex;flex-direction:column;width:min(100%,calc(var(--width-agent) * 1.5));max-width:100%;height:100dvh;
  background:var(--raised);color:var(--ink);border-left:var(--line-1) solid var(--rule);box-shadow:var(--shadow-3);animation:rise var(--d-2) var(--ease)}
.signin-sheet-head{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3) var(--space-3) var(--space-3) var(--space-5);border-bottom:var(--line-1) solid var(--rule-soft)}
.signin-sheet-head h2{margin:0;font-size:var(--t-heading);line-height:var(--lh-heading)}
.signin-sheet-head .btn{margin-left:auto}
.signin-sheet-body{flex:1;min-height:0;overflow-y:auto;padding:var(--space-2) var(--space-5) var(--space-6)}
.signin-sheet-foot{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3) var(--space-5);border-top:var(--line-1) solid var(--rule-soft);background:var(--panel)}
.signin-sheet-foot .signin-spacer{flex:1}
.signin-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);gap:var(--space-4);padding:var(--space-4) 0;border-bottom:var(--line-1) solid var(--rule-soft)}
.signin-field[hidden]{display:none}
.signin-field > div:first-child label{font-weight:var(--w-medium)}
.signin-field .help{display:block;margin-top:var(--space-1)}
.signin-field textarea{resize:none}
.signin-error{margin:var(--space-1) 0 0;color:var(--danger);font-size:var(--t-small);line-height:var(--lh-small)}
.signin-control{display:flex;align-items:center;gap:var(--space-1)}
.signin-control > input,.signin-control > select{flex:1;min-width:0}
.signin-unit{flex:none;color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small)}
.signin-consent{display:grid;gap:var(--space-2)}
.signin-logo{display:inline-grid;place-items:center;flex:none;width:var(--icon-md);height:var(--icon-md);color:var(--ink)}
.signin-logo svg{width:100%;height:100%}
.signin-logo .ic{color:var(--muted)}
.signin-icon{font-size:var(--t-caption);line-height:var(--lh-caption);font-weight:var(--w-medium)}
.signin-toolbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-2);margin:0 0 var(--space-3)}
.signin-toolbar input[type=search]{min-width:var(--width-col)}
.signin-toolbar .signin-spacer{flex:1}
.signin-radio{display:grid;gap:var(--space-2)}
.signin-subhead{margin:var(--space-5) 0 0;color:var(--muted);font-size:var(--t-small);line-height:var(--lh-small);font-weight:var(--w-medium)}
.signin-cards{display:grid;gap:var(--space-3)}
@media (max-width:48rem){
  .signin-field{grid-template-columns:1fr;gap:var(--space-2)}
  .signin-sheet{width:100%;border-left:0}
}

.sw-bg{--sw:var(--bg)}
.sw-panel{--sw:var(--panel)}
.sw-card{--sw:var(--card)}
.sw-raised{--sw:var(--raised)}
.sw-hover{--sw:var(--hover)}
.sw-scrim{--sw:var(--scrim)}
.sw-ink{--sw:var(--ink)}
.sw-muted{--sw:var(--muted)}
.sw-faint{--sw:var(--faint)}
.sw-rule{--sw:var(--rule)}
.sw-rule-soft{--sw:var(--rule-soft)}
.sw-rule-strong{--sw:var(--rule-strong)}
.sw-border-control{--sw:var(--border-control)}
.sw-ink-hover{--sw:var(--ink-hover)}
.sw-brand{--sw:var(--brand)}
.sw-brand-hover{--sw:var(--brand-hover)}
.sw-brand-ink{--sw:var(--brand-ink)}
.sw-brand-soft{--sw:var(--brand-soft)}
.sw-focus-ring{--sw:var(--focus-ring)}
.sw-ok{--sw:var(--ok)}
.sw-ok-bg{--sw:var(--ok-bg)}
.sw-warn{--sw:var(--warn)}
.sw-warn-bg{--sw:var(--warn-bg)}
.sw-danger{--sw:var(--danger)}
.sw-danger-bg{--sw:var(--danger-bg)}
.sw-c1{--sw:var(--c1)}
.sw-c2{--sw:var(--c2)}
.sw-c3{--sw:var(--c3)}
.sw-c4{--sw:var(--c4)}
.sw-c5{--sw:var(--c5)}
.sw-home-bg{--sw:var(--home-bg)}
.sw-home-tile{--sw:var(--home-tile)}
.sw-home-tile-2{--sw:var(--home-tile-2)}
.sw-home-line{--sw:var(--home-line)}
.sw-home-line-2{--sw:var(--home-line-2)}
.sw-home-line-3{--sw:var(--home-line-3)}
.sw-home-ink{--sw:var(--home-ink)}
.sw-home-ink-2{--sw:var(--home-ink-2)}
.sw-home-ink-3{--sw:var(--home-ink-3)}
.sw-home-num{--sw:var(--home-num)}
.sw-home-ok{--sw:var(--home-ok)}
.sw-home-ok-glow{--sw:var(--home-ok-glow)}
.sw-home-ok-bg{--sw:var(--home-ok-bg)}
.sw-home-warn{--sw:var(--home-warn)}
.sw-home-warn-bg{--sw:var(--home-warn-bg)}
.sw-home-danger{--sw:var(--home-danger)}
.sw-home-danger-bg{--sw:var(--home-danger-bg)}
.sw-home-scrim{--sw:var(--home-scrim)}
.sw-home-shade-top{--sw:var(--home-shade-top)}
.sw-home-shade-side{--sw:var(--home-shade-side)}
.sw-home-shade-mid{--sw:var(--home-shade-mid)}
.sw-home-shade-edge{--sw:var(--home-shade-edge)}
.sw-brick{--sw:var(--brick)}
.sw-brick-2{--sw:var(--brick-2)}
.sw-brick-soft{--sw:var(--brick-soft)}
.sw-brick-ink{--sw:var(--brick-ink)}
.sw-brick-glow{--sw:var(--brick-glow)}
.sw-brick-edge{--sw:var(--brick-edge)}
.len-space-05{--len:var(--space-05)}
.len-space-1{--len:var(--space-1)}
.len-space-2{--len:var(--space-2)}
.len-space-3{--len:var(--space-3)}
.len-space-4{--len:var(--space-4)}
.len-space-5{--len:var(--space-5)}
.len-space-6{--len:var(--space-6)}
.len-space-8{--len:var(--space-8)}
.len-space-10{--len:var(--space-10)}
.len-space-12{--len:var(--space-12)}
.len-space-16{--len:var(--space-16)}
.len-space-20{--len:var(--space-20)}
.len-space-24{--len:var(--space-24)}
.len-space-32{--len:var(--space-32)}
.box-icon-xs{--icon:var(--icon-xs)}
.box-icon-sm{--icon:var(--icon-sm)}
.box-icon-md{--icon:var(--icon-md)}
.box-icon-lg{--icon:var(--icon-lg)}
.box-icon-xl{--icon:var(--icon-xl)}
