/* ============================================
   Fonts
   ============================================ */

/* Self-hosted SF Pro Text, Regular only: the page uses one weight.

   It sits after -apple-system in the stack, never before. Apple devices
   resolve at the system font, which moves between SF's Text and Display cuts
   on its own, and never request this file. Move it earlier and every Apple
   device downloads a worse rendering than it already had. */
@font-face {
    font-family: 'SF Pro Text';
    src: url('fonts/SFProText/SFProTextRegular.woff') format('woff');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* ============================================
   Tokens
   ============================================ */

:root {
    color-scheme: light;

    --content-width: 720px;
    --page-edge: clamp(1.75rem, 1.5rem + 1vw, 2rem);   /* 28 on phones, 32 on desktop */

    /* Growth tracks. Every fluid value on the page grows along one of two
       tracks, each running 0 → 100px across its span of viewport widths:

           --fluid        320 → 800px   spacing and the footer
           --fluid-body   430 → 800px   body text, which holds on every phone

       Each value states its own two ends,

           calc(FLOOR + (CAP − FLOOR) * var(--fluid) / 100)

       so retuning anything means changing the two numbers on its line. */
    --fluid:      clamp(0px, (100vw - 320px) / 4.8, 100px);
    --fluid-body: clamp(0px, (100vw - 430px) / 3.7, 100px);

    /* Type — one weight; body, small, and a greeting at twice the body.

       Body is everything but the footer: 16/24 on every phone, up to 430px
       (the widest iPhone), growing to 22/30 by 800px. Small is the footer
       alone: 15/23 → 17/25. The greeting is twice the body, 32/40 → 44/52,
       so it moves with it.

       Leading is size + 8 throughout: a constant gap keeps the white between
       lines optically even from one size to the next, where a ratio would
       open small text up and pinch large text shut. */
    --font-size:           calc(16px + 6 * var(--fluid-body) / 100);   /* 16 → 22 */
    --line-height:         calc(24px + 6 * var(--fluid-body) / 100);   /* 24 → 30 */
    --font-size-small:     calc(15px + 2 * var(--fluid) / 100);        /* 15 → 17 */
    --line-height-small:   calc(23px + 2 * var(--fluid) / 100);        /* 23 → 25 */
    --font-size-display:   calc(var(--font-size) * 2);                 /* 32 → 44 */
    --line-height-display: calc(var(--font-size-display) + 8px);       /* 40 → 52 */

    /* The dock's label: interface furniture, fixed at every width. */
    --font-label: 12px;
    --line-height-label: 16px;

    /* Tracking — normal, deliberately. Safari and Chrome on Apple devices
       apply SF Pro's own tracking table to the system font, size by size
       (measured: −0.44px at 17, +0.07px at 11). Any letter-spacing here would
       be added on top. The self-hosted file has no table, so there it is
       applied by hand; see [data-font="static"]. */
    --tracking:       normal;
    --tracking-small: normal;
    --tracking-label: normal;

    /* Palette — one ink, one ground, one muted grey, one accent; and the
       loading text at rest, dim enough for the ink's sweep to show. */
    --color-bg: #FFFFFF;
    --color-ink: #0D0D0D;
    --color-muted: #8F8F8F;
    --color-faint: #9E9E9E;
    --color-line: rgba(0, 0, 0, 0.1);
    --color-accent: #007AFF;

    /* Spacing — one step, on the same track as the type but stated in its
       own right, so the two can be tuned independently. */
    --step: calc(28px + 12 * var(--fluid) / 100);            /*  28 → 40 */
    --space-page:    calc(72px + 88 * var(--fluid) / 100);   /*  72 → 160 */
    --space-section: calc(var(--step) * 1.4);                /*  39 → 56 */
    --space-block:   calc(var(--step) * 0.8);                /*  22 → 32 */
    --space-meta:    calc(var(--step) * 0.6);                /*  17 → 24 */

    /* Frames — square-cornered, and inset by a share rather than a step: a
       frame is an object and scales with itself. 32px at the full column. */
    --frame-inset: 4.4444%;                     /* 32 of 720 */
    --stroke-frame: rgba(186, 191, 197, 0.2);   /* reads on either ground */

    /* The index: a project's banner as a thumbnail, fixed in width. */
    --thumb-width: 300px;

    /* Motion — calm and modest throughout. Nothing travels more than a few
       pixels, nothing arrives quickly, and every curve starts and ends
       softly, so no change has an edge. */
    --ease-calm:  cubic-bezier(0.45, 0, 0.25, 1);    /* changes of shape */
    --enter-ease: cubic-bezier(0.25, 0.46, 0.3, 1);  /* things arriving */
    --ui-ease:    cubic-bezier(0.3, 0, 0.2, 1);      /* answering the reader */

    --enter-rise:  8px;
    --enter-ms:    800ms;    /* content arriving as it is scrolled to */
    --hero-ms:     1100ms;   /* the greeting cycling; siteapp.js reads this */
    --dock-ms:     900ms;    /* the dock settling in on load */
    --ui-ms:       320ms;    /* hover, press, a label appearing */
    --theme-ms:    500ms;    /* light to dark and back */
    --collapse-ms: 850ms;    /* projects morphing between layouts */
    --shimmer-ms:  2000ms;   /* one sweep of the loading text; siteapp.js reads this */
    --loader-ms:   700ms;    /* the loading screen fading away */

    /* Dock — measured from the iOS 26 Safari bottom bar: 48pt capsule, 44pt
       targets, 8pt gap, 5pt inset, 22pt glyphs. */
    --dock-height: 48px;
    --dock-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    --dock-action-size: 44px;
    --dock-action-gap: 8px;
    --dock-actions-inset: 5px;
    --dock-icon-size: 22px;
    --dock-progress-height: 5px;
    --dock-progress-gap: 10px;
    --dock-clearance: calc(
        var(--dock-bottom) + var(--dock-height) +
        var(--dock-progress-gap) + var(--dock-progress-height) + 24px
    );

    /* Liquid glass, after Apple's. A light tint lets the colour behind come
       through; blur and saturation make it read as a lens rather than a
       veil; a faint rim of light traces its edge; a sheen falls from the
       top as if lit from above; and a faint inner glow gives the glass its
       thickness. */
    --glass-tint: rgba(255, 255, 255, 0.42);
    --glass-filter: blur(14px) saturate(180%) brightness(1.06);
    --glass-rim-light: rgba(255, 255, 255, 0.95);
    --glass-rim-return: rgba(255, 255, 255, 0.45);
    --glass-rim-shade: rgba(255, 255, 255, 0.05);
    --glass-sheen: linear-gradient(rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0) 60%);
    --glass-depth:
        0 0 0 0.5px rgba(0, 0, 0, 0.08),
        0 6px 20px rgba(0, 0, 0, 0.08),
        inset 0 0 12px rgba(255, 255, 255, 0.35);

    /* The beam (dark theme only): five hues in the order they pass, one lap
       time, and how strongly the stroke and the glow inside the edge show. */
    --beam-1: rgb(255, 50, 100);
    --beam-2: rgb(100, 70, 255);
    --beam-3: rgb(40, 140, 255);
    --beam-4: rgb(30, 185, 170);
    --beam-5: rgb(50, 200, 80);
    --beam-lap: 2.4s;
    --beam-stroke: 0.6;
    --beam-glow: 0.42;
}

/* Whole pixels where the browser can round, so the type steps through
   each size rather than gliding between them, and every baseline lands on a
   pixel. Elsewhere the values above stand. */
@supports (width: round(1.5px, 1px)) {
    :root {
        --font-size:         round(calc(16px + 6 * var(--fluid-body) / 100), 1px);
        --line-height:       round(calc(24px + 6 * var(--fluid-body) / 100), 1px);
        --font-size-small:   round(calc(15px + 2 * var(--fluid) / 100), 1px);
        --line-height-small: round(calc(23px + 2 * var(--fluid) / 100), 1px);
    }
}

/* Apple's tracking table, by hand, wherever the self-hosted file is the face
   actually drawing (siteapp.js sets this once it has loaded):
   16 −0.31, 22 −0.26 · 15 −0.23, 17 −0.43 · 12 0. */
[data-font="static"] {
    --tracking:       calc(-0.31px + 0.05 * var(--fluid-body) / 100);
    --tracking-small: calc(-0.23px - 0.20 * var(--fluid) / 100);
    --tracking-label: 0px;
}

[data-theme="dark"] {
    color-scheme: dark;

    --color-bg: #000000;
    --color-ink: #FFFFFF;
    --color-muted: #AFAFAF;
    --color-faint: #666666;
    --color-line: rgba(255, 255, 255, 0.1);

    --glass-tint: rgba(28, 28, 30, 0.45);
    --glass-filter: blur(14px) saturate(170%) brightness(0.92);
    --glass-rim-light: rgba(255, 255, 255, 0.45);
    --glass-rim-return: rgba(255, 255, 255, 0.2);
    --glass-rim-shade: rgba(255, 255, 255, 0.02);
    --glass-sheen: linear-gradient(rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0) 60%);
    --glass-depth:
        0 0 0 0.5px rgba(255, 255, 255, 0.06),
        0 8px 24px rgba(0, 0, 0, 0.4),
        inset 0 0 12px rgba(255, 255, 255, 0.06);
}

/* ============================================
   Base
   ============================================ */

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;   /* iOS would enlarge text in landscape */
    text-size-adjust: 100%;
}

body {
    /* Apple devices get the real SF Pro, optically sized, with no download.
       Everyone else falls to the self-hosted file, then Inter. */
    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Inter', system-ui, sans-serif;
    font-weight: 400;
    font-size: var(--font-size);
    line-height: var(--line-height);
    letter-spacing: var(--tracking);
    font-synthesis: none;
    font-kerning: normal;
    font-optical-sizing: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;

    color: var(--color-ink);
    background-color: var(--color-bg);

    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
    min-height: 100dvh;   /* tracks the viewport as iOS toolbars collapse */
    padding-bottom: var(--dock-clearance);

    transition:
        color var(--theme-ms) var(--ui-ease),
        background-color var(--theme-ms) var(--ui-ease);
}

/* One weight for the page; headings stay structural. */
h1, h2 {
    font-size: inherit;
    font-weight: inherit;
}

/* Small: the footer, and nowhere else. */
.site-footer {
    font-size: var(--font-size-small);
    line-height: var(--line-height-small);
    letter-spacing: var(--tracking-small);
}

/* Paragraphs choose their breaks as a whole, so none ends on a lone word. */
p {
    text-wrap: pretty;
}

::selection {
    background-color: var(--color-ink);
    color: var(--color-bg);
}

/* The accent, not the ink: legible against text of either colour. The ring
   follows each element's own shape, round on the dock, square on the work. */
:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* ============================================
   Loading screen

   Shown from the first paint, while the page loads: the head sets
   .is-loading, and siteapp.js removes it once the page is ready, at which
   the screen fades away and the page makes its entrance behind it.

   The message shimmers, after transitions.dev's Shimmer text: a narrow band
   of ink sweeps across faint text, clipped to the letters.
   ============================================ */

.loader {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: grid;
    place-items: center;
    background-color: var(--color-bg);
    opacity: 0;
    visibility: hidden;
    transition:
        opacity var(--loader-ms) var(--ease-calm),
        visibility 0s var(--loader-ms);
}

.is-loading .loader {
    opacity: 1;
    visibility: visible;
    transition: none;
}

/* The body's desktop size, at every width. */
.shimmer {
    font-size: 22px;
    line-height: 30px;
    background:
        linear-gradient(90deg,
            var(--color-faint) 40%,
            var(--color-ink) 50%,
            var(--color-faint) 60%)
        100% 0 / 400% 100% no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: shimmer var(--shimmer-ms) linear infinite;
}

@keyframes shimmer {
    to { background-position: 0 0; }
}

/* The dock waits, so its entrance plays when the page appears. */
.is-loading .dock {
    animation: none;
    opacity: 0;
}

/* ============================================
   Dock

   Bottom right, at the page gutter: a progress bar over a capsule of
   actions, the bar always exactly as wide as the capsule beneath it.
   ============================================ */

.dock {
    position: fixed;
    right: var(--page-edge);
    bottom: var(--dock-bottom);
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: var(--dock-progress-gap);

    /* A long press names a control; without this iOS raises its own callout. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;

    animation: dock-enter var(--dock-ms) var(--enter-ease) both;
}

@keyframes dock-enter {
    from { opacity: 0; transform: translateY(8px); }
}

/* The glass. Shared by the capsule and its label. */
.dock-label,
.dock-actions {
    background-color: var(--glass-tint);
    background-image: var(--glass-sheen);
    -webkit-backdrop-filter: var(--glass-filter);
    backdrop-filter: var(--glass-filter);
    box-shadow: var(--glass-depth);
}

/* The glass's rim: a 1px ring, cut from a full fill by masking out its
   middle, with light at the top left and a softer return opposite. As the
   page scrolls the light travels round — clockwise going down, back going
   up — turned by --rim-turn, which siteapp.js eases on the dock. */
.dock-label::before,
.dock-actions::before {
    content: '';
    position: absolute;
    inset: 0;
    padding: 1px;
    border-radius: inherit;
    mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
    pointer-events: none;
    background: conic-gradient(from calc(-45deg + var(--rim-turn, 0deg)),
        var(--glass-rim-light),
        var(--glass-rim-shade) 25%,
        var(--glass-rim-return) 50%,
        var(--glass-rim-shade) 75%,
        var(--glass-rim-light));
}

/* ---- The beam: dark theme only ----
   An arc of colour that laps the dock's rim with a soft glow inside the
   edge, after Libraries.dev's Border Beam, built here in plain CSS. Light
   from colour needs a dark ground to read as light; on the light theme it
   reads as a stain, so there the glass keeps its turning rim. */

/* The beam's place on the rim. Registered so it can be animated; the dock
   turns it a full circle every lap, and the stroke and glow both read it,
   so they travel as one. Where @property is unsupported it holds still and
   the dock is simply unanimated. */
@property --beam-angle {
    syntax: '<angle>';
    inherits: true;
    initial-value: 0deg;
}

@keyframes beam-spin {
    to { --beam-angle: 360deg; }
}

/* The hues drift slowly as the beam turns, so its colour never settles. */
@keyframes beam-hue {
    50% { filter: hue-rotate(40deg); }
}

/* Under the beam, the glass's own faint ring. */
[data-theme="dark"] .dock-actions {
    box-shadow: var(--glass-depth), inset 0 0 0 1px var(--glass-rim-return);
    animation: beam-spin var(--beam-lap) linear infinite;
}

/* The stroke: the beam's arc, white at its heart. */
[data-theme="dark"] .dock-actions::before {
    background: conic-gradient(from var(--beam-angle),
        transparent 0 55%,
        var(--beam-1) 66%,
        var(--beam-2) 72%,
        #FFF 76%,
        var(--beam-3) 80%,
        var(--beam-4) 86%,
        var(--beam-5) 92%,
        transparent);
    opacity: var(--beam-stroke);
    animation: beam-hue 12s ease-in-out infinite;
}

/* The glow: the same arc, shown only in a soft band just inside the edge. */
[data-theme="dark"] .dock-actions::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: conic-gradient(from var(--beam-angle),
        transparent 0 50%,
        var(--beam-1) 62%,
        var(--beam-2) 70%,
        var(--beam-3) 78%,
        var(--beam-4) 86%,
        var(--beam-5) 92%,
        transparent);
    mask:
        linear-gradient(#000, transparent 12px calc(100% - 12px), #000),
        linear-gradient(to right, #000, transparent 24px calc(100% - 24px), #000);
    opacity: var(--beam-glow);
    pointer-events: none;
    animation: beam-hue 12s ease-in-out infinite;
}

/* Names the control under the pointer; siteapp.js fills and places it. */
.dock-label {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 0;
    padding: 6px 12px;
    border-radius: 999px;
    white-space: nowrap;

    font-size: var(--font-label);
    line-height: var(--line-height-label);
    letter-spacing: var(--tracking-label);
    color: var(--color-ink);

    opacity: 0;
    transform: translate(-50%, 3px);
    pointer-events: none;
    transition:
        opacity var(--ui-ms) var(--ui-ease),
        transform var(--ui-ms) var(--ui-ease);
}

.dock-label.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}

.dock-progress {
    height: var(--dock-progress-height);
    border-radius: 999px;
    background-color: var(--color-line);
    overflow: hidden;
}

/* Scaled by siteapp.js. Quick and flat, because it tracks the scroll and
   anything slower would visibly trail the page. */
.dock-progress-fill {
    height: 100%;
    background-color: var(--color-accent);
    transform: scaleX(0);
    transform-origin: left;
    will-change: transform;
    transition: transform 150ms ease-out;
}

.dock-actions {
    position: relative;
    height: var(--dock-height);
    display: flex;
    align-items: center;
    gap: var(--dock-action-gap);
    padding: 0 var(--dock-actions-inset);
    border-radius: 999px;

    transition:
        background-color var(--theme-ms) var(--ui-ease),
        box-shadow var(--theme-ms) var(--ui-ease);
}

/* Sized by width, not flex-basis: Safari ignores flex-basis when working out
   the capsule's own width, and would size it to the glyphs. */
.dock-action {
    flex: none;
    width: var(--dock-action-size);
    height: var(--dock-action-size);
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    color: var(--color-ink);
    cursor: pointer;

    transition:
        color var(--theme-ms) var(--ui-ease),
        transform var(--ui-ms) var(--ui-ease);
}

/* Under a pointer, the icon in hand swells a touch: enough to answer,
   never enough to bounce. Pointers only, or a tap would leave it swollen. */
@media (hover: hover) {
    .dock-action:hover {
        transform: scale(1.06);
    }
}

.dock-action:active {
    transform: scale(0.97);
}

.dock-action svg {
    width: var(--dock-icon-size);
    height: var(--dock-icon-size);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;   /* SF Regular's weight at 22px */
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ============================================
   Page

     .main               section gap, page frame 72 → 160
       .hero             block gap
       .works            section gap
         .project        block gap
           .project-meta meta gap
           .stack        framed images
   ============================================ */

.container {
    width: 100%;
    padding: 0 var(--page-edge);
}

.main {
    width: 100%;
    max-width: var(--content-width);
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-section);
    padding: var(--space-page) 0;
}

/* ============================================
   Hero
   ============================================ */

.hero {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-block);
}

/* A fade and nothing more: the greeting re-fades with every language, and a
   repeating rise would read as restless. */
.hero-hello,
.hero-line,
.hero-name {
    opacity: 0;
    transition: opacity var(--hero-ms) var(--enter-ease);
}

.hero-hello.visible,
.hero-line.visible,
.hero-name.visible {
    opacity: 1;
}

/* Each line's box is held from first paint, so filling it shifts nothing. */
.hero-hello {
    font-size: var(--font-size-display);
    line-height: var(--line-height-display);
    min-height: var(--line-height-display);
    white-space: nowrap;
}

/* Its two lines are set by hand, so they are left as written: Safari's
   pretty wrapping would rebalance them and break the second early. Where
   the second is too long for a phone, it breaks before the role, which is
   held whole, rather than stranding its last word. */
.hero-name {
    text-wrap: wrap;
}

.hero-name span {
    white-space: nowrap;
}

/* Tabular figures hold the ticking seconds still. */
.hero-line {
    min-height: var(--line-height);
    font-variant-numeric: tabular-nums;
}

/* ============================================
   Projects
   ============================================ */

.works {
    display: flex;
    flex-direction: column;
    gap: var(--space-section);
}

/* One gap between every part of a project, so the distance the eye measures
   is the distance declared. */
.project {
    display: flex;
    flex-direction: column;
    gap: var(--space-block);
}

.project-meta {
    display: flex;
    flex-direction: column;
    gap: var(--space-meta);
}

.project-type,
.meta-values {
    color: var(--color-muted);
}

/* Collapsed: an index. One row per project, its banner as a 300px
   thumbnail and its title and type beside it, the title's capitals trimmed
   flush with the top of the image. */
.projects-collapsed .project-meta,
.projects-collapsed .project-description,
.projects-collapsed .stack {
    display: none;
}

.projects-collapsed .works {
    gap: var(--space-block);
}

.projects-collapsed .project {
    display: grid;
    grid-template-columns: var(--thumb-width) 1fr;
    align-items: start;
    cursor: pointer;
}

.projects-collapsed .banner {
    grid-area: 1 / 1;
}

.projects-collapsed .project-title {
    grid-area: 1 / 2;
}

.projects-collapsed hgroup {
    text-box: trim-start cap alphabetic;
}

/* ============================================
   Images

   .stack        one image at the column's width, framed
   .stack-row    mockups side by side, framed together

   Nothing is cropped. Give every <img> its real width and height so the
   page never shifts as images arrive.
   ============================================ */

.banner,
.stack img {
    width: 100%;
    height: auto;
    display: block;
}

/* A web screenshot is a long white slab; framed, it reads as a screen. */
.stack {
    padding: var(--frame-inset);
    border: 1px solid var(--stroke-frame);
}

/* A row of mockups is one composition, so its gap and sizes are shares of
   the row and it scales as a single object, keeping its arrangement at every
   width. At the full column these are the 32px gap and 175px mockups the
   design specifies. */
.stack-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4.8930%;          /* 32 of 654 */
}

.stack-row img {
    width: 26.7584%;       /* 175 of 654 */
}

/* ============================================
   Motion
   ============================================ */

/* Content rises gently into place as it is reached; siteapp.js adds .is-in. */
[data-enter] {
    opacity: 0;
    transform: translateY(var(--enter-rise));
    transition:
        opacity var(--enter-ms) var(--enter-ease),
        transform var(--enter-ms) var(--enter-ease);
}

[data-enter].is-in {
    opacity: 1;
    transform: none;
}

/* The collapse morph. siteapp.js names each project so they travel one by
   one; the hero and footer are named so they hold still rather than being
   stretched with the page. Every part runs on the one curve for the one
   duration, so the whole change settles together. */
.hero        { view-transition-name: hero; }
.site-footer { view-transition-name: footer; }

::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
    animation-duration: var(--collapse-ms);
    animation-timing-function: var(--ease-calm);
}

/* ============================================
   Preferences
   ============================================ */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }

    [data-enter] {
        opacity: 1;
        transform: none;
    }

    /* Endless animations stop outright: at 0.01ms a loop would flicker. The
       beam holds still where it starts; the loading text stays muted. */
    .shimmer,
    .dock-actions,
    .dock-actions::before,
    .dock-actions::after {
        animation: none !important;
    }
}

/* Only for those who ask: dark's grey already clears the threshold. */
@media (prefers-contrast: more) {
    [data-theme="light"] { --color-muted: #6E6E73; }
}

@media (prefers-reduced-transparency: reduce) {
    [data-theme="light"] { --glass-tint: #F1F1F1; --glass-filter: none; }
    [data-theme="dark"]  { --glass-tint: #1C1C1E; --glass-filter: none; }
}

/* Without backdrop-filter the glass goes near-opaque to stay legible. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    [data-theme="light"] { --glass-tint: #F1F1F1; }
    [data-theme="dark"]  { --glass-tint: #1C1C1E; }
}

/* ============================================
   Responsive
   ============================================ */

@media (max-width: 700px) {
    :root {
        --font-label: 11px;
        --line-height-label: 14px;
    }

    [data-font="static"] {
        --tracking-label: 0.06px;   /* Apple's table at 11 */
    }

    /* There is no room for text beside a 300px thumbnail, so each index row
       stacks its title above the image at the column's full width. */
    .projects-collapsed .project {
        display: flex;
    }
}
