/**
 * HACKENSHAW - a reveal.js theme matching hackenshaw.com
 *
 * Dark CRT terminal: charcoal ground, chartreuse phosphor, monospace throughout,
 * scanlines + flicker + chromatic aberration. Built for reveal.js 6.x.
 *
 * Usage:
 *   <link rel="stylesheet" href="../vendor/reveal/reset.css">
 *   <link rel="stylesheet" href="../vendor/reveal/reveal.css">
 *   <link rel="stylesheet" href="../theme/hackenshaw.css" id="theme">
 *
 * All motion is disabled under prefers-reduced-motion.
 */

/* ---- Palette (shared with the rest of the site) ---- */
:root {
    --hk-bg: #272727;
    --hk-panel: #181818;
    --hk-fg: chartreuse;
    --hk-dim: #5a8f2a;
    --hk-rule: rgba(127, 255, 0, 0.25);
    --hk-glow: rgba(127, 255, 0, 0.7);

    /* reveal's theme contract */
    --r-background-color: var(--hk-bg);
    --r-main-color: var(--hk-fg);
    --r-heading-color: var(--hk-fg);
    --r-link-color: var(--hk-fg);
    --r-link-color-hover: #ffffff;
    --r-link-color-dark: var(--hk-dim);
    --r-selection-background-color: rgba(127, 255, 0, 0.3);
    --r-selection-color: #ffffff;
    --r-overlay-element-bg-color: 127, 255, 0;
    --r-overlay-element-fg-color: 39, 39, 39;

    --r-main-font: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --r-heading-font: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --r-code-font: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    --r-main-font-size: 24px;
    --r-heading-margin: 0 0 24px 0;
    --r-heading-line-height: 1.15;
    --r-heading-letter-spacing: 0.08em;
    --r-heading-text-transform: uppercase;
    --r-heading-font-weight: 700;
    --r-heading-text-shadow: none;
    --r-heading1-text-shadow: none;
    --r-heading1-size: 2.6em;
    --r-heading2-size: 1.75em;
    --r-heading3-size: 1.3em;
    --r-heading4-size: 1.05em;
    --r-block-margin: 22px;
}

.reveal-viewport {
    background-color: var(--hk-bg);
}

.reveal {
    font-family: var(--r-main-font);
    font-size: var(--r-main-font-size);
    color: var(--hk-fg);
}

/* ---- CRT overlay: scanlines, flicker, sits above the deck ---- */
body::before,
body::after {
    content: " ";
    display: block;
    position: fixed;
    inset: 0;
    z-index: 60;
    pointer-events: none;
}

body::before {
    background:
        linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
        linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
    background-size: 100% 3px, 3px 100%;
}

body::after {
    background: rgba(18, 16, 16, 0.1);
    opacity: 0;
    animation: hk-flicker 0.15s infinite;
}

@keyframes hk-flicker {
    0%   { opacity: 0.28; }
    15%  { opacity: 0.91; }
    25%  { opacity: 0.84; }
    40%  { opacity: 0.27; }
    50%  { opacity: 0.96; }
    60%  { opacity: 0.20; }
    75%  { opacity: 0.37; }
    90%  { opacity: 0.70; }
    100% { opacity: 0.24; }
}

@keyframes hk-text-shadow {
    0%   { text-shadow: 0.44px 0 1px rgba(0, 30, 255, 0.5), -0.44px 0 1px rgba(255, 0, 80, 0.3), 0 0 3px; }
    25%  { text-shadow: 1.61px 0 1px rgba(0, 30, 255, 0.5), -1.61px 0 1px rgba(255, 0, 80, 0.3), 0 0 3px; }
    50%  { text-shadow: 0.08px 0 1px rgba(0, 30, 255, 0.5), -0.08px 0 1px rgba(255, 0, 80, 0.3), 0 0 3px; }
    75%  { text-shadow: 1.89px 0 1px rgba(0, 30, 255, 0.5), -1.89px 0 1px rgba(255, 0, 80, 0.3), 0 0 3px; }
    100% { text-shadow: 2.62px 0 1px rgba(0, 30, 255, 0.5), -2.62px 0 1px rgba(255, 0, 80, 0.3), 0 0 3px; }
}

/* ---- Headings ---- */
.reveal h1,
.reveal h2,
.reveal h3,
.reveal h4,
.reveal h5,
.reveal h6 {
    color: var(--hk-fg);
    font-family: var(--r-heading-font);
    font-weight: var(--r-heading-font-weight);
    letter-spacing: var(--r-heading-letter-spacing);
    text-transform: var(--r-heading-text-transform);
    line-height: var(--r-heading-line-height);
    margin: var(--r-heading-margin);
    word-wrap: break-word;
}

.reveal h1 { font-size: var(--r-heading1-size); }
.reveal h2 { font-size: var(--r-heading2-size); }
.reveal h3 { font-size: var(--r-heading3-size); }
.reveal h4,
.reveal h5,
.reveal h6 { font-size: var(--r-heading4-size); }

.reveal h1,
.reveal h2 {
    animation: hk-text-shadow 1.6s infinite;
}

.reveal h3,
.reveal h4 {
    color: var(--hk-dim);
}

/* ---- Body copy ---- */
.reveal p {
    line-height: 1.4;
    margin: var(--r-block-margin) 0;
}

.reveal strong,
.reveal b {
    color: var(--hk-fg);
    font-weight: 700;
}

.reveal em {
    color: var(--hk-dim);
    font-style: italic;
}

.reveal small {
    font-size: 0.7em;
    color: var(--hk-dim);
}

/* ---- Lists: terminal arrows instead of bullets ---- */
.reveal ul {
    list-style: none;
    margin-left: 0;
}

.reveal ul li {
    position: relative;
    padding-left: 1.4em;
    margin-bottom: 0.45em;
}

.reveal ul li::before {
    content: "\2192";
    position: absolute;
    left: 0;
    color: var(--hk-dim);
}

.reveal ul ul li::before {
    content: "\00B7";
}

.reveal ol {
    list-style: none;
    counter-reset: hk-ol;
    margin-left: 0;
}

.reveal ol li {
    counter-increment: hk-ol;
    position: relative;
    padding-left: 2.2em;
    margin-bottom: 0.45em;
}

.reveal ol li::before {
    content: counter(hk-ol) ".";
    position: absolute;
    left: 0;
    color: var(--hk-dim);
}

/* A list standing directly on a slide. reveal centres slide text and the
   markers above are pinned to the list's left edge, so a full-width list put
   each arrow at the far left and its text in the middle, a long way apart.
   Shrink the list to its widest item and centre the block instead: the
   items stay left-aligned against their markers and the list as a whole
   sits in the middle. Lists inside .cols, .panel and friends are not
   direct children and keep the full width of their box. */
.reveal .slides section > ul,
.reveal .slides section > ol {
    display: table;
    margin-left: auto;
    margin-right: auto;
    text-align: left;
}

.reveal dt {
    color: var(--hk-fg);
    font-weight: 700;
}

.reveal dd {
    color: var(--hk-dim);
}

/* ---- Links ---- */
.reveal a {
    color: var(--hk-fg);
    text-decoration: none;
    border-bottom: 1px solid var(--hk-rule);
    transition: border-color 0.18s ease, filter 0.18s ease;
}

.reveal a:hover,
.reveal a:focus-visible {
    color: var(--hk-fg);
    border-bottom-color: var(--hk-fg);
    filter: drop-shadow(0 0 8px var(--hk-glow));
    outline: none;
}

/* ---- Panels: quotes, code, tables ---- */
.reveal blockquote {
    display: block;
    position: relative;
    width: 90%;
    margin: var(--r-block-margin) auto;
    padding: 1rem 1.4rem;
    background: var(--hk-panel);
    border-left: 3px solid var(--hk-fg);
    border-radius: 12px;
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
    font-style: normal;
    color: var(--hk-fg);
    text-align: left;
}

.reveal blockquote cite,
.reveal blockquote footer {
    display: block;
    margin-top: 0.6rem;
    color: var(--hk-dim);
    font-size: 0.7em;
    font-style: normal;
}

.reveal pre {
    display: block;
    position: relative;
    width: 92%;
    margin: var(--r-block-margin) auto;
    font-family: var(--r-code-font);
    font-size: 0.7em;
    line-height: 1.35;
    text-align: left;
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
    border-radius: 12px;
    overflow: hidden;
}

.reveal pre code {
    display: block;
    padding: 1rem 1.2rem;
    overflow: auto;
    max-height: 620px;
    background: var(--hk-panel);
    color: var(--hk-fg);
    border: 1px solid var(--hk-rule);
    border-radius: 12px;
    word-wrap: normal;
}

.reveal code {
    font-family: var(--r-code-font);
}

/* inline code */
.reveal :not(pre) > code {
    padding: 0.12em 0.4em;
    background: var(--hk-panel);
    border: 1px solid var(--hk-rule);
    border-radius: 6px;
    font-size: 0.85em;
}

.reveal table {
    margin: var(--r-block-margin) auto;
    border-collapse: collapse;
    border-spacing: 0;
    background: var(--hk-panel);
    border-radius: 12px;
    overflow: hidden;
}

.reveal table th {
    background: rgba(127, 255, 0, 0.09);
    color: var(--hk-fg);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.8em;
}

.reveal table th,
.reveal table td {
    padding: 0.5em 0.9em;
    border-bottom: 1px solid var(--hk-rule);
    text-align: left;
}

.reveal table tbody tr:last-child th,
.reveal table tbody tr:last-child td {
    border-bottom: none;
}

/* ---- Images ---- */
.reveal section img {
    margin: var(--r-block-margin) 0;
    background: transparent;
    border: 1px solid var(--hk-rule);
    border-radius: 12px;
    box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
}

.reveal section img.plain {
    border: none;
    box-shadow: none;
    background: transparent;
}

/* The header mark on a title slide. Was a cropped portrait, so it was circled
   and ringed; it is now the logo — angular artwork on transparency, which a
   circle clips and a ring fences in. Contain keeps the mark whole and the glow
   follows its edge. Scaled up from 190px for the same reason the shared sheet
   is: the logo carries its own padding, so an equal box reads smaller. */
.reveal section img.avatar {
    width: 216px;
    height: 216px;
    object-fit: contain;
    border: none;
    /* faint on purpose: drop-shadow traces the logo's own outline, so a
       strong one reads as a fringe around the artwork. Blur is roughly
       double the shared sheet's because this mark is roughly double the
       size. There is no hover state on a slide. */
    filter: drop-shadow(0 0 10px rgba(127, 255, 0, 0.14));
}

/* ---- Chrome: controls, progress, slide numbers ---- */
/* Decks should pass `controls: false` to Reveal.initialize - that is the real
   switch. This is the theme-level safety net for decks that forget: reveal sets
   display on .controls inline at runtime, so only !important wins here. */
.reveal .controls {
    display: none !important;
}

.reveal .progress {
    height: 4px;
    background: rgba(127, 255, 0, 0.12);
    color: var(--hk-fg);
}

.reveal .slide-number {
    background: transparent;
    color: var(--hk-dim);
    font-family: var(--r-code-font);
    font-size: 14px;
    letter-spacing: 0.04em;
}

.reveal .slide-number a {
    border-bottom: none;
}

.reveal .backgrounds {
    background-color: var(--hk-bg);
}

/* ---- Theme helpers usable in decks ---- */

/* blinking terminal cursor: <span class="cursor"></span> */
.reveal .cursor {
    display: inline-block;
    width: 0.55em;
    height: 1em;
    background: var(--hk-fg);
    vertical-align: text-bottom;
    animation: hk-blink 1.1s steps(1) infinite;
}

@keyframes hk-blink {
    50% { opacity: 0; }
}

/* shell prompt line: <p class="prompt">$ whoami</p> */
.reveal .prompt {
    color: var(--hk-dim);
    font-size: 0.72em;
    letter-spacing: 0.03em;
    text-align: left;
}

.reveal .prompt span {
    color: var(--hk-fg);
}

/* dim caption / tagline */
.reveal .tagline {
    color: var(--hk-dim);
    font-size: 0.72em;
    letter-spacing: 0.05em;
}

/* bordered panel, same idiom as the site's terminal box */
.reveal .panel {
    background: var(--hk-panel);
    border: 1px solid var(--hk-rule);
    border-radius: 20px;
    padding: 1.2rem 1.6rem;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
    text-align: left;
}

/* simple two-column split: <div class="cols"><div>..</div><div>..</div></div> */
.reveal .cols {
    display: flex;
    gap: 1.6rem;
    align-items: flex-start;
    text-align: left;
}

.reveal .cols > * {
    flex: 1;
    min-width: 0;
}

/* muted text */
.reveal .dim {
    color: var(--hk-dim);
}


/* ---- Shared deck helpers -------------------------------------------------
   Promoted out of the decks once more than one of them needed the same rule.
   A deck keeps a <style> block only for things genuinely its own; anything
   two decks want lives here, so it cannot drift between copies.
   ------------------------------------------------------------------------ */

/* live-demo badge: <span class="demo-tag">LIVE DEMO</span> */
.reveal .demo-tag {
    display: inline-block;
    padding: 0.15em 0.7em;
    margin-bottom: 0.6em;
    border: 1px solid var(--hk-fg);
    border-radius: 999px;
    font-size: 0.62em;
    letter-spacing: 0.18em;
    color: var(--hk-fg);
}

/* the presenter's run list on a demo slide: <div class="panel runlist"> */
.reveal .runlist {
    text-align: left;
    font-size: 0.8em;
}

/* "command — what it does" pairs: <table class="cmds"> */
.reveal table.cmds {
    width: 100%;
    font-size: 0.82em;
}

.reveal table.cmds td:first-child {
    white-space: nowrap;
    color: var(--hk-fg);
}

.reveal table.cmds td:last-child {
    color: var(--hk-dim);
}

/* one idea, stated large and alone: <p class="big"> */
.reveal .big {
    font-size: 1.25em;
    line-height: 1.35;
}

/* QR beside its link: <div class="cols qr-row">. The QR column hugs the
   image; the text takes the rest. */
.reveal .qr-row {
    align-items: center;
    gap: 2.2rem;
}

.reveal .qr-row .qr {
    flex: 0 0 auto;
}

/* The QR is wrapped in a link back to the generator; reveal's own
   anchor styling would otherwise draw a colour and underline on it. */
.reveal .qr-row .qr a {
    display: block;
    border: none;
    text-decoration: none;
}

.reveal .qr-row .qr img {
    margin: 0;
    display: block;
}

.reveal .qr-row .url {
    font-size: 0.9em;
    line-height: 1.35;
}

/* QR centred under a heading: <div class="qr-center">. .prompt is
   left-aligned by default, which is right beside a QR and wrong below one. */
.reveal .qr-center {
    text-align: center;
    margin: 0.6em 0 0.4em;
}

.reveal .qr-center a.gen {
    display: inline-block;
    border: none;
    text-decoration: none;
}

.reveal .qr-center img {
    margin: 0 auto;
    display: block;
}

.reveal .qr-center .prompt {
    text-align: center;
    margin-top: 0.5em;
}

.reveal .qr-center .prompt a {
    color: var(--hk-fg);
}

/* ---- The word NEXT ------------------------------------------------------
   Brand rule: wherever the word NEXT appears as the name of the thing — the
   button on nextslide.hackenshaw.com/click, the Next Slide Please deck — it
   is set in the same pixel face as the QR KEEPER title, not in the body
   mono. Everywhere else "next" is just a word and stays as it is.

       <span class="nextword">NEXT</span>

   The face is vendored at /assets/fonts/ rather than pulled from Google
   Fonts: every vhost aliases /assets/, so one copy serves all of them
   same-origin, and nothing on the page waits on a third party. Press Start
   2P is OFL-1.1; the licence sits beside the file.

   Press Start 2P has unusual vertical metrics, and both of the obvious
   symptoms come straight out of them:

       unitsPerEm 1000   ascent 1000   descent 0   capHeight 1000
       cap ink spans y +0.125 .. +1.000 em

   Its capitals do NOT sit on the baseline — they float 0.125em above it —
   and the face has no descent at all, so its whole inline box is above the
   baseline.

   font-size: the visual cap is (1.000 - 0.125) = 0.875 of the font size, so
   0.8em lands the cap on a 0.70em monospace cap height.

   vertical-align: -0.125em lowers it by 0.125 of its OWN size, which is the
   exact ink offset, dropping the capitals onto the surrounding baseline.
   ------------------------------------------------------------------------ */
@font-face {
    font-family: "Press Start 2P";
    src: url("/assets/fonts/press-start-2p.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

.reveal .nextword {
    font-family: "Press Start 2P", var(--r-main-font);
    font-size: 0.8em;
    vertical-align: -0.125em;
    letter-spacing: 0.06em;
    white-space: nowrap;
    text-transform: uppercase;
    /* Never dimmed: the word is the brand wherever it appears, including
       inside dim running text like the keyboard readout. */
    color: var(--hk-fg);
    /* The glow is deliberately lighter here than the 0.5em/0.6 the site
       stylesheet uses, and this is the one value the two rules do not share.

       reveal authors a deck at 960x700 and scales the whole slide to the
       viewport — about 1.5x on a 1080p screen, 2x on a 1440p one. An em-based
       glow scales with it, so the identical ratio produced a halo roughly
       twice as wide as the same word on the click page, which renders in real
       pixels. Matching the ratio is what made them look different; matching
       the appearance means a smaller ratio here. */
    text-shadow: 0 0 0.26em rgba(127, 255, 0, 0.42);
    /* the deck animates a chromatic-aberration shadow on h1/h2; the brand
       stays still so it reads the same here as anywhere else */
    animation: none;
}

/* warning panel: <div class="panel warn"> - .panel, ringed in phosphor.
   Deliberately tight: the theme gives every <p> a 22px block margin,
   which on a short note doubles the height of the box. Margins are
   zeroed and restated, and the glow is kept narrow enough not to wash
   over whatever sits above - neither .panel nor a preceding grid carries a
   margin of its own, so the box would otherwise sit flush against it. */
.reveal .warn {
    border: 1px solid var(--hk-fg);
    box-shadow: 0 0 24px rgba(127, 255, 0, 0.10);
    padding: 0.8rem 1.2rem;
    margin-top: 1.1rem;
}

/* One size for everything in the box. Left alone, the lead line inherits the
   full 1em body size and towers over the note under it; and a deck that leads
   with a .prompt gets 0.72em instead, so the same panel came out at two
   different sizes in two decks. This is more specific than .reveal .prompt,
   so a prompt lead is sized here too and only keeps its colour. */
.reveal .warn p {
    margin: 0;
    font-size: 0.78em;
}

.reveal .warn p + p {
    margin-top: 0.4em;
    color: var(--hk-dim);
}

/* title-slide mark: <svg class="titlemark"> - the deck's own logo, drawn in
   phosphor where the avatar would otherwise sit. Each deck supplies its own
   geometry; the size, spacing, glow and part colours are shared. Inline SVG
   rather than a raster, so it is sharp at any projector size, carries the
   palette instead of fighting it, and costs the offline bundle nothing. */
.reveal .titlemark {
    width: 190px;
    height: auto;
    display: block;
    margin: 0 auto var(--r-block-margin);
    filter: drop-shadow(0 0 16px rgba(127, 255, 0, 0.25));
}

/* Transparent, so the slide shows through the mark rather than a plate of
   panel colour. */
.reveal .titlemark .mark-body {
    fill: none;
    stroke: var(--hk-fg);
    stroke-width: 2.5;
}

.reveal .titlemark .mark-line {
    fill: none;
    stroke: var(--hk-fg);
    stroke-width: 5.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.reveal .titlemark .mark-node {
    fill: var(--hk-fg);
}

/* a solid block inside a mark - a cursor shape, deliberately not blinking.
   A logo that flashes pulls the eye off the title next to it, and the live
   .cursor on the prompt below is already doing that job. */
.reveal .titlemark .mark-cursor {
    fill: var(--hk-fg);
}

/* title lockup: the deck's mark beside its title rather than stacked above it.

     <div class="titlelock">
       <svg class="titlemark">...</svg>        (or <img class="avatar">)
       <div>
         <h1>...</h1>
         <p class="tagline">...</p>
         <p class="prompt">...</p>
       </div>
     </div>

   Both the mark and the avatar carry their own block margins, which are wrong
   the moment they sit in a row, so the first child's margin is reset here
   rather than in each deck. */
.reveal .titlelock {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2.2rem;
    text-align: left;
}

.reveal .titlelock > :first-child {
    flex: 0 0 auto;
    margin: 0;
}

/* A full 2.6em h1 beside a 190px mark overruns the slide on a longer title.
   This is still comfortably the largest type in the deck. */
.reveal .titlelock h1 {
    font-size: 2.2em;
    margin: 0;
}

.reveal .titlelock .tagline {
    margin: 0.35em 0 0;
}

.reveal .titlelock .prompt {
    margin: 0.8em 0 0;
}

/* ---- Syntax highlighting (highlight.js), kept monochrome-phosphor ---- */
.reveal .hljs {
    background: var(--hk-panel);
    color: var(--hk-fg);
}

.reveal .hljs-comment,
.reveal .hljs-quote {
    color: #4a7a22;
    font-style: italic;
}

.reveal .hljs-keyword,
.reveal .hljs-selector-tag,
.reveal .hljs-literal,
.reveal .hljs-doctag {
    color: #c8ff7a;
    font-weight: 700;
}

.reveal .hljs-string,
.reveal .hljs-regexp,
.reveal .hljs-addition {
    color: #9fe870;
}

.reveal .hljs-number,
.reveal .hljs-symbol,
.reveal .hljs-bullet {
    color: #d4ff8f;
}

.reveal .hljs-title,
.reveal .hljs-title.function_,
.reveal .hljs-section,
.reveal .hljs-name {
    color: chartreuse;
    font-weight: 700;
}

.reveal .hljs-attr,
.reveal .hljs-attribute,
.reveal .hljs-variable,
.reveal .hljs-template-variable {
    color: #8fd44a;
}

.reveal .hljs-built_in,
.reveal .hljs-type,
.reveal .hljs-class .hljs-title {
    color: #b6ff5c;
}

.reveal .hljs-meta,
.reveal .hljs-punctuation,
.reveal .hljs-operator {
    color: var(--hk-dim);
}

.reveal .hljs-deletion {
    color: #ff6b6b;
}

.reveal .hljs-emphasis { font-style: italic; }
.reveal .hljs-strong { font-weight: 700; }

/* highlight.js line-highlight used by reveal's data-line-numbers */
.reveal .hljs .hljs-ln-numbers,
.reveal .hljs-ln-numbers {
    color: #3f6b1c;
}

.reveal pre code .highlight-line {
    background: rgba(127, 255, 0, 0.10);
}

/* ---- Shared deck helpers: demos, diagrams and pieces more than one deck uses
   Moved here from the decks' own <style> blocks on 2026-10-02 so they can't
   drift apart - they had (button sizes, slider thumbs, diagram tracking). A
   deck keeps in its own <style> only what is genuinely its own, plus the
   odd deliberate override, which says so. Before copying a rule into a deck,
   look here. ------------------------------------------------------------ */

/* Diagrams drawn as inline SVG: <svg class="diagram"> */
.reveal svg.diagram {
    display: block;
    width: 100%;
    height: auto;
}
.reveal svg.diagram .node {
    fill: var(--hk-panel);
    stroke: var(--hk-fg);
    stroke-width: 2;
    transition: fill 0.15s ease;
}
.reveal svg.diagram .node.soft {
    stroke-dasharray: 7 6;
    stroke: var(--hk-dim);
}
.reveal svg.diagram .flow, .reveal svg.diagram .wire {
    fill: none;
    stroke: var(--hk-dim);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.reveal svg.diagram .wire.bad {
    stroke-dasharray: 6 6;
}
.reveal svg.diagram .wire.off {
    stroke-dasharray: 4 8;
    opacity: 0.4;
}
.reveal svg.diagram .lit {
    fill: var(--hk-fg);
}
/* a lit wire is a bright line, never a filled shape */
.reveal svg.diagram .wire.lit {
    stroke: var(--hk-fg);
    fill: none;
}
.reveal svg.diagram .fill {
    fill: var(--hk-fg);
}
.reveal svg.diagram .obs {
    cursor: pointer;
}
.reveal svg.diagram .obs.off {
    opacity: 0.3;
}
.reveal svg.diagram .ring {
    fill: none;
    stroke: var(--hk-rule);
    stroke-width: 2;
}
.reveal svg.diagram .ringpulse {
    fill: none;
    stroke: var(--hk-fg);
    stroke-width: 3;
}
.reveal svg.diagram text {
    fill: var(--hk-fg);
    font-family: var(--r-main-font);
    font-size: 17px;
    letter-spacing: 0.06em;
    text-anchor: middle;
    dominant-baseline: central;
    transition: fill 0.15s ease;
}
.reveal svg.diagram text.small {
    font-size: 14px;
    fill: var(--hk-dim);
    letter-spacing: 0.03em;
}
.reveal svg.diagram text.left {
    text-anchor: start;
}
.reveal svg.diagram text.big {
    font-size: 44px;
    font-weight: 700;
    fill: var(--hk-dim);
}
.reveal svg.diagram text.sign {
    font-size: 26px;
    font-weight: 700;
}
.reveal svg.diagram .on .node {
    fill: var(--hk-fg);
}
.reveal svg.diagram .on text {
    fill: var(--hk-bg);
}
.reveal svg.diagram.shake {
    animation: jolt 0.35s linear;
}
@keyframes jolt {
    20% { transform: translate(-6px, 3px); }
    40% { transform: translate(5px, -4px); }
    60% { transform: translate(-4px, -2px); }
    80% { transform: translate(3px, 2px); }
}

@media (prefers-reduced-motion: reduce) {
    .reveal svg.diagram.shake {
        animation: none;
    }
}

/* The IT process cycle: <div class="cyclefig"> - boxes in SVG, labels as HTML spans over them so they get the heading font; a lit step is inverted */
.reveal .cyclefig {
    position: relative;
    margin: 0 auto;
    width: 24em;
    aspect-ratio: 420 / 290;
}
.reveal .cyclefig svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.reveal .cyclefig .node {
    fill: var(--hk-panel);
    stroke: var(--hk-fg);
    stroke-width: 1.6;
}
.reveal .cyclefig .node.on {
    fill: var(--hk-fg);
}
.reveal .cyclefig .flow {
    fill: none;
    stroke: var(--hk-dim);
    stroke-width: 2.1;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.reveal .cyclefig span {
    position: absolute;
    transform: translate(-50%, -50%);
    color: var(--hk-fg);
    font-family: var(--r-heading-font);
    font-weight: var(--r-heading-font-weight);
    letter-spacing: var(--r-heading-letter-spacing);
    text-transform: var(--r-heading-text-transform);
    font-size: 0.75em;
    line-height: 1;
    white-space: nowrap;
    animation: hk-text-shadow 1.6s infinite;
}
.reveal .cyclefig span.on {
    color: var(--hk-bg);
    animation: none;
}

@media (prefers-reduced-motion: reduce) {
    .reveal .cyclefig span {
        animation: none;
    }
}

/* Live demos: a canvas on a panel ground, its controls and hint. Clicking the canvas gives it the keyboard; Escape hands it back */
.reveal canvas.stage {
    display: block;
    max-width: 100%;
    margin: 0 auto;
    background: var(--hk-panel);
    border: 1px solid var(--hk-rule);
    border-radius: 14px;
    outline: none;
}
.reveal canvas.stage:focus-visible, .reveal canvas.stage:focus {
    border-color: var(--hk-fg);
}
.reveal section[data-demo] h2 {
    margin-bottom: 14px;
}
.reveal .slides > section > .prompt {
    text-align: center;
}
.reveal .ctl {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
    align-items: center;
    margin-top: 0.7rem;
}
.reveal .ctl button {
    font: inherit;
    font-size: 0.56em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.35em 0.9em;
    color: var(--hk-dim);
    background: transparent;
    border: 1px solid var(--hk-rule);
    border-radius: 999px;
    cursor: pointer;
}
.reveal .ctl button:hover {
    color: var(--hk-fg);
    border-color: var(--hk-fg);
}
.reveal .ctl button.on {
    color: var(--hk-bg);
    background: var(--hk-fg);
    border-color: var(--hk-fg);
}
.reveal .ctl label {
    font-size: 0.56em;
    color: var(--hk-dim);
    letter-spacing: 0.05em;
}
.reveal .hint {
    margin: 0.5em 0 0;
    font-size: 0.52em;
    color: var(--hk-dim);
    letter-spacing: 0.04em;
}
.reveal .slider {
    display: block;
    width: 70%;
    max-width: 26em;
    margin: 0.4em auto 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
}
.reveal .ctl .slider {
    display: inline-block;
    width: 13em;
    max-width: none;
    margin: 0;
    vertical-align: middle;
}
.reveal .slider:focus {
    outline: none;
}
.reveal .slider::-webkit-slider-runnable-track {
    height: 0.5em;
    border-radius: 999px;
    background: var(--hk-panel);
    border: 1px solid var(--hk-rule);
}
.reveal .slider::-moz-range-track {
    height: 0.5em;
    border-radius: 999px;
    background: var(--hk-panel);
    border: 1px solid var(--hk-rule);
}
.reveal .slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 1.3em;
    height: 1.3em;
    margin-top: -0.45em;
    border: 0;
    border-radius: 50%;
    background: var(--hk-fg);
    box-shadow: 0 0 0.7em var(--hk-glow);
}
.reveal .slider::-moz-range-thumb {
    width: 1.3em;
    height: 1.3em;
    border: 0;
    border-radius: 50%;
    background: var(--hk-fg);
    box-shadow: 0 0 0.7em var(--hk-glow);
}

/* Two ways: two code captions over one split canvas (.compare), or two code blocks side by side (.twocode) */
.reveal .compare .cols {
    gap: 1rem;
    margin-bottom: 0.6rem;
}
.reveal .compare pre {
    font-size: 0.48em;
    margin: 0;
    width: 100%;
    box-shadow: none;
}
.reveal .compare pre code {
    padding: 0.6rem 0.9rem;
}
.reveal .twocode pre {
    font-size: 0.5em;
    margin: 0;
    width: 100%;
    box-shadow: none;
}
.reveal .twocode h4 {
    margin: 0 0 0.4em;
    font-size: 0.66em;
}

/* The voice command slide: a waiting pulse, then a voice meter and what it said */
.reveal .waitline {
    font-size: 0.85em;
    letter-spacing: 0.06em;
    margin-top: 0.6em;
}
.reveal .waiting {
    display: inline-block;
    width: 0.5em;
    height: 0.5em;
    border-radius: 50%;
    background: var(--hk-fg);
    margin-right: 0.5em;
    animation: hk-pulse 1.1s ease-in-out infinite;
}
.reveal .voiceline {
    display: none;
    align-items: center;
    gap: 0.5em;
}
.reveal section.speaking .waitstate {
    display: none;
}
.reveal section.speaking .voiceline {
    display: inline-flex;
}
.reveal .voiceline .bars {
    display: inline-flex;
    align-items: center;
    gap: 0.14em;
    height: 1em;
}
.reveal .voiceline .bars i {
    display: block;
    width: 0.2em;
    height: 1em;
    border-radius: 0.1em;
    background: var(--hk-fg);
    box-shadow: 0 0 0.4em var(--hk-glow);
    transform: scaleY(0.15);
    animation: hk-talk 0.42s ease-in-out infinite alternate;
}
.reveal .voiceline .bars i:nth-child(1) {
    animation-delay: -0.10s;
    animation-duration: 0.38s;
}
.reveal .voiceline .bars i:nth-child(2) {
    animation-delay: -0.30s;
    animation-duration: 0.46s;
}
.reveal .voiceline .bars i:nth-child(3) {
    animation-delay: -0.05s;
    animation-duration: 0.34s;
}
.reveal .voiceline .bars i:nth-child(4) {
    animation-delay: -0.22s;
    animation-duration: 0.50s;
}
.reveal .voiceline .bars i:nth-child(5) {
    animation-delay: -0.14s;
    animation-duration: 0.36s;
}
.reveal .voiceline .bars i:nth-child(6) {
    animation-delay: -0.36s;
    animation-duration: 0.44s;
}
.reveal .voiceline .bars i:nth-child(7) {
    animation-delay: -0.02s;
    animation-duration: 0.40s;
}
.reveal .voiceline .bars i:nth-child(8) {
    animation-delay: -0.26s;
    animation-duration: 0.48s;
}
.reveal .voiceline .bars i:nth-child(9) {
    animation-delay: -0.18s;
    animation-duration: 0.37s;
}
.reveal .voiceline .bars i:nth-child(10) {
    animation-delay: -0.32s;
    animation-duration: 0.43s;
}
.reveal .voiceline .bars i:nth-child(11) {
    animation-delay: -0.08s;
    animation-duration: 0.39s;
}
.reveal .voiceline .bars i:nth-child(12) {
    animation-delay: -0.28s;
    animation-duration: 0.47s;
}
.reveal .voiceline .bars i:nth-child(13) {
    animation-delay: -0.12s;
    animation-duration: 0.35s;
}
.reveal section.spoken .voiceline .bars i {
    animation: none;
    transform: scaleY(0.15);
    transition: transform 0.2s ease;
}
.reveal .voiceline .said {
    color: #ffffff;
    text-shadow: 0 0 0.5em var(--hk-glow);
    animation: hk-said 0.35s cubic-bezier(0.2, 1.6, 0.4, 1);
}
@keyframes hk-pulse {
    0%, 100% { opacity: 0.25; box-shadow: none; }
    50% { opacity: 1; box-shadow: 0 0 0.8em var(--hk-glow); }
}
@keyframes hk-talk {
    0% { transform: scaleY(0.15); }
    100% { transform: scaleY(1.6); }
}
@keyframes hk-said {
    0% { opacity: 0; transform: scale(1.4); }
    100% { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .reveal .waiting {
        animation: none;
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .reveal .voiceline .bars i {
        animation: none;
        transform: scaleY(0.6);
    }
}

/* A full-window game: .arena outside .reveal (reveal transforms slides, and a transformed ancestor would pin a fixed child to the slide), with the phone-join box. It sits over reveal's slide number and progress bar (31 and under) and under the CRT overlay (60), so the room still wears it; a deck with its own stack overrides the z-index */
.arena {
    position: fixed;
    inset: 0;
    z-index: 50;
    background: #101010;
    display: flex;
    align-items: center;
    justify-content: center;
}
.arena[hidden] {
    display: none;
}
.arena canvas {
    object-fit: contain;
}
.arena > .playwrap {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
}
.playwrap {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
}
.playwrap canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
.playwrap .joinbox {
    position: absolute;
    top: 3.5%;
    right: 3%;
    width: 14%;
    min-width: 92px;
    text-align: center;
}
.playwrap .joinbox b {
    color: var(--hk-fg);
    font-weight: 400;
}
.playwrap .joinbox img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 6px;
}
.playwrap .joinbox p {
    margin: 0.5em 0 0;
    font-family: var(--r-main-font);
    /* small enough that playground.hackenshaw.com/join (30 characters) fits
       the box's 14% width at any window size; any bigger and it spills past
       the right edge of the screen */
    font-size: clamp(7px, 0.72vw, 15px);
    letter-spacing: 0.04em;
    line-height: 1.35;
    color: var(--hk-dim);
}
html.manual .playwrap .joinbox {
    display: none;
}

/* Workshop pieces shared by the game design decks: cards, the MDA lenses, loops, UML boxes, the timer */
.reveal .nowrap {
    white-space: nowrap;
}
.reveal .dense {
    font-size: 0.82em;
}
.reveal .cards .panel h3 {
    margin: 0 0 0.4em;
    color: var(--hk-fg);
}
.reveal .cards .panel p {
    font-size: 0.78em;
    margin: 0.35em 0;
}
.reveal .fun {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.7rem;
    text-align: left;
}
.reveal .fun .panel {
    padding: 0.7rem 0.9rem;
    border-radius: 14px;
}
.reveal .fun h4 {
    margin: 0 0 0.3em;
    font-size: 0.8em;
}
.reveal .fun p {
    margin: 0;
    font-size: 0.68em;
    line-height: 1.4;
}
.reveal .fun p + p {
    color: var(--hk-dim);
    margin-top: 0.3em;
}
.reveal .lens {
    margin-top: 1.2em;
    font-size: 0.8em;
    text-align: left;
}
.reveal .lens p {
    margin: 0.2em 0;
}
.reveal .loops h3 {
    margin: 0.2em 0 0.1em;
}
.reveal .loops p {
    margin: 0;
    line-height: 1.45;
}
.reveal .loops svg.diagram {
    max-width: 330px;
    margin: 0 auto;
}
.reveal .uml {
    display: inline-block;
    min-width: 11em;
    background: var(--hk-panel);
    border: 2px solid var(--hk-fg);
    border-radius: 6px;
    text-align: left;
    font-size: 0.78em;
    line-height: 1.5;
}
.reveal .uml > div {
    padding: 0.35em 0.9em;
}
.reveal .uml > div + div {
    border-top: 1px solid var(--hk-dim);
}
.reveal .uml .name {
    text-align: center;
    font-weight: 700;
    letter-spacing: 0.06em;
}
.reveal .uml .members {
    color: var(--hk-dim);
}
.reveal .uml [data-m] {
    display: inline-block;
    padding: 0 0.2em;
    margin-left: -0.2em;
    border-radius: 3px;
    transition: background 0.12s ease, color 0.12s ease;
}
.reveal .uml [data-m].on {
    background: var(--hk-fg);
    color: var(--hk-bg);
}
.reveal .uml .val {
    color: var(--hk-fg);
}
.reveal .uml-row {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 1.4rem;
    flex-wrap: wrap;
}
.reveal .pong .uml {
    font-size: 0.64em;
    line-height: 1.4;
    min-width: 0;
    width: 23em;
    white-space: nowrap;
}
.reveal .timer {
    font-size: 4.2em;
    font-weight: 700;
    letter-spacing: 0.08em;
    line-height: 1;
    margin: 0.1em 0 0;
}
.reveal .timer.done {
    animation: hk-blink 0.8s steps(1) infinite;
}
.reveal .timer + .slider {
    margin-top: 0.9em;
}

/* ---- Print / PDF export: drop the CRT effects ---- */
@media print {
    body::before,
    body::after {
        display: none;
    }

    .reveal h1,
    .reveal h2 {
        animation: none;
        text-shadow: none;
    }
}

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
    body::after,
    .reveal h1,
    .reveal h2,
    .reveal .cursor {
        animation: none;
    }
}
