En tjeneste av

Føx er webutviklingsgrenen til Kvåle Solutions. Føx spesialiserer seg på å lage lette, effektive og semantiske nettsider. En Føx nettside er som en rev: lett, kjapp og lur.

Å lage et nettsted innebærer mye mer enn bare programmering. Hvis du bruker Føx, trenger du ikke reise langt for å finne de andre tjenestene du behøver. Kvåle Solutions har flere andre avdelinger og tilbyr grafisk design, typesetting og språktjenester. Ditt prosjekt kan bli helt fullført av Kvåle Solutions med bare ett fast kontaktpunkt (SPOC).

Føx lagde nettstedet du er på akkurat nå. Utforsk den gjerne, og hvis du er interessert i prosessen bak skapingen, kan du scrolle ned for å lese utviklerens oppsummering.


Byggingen av Kvåle Solutions-nettstedet

Nettstedet til Kvåle Solutions er en komplett graf; hver side kan nås fra hvilken som helst annen side. Dette designet reflekterer sammenkoblingen av de fire hovedtjenestene, og gjør det enkelt for kunder å navigere nettstedet.

INDEX

Siden er tilgjengelig på engelsk, bokmål og nynorsk, som er håndtert ved bruk av /nb/ og /nn/ undermapper.

Vi tok en litt ukonvensjonell fremgang med PC-versjonen til fremsiden. Vi ønsket at hvert kort skulle være stort og synlig, og vi ønsket siden skulle være ikke-scrollbar. Det var umulig å møte begge disse kravene dersom vi plasserte logoen som overskrift. Så... vi satt logoen i senteret i stedet. En slik ukonvensjonell løsning kan forfjamse noen kunder, men det er en risiko verdt å ta. Vi lever i en oppmerksomhetsøkonomi, og hvis du kombinerer ukonvensjonalitet med høy kvalitet, så vinner du oppmerksomhetskonkurransen.

Mobilversjonen til frontsiden er derimot en scrollbar side med et normalt oppsett, da størrelsesbegrensingene gjorde den andre løsningen suboptimal. Vi tar alltid hensyn til forskjellige medier og designer sidene våre deretter, og vi holder en konsekvent stil på tvers av de mediene, men vi lar aldri begrensingene av ett medium holde oss tilbake med et annet.

Hvert eneste bilde på tvers av nettstedet er en .svg, takket være Æxis og TeXtract som har supplert nettstedet med deres vektor-baserte grafikk.

Den mest teknisk avanserte siden er den som tilhører Aexis, som viser fram en Bézier-kurve som bøyer seg som en funksjon av den besøkendes scrolling ned siden.

bezier_scroll.js
  
const track = document.querySelector('.animation-track');

if (track) {
    const mainCurve = document.getElementById('mainCurve');
    const dash1 = document.getElementById('dash1');
    const dash2 = document.getElementById('dash2');
    const cp1 = document.getElementById('cp1');
    const cp2 = document.getElementById('cp2');

    let ticking = false;

    const updateCurve = () => {
        const rect = track.getBoundingClientRect();

        // Jeg fant disse to gjennom finjustering, jeg syns det gir en fin balanse mellom å la kurven
        // bøye seg, men ikke bruke for mye tid
        const progress = Math.max(0, (window.innerHeight / 2) - rect.top);
        const baseMove = Math.min(400, progress * 0.8);

        // Siden jeg ville at hele kurven og styringspunktene skulle være synlige på slutten,
        // så var jeg mer begrenset vertikalt enn horisontalt,
        // så jeg gjorde slik at styringspunktene bevegde seg mer horisontalt enn vertikalt
        const moveY = baseMove * 0.55;
        const moveX = baseMove * 2;

        // Jeg fikk dem til å bevege seg i motsatte retninger for den mest dramatiske effekten
        const newCy1 = 250 - moveY;
        const newCy2 = 250 + moveY;
        const newCx1 = 400 + moveX;
        const newCx2 = 600 - moveX;

        cp1.setAttribute('cx', newCx1);
        cp1.setAttribute('cy', newCy1);
        cp2.setAttribute('cx', newCx2);
        cp2.setAttribute('cy', newCy2);
        dash1.setAttribute('x2', newCx1);
        dash1.setAttribute('y2', newCy1);
        dash2.setAttribute('x2', newCx2);
        dash2.setAttribute('y2', newCy2);
        mainCurve.setAttribute('d', `M 100 250 C ${newCx1} ${newCy1}, ${newCx2} ${newCy2}, 900 250`);

        ticking = false;
    };

    const onScroll = () => {
        if (!ticking) {
            window.requestAnimationFrame(updateCurve);
            ticking = true;
        }
    };

    const observerOptions = {
        root: null,
        rootMargin: '100px',
        threshold: 0
    };

    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                window.addEventListener('scroll', onScroll, { passive: true });
            } else {
                window.removeEventListener('scroll', onScroll);
            }
        });
    }, observerOptions);

    observer.observe(track);
}
                        

Aexis har også en 2 699x zoom inn i et LaTeX-generert .svg bilde av en Fibonacci-mosaikk, helt kodet i vanilla JS.

fibonacci_zoom.js
        
        const zoomBtn = document.getElementById('zoom-trigger-btn');

if (zoomBtn) {
    const fibSvg = document.getElementById('fibonacci-svg');
    
    // Koordinatene av det innerste kvadratet ble funnet ved bruk av DevTools.
    const fStart = { x: 12.79, y: 12.04, w: 539.86, h: 321.62 };
    const fTarget = { x: 387.571, y: 244.817 };
    const startCenter = { x: fStart.x + (fStart.w / 2), y: fStart.y + (fStart.h / 2) };

    const fEndWidth = 0.2;
    const fDuration = 17000; 
    const fPauseTime = 2500;
    const fEndHeight = (fEndWidth * fStart.h) / fStart.w;

    let fStartTime = null;
    let isZooming = false;

    const formatX = (num) => Math.round(num).toLocaleString() + "x";

    function runFibonacciZoom(timestamp) {
        if (!fStartTime) fStartTime = timestamp;
        const elapsed = timestamp - fStartTime;
        let directionProgress;
        let isPaused = false;

        if (elapsed < fDuration) {
            directionProgress = elapsed / fDuration;
        } else if (elapsed < fDuration + fPauseTime) {
            directionProgress = 1;
            isPaused = true;
        } else if (elapsed < (fDuration * 2) + fPauseTime) {
            const timeIntoOutro = elapsed - fDuration - fPauseTime;
            directionProgress = 1 - (timeIntoOutro / fDuration);
        } else {
            stopZoom();
            return;
        }

        // Jeg måtte legge inn en glidende overgang til målkoordinatene for å unngå
        // et stygt hopp inn i posisjon
        const glideEase = 1 - Math.pow(1 - directionProgress, 10);
        const zoomEase = Math.pow(directionProgress, 2);
        
        const currentW = fStart.w * Math.pow((fEndWidth / fStart.w), zoomEase);
        const currentH = fStart.h * Math.pow((fEndHeight / fStart.h), zoomEase);
        const multiplier = fStart.w / currentW;

        if (isPaused) {
            zoomBtn.textContent = `${pageText.maxZoom}${formatX(multiplier)}`;
        } else {
            zoomBtn.textContent = `${pageText.zooming}${formatX(multiplier)}`;
        }

        const currentCenterX = startCenter.x + (fTarget.x - startCenter.x) * glideEase;
        const currentCenterY = startCenter.y + (fTarget.y - startCenter.y) * glideEase;
        const currentX = currentCenterX - (currentW / 2);
        const currentY = currentCenterY - (currentH / 2);

        fibSvg.setAttribute('viewBox', `${currentX} ${currentY} ${currentW} ${currentH}`);

        if (isZooming) {
            requestAnimationFrame(runFibonacciZoom);
        }
    }

    function stopZoom() {
        isZooming = false;
        fStartTime = null;
        zoomBtn.textContent = pageText.diveBackIn;
        zoomBtn.style.opacity = "1";
        zoomBtn.style.pointerEvents = "auto";
        fibSvg.setAttribute('viewBox', `${fStart.x} ${fStart.y} ${fStart.w} ${fStart.h}`);
    }

    zoomBtn.addEventListener('click', () => {
        if (isZooming) return;
        isZooming = true;
        fStartTime = null;
        zoomBtn.style.opacity = "0.9";
        zoomBtn.style.pointerEvents = "none";
        requestAnimationFrame(runFibonacciZoom);
    });
}           
                          

Så var det utfordringen av å legge in kodevindu på både denne siden og TeXtract, der vi måtte få fargeleggingen til å lese syntaksen til JavaScript og LaTeX, respektivt. Vi brukte Shiki for dette, og la til en ekstra .json fil for at fargeleggingen skulle være korrekt for expl3 kode, da ExplSyntax-miljøet endrer den normale syntaksen til LaTeX.

Til slutt så brukte vi den mest oppdaterte, minst oppblåste metoden for å gjøre favikonet vårt kompatibelt med forskjellige nettlesere. Løsningen vår var like enkel som favikonet selv.

Favicon
favikon.html

<head>
    ⋮ 
    <link rel="icon" href="/favicon.ico" sizes="32x32">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
</head> 
                

Som vanlig, tilførte Æxis vektorgrafikken, noe som lot favikonet vårt hovedsaklig være en .svg. Vi brukte Inkscape for å lage en 180 × 180 piksel .png favikon for Apple, og vi brukte GIMP for å lage en .ico reserveløsning, utstyrt med både 32 × 32 og 16 × 16 versjoner. Med litt forarbeid og tre linjer kode garanterte vi at favikonet vårt ville fungere i praktisk talt alle situasjoner.

Denne framgangsmåten er emblematisk av vår kjernefilosofi av minimalistisk webutvikling. Hvis du ønsker enkle, elegante, lette, kjappe og semantiske nettsted, alt levert sømløst i tandem med andre relaterte tjenester, så er Føx / Kvåle Solutions det rette valget for deg.