reformat files

This commit is contained in:
Mihkel Martin Kasterpalu 2025-02-12 05:19:49 +02:00
parent 2faf50961e
commit 499c497bff
9 changed files with 362 additions and 362 deletions

View file

@ -1,9 +1,9 @@
import type { TagsObj } from '$lib/types';
const badges: TagsObj = {
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
};
export default badges;

View file

@ -7,76 +7,76 @@ import monospaceeImg from '$lib/assets/monospacee.jpg?enhanced';
import saueauguImg from '$lib/assets/saueaugu.jpg?enhanced';
const projects: Project[] = [
{
name: 'SUPIKÖÖGIPOSID',
image: {
src: skpImg,
credit: {
type: ImageCreditType.instagram,
author: 'Mimmu',
href: 'https://www.instagram.com/musamimmu/'
},
alt: 'Pilt neljaliikmeliseslt räpibändist SUPIKÖÖGIPOISID. Pilt on tehtud pilves ilmaga õues. Pildil on 4 mees valgete triiksärkide, mustade lipsude ja mustade tagidega.'
},
description: 'Räpikollektiiv. Alustasime 2019.',
link: 'https://skpoisid.bandcamp.com/',
tags: [badges['muusika']]
},
{
name: 'Dysaster Collective',
image: {
src: dysasterImg,
credit: {
type: ImageCreditType.instagram,
author: 'Mattias Mägi',
href: 'https://www.instagram.com/mattias.mix/'
},
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
},
description: '2022 asutatud mitmekülgne loomekollektiiv.',
link: 'https://dysaster.ee',
tags: [badges['asutaja'], badges['veeb']]
},
{
name: 'monospac.ee',
image: {
src: monospaceeImg,
credit: {
type: ImageCreditType.instagram,
author: 'Liisa Jõhvik',
href: 'https://www.instagram.com/liisajohvik.photo/'
},
alt: 'DJ duo monospacee nende Silent Disco setil Tartu Uus Teater saalis aastal 2024. Pilt on külje pealt, esiplaanil DJ Mimmu ja ta tagant paistab DJ Rx. Nad on valgustatud punasesega, taustal häguselt näha siniselt valgustatud kolmandat DJd.'
},
description: 'DJ duo kaasliikmega Rx.',
link: 'https://monospac.ee',
tags: [badges['muusika'], badges['veeb']]
},
{
name: 'Saueagu Teatritalu',
image: {
src: saueauguImg,
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
},
description: 'Taluteater Läänemaal. Tegin veebilehe.',
link: 'https://saueaugu.ee',
tags: [badges['veeb']]
},
{
name: 'Tartu Häkkerikoda',
image: {
src: '/assets/hakkerikoda.svg',
credit: {
type: ImageCreditType.web,
author: 'treierxyz',
href: 'https://treier.xyz'
},
alt: 'Tartu Häkkerikoda logo'
},
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
link: 'https://hakkerikoda.ee',
tags: [badges['veeb']]
}
{
name: 'SUPIKÖÖGIPOSID',
image: {
src: skpImg,
credit: {
type: ImageCreditType.instagram,
author: 'Mimmu',
href: 'https://www.instagram.com/musamimmu/'
},
alt: 'Pilt neljaliikmeliseslt räpibändist SUPIKÖÖGIPOISID. Pilt on tehtud pilves ilmaga õues. Pildil on 4 mees valgete triiksärkide, mustade lipsude ja mustade tagidega.'
},
description: 'Räpikollektiiv. Alustasime 2019.',
link: 'https://skpoisid.bandcamp.com/',
tags: [badges['muusika']]
},
{
name: 'Dysaster Collective',
image: {
src: dysasterImg,
credit: {
type: ImageCreditType.instagram,
author: 'Mattias Mägi',
href: 'https://www.instagram.com/mattias.mix/'
},
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
},
description: '2022 asutatud mitmekülgne loomekollektiiv.',
link: 'https://dysaster.ee',
tags: [badges['asutaja'], badges['veeb']]
},
{
name: 'monospac.ee',
image: {
src: monospaceeImg,
credit: {
type: ImageCreditType.instagram,
author: 'Liisa Jõhvik',
href: 'https://www.instagram.com/liisajohvik.photo/'
},
alt: 'DJ duo monospacee nende Silent Disco setil Tartu Uus Teater saalis aastal 2024. Pilt on külje pealt, esiplaanil DJ Mimmu ja ta tagant paistab DJ Rx. Nad on valgustatud punasesega, taustal häguselt näha siniselt valgustatud kolmandat DJd.'
},
description: 'DJ duo kaasliikmega Rx.',
link: 'https://monospac.ee',
tags: [badges['muusika'], badges['veeb']]
},
{
name: 'Saueagu Teatritalu',
image: {
src: saueauguImg,
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
},
description: 'Taluteater Läänemaal. Tegin veebilehe.',
link: 'https://saueaugu.ee',
tags: [badges['veeb']]
},
{
name: 'Tartu Häkkerikoda',
image: {
src: '/assets/hakkerikoda.svg',
credit: {
type: ImageCreditType.web,
author: 'treierxyz',
href: 'https://treier.xyz'
},
alt: 'Tartu Häkkerikoda logo'
},
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
link: 'https://hakkerikoda.ee',
tags: [badges['veeb']]
}
];
export default projects;

View file

@ -3,70 +3,70 @@ import getPoolSize from './getPoolSize';
// Based on code generated by Claude 3.5 Sonnet
class QuestionBalanceStore {
private balances: Map<string, number>;
private lastAccessed: Map<string, number>;
private balances: Map<string, number>;
private lastAccessed: Map<string, number>;
constructor() {
this.balances = new Map();
this.lastAccessed = new Map();
constructor() {
this.balances = new Map();
this.lastAccessed = new Map();
// Clean up expired sessions every hour
setInterval(() => this.cleanup(), 1000 * 60 * 60);
}
// Clean up expired sessions every hour
setInterval(() => this.cleanup(), 1000 * 60 * 60);
}
// Get remaining questions for a session
getBalance(sessionToken: string): number {
this.lastAccessed.set(sessionToken, Date.now());
return this.balances.get(sessionToken) || 0;
}
// Get remaining questions for a session
getBalance(sessionToken: string): number {
this.lastAccessed.set(sessionToken, Date.now());
return this.balances.get(sessionToken) || 0;
}
// Add questions to a session (e.g., after answering)
addQuestions(sessionToken: string, count: number = 1): void {
this.lastAccessed.set(sessionToken, Date.now());
// Add questions to a session (e.g., after answering)
addQuestions(sessionToken: string, count: number = 1): void {
this.lastAccessed.set(sessionToken, Date.now());
const currentBalance = this.balances.get(sessionToken) || 0;
this.balances.set(sessionToken, currentBalance + count);
}
const currentBalance = this.balances.get(sessionToken) || 0;
this.balances.set(sessionToken, currentBalance + count);
}
// Use a question from the balance
async useQuestion(sessionToken: string): Promise<boolean> {
this.lastAccessed.set(sessionToken, Date.now());
// Use a question from the balance
async useQuestion(sessionToken: string): Promise<boolean> {
this.lastAccessed.set(sessionToken, Date.now());
const currentBalance = this.balances.get(sessionToken) || 0;
const currentBalance = this.balances.get(sessionToken) || 0;
// New users can neither ask nor answer in this case
// Allow one question for this edge case
if (currentBalance === 0) {
const poolSize = await getPoolSize();
return poolSize === 0;
}
// New users can neither ask nor answer in this case
// Allow one question for this edge case
if (currentBalance === 0) {
const poolSize = await getPoolSize();
return poolSize === 0;
}
this.balances.set(sessionToken, currentBalance - 1);
return true;
}
this.balances.set(sessionToken, currentBalance - 1);
return true;
}
// Clean up sessions that haven't been accessed in 24 hours
private cleanup(): void {
const now = Date.now();
const expiryTime = 24 * 60 * 60 * 1000; // 24 hours
// Clean up sessions that haven't been accessed in 24 hours
private cleanup(): void {
const now = Date.now();
const expiryTime = 24 * 60 * 60 * 1000; // 24 hours
for (const [token, lastAccess] of this.lastAccessed.entries()) {
if (now - lastAccess > expiryTime) {
this.balances.delete(token);
this.lastAccessed.delete(token);
}
}
}
for (const [token, lastAccess] of this.lastAccessed.entries()) {
if (now - lastAccess > expiryTime) {
this.balances.delete(token);
this.lastAccessed.delete(token);
}
}
}
// For debugging in development
debugInfo(): unknown {
if (!dev) return null;
return {
totalSessions: this.balances.size,
sessions: Object.fromEntries(this.balances),
lastAccessed: Object.fromEntries(this.lastAccessed)
};
}
// For debugging in development
debugInfo(): unknown {
if (!dev) return null;
return {
totalSessions: this.balances.size,
sessions: Object.fromEntries(this.balances),
lastAccessed: Object.fromEntries(this.lastAccessed)
};
}
}
// Create singleton instance

View file

@ -3,11 +3,11 @@ import { questions } from '$lib/server/db/schema';
import { sql } from 'drizzle-orm';
export default async () => {
const results = await db
.select({
poolSize: sql`COUNT(CASE WHEN answer_count < 5 THEN 1 END)`
})
.from(questions);
const results = await db
.select({
poolSize: sql`COUNT(CASE WHEN answer_count < 5 THEN 1 END)`
})
.from(questions);
return Number(results[0].poolSize);
return Number(results[0].poolSize);
};

View file

@ -3,56 +3,56 @@ import { Ratelimit } from '@upstash/ratelimit';
import { UPSTASH_REDIS_TOKEN, UPSTASH_REDIS_URL } from '$env/static/private';
const redis = new Redis({
url: UPSTASH_REDIS_URL,
token: UPSTASH_REDIS_TOKEN
url: UPSTASH_REDIS_URL,
token: UPSTASH_REDIS_TOKEN
});
export const ratelimit = {
noSesh: new Ratelimit({
redis,
prefix: 'ratelimit:noSesh',
limiter: Ratelimit.slidingWindow(1, '1m'),
enableProtection: true
}),
pakubiiti: new Ratelimit({
redis,
prefix: 'ratelimit:pakubiiti',
limiter: Ratelimit.slidingWindow(5, '15s'),
enableProtection: true
}),
pakubiitiIP: new Ratelimit({
redis,
prefix: 'ratelimit:pakubiitiIP',
limiter: Ratelimit.slidingWindow(5, '15s'),
enableProtection: true
}),
rahvaAnswer: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaAnswer',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaAnswerIP: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaAnswerIP',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestion: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestion',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestionIP: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestionIP',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestionAPI: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestionAPI',
limiter: Ratelimit.slidingWindow(15, '15s')
})
noSesh: new Ratelimit({
redis,
prefix: 'ratelimit:noSesh',
limiter: Ratelimit.slidingWindow(1, '1m'),
enableProtection: true
}),
pakubiiti: new Ratelimit({
redis,
prefix: 'ratelimit:pakubiiti',
limiter: Ratelimit.slidingWindow(5, '15s'),
enableProtection: true
}),
pakubiitiIP: new Ratelimit({
redis,
prefix: 'ratelimit:pakubiitiIP',
limiter: Ratelimit.slidingWindow(5, '15s'),
enableProtection: true
}),
rahvaAnswer: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaAnswer',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaAnswerIP: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaAnswerIP',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestion: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestion',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestionIP: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestionIP',
limiter: Ratelimit.slidingWindow(8, '15s'),
enableProtection: true
}),
rahvaQuestionAPI: new Ratelimit({
redis,
prefix: 'ratelimit:rahvaQuestionAPI',
limiter: Ratelimit.slidingWindow(15, '15s')
})
};

View file

@ -4,19 +4,19 @@ import { json } from '@sveltejs/kit';
import { createChallenge, verifySolution } from 'altcha-lib';
export async function GET() {
const challenge = await createChallenge({
hmacKey: ALTCHA_HMAC,
maxNumber: 100000 // the maximum random number
});
console.log('challange get');
const challenge = await createChallenge({
hmacKey: ALTCHA_HMAC,
maxNumber: 100000 // the maximum random number
});
console.log('challange get');
return json(challenge);
return json(challenge);
}
export async function POST({ request }) {
const { payload }: { payload: string } = await request.json();
console.log('challange done');
const ok = await verifySolution(payload, ALTCHA_HMAC);
const { payload }: { payload: string } = await request.json();
console.log('challange done');
const ok = await verifySolution(payload, ALTCHA_HMAC);
return json({ ok });
return json({ ok });
}

View file

@ -1,12 +1,12 @@
import { z } from 'zod';
export const formSchema = z.object({
questionId: z.string().length(21),
answer: z
.string()
.min(2, 'Vastus peab olema vähemalt 2 tähemärki.')
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.'),
altcha: z.string()
questionId: z.string().length(21),
answer: z
.string()
.min(2, 'Vastus peab olema vähemalt 2 tähemärki.')
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.'),
altcha: z.string()
});
export type FormSchema = typeof formSchema;

View file

@ -1,11 +1,11 @@
import { z } from 'zod';
export const formSchema = z.object({
question: z
.string()
.min(2, 'Küsimus peab olema vähemalt 2 tähemärki.')
.max(50, 'Küsimus ei või olla pikem kui 50 tähemärki.'),
altcha: z.string()
question: z
.string()
.min(2, 'Küsimus peab olema vähemalt 2 tähemärki.')
.max(50, 'Küsimus ei või olla pikem kui 50 tähemärki.'),
altcha: z.string()
});
export type FormSchema = typeof formSchema;

View file

@ -14,165 +14,165 @@ import jetImg from '$lib/assets/vaukuivali/fighters.jpg?enhanced';
import hearingaidImg from '$lib/assets/vaukuivali/eardamage.jpg?enhanced';
export const soundCheckpoints: Record<number, SoundCheckpoint> = {
0: {
title: '',
description: 'Kesket metsa mingis koopas, kedagi pole ümber',
image: undefined
},
30: {
title: '"Vaikus"',
description: 'ehk elutoa pasiivne müra',
image: {
src: roomImg,
credit: {
type: ImageCreditType.web,
author: 'Kam Idris',
href: 'https://unsplash.com/@ka_idris'
},
alt: 'Modernse ja minimalistliku disainiga siseruum.'
}
},
40: {
title: 'Tikk takk',
description: 'Mehaanilise kella tiksumine (va täistundidel)',
image: {
src: watchImg,
credit: {
type: ImageCreditType.web,
author: 'János Venczák',
href: 'https://unsplash.com/@venczakjanos'
},
alt: 'Lahti võetud vanamoodne käekell. Näha on kella sisemust, hammasrattaid.'
}
},
50: {
title: 'Tava jutt',
description: 'Rahulik vestlus kodus',
image: {
src: convoImg,
credit: {
type: ImageCreditType.web,
author: 'Toa Heftiba',
href: 'https://unsplash.com/@heftiba'
},
alt: 'Noor paar köögis. Mees lõikab taldriku peal pannkooki, naine istub ta kõrval pliidi peal ja vaatab.'
}
},
60: {
title: 'Ma sain nurgad täis',
description: 'Bingo õhtu Gennis (keset mängu)',
image: {
src: gennImg,
credit: {
type: ImageCreditType.web,
author: 'Laila Kaasik',
href: 'https://tartu.postimees.ee/8154041/lallavad-pidutsejad-panid-tartu-otsima-tasakaalu-ooelu-ja-oorahu-vahel'
},
alt: 'Genialistide klubi tegutseb Tartus Magasini tänavas. Pilt on õhtusest ajast, rohkelt inimesti klubi välialal.'
}
},
70: {
title: 'Pult on kadunud',
description: 'Telekas, mis mängib natuke liiga valjult',
image: {
src: tvImg,
credit: {
type: ImageCreditType.web,
author: 'Jonas Leupe',
href: 'https://unsplash.com/@jonasleupe'
},
alt: 'Keegi vaatab televiisorist filmi. Esiplaanil fookuses teleka pult, tagaplaanil udune tuba, mille seina vastas on telekas.'
}
},
80: {
title: 'USAs oleks hullem',
description: 'Riia mäe liiklus (ootad bussi Kaubamaja ees)',
image: {
src: trafficImg,
credit: {
type: ImageCreditType.web,
author: 'Google Street View',
href: 'https://maps.app.goo.gl/ZfADP4LnUid7d571A'
},
alt: 'Aastal 2012 tehtud Google Street View pilt. Näha on Tartu Kaubamaja ning selle Riia tänava küljel olevat bussipeatust.'
}
},
90: {
title: 'USAs oleks rohkem',
description: 'Harley sõidab sinust mööda',
image: {
src: harleyImg,
credit: {
type: ImageCreditType.web,
author: 'Harley-Davidson',
href: 'https://unsplash.com/@harleydavidson'
},
alt: 'Uue välimusega Harley-Davidson mootorrattas sõidab kiiresti mööda sirget maanteed.'
}
},
100: {
title: 'Põgenesid terminalist',
description: 'Boeing 707 1 meremiil enne maandumist',
image: {
src: landingImg,
credit: {
type: ImageCreditType.web,
author: 'Scott Fillmer',
href: 'https://unsplash.com/@scottfillmer'
},
alt: 'Continental Airlines Boeing 777 maandub uduses Houston IAH lennujaamas.'
}
},
110: {
title: 'Maanteeraev',
description: 'Autosignaal 1m kauguselt',
image: {
src: carCrashImg,
credit: {
type: ImageCreditType.instagram,
author: 'Jordan Besson',
href: 'https://www.instagram.com/mr.blue.photographie'
},
alt: 'Dramaatiline auto trikk filmi jaoks. Kahe auto kokkupõrge.'
}
},
120: {
title: 'Mootorsaag',
description: 'Nüüd on juba valus. Soovitan kanda kõrvatroppe.',
image: {
src: chainsawImg,
credit: {
type: ImageCreditType.web,
author: 'Benjamin Jopen',
href: 'https://unsplash.com/@benjopen'
},
alt: 'Oranži ja musta värvi mootorsega lõigatakse langenud puud väiksemateks tükkideks.'
}
},
130: {
title: 'Kuidas sa nii lähedale said?',
description: 'Turboreaktiivmootoriga hävitaja lendutõus 15m kauguselt',
image: {
src: jetImg,
credit: {
type: ImageCreditType.web,
author: 'Colin Lloyd',
href: 'https://unsplash.com/@onthesearchforpineapples'
},
alt: 'Kaheksa F-16 hävitajat lendavad koos formatsioonis taevas.'
}
},
150: {
title: 'Aia mu kõrvad',
description: 'Tubli töö! Su trummikile rebenes!',
image: {
src: hearingaidImg,
credit: {
type: ImageCreditType.web,
author: 'Mark Paton',
href: 'https://unsplash.com/@heftiba'
},
alt: 'Lähivõte inimesest sisestamas oma kõrva kuuldeaparaati.'
}
}
0: {
title: '',
description: 'Kesket metsa mingis koopas, kedagi pole ümber',
image: undefined
},
30: {
title: '"Vaikus"',
description: 'ehk elutoa pasiivne müra',
image: {
src: roomImg,
credit: {
type: ImageCreditType.web,
author: 'Kam Idris',
href: 'https://unsplash.com/@ka_idris'
},
alt: 'Modernse ja minimalistliku disainiga siseruum.'
}
},
40: {
title: 'Tikk takk',
description: 'Mehaanilise kella tiksumine (va täistundidel)',
image: {
src: watchImg,
credit: {
type: ImageCreditType.web,
author: 'János Venczák',
href: 'https://unsplash.com/@venczakjanos'
},
alt: 'Lahti võetud vanamoodne käekell. Näha on kella sisemust, hammasrattaid.'
}
},
50: {
title: 'Tava jutt',
description: 'Rahulik vestlus kodus',
image: {
src: convoImg,
credit: {
type: ImageCreditType.web,
author: 'Toa Heftiba',
href: 'https://unsplash.com/@heftiba'
},
alt: 'Noor paar köögis. Mees lõikab taldriku peal pannkooki, naine istub ta kõrval pliidi peal ja vaatab.'
}
},
60: {
title: 'Ma sain nurgad täis',
description: 'Bingo õhtu Gennis (keset mängu)',
image: {
src: gennImg,
credit: {
type: ImageCreditType.web,
author: 'Laila Kaasik',
href: 'https://tartu.postimees.ee/8154041/lallavad-pidutsejad-panid-tartu-otsima-tasakaalu-ooelu-ja-oorahu-vahel'
},
alt: 'Genialistide klubi tegutseb Tartus Magasini tänavas. Pilt on õhtusest ajast, rohkelt inimesti klubi välialal.'
}
},
70: {
title: 'Pult on kadunud',
description: 'Telekas, mis mängib natuke liiga valjult',
image: {
src: tvImg,
credit: {
type: ImageCreditType.web,
author: 'Jonas Leupe',
href: 'https://unsplash.com/@jonasleupe'
},
alt: 'Keegi vaatab televiisorist filmi. Esiplaanil fookuses teleka pult, tagaplaanil udune tuba, mille seina vastas on telekas.'
}
},
80: {
title: 'USAs oleks hullem',
description: 'Riia mäe liiklus (ootad bussi Kaubamaja ees)',
image: {
src: trafficImg,
credit: {
type: ImageCreditType.web,
author: 'Google Street View',
href: 'https://maps.app.goo.gl/ZfADP4LnUid7d571A'
},
alt: 'Aastal 2012 tehtud Google Street View pilt. Näha on Tartu Kaubamaja ning selle Riia tänava küljel olevat bussipeatust.'
}
},
90: {
title: 'USAs oleks rohkem',
description: 'Harley sõidab sinust mööda',
image: {
src: harleyImg,
credit: {
type: ImageCreditType.web,
author: 'Harley-Davidson',
href: 'https://unsplash.com/@harleydavidson'
},
alt: 'Uue välimusega Harley-Davidson mootorrattas sõidab kiiresti mööda sirget maanteed.'
}
},
100: {
title: 'Põgenesid terminalist',
description: 'Boeing 707 1 meremiil enne maandumist',
image: {
src: landingImg,
credit: {
type: ImageCreditType.web,
author: 'Scott Fillmer',
href: 'https://unsplash.com/@scottfillmer'
},
alt: 'Continental Airlines Boeing 777 maandub uduses Houston IAH lennujaamas.'
}
},
110: {
title: 'Maanteeraev',
description: 'Autosignaal 1m kauguselt',
image: {
src: carCrashImg,
credit: {
type: ImageCreditType.instagram,
author: 'Jordan Besson',
href: 'https://www.instagram.com/mr.blue.photographie'
},
alt: 'Dramaatiline auto trikk filmi jaoks. Kahe auto kokkupõrge.'
}
},
120: {
title: 'Mootorsaag',
description: 'Nüüd on juba valus. Soovitan kanda kõrvatroppe.',
image: {
src: chainsawImg,
credit: {
type: ImageCreditType.web,
author: 'Benjamin Jopen',
href: 'https://unsplash.com/@benjopen'
},
alt: 'Oranži ja musta värvi mootorsega lõigatakse langenud puud väiksemateks tükkideks.'
}
},
130: {
title: 'Kuidas sa nii lähedale said?',
description: 'Turboreaktiivmootoriga hävitaja lendutõus 15m kauguselt',
image: {
src: jetImg,
credit: {
type: ImageCreditType.web,
author: 'Colin Lloyd',
href: 'https://unsplash.com/@onthesearchforpineapples'
},
alt: 'Kaheksa F-16 hävitajat lendavad koos formatsioonis taevas.'
}
},
150: {
title: 'Aia mu kõrvad',
description: 'Tubli töö! Su trummikile rebenes!',
image: {
src: hearingaidImg,
credit: {
type: ImageCreditType.web,
author: 'Mark Paton',
href: 'https://unsplash.com/@heftiba'
},
alt: 'Lähivõte inimesest sisestamas oma kõrva kuuldeaparaati.'
}
}
};