/* ============================================================================
   sillywizardsguild.com — one stylesheet. Tokens first; nothing hard-coded below :root.
   Type: antidot.gr, layout wk.com. Forked from southnorth.se 2026-08-20. The ground
   is South North's beige; the accent is the Guild's red. That pairing is the one
   open problem in the system — see the note on --red below.
   ========================================================================== */


/* --- typefaces, served from this site (2026-09-21) ------------------------- *
   Archivo, Inter and JetBrains Mono, OFL-licensed, downloaded once from Google Fonts and
   served from assets/fonts/. They used to load from Google's font service: two render-
   blocking requests on other origins, and every visitor's address sent to Google — the SEO
   audit of 2026-09-21 measured ~2s of blocking on mobile. Latin and latin-ext only;
   unicode-range means a page fetches only the files it needs. The variable files cover 400
   and 500 in one download, hence two rules per file. */
@font-face{font-family:'Archivo';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/archivo-400-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Archivo';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/archivo-400-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Archivo';font-style:normal;font-weight:500;font-display:swap;src:url('assets/fonts/archivo-400-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Archivo';font-style:normal;font-weight:500;font-display:swap;src:url('assets/fonts/archivo-400-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/inter-400-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/inter-400-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url('assets/fonts/inter-400-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url('assets/fonts/inter-400-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/jetbrains-mono-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:swap;src:url('assets/fonts/jetbrains-mono-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

:root{
  /* --- palette — three colours, and no fourth ---------------------------- *
   * Measured: ink on paper 16.17, mono-dim on paper 4.59, mono-dim on greige 3.36.
   *
   * ONE ACCENT, TWO VALUES, and the split is not a style choice. --red is 3.11 on
   * this ground — it was chosen against black, where it is 5.92. AA wants 3.0 for
   * large text and non-text marks, which it passes, and 4.5 for small text, which
   * it does not. South North's blue ran 6.69 here and was safe at any size, which
   * is why every component inherited from it sets the accent freely on 11px mono.
   *
   * So: --red carries large text, graphic marks and the About ground. --red-text
   * carries anything set below 24px. Decision of 2026-08-20 in aios/LOG.md.
   * Do not set --red on small type, and do not use --red-text as a ground. */
  --red:#FF3D00;       /* ACCENT — large text, marks, the About ground */
  --red-text:#C92B00;  /* the same accent, deep enough to read small — 4.81 */
  /* Type that sits ON the accent. Not a fourth colour — it is --ink's value under
     a role name, because the thing it must contrast with is the accent rather than
     the page. The accent does not change per surface, so neither does this: 5.19 on
     every ground. Using --ink here instead breaks the moment the ground inverts. */
  --on-red:#141414;
  --paper:#F2F0EA;     /* page ground */
  --ink:#141414;       /* text */
  --greige:#D6CEC2;    /* frames, secondary surfaces, placeholder boxes */
  /* The home page's slow ground. IT ONLY EVER LIGHTENS, and that is a contrast
     rule rather than a preference: every ratio in this file is measured against
     --paper, so a ground that drifted DARKER would quietly make all of them
     optimistic. --red is the binding one at 3.11 against a 3.0 floor — the ground
     may darken to L 0.838 before it fails, which is barely six steps, so the
     whole margin would be spent on an effect nobody asked to be dangerous.

     WHICH LEAVES LITTLE ROOM UPWARDS, and is why this tone is warm rather than
     merely paler. --paper is already L* 94.8, so lightening alone tops out about
     five points below pure white and the first attempt at six steps read as
     nothing at all. #FEFAEC is L* 98.2 — ΔL* 3.4, half again on the old lift —
     and carries a clear warm shift with it. Two axes of difference rather than
     one, both free: luminance is what contrast is computed from, so a warmer tone
     at the same lightness costs the ratios nothing. */
  --paper-lift:#FEFAEC;
  /* How much --paper is laid over the hero film. Raise it and the type is safer
     and the film fainter; lower it and the reverse. Re-measure the hero contrast
     whenever this moves. */
  --mono-dim:#6E6C69;  /* captions — 4.59 on paper */
  --hairline:rgba(20,20,20,.14);

  /* The OPEN list of a <select> is painted by the OS, not by this stylesheet, and
     it is not one of our surfaces. These two are the beige page's ground and ink
     under a role name, and they are the only tokens that must NOT invert inside a
     token swap — a menu that inherited the red card would set red type on a red
     ground wherever the platform honours our colours there. Not a fourth colour:
     the same two values, held still. */
  --menu-ground:#F2F0EA;
  --menu-ink:#141414;

  /* --- type ------------------------------------------------------------- *
   * When the PP Neue Montreal licence lands, change --font on this one line
   * and delete the Inter <link> in the three page heads. Nothing else moves. */
  --font:'Inter','Helvetica Neue',Arial,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,'SF Mono',monospace;
  /* THE DISPLAY FACE, AND THE TWO VALUES THAT TRAVEL WITH IT. Only the home page
     reads these — the name, the four section labels and the scope statement.
     Everything else on the site stays on --font, which is the grotesk.

     A FACE IS NOT A ONE-LINE SWAP. Four things are cut for whichever face is
     named here, and every one of them is silently wrong for the others:
       --fw-display   the weight it actually ships
       --ls-display   its tracking at display size
       --fs-name      the size that fills the measure, from its advance width
       the hero line-height, from its per-column ink collision
     The last two are further down and carry their own measurements.

     UNDER TEST, 2026-08-25. Swap by moving one block's values up here:

       Archivo            500   -.03em   OFL · in use
       Instrument Serif   400   0        OFL · --fs-name 264px / line-height .95

     Both are OFL, verified in google/fonts under ofl/, so either can go live. A
     third row here was an ifonts.xyz personal-use demo; it and its self-hosted
     woff2 were deleted on 2026-09-08 rather than licensed. Nothing in this table
     is now a licence risk, and adding a row that is means adding the warning too:
     a face is one line to swap, which makes it one line to ship.

     WEIGHT IS THE REAL DIFFERENCE BETWEEN THE FIRST TWO. Instrument Serif ships
     400 only — the Google API refuses 500, 600 and 700 — so asking for bold gets
     a synthesised one, which thickens hairline and stem alike and flattens the
     contrast the face exists for. Archivo carries a true 100-900 axis, so 700 is
     a drawn weight rather than a smeared one. */
  --font-display:'Archivo','Helvetica Neue',Arial,sans-serif;
  --fw-display:500;
  --ls-display:-.03em;

  --fs-display:clamp(3.75rem,12vw,16.4062rem);
  /* THE NAME. Every number here is measured against the face AND THE WEIGHT named
     in --font-display / --fw-display. Weight changes the advance, so it changes
     this: a lighter cut is narrower and asks for a LARGER size to fill the same
     measure. "Silly Wizards" rasterised at 200px, Archivo:

       weight   advance   fills 1152 at   ink collision   vw limit at 320
        400     5.631em       205px           .910           15.54vw
        500     5.752em       200px           .910           15.21vw   (in use)
        600     5.888em       196px           .910           14.86vw
        700     6.065em       190px           .915           14.43vw

     Instrument Serif, same measurement: 4.318em / 264px / .91.

     UP 15%, 2026-09-14, at Filippos's word. Every term moved together — floor, middle
     and cap — so the curve keeps its shape and only its size changes:
     2.4797/12.9375/17.25 became 2.8516/14.875/19.8375.

     The cap is re-cut with --max-w. "Silly Wizards" is 5.752em in Archivo 500, so it
     fills the 1872px measure at --max-w (125rem, less two 64px pads) at 325px. The
     ceiling is 19.8375rem = 317px, which lands just inside that instead of exactly on
     it. (This paragraph said "238 here" until today, which was the 14.875rem ceiling
     from before --max-w was raised; the number had not been re-cut with it.)

     THE MIDDLE IS BOUND BY THE NARROWEST VIEWPORT, not the widest — --pad-x floors at
     20px, so below 400 the measure is (100vw - 40) rather than 90vw, and the table
     above puts the limit at 320 at 15.21vw for Archivo 500. 14.875vw is 97.8% of that,
     so the increase spends most of the remaining headroom and there is no room for a
     second one: 20% was asked for and does not fit. The span is a block and would WRAP
     at the space if it ever overran, making three lines.

     THE FOLD CAP HAS MOVED OUT OF THIS TOKEN, to the short-screen block further down
     (max-height 820), for exactly the reason the lead's did earlier today. Do not write
     that block's selector out in full anywhere above it: aios/checks/type-scale.mjs
     finds the guarded lead with a regex that anchors on the literal media query and
     then takes the next `--fs-lead:` it sees, so a mention of it in prose up here makes
     the checker read the unguarded token instead and report a fold fault that is not
     real. That is exactly what this comment did when it was first written.

     Baked in here the cap bound at ordinary window
     heights too. At 1440x900 it pinned the name to 168.75px for every reader, so this
     increase would have changed nothing on a laptop, and a larger text setting changed
     nothing either. The base cap is now 21.5625svh and short screens keep 18.75svh.

     MEASURED TODAY, replacing three figures in this block that had gone stale: at 1440
     the width rule binds at 186.3px (this said 216), the prose said 25svh and 26svh
     while the token said 18.75, and the 1280x720 fold lands with 4.5px to spare, not
     the 9 claimed — see the 22px margin note at .hero .lead, which is now 4.5. */
  --fs-name:min(clamp(2.8516rem,14.875vw,19.8375rem),21.5625svh);
  --fs-large:clamp(2.5rem,6.4vw,6.5625rem);
  /* min() guards the fold rather than a width breakpoint: 3.75svh is 27px at the 720px
     height the hero is specified against, and stops binding above ~830px of height. */
  --fs-lead:clamp(1.25rem,1.45rem + 0.77vw,2.6rem);
  /* Floor raised from 22px so the accent numbers are never set below the 24px
     that makes --red legal as large text. --fs-section has one consumer. */
  --fs-section:clamp(1.875rem,2.375rem + 1.25vw,4.875rem);
  --fs-title:clamp(1.3281rem,1.625rem + 0.62vw,2.75rem);
  /* Was a flat 16px, so it never grew at all. The floor is unchanged; only the
     ceiling is new, and it is reached at about 1715. */
  --fs-body:clamp(1.25rem,1.25rem + 0.52vw,2.1875rem);
  /* Two more, so nothing has to be written as flat px. --fs-small is the 14-15px register
     (terms, cards, footer, columns) and --fs-micro the 12-13px one (case body, numbers). */
  --fs-small:clamp(1.0938rem,1.0938rem + 0.35vw,1.75rem);
  --fs-micro:clamp(0.9375rem,0.9375rem + 0.3vw,1.5rem);
  --fs-mono:clamp(0.8594rem,0.8594rem + 0.22vw,1.25rem);
  /* TWO STEPS ADDED 2026-09-14, to end sizes written outside the scale. An audit of
     every font-size found 33 hand-rolled values, 13 of them flat rem that never grew
     with the viewport at all — the exact fault this scale exists to prevent, invisible
     because the checker only ever validated the tokens, never local declarations.
     Both are now in that checker's TOKENS list.
       --fs-head  the 40-44px step. Six declarations sat in that band with nowhere to
                  land, between --fs-title (34.9) and --fs-section (56).
       --fs-nano  the small-label register. The alternative was sending 10-14px labels
                  to --fs-mono at 16.9, which is a 27-61% jump, not a consolidation. */
  --fs-head:clamp(1.5rem,1.75rem + 0.9vw,3.375rem);
  --fs-nano:clamp(0.6875rem,0.6875rem + 0.16vw,1rem);

  --track-display:-.035em;
  --track-mono:.14em;

  /* --- rhythm ----------------------------------------------------------- */
  --measure:34em;
  /* Raised from 1280 on 2026-09-08. At 1280 the content stopped growing while
     screens did not: 85% of a 1512 laptop, 74% of a 1728, 62% of a 2056, half of a
     2560 — with 16px body text throughout. The page was not small, it was capped,
     and every ceiling in the scale below was reached by about 1500. */
  --max-w:125rem;
  --pad-x:clamp(20px,5vw,64px);
  --pad-section:clamp(72px,10vw,140px);
  --gap-grid:clamp(28px,4vw,72px);
}

/* THE FOLD GUARD LIVES HERE, NOT INSIDE --fs-lead (2026-09-14). As `min(…,3.75svh)` on the
   token it bound at ordinary window heights too — at 900px it capped the lead at 33.75 for
   every reader, so a larger text setting changed nothing, which is the one thing the rem
   conversion exists to prevent. Scoped to short viewports it does its actual job: hold the
   name, the motto and the proposition on a 1280x720 screen, and stay out of the way above. */
@media (max-height:820px){
  :root{ --fs-lead:min(clamp(1.25rem,1.45rem + 0.77vw,2.6rem),3.75svh); }
  /* THE NAME'S FOLD CAP BELONGS HERE TOO, 2026-09-14, and for the same reason. Raised
     15% with the rest of the token, the base cap is 21.5625svh — which at 720 would set
     the name at 155px and push the tag line about 20px past a fold that has 4.5px left.
     Holding the old 18.75svh on short viewports only means 1280x720 still renders the
     name at exactly 135px, unchanged, while every taller window takes the increase. */
  :root{ --fs-name:min(clamp(2.8516rem,14.875vw,19.8375rem),18.75svh); }
}

/* --- reset ---------------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--paper);color:var(--ink);
  font-family:var(--font);font-size:var(--fs-body);line-height:1.6;
  font-weight:400;letter-spacing:-.005em;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
a{color:inherit;text-decoration:none}
/* A postal address is <address>, which every browser italicises by default. */
address{font-style:normal}
img{max-width:100%;display:block}
:focus-visible{outline:2px solid var(--red);outline-offset:3px}

.wrap{max-width:var(--max-w);margin:0 auto;padding:0 var(--pad-x)}
.skip{position:absolute;left:-9999px}
.skip:focus{left:var(--pad-x);top:12px;z-index:20;background:var(--red-text);color:var(--paper);padding:8px 14px;font-family:var(--font-mono);font-size:var(--fs-mono);text-transform:uppercase;letter-spacing:var(--track-mono)}

/* --- shared type ---------------------------------------------------------- */
/* Leading was .95, which was tuned against "South North / Studios" — a first line
   with no descender. "Silly Wizards" has one, and at 140px the y of Silly overlapped
   the cap of Guild by 2.1px, measured on the glyph ink rather than the line box.
   The ink of the two lines is 135.1px of a 140px em, so .965 is the collision floor;
   1.04 clears it by 10.5px at full size and stays proportional down the clamp. */
.display{
  font-family:var(--font-display);font-size:var(--fs-display);line-height:1.04;
  letter-spacing:var(--ls-display);font-weight:var(--fw-display);
}
.display span{display:block}
/* A display headline in the accent. --fs-display floors at 48px, so --red is legal
   here as large text at 3.11. work.html's "Creative" deliberately does not carry this —
   it is a page title, not a name or a subject. */
/* THE GAP IN "AI + IQ" IS A WORD SPACE, NOT TRACKING. Archivo sets a space at
   .205em, so at display size the two halves drift apart and the plus floats
   between them. --track-display is already -.035em and tightening it further
   would close the letters before it closed the gaps. -.07em on the word space
   takes each one to about .135em — a third out — and leaves the letterforms
   alone. Used once, on the AI page heading. */
.display--accent{color:var(--red);word-spacing:-.07em}

/* The hero wordmark is set in the accent — Filippos, 2026-08-20, reinstating half
   of the 2026-08-18 trial. --fs-display floors at 48px, so this is large text by a
   wide margin and --red is legal on it at 3.11. The tag string below it is NOT
   restored: it is 13px and would need --red-text, and two weights of the accent
   stacked in one block reads as an error rather than a hierarchy.

   Hero only. On about.html the same name sets on the red ground, where --red is
   already --ink, so it stays black there without a second rule — which is the
   token swap doing its job. work.html's "Creative" is a page title, not the name, and
   is deliberately out of this selector.

   The tag string under it is red too, and takes --red-text because it is 13px:
   --red would be 3.11 on it, and small text needs 4.5. The two values differ by
   design and not by accident. Small type at wide tracking reads lighter than
   display type of the same colour, so the deeper value under the brighter one is
   what makes the two read as one red rather than as a mismatch. */
/* THE NAME IS THE ONE THING ON THE SITE SET IN THE DISPLAY FACE. Scoped to
   .hero so work.html's "Creative" and About's wordmark keep the grotesk — the face is
   the Guild's signature, not a heading style, and it stops meaning that the
   moment a second page borrows it. The tracking is reset: --track-display is
   -.035em, cut for Inter, and a drawn face is spaced by its own sidebearings.
   Negative tracking on top of those collides the strokes. */
/* Two lines, closed up until they read as one mark rather than two words. THE
   LINE-HEIGHT IS MEASURED FROM THE INK, COLUMN BY COLUMN. Rasterised at 200px and
   compared per column, "Silly Wizards" and "Guild" first touch at .915 in Archivo
   700 and at .91 in Instrument Serif — near enough that one value serves both, and
   .95 leaves about .035em of clearance in either. It is a coincidence of two faces
   with ordinary descenders, not a constant: a display face with short descenders
   collided at .735 and took
   .75, because it barely descended at all. Re-measure whenever --font-display or
   --fw-display changes; never adjust this by eye. */
.hero .display{
  font-family:var(--font-display);font-size:var(--fs-name);
  letter-spacing:var(--ls-display);font-weight:var(--fw-display);line-height:.95;
}
.hero .display span{color:var(--red)}
.hero .tagstring{color:var(--red-text)}

/* --- the name drifts ------------------------------------------------------- *
   RESTORED 2026-09-16, hours after it was retired. "i want the big wordmark on the home
   page to be orange again along with the little wizard icon fullstop at the end and bring
   back the gradient animation as well." The name went --ink that morning on his word and
   came back to --red and the 60s drift on his word the same day; this block is the
   original, restored whole rather than rebuilt, so nothing drifted in the round trip.

   WHAT THAT COSTS, unchanged and still his call: --red is 3.113 on --paper against a
   large-text floor of 3.0 — 0.113 of headroom, the complaint of 2026-09-08. --ink measures
   16.17. There is no film behind the name any more, so this is measured on plain paper and
   there is no worse case hiding behind it. An accepted shortfall, not an open question.

   A 60s cycle: five seconds held on --red, then a 55s sweep that leaves the
   orange, travels down through the reference's reds, crimsons and magentas to a
   deep purple at the halfway mark, and comes back. It lands on --red exactly as
   the cycle ends, so the loop closes on itself and the next five-second hold is
   the same colour the last one finished on. Nothing jumps.

   THE PALETTE IS CUT AT THE TOP, and that is not a taste decision. This ground is
   --paper and the name is large text, so every colour it passes through has to
   clear 3.0. The reference image's sunlit dune tops do not: #FFA51C measures 1.73,
   #F5811F 2.29 and #EA6A15 2.80. --red itself is 3.113, which makes it the
   BRIGHTEST legal point in the whole sweep — so the loop can only run darker than
   the colour it starts from. Everything below is measured on #F2F0EA:

     #FF3D00  3.11   the accent, start and end
     #E8481C  3.43   red-orange
     #C4204E  5.03   crimson
     #96125A  7.28   deep magenta
     #3B0F52  13.39  deep purple, the apex at the halfway mark

   THE STRIP IS FIVE ELEMENT-WIDTHS WIDE and its outer fifths are flat --red, so
   the window sees one solid colour at each end of the travel. That is what makes
   the five-second hold read as held rather than as a slow start. The middle three
   fifths carry the journey, mirrored around the purple so the return is the way
   it came and the ends agree.

   The mark is not type. .wizard-stop is a mask filled with currentColor, so text
   that goes transparent for background-clip would take the mark with it; it gets
   the same journey as a flat background-color on the same 60s clock instead, keyed
   to the colour at the centre of the window.

   IT STARTS MID-STRIDE (Filippos, 2026-09-11: no delay before it moves). Both
   animations carry -5s, so the page opens at the end of the hold and the sweep begins
   on load. The hold still happens once a cycle, now between cycles, at 55s. About
   shares swg-name-drift without the offset, so its hold stays at the start. */
@keyframes swg-name-drift{
  0%,8.3333%{background-position:0% 50%}    /* 5s of 60s, held on --red */
  100%{background-position:100% 50%}        /* 55s across the strip, back to --red */
}
@keyframes swg-mark-drift{
  0%,8.3333%{background-color:#FF3D00}
  30%{background-color:#C4204E}
  54.1666%{background-color:#3B0F52}        /* the window's centre reaches the apex */
  78%{background-color:#C4204E}
  100%{background-color:#FF3D00}
}
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  .hero .display{
    background-image:linear-gradient(95deg,
      #FF3D00 0%,   #FF3D00 20%,
      #E8481C 30%,  #C4204E 38%,  #96125A 44%,
      #3B0F52 50%,
      #96125A 56%,  #C4204E 62%,  #E8481C 70%,
      #FF3D00 80%,  #FF3D00 100%);
    background-size:500% 100%;
    background-position:0% 50%;
    -webkit-background-clip:text;background-clip:text;
    animation:swg-name-drift 60s linear -5s infinite;
  }
  .hero .display span{color:transparent;-webkit-text-fill-color:transparent}
  .hero .wizard-stop{
    background-color:#FF3D00;
    animation:swg-mark-drift 60s linear -5s infinite;
  }
}
/* The site's rule is that motion is opt-in, and a colour cycle is motion. Asked to
   reduce it, the name is simply --red — which is where the cycle spends its held
   seconds anyway, so nothing is lost but the travel. */
@media (prefers-reduced-motion:reduce){
  .hero .display{animation:none;background-image:none}
  .hero .display span{color:var(--red);-webkit-text-fill-color:var(--red)}
  .hero .wizard-stop{animation:none;background-color:currentColor}
}
/* A span inside a lead is a deliberate line break, set the same way .display sets
   its own lines. Used where a sentence would otherwise leave a one-word orphan at
   the end of the row above it. */
/* text-wrap:balance evens the lines instead of filling the first and leaving a
   short second. The Projects lead ran 503px against 219 on a 528px measure — the
   first line almost full width, which is what reads as "too long". Balanced it is
   372/350. It is set here rather than as a one-off max-width because balance
   re-solves for whatever the copy becomes; a hand-tuned measure only fits the
   sentence it was tuned to. Browsers cap balancing at a few lines, which is
   exactly the range a lead occupies. */
.lead{font-size:var(--fs-lead);line-height:1.35;letter-spacing:-.015em;max-width:22em;font-weight:400;text-wrap:balance}
.lead span{display:block}

/* balance rather than pretty, and it is measured. Across eleven viewports the rag
   checker counts 20 faults unset, 19 with pretty and 4 with balance — pretty only
   fixes a single-word widow and does nothing about a short last line with two words
   in it, which is most of what these paragraphs were producing. */
.prose{max-width:var(--measure)}
.prose p{text-wrap:balance}
.prose p + p{margin-top:1.2em}

.mono{
  font-family:var(--font-mono);font-size:var(--fs-mono);font-weight:400;
  letter-spacing:var(--track-mono);color:var(--mono-dim);
}
.mono-up{text-transform:uppercase}

.tagstring{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:.1em;
  color:var(--mono-dim);text-transform:lowercase;line-height:1.9;
}

/* Numbered section headers — number and label the same size, number in blue. */
.section-head{
  font-family:var(--font-display);font-size:var(--fs-section);font-weight:var(--fw-display);
  letter-spacing:var(--ls-display);line-height:1;
  display:flex;gap:.6em;align-items:baseline;
  padding-bottom:22px;border-bottom:1px solid var(--hairline);margin-bottom:clamp(32px,4vw,56px);
}
.section-head .num{
  color:var(--red);font-family:var(--font);font-weight:500;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
}

/* Content that is not final. Visible on purpose. */

/* --- nav ------------------------------------------------------------------ */
.nav{position:sticky;top:0;z-index:10;background:var(--paper);border-bottom:1px solid var(--hairline)}
.nav-in{display:flex;align-items:center;justify-content:space-between;gap:24px;padding-block:22px}
/* No mark in the nav — the wordmark is plain type in the grotesk. The blackletter
   files in refs/ are provisional and nothing loads them; when they land, the gap
   below carries the spacing. */
.nav-name{font-size:1.4063rem;font-weight:500;letter-spacing:-.015em;white-space:nowrap;display:inline-flex;align-items:center;gap:10px}
.nav-name:hover{color:var(--red-text)}
.nav-links{display:flex;gap:28px}
/* THE NAV SETS ITS OWN SIZE (Filippos, 2026-09-14: the top reads small beside the rest).
   The wordmark is 18px and the links 13px, where they were 15 and the shared 11px caption
   size. The links no longer take --fs-mono, so tile captions, the tag line and the category
   lines are untouched. 13px uppercase mono in --mono-dim is 4.59 on paper, clear of the 4.5
   floor small type has to hold. Phones keep the smaller nav in the 600 block below, which is
   also what keeps four links beside the wordmark at 320. */
.nav-links a{
  font-family:var(--font-mono);font-size:1.0156rem;letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);transition:color .15s;
}
.nav-links a:hover,.nav-links a[aria-current="page"]{color:var(--red-text)}

/* --- hero ----------------------------------------------------------------- */
/* THE HERO IS SPACED TO A REQUIREMENT, NOT TO TASTE: at 1280x720 a visitor must
   see the name, the motto and the proposition without scrolling. That is 720px to
   hold the 65px nav, the padding, a two-line name, the motto and two paragraphs.
   The old 9vw/6vw pair spent 192px of it on air at that width and pushed the
   proposition under the fold, so both are cut roughly in half. Spacing was
   reduced before size, because the name is the thing worth keeping big. */
.hero{
  min-height:calc(100svh - 74px);padding-block:clamp(40px,5vw,88px) 32px;
  display:flex;flex-direction:column;position:relative;
}

/* --- the hero film: REMOVED 2026-09-08, and not to be retried ------------- *
   There was a loop behind the name. It is gone, with the veil token that served it,
   and the full measurement is in aios/LOG.md under that date. The short version:

   THE PALETTE HAS NO CONTRAST BUDGET TO SPEND ON A BACKGROUND. Every colour the hero
   sets sits within a third of a point of its floor — --red 3.113 against 3.0,
   --mono-dim 4.59 against 4.5, --red-text 4.81 against 4.5. That is by design; --red
   was chosen against black, where it is 5.92. The wordmark is the tightest of the
   three, so the ground beneath it can fall no more than 3.8% below paper.

   Measured as a per-pixel minimum across all 660 frames, the loop's dark figure
   crosses every horizontal band at luma ~40. No veil, crop, object-position or filter
   reaches the threshold: --red wants the veil at .98, which is a film 2% visible.

   The video is still in assets/video/. It is legal anywhere nothing is set over it —
   a band of its own is the obvious home. It is not legal behind type on this palette,
   and the arithmetic above is why, so that this is not tried a third time. */
/* THE WIZARD IS THE FULL STOP. It closes the wordmark the way a period would, so
   it sits ON the baseline and takes the size from the type rather than a fixed px:
   .175em of --fs-name, which is 42px against the 238px cap and 10px on a 58px phone.
   It was .26em until Filippos cut it by a third on 2026-09-08 — at 62px the mark read
   as a character in the word rather than the mark that ends it.

   IT IS NOT A <span>, and that is not a style choice. .display span is display:block
   — it is what puts "Guild" on its own line — so a span here would drop the mark onto
   a third line of its own. It began as an <i> and became an <a> when it was linked.

   IT IS THE SECOND DOOR TO THE HERITAGE SITE, 2026-09-08. Same href and the same kind
   of label as .wizard-egg in the footer, so the two marks behave alike. Being a link
   is why the h1 carries an explicit aria-label: a link inside a heading donates its
   accessible name to that heading, and without the override the page's main heading
   would announce as "Silly Wizards Guild The old guild hall". The label pins the
   heading to the wordmark and leaves the link to announce itself.

   NO OPACITY SHIFT ON HOVER, unlike the footer mark. That one rests at .38 and comes
   up to full because it is an aside; this one is punctuation inside the wordmark and
   has to be at full strength always, so the lift is the whole affordance.

   MASKED RATHER THAN PLACED, the .threshold-mark pattern: the artwork supplies the
   shape and currentColor supplies the ink, so the wizard inherits --red from the
   span it closes and would follow the wordmark anywhere the tokens take it. A flat
   PNG would have to be re-exported for that.

   image-rendering is not set because a mask is not resampled ink — the alpha is
   scaled, and at these sizes the pixel edges stay square without asking. */
/* THE MARK SWAPPED ON 2026-09-17 (see .wizard-egg): wizard-mark.svg, 16 x 16 and exactly
   square. The old artwork was 246 x 257, so the box already held it edge to edge; the size
   is unchanged at his word. Simon's handover suggests trying .15em because the new figure
   is denser — a taste call left open, not a fix. */
.wizard-stop{
  display:inline-block;width:.175em;height:.175em;
  margin-left:.025em;vertical-align:baseline;
  background-color:currentColor;
  -webkit-mask:url('assets/wizard-mark.svg') center/contain no-repeat;
  mask:url('assets/wizard-mark.svg') center/contain no-repeat;
  transition:transform .18s;
}
.wizard-stop:hover,.wizard-stop:focus-visible{transform:translateY(-.05em) rotate(-8deg)}
.wizard-stop:focus-visible{outline:2px solid var(--red);outline-offset:.06em}
@media (prefers-reduced-motion:reduce){
  .wizard-stop{transition:none}
  .wizard-stop:hover,.wizard-stop:focus-visible{transform:none}
}

/* The second half of the lockup. Mono, tracked, at the accent's small-text value:
   --red is 3.11 on this ground and illegal below 24px, --red-text is 4.81. */
.motto{
  margin-top:clamp(14px,1.6vw,22px);
  /* 19.5px is 13 up by half, Filippos 2026-09-08. STILL --red-text, NOT --red:
     the accent's large-text value only becomes legal at 24px and this is under it.
     Tracking comes down from .14em to .11em because tracking is a function of size
     — .14em was set for 13px, and held at 19.5 it opens the word spaces up until
     the line reads as separated caps rather than a phrase. */
  font-family:var(--font-mono);font-size:1.6759rem;letter-spacing:.11em;
  text-transform:uppercase;color:var(--red-text);line-height:1.4;
}
.hero-body{margin-top:clamp(24px,2.6vw,40px);display:flex;flex-direction:column;gap:14px}
/* The line under the proposition. Smaller and on the prose measure, so it reads
   as the sentence that explains the one above rather than a second claim. */
.lead-sub{font-size:max(1.0625rem,var(--fs-body));line-height:1.55;letter-spacing:-.005em;max-width:32em}
/* THE HOME LEAD IS WIDER THAN THE DEFAULT, and 32em is measured, not chosen.

   The 23.4em it used to be existed for one reason: the old lead ended in a <span>
   that .lead span sets as its own block line, and that clause needed 22.53em. The
   copy of 2026-09-08 has no span, so that number stopped meaning anything.

   THE NEW NUMBER IS THE 1280x720 FOLD. That screen must show the name, the motto
   and the proposition without scrolling. The new copy is half again as long as the
   old, and at the default 22em it sets SEVEN lines and ends 87px under the fold.
   Widening is what recovers it, and it recovers it without touching the wordmark:

     measure   lines   lead ends   fold at 720
      22em       7        807         -87
      25em       5        734         -14
      28em       5        734         -14
      32em       4        698         +22   <- in use
      34em       4        698         +22

   32em is where text-wrap:balance drops it to four lines; 34 buys nothing more. At
   27px that is about 64 characters, the top of the comfortable range rather than
   outside it. The alternative was cutting --fs-name's svh cap, which would have
   shrunk the name on every short screen to buy room for a paragraph. The paragraph
   gave way instead.

   FOUR AND A HALF PIXELS IS THE WHOLE MARGIN, measured 2026-09-14. This said twenty-two
   and had not been re-measured since; the hero has grown since then and nobody checked,
   which is the failure this note exists to prevent. The tag line now ends at 716 of 720.
   Anything added to this hero, or any growth in the lead's size or leading, has to be
   measured at 1280x720 again — and there is now almost nothing left to spend. */
.hero .lead{max-width:32em}

.scroll-cue{
  margin-top:auto;padding-top:48px;align-self:flex-start;
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--mono-dim);transition:color .15s;
}
.scroll-cue:hover{color:var(--red-text)}

/* --- page header (work / contact) ----------------------------------------- */
.page-head{padding-block:clamp(56px,8vw,110px) clamp(40px,5vw,72px)}
.page-head .display{margin-bottom:clamp(24px,3vw,40px)}
.page-head-row{display:flex;justify-content:space-between;align-items:flex-end;gap:32px;flex-wrap:wrap}
.page-head .lead-sub{margin-top:clamp(18px,2vw,28px)}

/* --- sections ------------------------------------------------------------- */
.section{padding-block:0 var(--pad-section)}
/* The studios header sits in the wrap; the doorway rows below it bleed full width. */
.doors-head{padding-block:0}
/* On the home page this header is a heading on its own, and .section-head's own
   margin carries the space down to the first doorway. On ai.html it also holds a
   sentence, and that margin belongs to the heading — so the prose was sitting flush
   against the doorways' top hairline at a measured 0px. This is a little wider than
   the section rhythm because it meets a full-bleed rule rather than open page. */
.doors-head .prose{margin-bottom:clamp(40px,5vw,72px)}
.section-doors{padding-block:0 var(--pad-section)}
.hero + .section{padding-block-start:clamp(56px,7vw,96px)}

/* --- work grid ------------------------------------------------------------ */
/* Masonry, not grid. Grid rows are as tall as their tallest cell, so a 16:9 tile
   sitting beside a 4:5 one left a 466px hole under its caption -- 41% of the section
   was empty. Columns let each tile take only the height it needs. The cost is that
   reading order becomes column-major, which the ( 01/08 ) indices already carry. */
.grid{columns:2;column-gap:var(--gap-grid)}
.tile{break-inside:avoid;margin-bottom:var(--gap-grid)}
.tile{display:block}
.ph{
  background:var(--greige);display:grid;place-items:center;
  border:1px solid transparent;transition:border-color .15s;
}
.ph-label{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--mono-dim);text-transform:lowercase;padding:0 16px;text-align:center;
}
/* Hover edge is the one red graphic accent on this page. Colour only — no motion. */
.tile:hover .ph{border-color:var(--red)}

/* A frame that carries real media holds it edge to edge and never lets it resize
   the frame — the aspect ratio is the design, the media fits inside it.

   TWO THINGS HERE ARE FIXES, AND BOTH WERE INVISIBLE UNTIL A SERIES MIXED RATIOS.

   place-items:stretch. .ph centres its contents so an empty frame can middle its
   label, and a centred grid item is NOT stretched — so `height:100%` below had
   nothing to resolve against and object-fit:cover never cropped anything. Every
   image simply sat at its natural height. It looked correct only while each image
   matched its frame's declared aspect exactly.

   [hidden]. `.ph img` is specificity (0,1,1) and the browser's own `[hidden]` rule
   is (0,1,0), so `display:block` beat it and NO FRAME IN A SERIES WAS EVER HIDDEN.
   They were all painted, stacked, and merely clipped by overflow:hidden above —
   which held up for as long as the first image happened to fill the frame. ASTRA
   pairs a 16:9 still with a 3:2 one: the first covered 308px of a 367px frame and
   the next image showed through the remaining 59. */
.ph--filled{overflow:hidden;place-items:stretch;position:relative}
/* Pin media to the frame rather than letting an intrinsic image ratio make the grid
   item taller than its aspect-ratio box. This also makes each slide's object-position
   a real focal point: `center bottom` now removes excess image from the top. */
.ph img,.ph video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.ph img[hidden],.ph video[hidden]{display:none}
.ph[data-reel]{cursor:pointer}
.ph[data-reel]:focus-visible{outline:2px solid var(--red);outline-offset:3px}

.tile figcaption{padding-top:16px;display:flex;flex-direction:column;gap:6px}

/* The small mono line says what the frame does. A reel that can move carries a native
   Next button, and a reel showing film carries Play beside it; controls stay below the
   frame because type over unknown media is a contrast gamble. */
.tile-do{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--red-text);text-transform:lowercase;cursor:pointer;
}
/* On the caption's left edge, where the title and the read-more hairline start
   (Filippos, 2026-09-11; they sat centred until then). A lone button would stretch
   across the caption column and centre its own text, so it keeps its own width
   instead, and the two-control row starts at the edge too. Wraps because a narrow
   column cannot hold both controls on one line. */
.tile figcaption>.tile-do{align-self:flex-start}
.tile-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
/* Punctuation, not an action: the separator takes the meta line's dim tone so the
   controls beside it read as two things to do, not one run-on phrase. */
.tile-sep{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--mono-dim);
}
.tile-do--button{appearance:none;border:0;background:none;padding:0}
.tile-do--button:hover{color:var(--ink)}
.tile-do--button:focus-visible{outline:2px solid var(--red);outline-offset:3px}
/* THE SOUND CONTROL SITS ON THE CAPTION'S TOP LINE (Filippos, 2026-09-14). Right side,
   directly under the frame, its top on the credit line — the client and agency names — which
   is the first thing the caption says; on own work with no credit it rides the name instead.
   The glyph is drawn in currentColor at 1.575em of the mono line it inherits, never a fixed
   pixel size, so it follows the type scale. No top nudge: the speaker's ink starts a fifth of
   the way down its box, which is about where a mono cap starts, so the two tops meet on their
   own. Its right edge is flush with the frame above (Filippos, 2026-09-14, replacing the 25px
   inset he asked for earlier the same day): the frame, the figure and the caption all share one
   right edge, so the glyph lines up with the picture rather than floating inside it. */
.tile-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.tile-do--icon{display:inline-flex;align-items:center;line-height:1}
.tile-do--icon svg{width:1.575em;height:1.575em;display:block}

/* Read more. A native <details>, so keyboard and screen readers work without script.
   Opens in place, inside the tile's own column — no motion, per the site's rules. */
.case{margin-top:14px;border-top:1px solid var(--hairline)}
.case summary{
  list-style:none;cursor:pointer;padding-top:12px;
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:lowercase;color:var(--mono-dim);transition:color .15s;
}
.case summary::-webkit-details-marker{display:none}
.case summary:hover{color:var(--red-text)}
.case[open] summary{color:var(--red-text)}
.case[open] summary::after{content:' \2014 close'}
/* Deliberately a step below the 15px used for section body copy: the case sits inside
   a tile, under a caption, and should not compete with the work above it. */
.case-body{padding:14px 0 4px;font-size:var(--fs-micro);line-height:1.75;max-width:34em}
.case-download,.case-links{margin-top:12px}
.case-download a,.case-links a{color:var(--red-text);border-bottom:1px solid currentColor}
.case-download a:hover,.case-links a:hover{color:var(--ink)}
.case-draft{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);margin-bottom:8px;
}
/* What we contributed, stated as a label rather than buried in the prose, so every
   case answers it the same way and the paragraphs stay narrative. */
.case-scope{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:lowercase;color:var(--red-text);margin-bottom:12px;
}
/* One mono line above the name: CLIENT in the accent, then +AGENCY in ink (Filippos,
   2026-09-11). The client is small type, so the accent is --red-text at 4.81 on paper;
   --red is 3.11 and fails the 4.5 floor at this size. The two wrap as whole names. */
.tile-credit{
  display:flex;flex-wrap:wrap;column-gap:1.1ch;
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;
}
.tile-collab{color:var(--ink);transition:color .15s}
.tile:hover .tile-collab{color:var(--red-text)}
/* The client carries the recognition; the project name carries the specifics. */
.tile-client{color:var(--red-text)}
.tile-title{font-size:var(--fs-title);font-weight:500;letter-spacing:-.02em;line-height:1.2}
/* The title is the way into the case page (2026-09-21). It reads as the title, not as a link,
   until the pointer or focus reaches it — the site's other quiet links behave the same way. */
a.tile-title{display:block;transition:color .15s}
a.tile-title:hover,a.tile-title:focus-visible{color:var(--red-text)}
.tile-meta{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--mono-dim);text-transform:lowercase;font-variant-numeric:tabular-nums;
}

/* --- network -------------------------------------------------------------- */
/* The agency names sit under their own label, the same mono device the client and
   award rows use. Names as text, never marks — that rule is about these, not clients. */
.net-list{display:flex;flex-direction:column;gap:18px;margin-top:clamp(32px,4vw,52px)}
/* 21em, and the number is the rag rather than a preference. At 24em the second line
   ended "... DDB · Mother ·" and left Less Rain and Space10 alone on the third, which
   split the independents across two lines for no reason. Swept 18 to 26: 18 through 21
   all break after DDB and 22 puts Mother back up, so 21 is the widest that holds the
   grouping — networks on the first two lines, the independent shops on the last.
   Filippos asked for it as "move Mother a row down"; this is that, done by measure
   rather than by a <br> that would only be right at one width. */
/* Up a fifth on the lead, 2026-09-14, at Filippos's word — which landed on --fs-head,
   the step added in the same pass, so it takes the token rather than a multiplier: 41px
   against the lead's 34.3. The 21em measure is in em on purpose. It scales WITH the size,
   which is the only reason the "move Mother a row down" grouping survived the increase —
   a px measure there would have broken it. Verified: Mother still opens line 3. */
.networks{font-size:var(--fs-head);line-height:1.45;letter-spacing:-.015em;max-width:21em}

/* --- statement band ------------------------------------------------------- */
/* Twice the hero paragraph's size — it was matched to it earlier and deliberately
   unmatched again. At this size it reads as a statement, so the leading tightens and
   the measure comes in to stop it running the full column width. */
.statement{
  font-family:var(--font-display);font-size:clamp(2.9688rem,4.4vw,3.75rem);
  font-weight:var(--fw-display);line-height:1.15;letter-spacing:var(--ls-display);
  max-width:16em;text-wrap:balance;
}
.statement .scope{color:var(--red)}

/* The statement's call to action. Set below the statement and well under it in size:
   the statement is the claim, this is the consequence. The address uses the same chip
   as the contact strip, one size down, so it reads as the same gesture. */
.scope-cta{
  margin-top:clamp(28px,3.4vw,44px);display:flex;flex-direction:column;
  align-items:flex-start;gap:clamp(16px,1.8vw,22px);max-width:34em;
}
.scope-cta p{font-size:var(--fs-body);line-height:1.55}
.cta-mail{
  display:inline-flex;align-items:center;gap:.4em;flex-wrap:wrap;
  font-size:var(--fs-body);font-weight:500;letter-spacing:-.02em;
  line-height:1.1;transition:color .15s;word-break:break-word;
}
.cta-mail:hover{color:var(--red-text)}

/* --- logo strips ---------------------------------------------------------- */
/* Client and award marks. Normalised to flat ink so a dozen source files with a
   dozen different colour treatments read as one row rather than a ransom note. */
.logos{display:flex;flex-direction:column;gap:clamp(32px,4vw,56px)}
.logo-row{display:flex;flex-direction:column;gap:20px}
.logo-label{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);
  padding-bottom:14px;border-bottom:1px solid var(--hairline);
}
.logo-strip{
  list-style:none;display:flex;flex-wrap:wrap;align-items:center;
  gap:clamp(18px,2.2vw,26px);
}
/* Each mark gets a bounded cell, capped on BOTH axes, and is contained inside it.
   Normalising on height alone sets a 6:1 wordmark at the same height as a roundel,
   which reads as one logo shouting. The files are trimmed and alpha-matted to ink,
   so no blend mode is involved and the row holds on any ground. */
.logo-strip li{display:flex;align-items:center;justify-content:center}
/* The caps live on the image, not the cell. A percentage max-height on a centred
   grid child has no definite container to resolve against, so it computes to none
   and a tall roundel overflows its cell onto whatever sits below. */
.logo-strip img{width:auto;height:auto;opacity:.82;transition:opacity .15s}
.logo-strip li:hover img{opacity:1}
/* Both axes are capped and both scale with k, so every mark is fitted into the same
   optical box rather than to the same height. Equalising height alone sets a 6.7:1
   wordmark six times wider than a roundel, which is the opposite of consistent. */

/* Clients ship pre-framed: every file is 350x250 with the mark centred and optically
   sized by hand, so the cells are uniform and no correction is applied. */
.logo-grid{
  display:grid;grid-template-columns:repeat(7,1fr);
  gap:clamp(8px,1.4vw,20px);align-items:center;
}
.logo-grid--awards{grid-template-columns:repeat(8,1fr)}
.logo-grid img{width:80%;height:auto;max-width:none;max-height:none}

/* --- belief lines (ai.html) ----------------------------------------------- */
/* Five statements set at display-statement scale and composed as ONE block, not
   five paragraphs. Three things are measured rather than chosen:

   The 44px cap. The longest statement is 25.23em at this weight and tracking, and
   the wrap is 1152px inside its padding, so 44px is the largest size that still
   holds it on one line. Above roughly 700px viewport every statement is one line;
   below it they wrap, which is why the gap is set the way it is.

   The gap in em, not px. It has to track the type through the clamp, so it is set
   on the container and the statements inherit their size from it — an em gap on a
   flex container resolves against the container's own font-size, so the size has
   to live here rather than on the children.

   The gap against the leading. Leading is 1.1, so lines inside a wrapped statement
   sit .1em apart; .34em between statements puts them 1.45em apart. The grouping
   survives wrapping instead of collapsing into ten equal lines. */
/* The belief lines follow the prose in "Why now" now, rather than owning a section
   of their own, so they need the gap the section break used to give them. */
.prose + .beliefs{margin-top:clamp(30px,3.6vw,52px)}
.beliefs{
  display:flex;flex-direction:column;gap:.34em;
  font-size:var(--fs-head);line-height:1.1;letter-spacing:-.022em;
  font-weight:400;max-width:26em;
}
.beliefs p{text-wrap:balance}

/* --- what we do ----------------------------------------------------------- */
.skills{display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(32px,4vw,64px) clamp(32px,4vw,72px)}
.skill{border-top:1px solid var(--red);padding-top:18px}
.skill-title{font-size:var(--fs-title);font-weight:500;letter-spacing:-.02em;line-height:1.2;margin-top:12px}
.skill-body{font-size:max(0.9375rem,var(--fs-small));line-height:1.6;color:var(--ink);margin-top:10px;max-width:32em}

/* --- the two hire sections, set louder ------------------------------------ *
   02 and 03 are what someone buys, so they carry more weight than the cards
   elsewhere: a 2px accent rule instead of 1, a title a step up the scale, and a
   number large enough to read as an index rather than a footnote.

   IT IS DONE WITH TYPE, NOT WITH A NEW GROUND. A greige band was the obvious way
   to make them stand out and it is not available: greige is L .623, which puts
   --red at 2.27 and --red-text at 3.52 on it. Both accent values are measured
   against --paper and neither survives the move — the rule above each card and
   the number inside it would go illegal together. The accent is what makes these
   read as this site, so the ground stays and the type gets louder instead. */

/* --- 02 · the offer band --------------------------------------------------- *
   THE ONE DARK GROUND ON THE SITE, Filippos 2026-09-08: black band, large cards in
   white and the accent, large type.

   IT ANSWERS THE CANON QUESTION ABOUT DARK GROUNDS. The anti-pattern says the site
   cannot go dark without reintroducing a light band to hold the client and award
   marks, because those are alpha-matted to #141414. It is answered by geography:
   the marks live in #proof, a separate section ABOVE this one, and keep their beige
   ground untouched. Nothing here goes near them. That objection binds a whole-site
   inversion, which this is not.

   BLACK BUYS CONTRAST, IT DOES NOT SPEND IT. Everywhere else --red is 3.11 on the
   beige and illegal under 24px, which is why --red-text exists at all. On this ground
   it measures 5.55, so the two values collapse and the accent is legal at any size
   for the first time on the site. The band is the one place the palette is not
   fighting itself. (5.55, not the 5.92 canon quotes: that figure is against pure
   black, and this ground is #0B0B0B. Both clear 4.5; the number is written here as
   measured rather than as remembered.)

   TOKENS SWAP, COLOURS ARE NOT HARD-CODED — the .page-red pattern, for the same
   reason: every rule underneath goes on reading --ink and --paper and comes out
   right. Cards then swap them back for their own surface, so an .offer is a small
   light page sitting on a dark one.

   MEASURED IN THE RENDERED PAGE, not calculated and hoped for:

     band       --ink 17.27 · --red 5.55 · --mono-dim 5.58
     light card  ink 16.17 · --red-text 4.81 at 13px
     accent card ink 5.19, and it carries the number, the title AND the body

   Every value clears its floor with room, which is not true anywhere else on this
   site. The accent card is the surprise: ink on --red at 5.19 means 16px body copy
   is legal on it without growing, so the card needs no special small-type rule.

   SQUARE, 2026-09-08. The first build rounded these to 20px after the supplied
   references, and Filippos cut it. He is right and the site was already saying so:
   every other corner here is square and the only border-radius in the file makes
   circles. A square card reads as a panel of the band rather than an object resting
   on it, which is what this section is.
   ========================================================================== */
.band-ink{
  --paper:#0B0B0B;                    /* ground and ink trade places */
  --ink:#F2F0EA;
  --red:#FF3D00;                      /* 5.92 here, so both values collapse to it */
  --red-text:#FF3D00;
  --mono-dim:#8C8880;                 /* 5.7 on the ground — 13px mono needs 4.5 */
  --hairline:rgba(242,240,234,.20);
  --greige:rgba(242,240,234,.10);
  background:var(--paper);color:var(--ink);
  padding-block:clamp(56px,7vw,104px);
  margin-bottom:0;
}
.band-ink ::selection{background:var(--ink);color:var(--paper)}

/* Twelve columns, and the cards do not all take the same number. Six equal boxes
   is a table; the brief asked for something with movement in it. Widths run 7-5,
   5-7, 7-5 so the seam walks across the band instead of ruling a line down it, and
   the colour alternates against the width rather than with it, so neither the wide
   card nor the accent is always the same thing. */
.offers{display:grid;grid-template-columns:repeat(12,1fr);gap:clamp(12px,1.3vw,20px)}
.offer{
  padding:clamp(26px,2.7vw,44px);
  display:flex;flex-direction:column;
  /* A floor, not a height: the grid already equalises a row, and this only stops a
     two-line card collapsing to nothing beside a long one. It came down from 330
     after the first render, where a short card like "Getting it built" held a void
     between its number and its title. */
  min-height:clamp(200px,20vw,270px);
}
.offer--wide{grid-column:span 7}
.offer--narrow{grid-column:span 5}
.offer--light{
  --paper:#F2F0EA; --ink:#141414; --red:#C92B00; --red-text:#C92B00; --mono-dim:#6E6C69;
  background:var(--paper);color:var(--ink);
}
/* On the accent the accent cannot be itself, so it becomes ink — the same trade
   .page-red makes. Ink measures 5.20 on this ground, so the body copy is legal at
   its own size rather than needing to grow. */
.offer--accent{
  --paper:#FF3D00; --ink:#141414; --red:#141414; --red-text:#141414;
  --mono-dim:rgba(20,20,20,.76);
  background:var(--paper);color:var(--ink);
}
.offer-num{
  font-family:var(--font-mono);font-size:var(--fs-nano);letter-spacing:var(--track-mono);
  color:var(--red-text);font-variant-numeric:tabular-nums;
}
/* margin-top:auto pins the title and its paragraph to the foot of the card, so the
   numbers line up across a row and the titles do too, whatever the copy length. */
/* BALANCE ON BOTH, AND THE BODY'S IS MEASURED RATHER THAN ASSUMED. Unset, the rag
   checker found real faults: "organisation" and "department" each alone on a title's
   last line, and card 06's paragraph ending on "everything." by itself. balance is
   the obvious tool for a heading. For the paragraphs the obvious tool was pretty —
   it is the one built for last lines — so all three were run across thirteen
   viewports from 320 to 1920:

     unset    15 faults
     pretty    3 faults   (1152 and 320)
     balance   0 faults

   pretty fixes single-word widows and does nothing about a last line that is short
   but has two words in it, which is most of what the stub rule catches. balance is
   capped at six lines by the browser and these paragraphs run three or four, so it
   applies in full. Re-run aios/checks/rag.js after any copy change here — fixing one
   break moves the next, and the checker RETURNS its faults rather than logging them,
   so call the function it gives back or you will read an empty console and believe
   anything is clean. */
.offer-title{
  font-family:var(--font-display);font-size:var(--fs-head);
  font-weight:var(--fw-display);letter-spacing:-.028em;line-height:1.05;
  margin-top:auto;padding-top:clamp(28px,3vw,48px);text-wrap:balance;
}
/* 28em is swept, not chosen. The checker was run at 1920, 1440, 1280 and 1024 for
   every measure from 28em to none; the faults per setting came out 1, 2, 5, 8, 5, 7,
   5 and 5. 28 is the floor of that curve, and its single remaining fault is the
   stranded "Funding" in card 02, bound with &nbsp; in the markup because that is the
   one fault CSS cannot reach. */
.offer-body{font-size:var(--fs-body);line-height:1.5;max-width:28em;margin-top:14px;text-wrap:balance}

/* One column under 860, where a 5-of-12 card would be too narrow to hold a title
   at this size without breaking every line. */
@media (max-width:860px){
  .offers{grid-template-columns:1fr}
  .offer--wide,.offer--narrow{grid-column:auto}
  .offer{min-height:0}
}

/* --- 03 · the terms strip -------------------------------------------------- *
   THE COUNTERPART TO 02, INVERTED ON EVERY AXIS. That band is black with cards held
   apart on a grid; this one is white with the cards abutting in a single strip, and
   the cards carry the two darks instead of the ground. Placed one after another so
   the alternation reads as one object rather than five, which is the point: these
   are five ways to buy the same thing, not five different things.

   WHITE, NOT --paper. The page is already #F2F0EA, so a beige band would be no band
   at all. Pure white is the only light value that reads as a surface change against
   it, and it is the reason this section is the ONE place --red-text measures 5.48
   rather than the 4.81 it manages on the beige.

   Measured in the rendered page:
     white band   --ink 18.42 · --red 3.55 at section size · --red-text 5.48
     ink card     --paper 17.27 · --red 5.55
     accent card   ink 5.19

   FIVE CARDS DIVIDE THE MEASURE. It was briefly a drifting rail with the cards
   overlapping like a spread deck; Filippos took the motion and the overlap back out
   on 2026-09-08 and the row returned. See the note on .terms below.
   ========================================================================== */
.band-paper{
  --paper:#FFFFFF;
  --mono-dim:#5F5C58;                 /* 6.65 on white — 13px mono needs 4.5 */
  --hairline:rgba(20,20,20,.16);
  background:var(--paper);color:var(--ink);
  padding-block:clamp(56px,7vw,104px);
  margin-bottom:var(--pad-section);
}
/* THE RAIL IS GONE, 2026-09-08. It drifted, the cards overlapped like a spread deck,
   and Filippos took both back out: a stable row, five cards side by side, no motion.
   What survives is the design the movement was carrying — portrait cards on a white
   band, alternating the two darks, number at the top and the title pinned to the foot.

   REMOVED WITH IT, and worth naming so none of it comes back by accident: the
   duplicate set of five (they existed only so the loop had no seam), the edge masks,
   the five-pitch keyframes, the overlap and its shadow, the extra right padding that
   kept copy clear of the card on top, and the reduced-motion branch — with nothing
   moving there is nothing left for that branch to switch off. */
.terms{
  display:grid;grid-template-columns:repeat(5,1fr);
  gap:clamp(12px,1.2vw,20px);
  margin-top:clamp(28px,3vw,44px);
}
/* NO FIXED RATIO, 2026-09-14. This was 16/25 — "the proportion from the rail, kept" —
   and that ratio was the cause of a horizontal scrollbar on this page at every width
   from about 1000 to 1300: measured on the committed sheet at 255px of page scroll at
   1024, 179 at 1180, 43 at 1280, and a 40px bleed past the measure at 1440. Committed,
   and nobody had noticed, because the section gets judged on a large display where the
   symptom is only a row 40px wider than the rule above it.

   AN ASPECT-RATIO RESOLVES BOTH WAYS, which is the whole fault. When the content — the
   icon at 88px, the two min-heights, the padding — needed more height than the allotted
   width could give at 16/25, the ratio turned that excess height back into WIDTH: 401px
   tall forced 257px wide where the grid had allotted 249, and five inflated tracks
   overran the measure. Raising the title size made it far worse, since taller content
   means a wider card. Cards holding variable-length copy size to that copy instead.
   Verified 0 overflow and 0 page scroll at 1024, 1180, 1280, 1440 and 1512. */
.term{
  padding:clamp(18px,1.7vw,26px);
  display:flex;flex-direction:column;
}
.term--ink{
  --paper:#0B0B0B; --ink:#F2F0EA; --red:#FF3D00; --red-text:#FF3D00;
  background:var(--paper);color:var(--ink);
}
.term--accent{
  --paper:#FF3D00; --ink:#141414; --red:#141414; --red-text:#141414;
  background:var(--paper);color:var(--ink);
}
.term-num{
  font-family:var(--font-mono);font-size:var(--fs-nano);letter-spacing:var(--track-mono);
  color:var(--red-text);font-variant-numeric:tabular-nums;
}
.term-icon{
  width:clamp(65px,6.12vw,88px);height:auto;align-self:flex-end;flex:none;
  margin-top:clamp(14px,1.7vw,24px);color:#FFF;overflow:visible;
  shape-rendering:crispEdges;
}
.term-icon-fill{fill:currentColor}
.term-title{
  font-family:var(--font-display);font-size:var(--fs-title);
  font-weight:var(--fw-display);letter-spacing:-.02em;line-height:1.05;
  min-height:2.2em;text-wrap:balance;
}
.term-body{
  min-height:4.35em;font-size:max(0.875rem,var(--fs-small));line-height:1.3;margin-top:10px;text-wrap:balance;
}

/* Five cards is too many across a narrow screen at this proportion — 210px of card
   at 1024 and less below it will not hold a title and a sentence. Two columns, then
   one, both letting go of the aspect ratio. */
@media (max-width:1000px){
  .terms{grid-template-columns:repeat(2,1fr)}
  .term{aspect-ratio:auto;min-height:clamp(150px,20vw,190px)}
}
@media (max-width:700px){
  .terms{grid-template-columns:1fr}
  .term{min-height:0}
  .term-title,.term-body{min-height:0}
}

/* --- 03 · the coda --------------------------------------------------------- *
   THE BROKEN RULE IS THE WHOLE DEVICE. Each column carries its own hairline, so
   what reads as one line across the band is interrupted twice — and the two breaks
   fall exactly where a handoff would. The headline claims less between you and the
   work; the rule draws it. Nothing else here is decorated.

   The accent appears once, as that 1px line. Sections 02 and 03 are already loud —
   a black band and a row of black and orange cards — so the close of the argument
   is the quiet thing on the page, and the column gap is set wider than the text
   needs so the breaks read as intentional rather than as a grid artefact.

   No numbers. 02 and 03 both number their items because those ARE enumerated
   offers; these three are one claim seen from three sides. */
.coda{margin-top:clamp(56px,7vw,104px)}
.coda-head{
  font-family:var(--font-display);font-size:clamp(1.875rem,3.8vw,3rem);
  font-weight:var(--fw-display);letter-spacing:var(--ls-display);line-height:1.1;
  max-width:14em;text-wrap:balance;margin-bottom:clamp(32px,4vw,56px);
}
.coda-cols{
  display:grid;grid-template-columns:repeat(3,1fr);
  column-gap:clamp(28px,3.4vw,56px);row-gap:clamp(32px,4vw,48px);
}
.coda-item{border-top:2px solid var(--red);padding-top:20px}
.coda-title{
  font-family:var(--font-display);font-size:var(--fs-title);
  font-weight:var(--fw-display);letter-spacing:-.02em;line-height:1.2;
  text-wrap:balance;
}
.coda-body{font-size:var(--fs-body);line-height:1.4;margin-top:12px;text-wrap:balance}

/* One column below 940, and the number is the rag rather than a guess. Three columns
   still FIT well below this — they fit at 834 — but a column under about 265px cannot
   hold a four-sentence paragraph without orphaning its last line. Swept: 834 gave
   three faults, 890 gave two, 900 and up were clean. 780 and 880 were both tried and
   both left a dirty band above them. Binding words would only have papered over a
   column too narrow for the copy, so the breakpoint moves instead — and the broken
   rule stops meaning anything once the columns stack anyway. */
@media (max-width:940px){
  .coda-cols{grid-template-columns:1fr}
}

/* --- the cookie policy, and the notice that points at it ------------------- *
   BORROWED SHAPE, INVERTED MESSAGE. The pattern is a full-bleed dark bar fixed to
   the foot of the page with a large square acknowledge control — the arrangement
   Filippos brought from thelightphone.com. What it says is the opposite of what
   such a bar usually says: that site sets Google Analytics cookies and asks your
   permission; this one sets none and simply tells you so. Audited 2026-09-10 with
   the page open: no cookies, no localStorage, no sessionStorage, no iframes, no
   third-party scripts, no service worker. There is no outside origin: since 2026-09-21 the fonts are served from this site too.

   IT IS A NOTICE, NOT A GATE. Nothing is withheld until you press it and nothing
   changes when you do, so it takes no overlay, traps no focus and blocks no
   scrolling. A consent dialogue would be theatre where there is no consent to give.
   ========================================================================== */
.cookie-note{
  --paper:#0B0B0B;                    /* ground and ink trade places, as .band-ink does */
  --ink:#F2F0EA;
  --hairline:rgba(242,240,234,.34);
  position:fixed;left:0;right:0;bottom:0;z-index:60;
  background:var(--paper);color:var(--ink);
  padding-block:clamp(20px,2.4vw,34px);
}
.cookie-note[hidden]{display:none}
.cookie-note-in{
  display:flex;align-items:center;justify-content:space-between;
  gap:clamp(20px,3vw,48px);flex-wrap:wrap;
}
.cookie-note-say{font-size:var(--fs-title);letter-spacing:-.01em;line-height:1.3;margin:0}
.cookie-note-say a{border-bottom:1px solid var(--hairline);padding-bottom:2px;transition:border-color .15s}
.cookie-note-say a:hover,.cookie-note-say a:focus-visible{border-bottom-color:var(--ink)}
/* Square, bordered, and larger than anything else on the bar — the one thing the
   reader is being offered. No radius: every corner on this site is square. */
.cookie-note-ok{
  flex:none;background:none;cursor:pointer;
  font-family:var(--font-display);font-size:clamp(1.25rem,2.2vw,1.875rem);
  font-weight:var(--fw-display);letter-spacing:-.02em;line-height:1;
  color:var(--ink);border:2px solid var(--ink);border-radius:0;
  padding:.42em .7em;transition:background-color .15s,color .15s;
}
.cookie-note-ok:hover,.cookie-note-ok:focus-visible{background:var(--ink);color:var(--paper)}
.cookie-note-ok:focus-visible{outline:2px solid var(--red);outline-offset:3px}
@media (max-width:620px){
  .cookie-note-in{flex-direction:column;align-items:flex-start}
  .cookie-note-ok{align-self:stretch;text-align:center}
}

/* THE ANSWER IS THE PAGE. Most cookie policies make a reader work through several
   paragraphs to reach the thing they came for. Here the whole answer is one
   sentence, so it runs at statement size before anything else and everything under
   it is the evidence. */
.policy{padding-block:0 var(--pad-section)}
.policy-answer{
  font-family:var(--font-display);font-size:clamp(2.125rem,4.4vw,3.5rem);
  font-weight:var(--fw-display);letter-spacing:var(--ls-display);line-height:1.08;
  max-width:14em;text-wrap:balance;
}
/* ONE SIZE FOR THE WHOLE DOCUMENT, Filippos 2026-09-10. Everything below the opening
   statement — the sub, the headings and every paragraph — sets at --fs-lead. A policy
   set in three sizes with a rule across it reads as a legal notice; set flat, at the
   size of the sentence a reader actually stops on, it reads as something written to
   be read. The accent rule is gone with the sizes.

   HEADINGS ARE STILL HEADINGS, by face and weight rather than by scale. They take the
   display face at its display weight against the body face at 400 — enough to find
   when scanning, not enough to interrupt.

   THE MEASURE HAD TO GROW WITH THE TYPE. max-width in em resolves against the
   element's OWN font-size, so leaving the old 34em while the text went from 16px to
   27px would have cut the line to about 34 characters. The size moves onto .policy-body
   and the measure comes in to 28em, which is ~58 characters at the top of the clamp. */
/* The sub sits OUTSIDE .policy-body, so it cannot inherit the size from there — it
   states --fs-lead itself, and shares the body's 28em so the whole wall aligns. */
.policy-sub{
  font-size:var(--fs-lead);line-height:1.45;letter-spacing:-.015em;
  max-width:28em;margin-top:clamp(18px,2vw,26px);text-wrap:balance;
}
.policy-body{
  font-size:var(--fs-lead);letter-spacing:-.015em;
  max-width:28em;
  margin-top:clamp(40px,5vw,72px);
}
.policy-part + .policy-part{margin-top:clamp(32px,4vw,52px)}
/* balance on the headings only, not the paragraphs. These are four to six words and
   break to two lines on a phone, where "What happens when you open a / page" left the
   last word alone. The paragraphs keep `pretty`: balance was measured against it on
   this page and lost, 17 faults to 15, because evening the lines of a long
   multi-sentence block pushes sentence starts to line ends. */
.policy-h{
  font-family:var(--font-display);font-size:inherit;
  font-weight:var(--fw-display);letter-spacing:-.02em;line-height:1.35;
  margin-bottom:.35em;text-wrap:balance;
}
.policy-part p{font-size:inherit;line-height:1.45;text-wrap:pretty}
.policy-part a{color:var(--red-text);border-bottom:1px solid currentColor}
.policy-part a:hover{color:var(--ink)}
.policy-part p + p{margin-top:.9em}
/* The controller line. Filippos wanted it small and at the foot: it is a legal
   identification, not a section anyone came here to read, and it earns no heading.
   It sits above the date rather than inside that row because it is a sentence and
   the row below is metadata. */
.policy-controller{
  font-size:var(--fs-mono);line-height:1.6;letter-spacing:.02em;
  color:var(--mono-dim);max-width:44em;
  margin-top:clamp(44px,5vw,72px);
}
.policy-controller + .policy-foot{margin-top:14px;padding-top:0;border-top:0}
.policy-foot{
  display:flex;flex-wrap:wrap;gap:10px clamp(20px,3vw,40px);
  margin-top:clamp(44px,5vw,72px);padding-top:20px;border-top:1px solid var(--hairline);
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);
}
.policy-foot a{color:var(--red-text);border-bottom:1px solid currentColor}
.policy-foot a:hover{color:var(--ink)}

/* --- the closing call to action ------------------------------------------- *
   The address is already at display size through .mail; what was missing was a
   line telling the reader what to do with it and a second way to do it. The
   phone sits under the address at title size in ink — 16.17 on this ground —
   rather than in the accent, because --red-text is the small-type value and this
   is not small type. */
.cta-head{
  font-size:var(--fs-head);font-weight:500;letter-spacing:-.02em;
  line-height:1.1;margin-bottom:clamp(18px,2vw,28px);
}
.cta-phone{margin-top:clamp(14px,1.6vw,22px)}
/* Padded to a real tap target. At the title size the text box is 25px tall, and a
   thumb wants about 44 — this is a primary call to action on the page where it is
   most likely to be tapped rather than clicked. The padding buys the height while
   the colour change matches the email link above it. */
.cta-phone a{
  display:inline-flex;align-items:center;gap:.42em;
  font-size:var(--fs-title);font-weight:500;letter-spacing:-.01em;color:var(--ink);
  padding:10px 0 8px;transition:color .15s;
}
.cta-phone-icon{
  width:.9em;height:.9em;flex:none;fill:currentColor;shape-rendering:crispEdges;
}
.cta-phone a:hover,.cta-phone a:focus-visible{color:var(--red-text)}

/* --- brand production systems (ai.html #systems) --------------------------- *
   The product section. It is more configurable than anything else on the site, so
   the rule it follows is that NOTHING NEW IS INVENTED TO MAKE IT LOOK LIKE A
   PRODUCT: the promise reuses .statement, the benefits reuse .skills, the enquiry
   reuses the red .form card and its token swap. What is new here is only what has
   no equivalent — the choice cards, the two-column configuration and the summary.
   ========================================================================== */
/* The nav is sticky and 65px tall, so a landing at #systems would otherwise put
   the section heading underneath it. */
#systems{scroll-margin-top:80px}
.systems-promise{margin-top:clamp(28px,3.4vw,48px);max-width:14em}
.systems-lede{margin-top:clamp(20px,2.4vw,32px)}
.systems-lede p + p{margin-top:.9em}

/* --- project planner ------------------------------------------------------ *
   Three choices produce one useful paragraph and carry it into the ordinary
   enquiry form. It is a framing tool, not an estimator: no score, price or
   platform recommendation is invented from three answers. */
.planner{margin-top:clamp(48px,6vw,88px);scroll-margin-top:88px}
.planner-head{
  display:flex;justify-content:space-between;align-items:baseline;gap:18px;
  padding-bottom:18px;border-bottom:1px solid var(--hairline);
}
.planner-progress{
  flex:none;font-family:var(--font-mono);font-size:var(--fs-mono);
  letter-spacing:var(--track-mono);text-transform:uppercase;color:var(--mono-dim);
}
.planner-progress span{color:var(--red-text)}
.planner .systems-group{border-top:1px solid var(--hairline);padding-top:20px}
.planner .systems-group:first-of-type{border-top:0;padding-top:0}
.planner-step-num{
  display:inline-block;margin-right:.7em;font-family:var(--font-mono);
  font-size:var(--fs-mono);font-weight:400;letter-spacing:var(--track-mono);
  color:var(--red-text);vertical-align:.2em;
}
.planner-result{
  margin-top:clamp(40px,5vw,72px);padding:clamp(24px,4vw,52px);
  background:var(--ink);color:var(--paper);
}
.planner-result-label{
  display:block;font-family:var(--font-mono);font-size:var(--fs-mono);
  letter-spacing:var(--track-mono);text-transform:uppercase;color:var(--red);
}
.planner-result-title{
  margin-top:18px;font-family:var(--font-display);font-size:var(--fs-section);
  font-weight:var(--fw-display);line-height:1;letter-spacing:var(--ls-display);
}
.planner-summary{
  margin-top:clamp(22px,2.8vw,34px);max-width:34em;
  font-size:var(--fs-lead);line-height:1.45;letter-spacing:-.015em;
}
.planner-cta{margin-top:clamp(24px,3vw,38px);background:var(--red);color:var(--on-red)}
.planner-cta:hover{opacity:.9}
.planner-reset{
  margin-left:18px;padding:8px 0;border:0;border-bottom:1px solid currentColor;
  background:transparent;color:var(--paper);cursor:pointer;font-family:var(--font-mono);
  font-size:var(--fs-mono);letter-spacing:var(--track-mono);text-transform:uppercase;
}
.planner-reset:hover{color:var(--red)}

/* The two actions. Neither is a <button>: both navigate, one within the page and
   one to a mail client, so both are links and read as links to assistive tech. */
.systems-actions{
  display:flex;flex-wrap:wrap;gap:14px;align-items:center;
  margin-top:clamp(22px,2.6vw,34px);
}
.systems-action{
  display:inline-block;background:var(--ink);color:var(--paper);
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;padding:13px 22px;transition:opacity .15s;
}
.systems-action:hover{opacity:.86}
/* The quiet one is a rule, not a fill, so the pair reads as primary and secondary
   without a second colour. --hairline is too faint to carry a control edge, so it
   takes the ink at the same weight the form underlines use. */

/* --- configuration: choices beside a summary ------------------------------- */
/* The selector is one group in a single column now — the two-column configuration
   and its live summary went with the second and third question. */
.systems-group{border:0;padding:0;margin:0;min-width:0;margin-top:clamp(40px,5vw,72px)}
.systems-legend{
  padding:0;font-size:var(--fs-title);font-weight:500;letter-spacing:-.015em;
  line-height:1.25;color:var(--ink);
}
.systems-note{
  font-size:var(--fs-nano);line-height:1.6;color:var(--mono-dim);margin-top:8px;max-width:36em;text-wrap:balance;
}

/* --- choice cards ---------------------------------------------------------- *
   Native radios, visually hidden but never display:none — a hidden-by-display
   control leaves the tab order and stops being announced. The label is the whole
   card, so the hit area is the card at every width.

   THE SELECTED STATE IS SAID THREE WAYS, because colour alone is not allowed to
   carry it: the card inverts to ink on paper, the mark fills, and the text goes to
   500. Any one of those would survive a reader who cannot separate the other two.
   The greige rest state is 11.81 against ink; inverted it is 16.17. */
.systems-cards{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:16px}
.systems-card{display:block;position:relative;cursor:pointer}
.systems-card-input{
  position:absolute;width:1px;height:1px;margin:0;padding:0;border:0;
  clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}
.systems-card-body{
  display:flex;align-items:flex-start;gap:11px;min-height:100%;
  background:var(--greige);color:var(--ink);
  padding:14px 15px;transition:background-color .15s,color .15s;
}
.systems-card-mark{
  flex:none;width:15px;height:15px;margin-top:.12em;border:1px solid var(--ink);
  border-radius:50%;position:relative;transition:border-color .15s;
}
.systems-card-text{font-size:max(0.9375rem,var(--fs-small));line-height:1.4;letter-spacing:-.005em}
.systems-card-input:checked + .systems-card-body{background:var(--ink);color:var(--paper)}
.systems-card-input:checked + .systems-card-body .systems-card-text{font-weight:500}
.systems-card-input:checked + .systems-card-body .systems-card-mark{border-color:var(--paper)}
/* The dot is the accent's one appearance in the cards. On ink it measures 5.19,
   well past the 3.0 a non-text mark needs. */
.systems-card-input:checked + .systems-card-body .systems-card-mark::after{
  content:'';position:absolute;inset:3px;border-radius:50%;background:var(--red);
}
.systems-card:hover .systems-card-body{background:#cdc4b6}
.systems-card-input:checked + .systems-card-body,
.systems-card-input:checked:hover + .systems-card-body{background:var(--ink)}
/* The page's focus treatment, moved onto the card because the control it belongs
   to has no box of its own to draw it around. */
.systems-card-input:focus-visible + .systems-card-body{
  outline:2px solid var(--red);outline-offset:3px;
}

/* --- editorial blocks ------------------------------------------------------ */
.systems-block{margin-top:clamp(48px,6vw,88px)}
.systems-h{font-size:var(--fs-title);font-weight:500;letter-spacing:-.015em;line-height:1.25}
.systems-h--prominent{
  font-family:var(--font-display);font-size:var(--fs-section);font-weight:var(--fw-display);
  line-height:1;letter-spacing:var(--ls-display);
}
/* The benefits borrow .skills wholesale. They carry no number, so the rule that
   sits above each one does the separating the numeral does elsewhere. */
/* The first benefit's red rule sat straight under the heading and read as part of
   it. The grid needs the same gap the steps and the evidence already take. */
.systems-benefits{margin-top:clamp(20px,2.2vw,28px)}
.systems-benefits .skill{padding-top:16px}
.systems-benefits .skill-title{margin-top:0}

.systems-steps{list-style:none;margin-top:clamp(20px,2.4vw,30px);counter-reset:sysstep}
.systems-steps li{
  counter-increment:sysstep;display:grid;
  grid-template-columns:2.4em minmax(0,1fr);gap:0 18px;
  border-top:1px solid var(--hairline);padding:16px 0;
}
.systems-steps li::before{
  content:counter(sysstep,decimal-leading-zero);
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  color:var(--red-text);font-variant-numeric:tabular-nums;padding-top:.25em;
}
.systems-step-name{font-weight:500;letter-spacing:-.01em}
.systems-step-body{grid-column:2;font-size:max(0.9375rem,var(--fs-small));line-height:1.6;color:var(--ink);margin-top:4px;max-width:40em}
.systems-coda{margin-top:clamp(28px,3.4vw,44px);max-width:18em}

/* The qualifier is one line, not two columns of yes and no: the section says who
   it suits and lets the reader decide, rather than arguing with them. */
.systems-fit-line{
  font-size:var(--fs-body);line-height:1.6;color:var(--mono-dim);
  border-top:1px solid var(--hairline);padding-top:16px;margin-top:clamp(24px,3vw,36px);max-width:40em;
}

/* --- the enquiry card ------------------------------------------------------ *
   .form supplies the red ground and the whole token swap; only the two lines that
   have no equivalent in the contact form are added here. */
.systems-enquiry{margin-top:clamp(48px,6vw,88px);scroll-margin-top:88px}
.systems-enquiry-note{font-size:var(--fs-body);line-height:1.55;color:var(--ink);max-width:40em;margin-bottom:6px}
.systems-enquiry-alt{
  font-size:var(--fs-nano);line-height:1.6;color:var(--ink);margin-top:14px;
}
.systems-enquiry-alt a{border-bottom:1px solid var(--mono-dim);padding-bottom:1px}
.systems-enquiry-alt a:hover{border-bottom-color:var(--ink)}

/* --- studios doorways ----------------------------------------------------- */
.doors{border-top:1px solid var(--hairline)}
.door{
  display:block;padding-block:clamp(28px,4vw,52px);
  border-bottom:1px solid var(--hairline);transition:background-color .15s,color .15s;
}
.door-in{
  max-width:var(--max-w);margin:0 auto;padding:0 var(--pad-x);
  display:grid;grid-template-columns:1fr auto;align-items:center;gap:24px;
}
.door-name{font-size:var(--fs-large);font-weight:500;letter-spacing:var(--track-display);line-height:1;display:block}
.door-desc{
  display:block;margin-top:14px;font-family:var(--font-mono);font-size:var(--fs-mono);
  letter-spacing:var(--track-mono);color:var(--mono-dim);text-transform:lowercase;
}
.door-arrow{font-size:var(--fs-head);line-height:1;transition:transform .2s}
/* Ink on the accent, not paper: beige on --red is 3.11, and the doorway carries an
   11px line as well as an 84px name. Ink is 5.19, and 4.65 at the opacity below.
   The desc was a hard-coded beige rgba, which is a token violation and was wrong on
   any ground but the one it was written for. */
.door:hover,.door:focus-visible{background:var(--red);color:var(--on-red);outline-offset:0}
.door:hover .door-desc,.door:focus-visible .door-desc{color:inherit;opacity:.88}
.door:hover .door-arrow{transform:translateX(8px)}

/* --- contact strip -------------------------------------------------------- */
/* The address is 12.66em wide at this weight and tracking, 13.6em with the chip —
   half as wide again as hello@southnorth.se, which --fs-large was safe for. At the
   old size it broke mid-domain, "…guild.c / om". Both sizes below are solved
   against the narrowest container each sits in, and verified 375 to 1440. */
.mail{
  display:inline-flex;align-items:center;gap:.4em;flex-wrap:wrap;
  font-size:clamp(1.375rem,5.6vw,4.5rem);font-weight:500;letter-spacing:var(--track-display);line-height:1.05;
  transition:color .15s;word-break:break-word;
}
.mail:hover{color:var(--red-text)}
/* Arrow chip — the one red graphic accent on the contact page. Three measured
   corrections, 2026-08-20:

   SIZE. The circle was .578em of the address and is now .775em, a third larger,
   and the glyph inside it grew by about half. The ratio came down from 1.7 to 1.55
   at the same time so the arrow fills more of the disc rather than floating in it.

   CENTRING. `place-items:center` centres the LINE BOX, not the glyph ink, and the
   arrow's ink sits high of its own line box — it was landing 1.44px below the
   circle's centre. Padding cannot be replaced by a transform here because the
   arrow is a bare text node, so .2em of bottom padding shifts the content box up
   by half that and lands the ink on centre. Measured after, not assumed: 0.25px.

   WEIGHT. font-weight does nothing to this glyph. Rendered to a canvas and counted,
   Inter's arrow has exactly the same ink at 400 and at 500 — 0% difference — so a
   heavier weight would have looked like a fix while changing nothing. The stroke is
   the only lever, and .035em adds about 40%. It is prefixed and non-standard; the
   glyph is decorative and aria-hidden, so where it is unsupported the arrow simply
   renders as it did before. */
.chip{
  display:inline-grid;place-items:center;width:1.55em;height:1.55em;line-height:1;
  background:var(--red);color:var(--on-red);font-size:.50em;border-radius:50%;
  flex:none;transform:translateY(-.34em);padding-bottom:.2em;
  -webkit-text-stroke:.035em currentColor;
}
.contact-block{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
.contact-block a:hover{color:var(--red-text)}

/* Contact: details and film left, the enquiry form right. The form is the page's
   one call to act, so it takes the fixed column and the reading matter takes the
   fluid one. The film closes the left column, which is short beside ten fields;
   without it the form leaves that side empty below the copy. */
.contact-layout{
  display:grid;grid-template-columns:1fr minmax(300px,460px);
  gap:clamp(32px,4.5vw,72px);align-items:start;
  padding-block:0 clamp(72px,10vw,140px);
}
.contact-main{display:flex;flex-direction:column;gap:clamp(28px,3.6vw,48px)}

/* THE ADDRESS TAKES THE WHOLE MEASURE, not the left column, and so it keeps the
   display size the clamp asks for. At 13.6em with the chip it needs 979px of the
   1152 the wrap gives at --max-w, and it clears at every width the clamp produces
   down to 320. In a column it did not: that is the break the old
   `.contact-main .mail` step-down existed to avoid, and spanning removes the
   cause rather than shrinking the type. flex-wrap stays on, so at some impossible
   width the chip drops rather than the domain splitting. */
.contact-mail{grid-column:1/-1}

/* The film is square — 1996 x 1996 — so it is sized by the column rather than
   cropped into a band. --greige sits behind it, the same surface the placeholder
   used, so a slow load does not flash the page ground. site.js pauses any
   video[autoplay] under prefers-reduced-motion and hands back the controls, so
   this needs nothing of its own. */
/* THE GREIGE GOES ON THE VIDEO, NOT ON THE FIGURE. It is there to hold the shape
   while the file loads, so the frame is all it needs to cover — and a figure-wide
   ground would put the caption on greige, where --mono-dim measures 3.36 and 11px
   type needs 4.5. On the page ground it is 4.59 and legal. */
.contact-film{margin:0}
.contact-film video{display:block;width:100%;height:auto;aspect-ratio:1/1;object-fit:cover;background:var(--greige)}
.contact-film figcaption{margin-top:12px}

/* --- enquiry form --------------------------------------------------------- *
   THE CARD IS THE RED SURFACE, NOT A THIRD ONE, and it is reached the way canon
   says a surface is reached: `.page-red` swaps tokens and every component inside
   inverts on its own. This block is that same swap scoped to one element, with
   the identical values — not a forked component style, which is the thing the
   two-surface rule forbids. Change the values in `.page-red` and change them
   here; they are one decision written twice, and they must not drift.

   What the swap buys, beyond canon: the tonal hierarchy comes back for free.
   Set as a forked style there is only --on-red, 5.19, for label and value and
   placeholder alike — small type on #FF3D00 clears 4.5 at #212121 and no lighter,
   two steps off the ink, so a "dim" is not available on that ground. Through the
   swap the labels take --mono-dim, black at .78 and 4.83 on the red, and the
   values take full ink at 5.92. Type carries the hierarchy and so does tone.
   ========================================================================== */
.form{
  --paper:#FF3D00;                    /* ground and ink trade places */
  --ink:#000000;
  --red:#000000;                      /* accents were red; on red they are ink */
  --red-text:#000000;                 /* both values collapse — 5.92 on the ground */
  --on-red:var(--paper);
  --mono-dim:rgba(0,0,0,.78);         /* 4.83 on the ground — 11px mono needs 4.5 */
  --hairline:rgba(0,0,0,.30);
  --greige:rgba(0,0,0,.16);
  background:var(--paper);color:var(--ink);
  padding:clamp(24px,3vw,40px);display:flex;flex-direction:column;gap:16px;
}
.form ::selection{background:var(--ink);color:var(--paper)}
.form-h{font-size:var(--fs-lead);font-weight:500;letter-spacing:-.015em;line-height:1.3;margin-bottom:4px}
/* A note under a form heading. Added for the paragraph beneath contact.html's
   heading; the AI page already had this treatment under its own namespace. */
.form-note{font-size:var(--fs-body);line-height:1.55;color:var(--ink);max-width:40em;margin-bottom:6px}
.field{display:flex;flex-direction:column;gap:5px}
.field label{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);
}
/* --mono-dim is the rule as well as the label. --hairline is 1.82 on this ground —
   legal nowhere — and the underline is the affordance that says where to type,
   not decoration. At .78 black it is 4.83 and doubles as the dim the labels use. */
.field input,.field textarea,.field select{
  font:inherit;font-size:var(--fs-body);color:var(--ink);
  background:transparent;border:0;border-bottom:1px solid var(--mono-dim);
  padding:8px 0;width:100%;transition:border-color .15s;
}
.field textarea{resize:vertical;min-height:88px;line-height:1.6}
/* Placeholders are examples, not labels, but they still have to be read. A browser
   default is the text colour at roughly half alpha, which would not clear 4.5. */
.field ::placeholder{color:var(--mono-dim);opacity:1}

/* SELECTS. appearance:none is what lets a select carry the same borderless
   underline as the inputs beside it — a native control keeps its own box and
   ground and would read as a different kind of field on this card. The arrow is
   then ours to draw, below. padding-right keeps a long option clear of it. */
.field select{appearance:none;-webkit-appearance:none;padding-right:1.6em;cursor:pointer}
/* The open list is the OS's surface, so it takes the two tokens that never
   invert. Left alone, several platforms render black-on-red or red-on-white. */
.field select option{color:var(--menu-ink);background:var(--menu-ground)}
/* The indicator, drawn from two borders so it stays on a token rather than being
   baked into an SVG data URI. .field is the label+control column and the control
   is its last child, so the chevron is measured from the BOTTOM and sits on the
   select's own centre line. */
.field--select{position:relative}
.field--select::after{
  content:'';position:absolute;right:3px;bottom:1.05em;
  width:.42em;height:.42em;font-size:var(--fs-body);
  border-right:2px solid var(--ink);border-bottom:2px solid var(--ink);
  transform:rotate(45deg);pointer-events:none;
}
/* The underline goes to full ink on focus — 5.92, against 4.83 at rest. Keyboard
   users get a real ring on top of that: the page's :focus-visible rule already
   draws `2px solid var(--red)`, and --red is ink inside this swap, so it lands
   black on the card with no override. A pointer click does not fire
   :focus-visible, so the quiet treatment still holds there. */
.field input:focus,.field textarea:focus,.field select:focus{outline:0;border-bottom-color:var(--ink)}
.send{
  align-self:flex-start;margin-top:8px;cursor:pointer;
  background:var(--ink);color:var(--paper);border:0;
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;padding:12px 22px;transition:opacity .15s;
}
.send:hover{opacity:.86}
/* Anti-spam with no script and no service: a field a person never sees. */
.hp{position:absolute;left:-9999px}

/* --- footer --------------------------------------------------------------- */
.footer{border-top:1px solid var(--hairline);padding:clamp(48px,6vw,88px) 0 32px}
/* THREE ZONES (Filippos's sketch, 2026-09-14): the site links, the studio and its
   contact block, and a photograph of the studio that runs off the right edge.

   IT WAS TWO COLUMNS BEFORE, and the middle one used to be Studios; that group is
   stated once in .network below instead. The middle zone now carries the words the
   footer never had — where the Guild actually works — and the contact details sit
   beneath them as their own block rather than as a second list.

   THE PHOTOGRAPH BLEEDS. Everything else in the footer sits on the wrap's measure,
   so the image cell reaches past its right gutter with margin-right:calc(50% - 50vw)
   — the standard full-bleed offset, measured from the cell's own centre. It is the
   only element on the site that leaves the measure, which is the point: the page
   ends by opening onto the room the work is made in. */
.foot-cols{
  display:grid;grid-template-columns:1fr 1.15fr 1.35fr;
  gap:clamp(28px,3vw,56px);align-items:start;
}
.foot-mid{display:flex;flex-direction:column;gap:clamp(24px,3vw,44px);max-width:34em}
.foot-studio{font-size:max(0.9375rem,var(--fs-small));line-height:1.55;max-width:26em;text-wrap:pretty}
/* TEXT, NOT A PANEL (Filippos, 2026-09-14). The contact block had a greige ground;
   these are the same details they always were, so they sit on the page ground like
   everything else, separated by space alone. */
.foot-col--card{gap:8px}
/* FRAMED, NOT BLED. It ran off the right edge, which put 508px of the page outside a
   1718px window: calc(50% - 50vw) offsets from the CELL's centre, and in a three-column
   grid that is nowhere near the page's. The frame now keeps the artwork's own 16:9 and
   shows all of it inside the column, which is what the sketch asked for. */
.foot-shot{margin:0;aspect-ratio:1600/899;width:100%}
.foot-shot img{display:block;width:100%;height:100%;object-fit:cover}
@media (max-width:900px){
  /* Stacked, and the photograph goes last and full width: at this size a third
     column would be a stripe, and the room is worth seeing or not showing at all. */
  .foot-cols{grid-template-columns:1fr}
}
.foot-col{display:flex;flex-direction:column;align-items:flex-start;gap:10px}
.foot-h{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);margin-bottom:6px;
}
.foot-col a,.foot-col address{font-size:max(0.9375rem,var(--fs-small));letter-spacing:-.01em}
.foot-col a:hover{color:var(--red-text)}
/* ============================================================================
   THE NETWORK LINE. One component, shared verbatim across sillywizardsguild.com,
   seriouslywow.com and standardwork.design — same class names, same markup
   shape, same place: the last row above the closing lock. Two things differ per
   site, both on purpose:

     the LABEL, because each site sits differently in the group. This one is the
       parent and names its studios; the studios name their parent and each other.

     the FACE of .network-link. Each site sets it in its own display face — but
       NOT here. The rule on this site is that the display face is the hero and
       nothing else (see --font-display), and a footer link is not the name.
       So the Guild's network line is the grotesk, one step up in size, and that
       is the rule holding rather than an inconsistency.

   THE LABEL IS A SENTENCE, NOT A COLUMN HEADING, which is why it is the body
   face in sentence case rather than the mono voice the .foot-h labels use.
   ========================================================================== */
.network{
  display:flex;justify-content:flex-end;align-items:baseline;gap:14px 32px;flex-wrap:wrap;
  margin-top:clamp(40px,5vw,72px);padding-top:22px;border-top:1px solid var(--hairline);
}
.network-item{display:inline-flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.network-label{font-size:var(--fs-nano);color:var(--mono-dim)}
.network-link{
  font-size:var(--fs-micro);font-weight:500;letter-spacing:-.02em;color:var(--ink);
  transition:color .12s;white-space:nowrap;
}
.network-link:hover{color:var(--red-text)}

.foot-lock{
  display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;
  margin-top:clamp(48px,6vw,88px);padding-top:22px;border-top:1px solid var(--hairline);
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);
}
/* THE ACCENT SQUARE IS GONE (Filippos, 2026-09-14). The wizard beside it now carries the
   accent itself, so the square was the same note played twice. */

/* THE WIZARD IS A DOOR, AND IT IS MEANT TO BE FOUND RATHER THAN READ. It opens
   heritage.html, the site as it stood before this one. It sits in the foot-lock
   beside the accent mark, at rest quiet enough to pass for one more piece of
   footer furniture and awake on hover.

   It carries a real accessible name, because "hidden" is a visual joke and not a
   licence to hand a screen reader an unlabelled link. `pixelated` is deliberate:
   the artwork is 96px pixel art and any smoothing at 22px turns it to mush.

   The opacity is the whole effect and it is bounded: the mark is decorative and
   its label is the accessible name, so the 3.0 rule for non-text marks does not
   bind on the glyph. It is set at .38 rather than lower so it stays findable. */
.wizard-egg{
  display:inline-flex;flex:none;line-height:0;
  /* ORANGE, AND AT FULL STRENGTH (Filippos, 2026-09-14). It was ink at .38, which is what
     made it read grey. It is an <img> of assets/wizard-mark-orange.svg rather than a mask:
     the file carries --red itself, so the egg needs no colour rule of its own.

     .82 AND NOT 1, SO THE HOVER STILL HAS SOMEWHERE TO GO. The door is meant to be found:
     it wakes on hover, and with both states at full strength the fade did nothing and only
     the tilt was left. In the accent at .82 it reads orange at rest and lifts to full. */
  opacity:.82;transition:opacity .18s,transform .18s;
}
/* THE WIZARD IS SIMON'S, FROM 2026-09-17. The pixel wizard the site launched with was
   not ours: it traces back to an artist trading as obinsun, sold as "8-Bit Wizard" since
   2015, with its greys flattened to one colour. Simon drew a replacement on a blank grid —
   16 x 16, 137 pixels, one colour — and every use swapped together: the wordmark full
   stop, the heritage doors, this egg, the favicon and the heritage icon. The handover note
   is in ~/Documents/raw-material/swg/swg-wizard-handover/.

   THE FILES ARE SVG, drawn on the 16 grid with shape-rendering crispEdges, so the pixels
   stay square at any size without a re-export. wizard-mark.svg fills with currentColor and
   is the mask source; -orange and -ink carry their colour for places that are an <img>.

   24px is kept from before, at Filippos's word ("keep the color changes sizes and
   everything"). It is 1.5 screen pixels per wizard pixel, so crispEdges rounds some cells
   to one pixel and some to two; 16px or 32px would be exact. Worth knowing, not a fault. */
.wizard-egg img{width:24px;height:24px;display:block;image-rendering:pixelated}
.wizard-egg:hover,.wizard-egg:focus-visible{opacity:1;transform:translateY(-2px) rotate(-6deg)}
@media (prefers-reduced-motion:reduce){
  .wizard-egg{transition:opacity .18s}
  .wizard-egg:hover,.wizard-egg:focus-visible{transform:none}
}
/* The foot-lock is a two-item flex row; the wizard joins the mark on the right
   so the lockup stays one cluster rather than becoming a third column. */
.foot-end{display:inline-flex;align-items:center;gap:14px;flex:none}

/* --- type-role exception -------------------------------------------------- *
   Display, section and statement roles are shared above. The belief sequence is
   the sole intentional exception: the same display face at its true regular
   weight, with the bright accent reserved for this large-text treatment. */
.page-ai .beliefs{
  font-family:var(--font-display);font-weight:400;letter-spacing:var(--ls-display);color:var(--red);
}

/* --- the home ground ------------------------------------------------------ *
   A radial lift that drifts across the page over a minute or so. Home only: it is
   an atmosphere for the one page that is mostly empty ground, and on work.html or
   contact.html it would sit under a grid and read as a rendering fault.

   THIS IS THE ONE PIECE OF AMBIENT MOTION ON THE SITE, and canon asks for motion
   near zero, so it is bounded on every side. The travel is a fraction of the
   viewport, the cycle is 72s, the two ends differ by six steps of one hue, and it
   alternates rather than looping — there is no seam to catch the eye. If you can
   see it move while looking at it, it is set too high.

   It animates TRANSFORM on a fixed layer rather than background-position on the
   body, so it is composited rather than repainting the page under the type sixty
   times a second. inset:-25% gives the layer room to travel without ever showing
   an edge. z-index:-1 puts it above the body ground and below every child.

   Under prefers-reduced-motion the layer stays and the animation stops: the lift
   is a colour, the drift is the motion, and only the motion is the problem. */
.page-home::before{
  content:'';position:fixed;inset:-40%;z-index:-1;pointer-events:none;
  /* THE FALLOFF DOES AS MUCH WORK AS THE TRAVEL. At 72% the lit core filled most
     of an already oversized layer, so the field was near flat and moving it
     changed nothing at any fixed point — which is why the first version was
     invisible even though it was running. Pulling the stop in to 58% gives the
     glow a shape, and a shape is the thing that can be seen to move. */
  background:radial-gradient(closest-side at 50% 50%,var(--paper-lift),transparent 58%);
  animation:paper-drift 54s ease-in-out infinite alternate;
  will-change:transform;
}
/* Travel: ±15% of the layer's 2592px at 1440 is about 389px each way, so ~780px
   across a 54s half-cycle — near 14px a second, against the 4 the first version
   moved. Slow enough to notice on returning to the tab rather than while looking
   at it. The scale runs alongside it so the glow swells as it crosses instead of
   sliding at a fixed size; the two together are what make the change legible at a
   tone this close to the ground.

   Both ends are solved against the viewport rather than eyeballed: at scale 1.18
   with the translate applied, the layer still spans -420..2638 horizontally and
   -344..1568 vertically at 1440x900, so no edge of it can reach the screen at
   either end of the cycle. inset:-40% is what buys that margin. */
@keyframes paper-drift{
  from{transform:translate3d(-15%,-10%,0) scale(1.00)}
  to  {transform:translate3d( 15%, 10%,0) scale(1.18)}
}
@media (prefers-reduced-motion:reduce){
  .page-home::before{animation:none;transform:translate3d(0,0,0)}
}

/* THE NAV HAS TO CARRY THE SAME GROUND OR IT READS AS A LID. It is sticky and
   opaque --paper, so it painted a flat band across the top of a page whose ground
   was no longer flat — the seam was the whole of the problem, not the hairline
   under it, which stays.

   IT CANNOT SIMPLY GO TRANSPARENT: content scrolls beneath a sticky bar, and the
   type would show through it. So the bar keeps its opaque fill and takes a second
   layer on top, an exact clone of the page's, clipped to the bar by overflow.

   THE GEOMETRY IS MATCHED IN TWO DIFFERENT UNITS ON PURPOSE. The page layer is
   position:fixed with inset:-40%, whose percentages resolve against the viewport
   MINUS any classic scrollbar. This one is absolute inside the bar, so:
     - horizontally, -40%/180% resolve against the bar's own width, which is that
       same scrollbar-less width — they agree by construction;
     - vertically, the bar is 65px tall, so a percentage would resolve against 65
       and land nowhere near. vh is the only unit that matches, and a vertical
       scrollbar does not affect vh, so it is safe where vw would not have been.
   The bar sits at the top of the viewport whether stuck or not — it is the first
   element on the page — so its origin and the viewport's coincide, and the two
   layers stay registered without a script.

   Both run the same keyframes for the same duration from the same first paint, so
   they advance together. */
.page-home .nav{overflow:hidden}
.page-home .nav::before{
  content:'';position:absolute;z-index:0;pointer-events:none;
  left:-40%;width:180%;top:-40vh;height:180vh;
  background:radial-gradient(closest-side at 50% 50%,var(--paper-lift),transparent 58%);
  animation:paper-drift 54s ease-in-out infinite alternate;
  will-change:transform;
}
/* The bar's contents are not positioned, so the layer above would paint over
   them. This lifts them back on top without changing the layout. */
.page-home .nav-in{position:relative;z-index:1}
@media (prefers-reduced-motion:reduce){
  .page-home .nav::before{animation:none;transform:translate3d(0,0,0)}
}

/* --- the threshold (heritage.html) ---------------------------------------- *
   The step between the working site and the old one. It holds one mark and one
   line, centred in the viewport, and nothing else — no nav, because a nav would
   make it a destination rather than a door.

   THE MARK IS MASKED, NOT TINTED. The artwork is black pixels on alpha, so
   painting it through a mask puts it on --ink and --red exactly, the same way the
   heritage page's door does, instead of a filter chain landing near them. The
   <span> carries no image of its own, so where masking is unsupported it collapses
   to nothing and the label alone still names the link — which is why the label is
   real text inside the anchor rather than an aria-label on it.

   BLACK AT REST, ACCENT ON HOVER. Quiet enough to keep the joke, and the colour
   arriving under the cursor is the whole of the reveal. */
.threshold{
  min-height:100svh;display:grid;place-items:center;padding:var(--pad-x);
}
.threshold-door{
  display:flex;flex-direction:column;align-items:center;gap:22px;
  padding:24px;text-align:center;
}
.threshold-mark{
  display:block;width:clamp(72px,9vw,104px);aspect-ratio:1;
  background-color:var(--ink);transition:background-color .18s,transform .18s;
  -webkit-mask:url('assets/wizard-mark.svg') center/contain no-repeat;
  mask:url('assets/wizard-mark.svg') center/contain no-repeat;
}
.threshold-label{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);transition:color .18s;
}
.threshold-door:hover .threshold-mark,
.threshold-door:focus-visible .threshold-mark{
  background-color:var(--red);transform:translateY(-4px);
}
.threshold-door:hover .threshold-label,
.threshold-door:focus-visible .threshold-label{color:var(--red-text)}
@media (prefers-reduced-motion:reduce){
  .threshold-mark{transition:background-color .18s}
  .threshold-door:hover .threshold-mark,
  .threshold-door:focus-visible .threshold-mark{transform:none}
}
/* The way out sits in the corner rather than the centre: leaving is not the
   thing the page is for, and it should not compete with the mark. */
.threshold-back{
  position:fixed;left:var(--pad-x);bottom:24px;
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  text-transform:uppercase;color:var(--mono-dim);transition:color .15s;
}
.threshold-back:hover{color:var(--red-text)}

/* --- responsive: 1280 grid, breaks at 900 / 600 --------------------------- */
@media (max-width:900px){
  :root{--gap-grid:clamp(24px,4vw,40px)}
  /* Already two columns at full width; nothing to collapse until 1fr below. */
  .logo-grid{grid-template-columns:repeat(5,1fr)}
  .logo-grid--awards{grid-template-columns:repeat(4,1fr)}
  /* One column. The reading matter and the film come first and the form follows,
     which is source order — the enquiry is the end of the page on a phone, not
     the middle of it. The film is capped so it stays an image, not a screenful. */
  .contact-layout{grid-template-columns:1fr}
  .contact-film{max-width:360px}
  /* One column, and the summary follows the choices rather than leading them —
     source order is already correct, so only the sticky comes off. A sticky panel
     in a single column would ride over the blocks below it. */
}
@media (max-width:600px){
  .grid{columns:1}
  /* Four nav items no longer fit beside the wordmark at 375, so the links tighten.
     THAT IS NOT ENOUGH AT 320, corrected 2026-09-14. --pad-x floors at 20px, so a
     320px phone leaves 280px of content width while the nav needs 404: the wordmark
     is 151 at 1.0938rem, the links 229 at 0.7813rem, plus the 24px gap. Tightening
     cannot close 124px without taking the links under 9px. The comment above this
     block claimed the 600 rules "keep four links beside the wordmark at 320"; they
     never did, and every page but the home page carried a real horizontal scrollbar
     because of it.

     flex-wrap, not a new breakpoint: the row wraps only when it genuinely does not
     fit, so 600 to ~430 keeps one line exactly as before and only the narrowest
     phones drop the links to a second row. Nothing is hidden and no label shortens. */
  .nav-in{padding-block:20px;flex-wrap:wrap;row-gap:10px}
  .nav-links a{font-size:0.7813rem;letter-spacing:.09em}
  .skills{grid-template-columns:1fr}
  /* Wrapping leaves a single orphan mark on a second line; a grid divides evenly. */
  .logo-grid{grid-template-columns:repeat(3,1fr);gap:10px}
  .logo-grid--awards{grid-template-columns:repeat(4,1fr)}
  .foot-cols{grid-template-columns:1fr;gap:36px}
  .network{justify-content:flex-start}
  .nav-links{gap:13px}
  .nav-name{font-size:1.0938rem}
  .hero{min-height:calc(100svh - 61px)}
  .door{padding-block:26px}
  /* One card per row: several of these labels want the full measure, and two
     columns at 600 broke them mid-phrase. The card is the whole label, so the tap
     target grows with it rather than needing a min-height. */
  .systems-cards{grid-template-columns:1fr}
  .planner-head{align-items:flex-start;flex-direction:column}
  .planner-reset{display:block;margin:16px 0 0}
  .systems-actions{gap:10px}
  .systems-action{width:100%;text-align:center}
  /* The address must wrap as a whole rather than splitting mid-domain. */
  .systems-enquiry-alt a{display:inline-block}
}

/* TALL SCREENS DROP THE FULL-HEIGHT HERO. min-height:100svh pins the scroll cue to the
   bottom of the first screen, so the empty stretch above it grows with the window's
   height rather than its content: 201px on a 390x844 phone, 1350px in a 505x2000 pane
   (Filippos, 2026-09-10: the gaps on mobile are too large). In portrait the hero sizes
   to its content and the cue sits its own 48px below the tags. Landscape screens, and
   the 1280x720 requirement above, are untouched. */
@media (max-aspect-ratio:3/4){
  .hero{min-height:0}
}

/* --- motion --------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition:none!important;animation:none!important;scroll-behavior:auto!important}
  .door:hover .door-arrow{transform:none}
}

/* ==========================================================================
   THE RED PAGE — about.html
   One page, two colours. `.page-red` swaps the ground and the ink at token
   level, so nav, footer and every shared component invert without a forked
   rule. Nothing here restyles a component; it only redefines what the tokens
   mean inside this page.

   THE TYPE ON THIS PAGE IS BLACK, AND THAT IS NOT A MISTAKE. Black on #FF3D00
   is 5.92 and passes AA; the beige is 3.11 and fails for body copy. This
   inverts the blue page it replaces, which ran light type at 6.7 — so it looks
   like a regression to anyone who knows that page. It is the only value on
   this ground that passes. Decision of 2026-08-20, in aios/LOG.md.
   ========================================================================== */
.page-red{
  --paper:#FF3D00;                    /* ground and ink trade places */
  --ink:#000000;
  --red:#000000;                      /* accents were red; on red they are ink */
  --red-text:#000000;                 /* both values collapse — 5.92 on the ground */
  --on-red:var(--paper);              /* the accent is ink here, so its type is the red */
  --mono-dim:rgba(0,0,0,.78);         /* 4.83 on the ground — 11px mono needs 4.5 */
  --hairline:rgba(0,0,0,.30);
  --greige:rgba(0,0,0,.16);
  background:var(--paper);color:var(--ink);
}
/* The footer mark reads --red, which is already --ink here, so it needs no
   override. ::selection inverts through the tokens rather than a second hex. */
.page-red ::selection{background:var(--ink);color:var(--paper)}
/* South North spread the nav across the full width on its About page and nowhere
   else. Inherited here, that made the menu jump when you moved between About and
   any other page — a treatment on a four-page site, a defect on a five-page one.
   Removed 2026-08-20; the nav is now identical on every page. */

/* Headline — wordmark and date range only, stacked, top-left. */
.about-head{min-height:55vh;display:flex;align-items:flex-start;padding-block:clamp(40px,7vw,96px) 0}
/* The scope line sets at half the wordmark, as the Abloh cover sets its date range
   below the name. At full display size "Work 1999 — 2026" wraps, and a wrapped date
   range reads as a mistake rather than a stack. */
.about-head .display span:last-child{font-size:.5em;letter-spacing:-.02em}

/* --- the name drifts here too, on a much shorter leash ---------------------- *
   Same 60s clock as the hero: five seconds held, a 55s sweep, back where it
   started. The PALETTE IS NOT THE HERO'S, and cannot be. This page is .page-red,
   so the ground is #FF3D00 — the very colour the hero drifts out of — and the
   wordmark rests on --ink, not on the accent. Measured on this ground:

     #000000  5.92   ink, where the cycle rests
     #2E0B47  4.72   darkest purple, clears even the 4.5 body floor
     #3B0F52  4.30   deep purple
     #5C1157  3.54   purple, the apex
     #96125A  2.34   deep magenta — FAILS
     #C4204E  1.62   crimson — FAILS
     #FF3D00  1.00   the accent is the ground here; it would vanish

   So the warm half of the reference is unusable and the drift is a slow purple
   bloom out of black rather than a journey across the whole image. The two pages
   still meet: this is the same purple end the hero reaches at its midpoint.

   THE SCOPE LINE IS NOT THE WORDMARK and keeps --ink. It sets at half the
   wordmark, which is 24px here but 23px once --fs-name hits its 46px floor —
   under the large-text threshold, where the floor becomes 4.5 and only #2E0B47
   would still pass. It is a date range, not the name; it stays put. */
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  .about-head .display{
    background-image:linear-gradient(95deg,
      #000000 0%,   #000000 20%,
      #2E0B47 32%,  #3B0F52 42%,
      #5C1157 50%,
      #3B0F52 58%,  #2E0B47 68%,
      #000000 80%,  #000000 100%);
    background-size:500% 100%;
    background-position:0% 50%;
    -webkit-background-clip:text;background-clip:text;
    animation:swg-name-drift 60s linear infinite;   /* the hero's keyframes: position only */
  }
  .about-head .display span{color:transparent;-webkit-text-fill-color:transparent}
  .about-head .display span:last-child{color:var(--ink);-webkit-text-fill-color:var(--ink)}
}
@media (prefers-reduced-motion:reduce){
  .about-head .display{animation:none;background-image:none}
  .about-head .display span{color:var(--ink);-webkit-text-fill-color:var(--ink)}
}

/* Byline strip — three mono micro-columns. */
.byline{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:start;gap:24px;
  padding-block:clamp(48px,7vw,88px) clamp(28px,3.5vw,44px);
}
.byline-col{
  font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);
  line-height:1.7;color:var(--mono-dim);
}
.byline-mid{text-align:center;color:var(--ink)}
.byline-end{text-align:right}

/* Section I — the credit line. One continuous sentence: project phrases at full
   size, attribution connectives at 40% and lifted to sit optically centred. Only
   attribution connectives shrink — "at twelve thousand frames a second" is part of
   the phrase, not a credit, and shrinking it would break the sentence. */
.section-credit{padding-block:clamp(24px,3vw,40px) clamp(56px,8vw,110px)}
/* balance, not pretty. `pretty` only protects the last line from an orphan, and
   these two paragraphs were still ending 30% and 23% of the longest line — one of
   them on "check-in." alone. Centred text shows an uneven rag more than ranged
   text does, so the whole block is evened rather than just its final line. */
.credit{
  font-size:var(--fs-head);line-height:1.18;letter-spacing:-.02em;
  text-align:center;max-width:20em;margin:0 auto;text-wrap:balance;
}
/* The credit is two paragraphs now rather than one long sentence, and .credit
   carries margin:0 auto, so a split would butt them together. */
.credit + .credit{margin-top:clamp(24px,3vw,44px)}
.credit small{font-size:.4em;letter-spacing:0;vertical-align:.14em}
.credit sup{font-size:.42em;vertical-align:super;line-height:0}
.credit-coda{
  font-size:var(--fs-lead);line-height:1.35;text-align:center;max-width:26em;
  margin:clamp(32px,4vw,56px) auto 0;
}
.awards-line{
  text-align:center;max-width:52em;margin:clamp(20px,2.4vw,32px) auto 0;
  font-size:var(--fs-nano);line-height:2;
}

/* Section II — dense columns, first-line indents, no paragraph spacing. */
.section-how{padding-block:0 clamp(56px,8vw,110px)}
.about-body{
  column-count:2;column-gap:clamp(28px,4vw,64px);
  font-size:max(0.9375rem,var(--fs-small));line-height:1.5;max-width:none;
}
.about-body p{margin:0;text-indent:1.7em}
.about-body p:first-child{text-indent:0}

/* Directors — right-aligned. */
.directors{padding-block:0 clamp(64px,9vw,120px);text-align:right}
.directors p{font-size:var(--fs-micro);line-height:1.6;letter-spacing:-.01em}

@media (max-width:900px){
  .about-body{column-count:1}
  .about-body p+p{text-indent:1.7em}
}
@media (max-width:600px){
  .byline{grid-template-columns:1fr;gap:14px}
  .byline-mid,.byline-end{text-align:left}
  .directors{text-align:left}
  .about-head{min-height:44vh}
}
/* Same fix as the hero, for the same reason: 44vh held 231px of air under the wordmark
   on a phone and 706px in a tall pane. In portrait the head sizes to its content. */
@media (max-aspect-ratio:3/4){
  .about-head{min-height:0;padding-bottom:clamp(40px,10vw,64px)}
}

/* --- the film behind the first screen: REMOVED 2026-09-16 --------------------- *
   Filippos wants a better film before it returns, so the ground is plain --paper again.
   The layer, its crossfade, the grayscale pass, the 100svh mask and the contrast arithmetic
   that went with them are at 9769a76^ in this file, beside the markup and idle gate in
   index.html. Restore both together or neither.

   THE ONE RULE THAT STAYS is below, and deliberately: `main` and `footer` were raised to
   z-index 1 to ride above the film layer. With the layer gone it decides nothing, but it is
   also what the page is currently laid out under, so it is left alone rather than changed
   in a commit that is supposed to remove a video and nothing else. */
.page-home main,
.page-home footer{position:relative;z-index:1}

/* --- the cursor dot (Filippos, 2026-09-15, after how.studio) ---------------- *
   A dot that takes the pointer's place, and opens to the word `click` over
   anything clickable. Three things here are decisions, not detail.

   IT IS NOT WHITE AND IT DOES NOT BLEND. how.studio sits on near-black, where a
   white dot reads. This ground is --paper, so white is invisible on it, and the
   obvious trick — mix-blend-mode:difference — is wrong for THIS palette rather
   than wrong in general. Measured: --paper differenced over --red #FF3D00 gives
   rgb(13,179,234), azure. That is the whole About page, the .offer--accent and
   .term--accent bands, and every red link the dot opens over. Canon allows three
   colours and greige; a blend that invents a fourth loses to canon. So the dot
   is flat --ink with --paper lettering, which reads correctly on paper AND on
   the red ground.

   IT ADAPTS BY INHERITANCE, WHICH IS WORTH KNOWING BEFORE ANYONE "FIXES" IT.
   `position:fixed` changes the containing block, not the inheritance chain: the
   dot is a DOM child of <body>, so where a page class redefines the tokens ON
   body the dot picks them up. Verified on About — `.page-red` sets --ink:#000000
   and --paper:#FF3D00, and the dot measures rgb(0,0,0) with red lettering, black
   on red, the site's own inversion. So NO `body.page-red .swg-cursor` override is
   needed and adding one would only fight the cascade.

   THE LIMIT OF THAT is mid-page bands. `.offer--accent` and `.term--accent`
   invert the tokens on a section, and a section is not an ancestor of a fixed
   child of body, so the dot does not follow them — it stays --ink over a red
   band, which is legible and on-palette, just not inverted. A near-black band
   would be the real failure; there is none today, so check any new one.

   IT MUST NOT COST THE SITE ITS AFFORDANCES. This file has nine cursor:pointer
   declarations: the site currently says what is clickable through the pointer
   itself. Replacing the pointer throws all nine away unless the dot answers for
   them, which is why it opens to a word rather than merely growing.

   THE !important IS MEASURED, NOT SUPERSTITION. `html.has-swg-cursor *` is
   (0,1,1). `.ph[data-reel]` on line 614 is (0,2,0) and beats it, so without this
   the system pointer returns on top of the dot over every reel poster;
   `.case summary` (659) and `.field select` (1581) tie at (0,1,1) and are decided
   by source order. Caught by silly-wizards-guild-49a1 reading the specificities
   rather than trusting the selector. */
@media (pointer:fine){
  /* Applied by site.js ONLY once the dot is in the document, so a blocked script,
     a syntax error or an old browser leaves a normal pointer rather than none. */
  html.has-swg-cursor,
  html.has-swg-cursor *{cursor:none !important}

  /* Text fields keep their own cursor — hiding the caret on the contact form
     would trade usability for a flourish. `select` is here too: a native popup
     does not feed pointer events back, so the dot would freeze where it was
     left, stranded beside a menu it cannot see. */
  html.has-swg-cursor input,
  html.has-swg-cursor textarea,
  html.has-swg-cursor select,
  html.has-swg-cursor [contenteditable="true"],
  html.has-swg-cursor.swg-cursor-off,
  html.has-swg-cursor.swg-cursor-off *{cursor:auto !important}
}

.swg-cursor{
  position:fixed;top:0;left:0;pointer-events:none;
  /* Above .cookie-note, which is the highest thing the site puts on screen. */
  z-index:9999;
  /* EVERY DIMENSION HERE IS rem, INCLUDING THE DISC. Filippos's ruling of
     2026-09-15 is that the reader's browser setting governs type, and the label
     inside this dot is type. Sizing the disc in px while its word scales would
     overflow the circle for anyone running a large setting, so the two move
     together: .5rem at rest, 3.375rem open, .625rem of lettering. */
  width:.5rem;height:.5rem;margin:-.25rem 0 0 -.25rem;border-radius:999px;
  background:var(--ink);
  display:grid;place-items:center;
  opacity:0;
  /* transform is animated by script in a rAF and is deliberately NOT in this
     list: the dot tracks the pointer exactly, and easing it would lag. What
     eases is the open and close. */
  transition:opacity .2s ease,
             width .22s cubic-bezier(.2,.8,.2,1),
             height .22s cubic-bezier(.2,.8,.2,1),
             margin .22s cubic-bezier(.2,.8,.2,1);
  will-change:transform;
}
.swg-cursor.is-on{opacity:1}
.swg-cursor.is-hidden{opacity:0}

.swg-cursor-say{
  font-family:var(--font-mono);font-size:.625rem;letter-spacing:.08em;
  text-transform:lowercase;color:var(--paper);
  opacity:0;transition:opacity .12s ease;white-space:nowrap;
}
.swg-cursor.is-over{width:3.375rem;height:3.375rem;margin:-1.6875rem 0 0 -1.6875rem}
.swg-cursor.is-over .swg-cursor-say{opacity:1}
/* A press reads as the dot tightening, not as a separate animation. */
.swg-cursor.is-down{width:.375rem;height:.375rem;margin:-.1875rem 0 0 -.1875rem}

/* Motion is opt-in. site.js returns before creating the dot under reduced
   motion, so this is belt-and-braces for a dot left over from a live setting
   change rather than the primary guard. */
@media (prefers-reduced-motion:reduce){
  .swg-cursor{display:none}
}

/* --- the player button ------------------------------------------------------ *
   Filippos, 2026-09-14: "a simple play button", no fanfare. One control in the
   lockup. No track name, no artwork, no progress bar, and no third party — the file
   is served from this origin, which is what keeps cookies.html true as written: that
   page names exactly two outside parties and promises "nothing that follows you
   anywhere else". A Spotify embed would have made three sentences on it false.

   IT DOES NOT TAKE .tile-do--icon's 1.575em. That value is 1.575 x --fs-mono on the
   case tiles, about 27px. .motto is 26.8px, so the same rule would draw a 42px icon
   beside a 27px word — the loudest thing in the lockup. .7em puts it just under the
   cap height of the words it sits with.

   font:inherit IS LOAD-BEARING, not tidiness. A <button> does not inherit font-size,
   so without it the .7em resolved against the browser default of about 13.3px and the
   icon rendered at 9px — half the intended size and almost invisible. The em has to
   have the motto's 26.8px to measure against.

   The colour is inherited, so it is --red-text like the motto rather than a second
   accent introduced here. */
.motto-play{
  background:none;border:0;padding:0;margin:0 0 0 .55em;
  font:inherit;color:inherit;cursor:pointer;vertical-align:baseline;
}
.motto-play svg{width:.7em;height:.7em;display:block}
.motto-play:focus-visible{outline:2px solid var(--red);outline-offset:3px}

/* --- case pages (projects/<slug>.html) ---------------------------------------- *
   Written by tools/cases/build.mjs, one per case, from the same catalogue and the same tile
   functions as the grids (SEO audit issue 1, 2026-09-21). The page is the tile given room: the
   credit and category lines are .tile-credit and .tile-meta unchanged, the reel is the tile's own
   frame with its own controls, and the title is the tile title's type at --fs-large. Nothing here
   introduces a colour, a face or a size the site did not already have. The one new idea is the
   position — ( 14 / 36 ) with previous and next — which turns the catalogue's order into a walk. */
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
.cs-top{display:flex;justify-content:space-between;align-items:baseline;gap:24px;padding-block:clamp(28px,4vw,48px) 0}
.cs-back{color:var(--ink);transition:color .15s}
.cs-back:hover,.cs-back:focus-visible{color:var(--red-text)}
.cs-pos{font-variant-numeric:tabular-nums}
.cs-head{display:flex;flex-direction:column;gap:clamp(12px,1.4vw,20px);padding-block:clamp(40px,6vw,96px) clamp(28px,3.5vw,48px)}
.cs-title{font-size:var(--fs-large);font-weight:500;letter-spacing:-.03em;line-height:1.02;max-width:15em;text-wrap:balance}
.cs-media{padding-bottom:clamp(48px,6vw,104px)}
.cs-reel{margin:0}
.cs-reel figcaption{flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center}
.cs-body{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(0,1fr);gap:clamp(32px,6vw,120px);padding-bottom:var(--pad-section)}
.cs-copy{font-size:var(--fs-small);line-height:1.55;max-width:var(--measure)}
.cs-copy p+p{margin-top:1em}
.cs-copy a{border-bottom:1px solid currentColor}
.cs-links{margin-top:1.4em;font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono);text-transform:uppercase}
.cs-links a{border:0;color:var(--red-text)}
.cs-facts{display:flex;flex-direction:column;gap:20px;font-family:var(--font-mono);font-size:var(--fs-mono);letter-spacing:var(--track-mono)}
.cs-facts dt{text-transform:uppercase;color:var(--mono-dim)}
.cs-facts dd{margin:6px 0 0;color:var(--ink);text-transform:lowercase;line-height:1.6}
.cs-pager{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap-grid);border-top:1px solid var(--hairline);padding-block:clamp(32px,4vw,56px) var(--pad-section)}
.cs-pager a{display:flex;flex-direction:column;gap:10px}
.cs-next{text-align:right;align-items:flex-end}
.cs-pager-title{font-size:var(--fs-title);font-weight:500;letter-spacing:-.02em;line-height:1.2;transition:color .15s}
.cs-pager a:hover .cs-pager-title,.cs-pager a:focus-visible .cs-pager-title{color:var(--red-text)}
@media (max-width:700px){
  .cs-body{grid-template-columns:1fr}
  .cs-pager{grid-template-columns:1fr}
  .cs-next{text-align:left;align-items:flex-start}
}
