reformat files
This commit is contained in:
parent
2faf50961e
commit
499c497bff
9 changed files with 362 additions and 362 deletions
|
@ -1,9 +1,9 @@
|
||||||
import type { TagsObj } from '$lib/types';
|
import type { TagsObj } from '$lib/types';
|
||||||
|
|
||||||
const badges: TagsObj = {
|
const badges: TagsObj = {
|
||||||
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
|
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
|
||||||
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
|
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
|
||||||
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
|
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export default badges;
|
export default badges;
|
||||||
|
|
|
@ -7,76 +7,76 @@ import monospaceeImg from '$lib/assets/monospacee.jpg?enhanced';
|
||||||
import saueauguImg from '$lib/assets/saueaugu.jpg?enhanced';
|
import saueauguImg from '$lib/assets/saueaugu.jpg?enhanced';
|
||||||
|
|
||||||
const projects: Project[] = [
|
const projects: Project[] = [
|
||||||
{
|
{
|
||||||
name: 'SUPIKÖÖGIPOSID',
|
name: 'SUPIKÖÖGIPOSID',
|
||||||
image: {
|
image: {
|
||||||
src: skpImg,
|
src: skpImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.instagram,
|
type: ImageCreditType.instagram,
|
||||||
author: 'Mimmu',
|
author: 'Mimmu',
|
||||||
href: 'https://www.instagram.com/musamimmu/'
|
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.'
|
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.',
|
description: 'Räpikollektiiv. Alustasime 2019.',
|
||||||
link: 'https://skpoisid.bandcamp.com/',
|
link: 'https://skpoisid.bandcamp.com/',
|
||||||
tags: [badges['muusika']]
|
tags: [badges['muusika']]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Dysaster Collective',
|
name: 'Dysaster Collective',
|
||||||
image: {
|
image: {
|
||||||
src: dysasterImg,
|
src: dysasterImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.instagram,
|
type: ImageCreditType.instagram,
|
||||||
author: 'Mattias Mägi',
|
author: 'Mattias Mägi',
|
||||||
href: 'https://www.instagram.com/mattias.mix/'
|
href: 'https://www.instagram.com/mattias.mix/'
|
||||||
},
|
},
|
||||||
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
|
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
|
||||||
},
|
},
|
||||||
description: '2022 asutatud mitmekülgne loomekollektiiv.',
|
description: '2022 asutatud mitmekülgne loomekollektiiv.',
|
||||||
link: 'https://dysaster.ee',
|
link: 'https://dysaster.ee',
|
||||||
tags: [badges['asutaja'], badges['veeb']]
|
tags: [badges['asutaja'], badges['veeb']]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'monospac.ee',
|
name: 'monospac.ee',
|
||||||
image: {
|
image: {
|
||||||
src: monospaceeImg,
|
src: monospaceeImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.instagram,
|
type: ImageCreditType.instagram,
|
||||||
author: 'Liisa Jõhvik',
|
author: 'Liisa Jõhvik',
|
||||||
href: 'https://www.instagram.com/liisajohvik.photo/'
|
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.'
|
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.',
|
description: 'DJ duo kaasliikmega Rx.',
|
||||||
link: 'https://monospac.ee',
|
link: 'https://monospac.ee',
|
||||||
tags: [badges['muusika'], badges['veeb']]
|
tags: [badges['muusika'], badges['veeb']]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Saueagu Teatritalu',
|
name: 'Saueagu Teatritalu',
|
||||||
image: {
|
image: {
|
||||||
src: saueauguImg,
|
src: saueauguImg,
|
||||||
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
|
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
|
||||||
},
|
},
|
||||||
description: 'Taluteater Läänemaal. Tegin veebilehe.',
|
description: 'Taluteater Läänemaal. Tegin veebilehe.',
|
||||||
link: 'https://saueaugu.ee',
|
link: 'https://saueaugu.ee',
|
||||||
tags: [badges['veeb']]
|
tags: [badges['veeb']]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Tartu Häkkerikoda',
|
name: 'Tartu Häkkerikoda',
|
||||||
image: {
|
image: {
|
||||||
src: '/assets/hakkerikoda.svg',
|
src: '/assets/hakkerikoda.svg',
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'treierxyz',
|
author: 'treierxyz',
|
||||||
href: 'https://treier.xyz'
|
href: 'https://treier.xyz'
|
||||||
},
|
},
|
||||||
alt: 'Tartu Häkkerikoda logo'
|
alt: 'Tartu Häkkerikoda logo'
|
||||||
},
|
},
|
||||||
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
|
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
|
||||||
link: 'https://hakkerikoda.ee',
|
link: 'https://hakkerikoda.ee',
|
||||||
tags: [badges['veeb']]
|
tags: [badges['veeb']]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default projects;
|
export default projects;
|
||||||
|
|
|
@ -3,70 +3,70 @@ import getPoolSize from './getPoolSize';
|
||||||
|
|
||||||
// Based on code generated by Claude 3.5 Sonnet
|
// Based on code generated by Claude 3.5 Sonnet
|
||||||
class QuestionBalanceStore {
|
class QuestionBalanceStore {
|
||||||
private balances: Map<string, number>;
|
private balances: Map<string, number>;
|
||||||
private lastAccessed: Map<string, number>;
|
private lastAccessed: Map<string, number>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.balances = new Map();
|
this.balances = new Map();
|
||||||
this.lastAccessed = new Map();
|
this.lastAccessed = new Map();
|
||||||
|
|
||||||
// Clean up expired sessions every hour
|
// Clean up expired sessions every hour
|
||||||
setInterval(() => this.cleanup(), 1000 * 60 * 60);
|
setInterval(() => this.cleanup(), 1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get remaining questions for a session
|
// Get remaining questions for a session
|
||||||
getBalance(sessionToken: string): number {
|
getBalance(sessionToken: string): number {
|
||||||
this.lastAccessed.set(sessionToken, Date.now());
|
this.lastAccessed.set(sessionToken, Date.now());
|
||||||
return this.balances.get(sessionToken) || 0;
|
return this.balances.get(sessionToken) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add questions to a session (e.g., after answering)
|
// Add questions to a session (e.g., after answering)
|
||||||
addQuestions(sessionToken: string, count: number = 1): void {
|
addQuestions(sessionToken: string, count: number = 1): void {
|
||||||
this.lastAccessed.set(sessionToken, Date.now());
|
this.lastAccessed.set(sessionToken, Date.now());
|
||||||
|
|
||||||
const currentBalance = this.balances.get(sessionToken) || 0;
|
const currentBalance = this.balances.get(sessionToken) || 0;
|
||||||
this.balances.set(sessionToken, currentBalance + count);
|
this.balances.set(sessionToken, currentBalance + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a question from the balance
|
// Use a question from the balance
|
||||||
async useQuestion(sessionToken: string): Promise<boolean> {
|
async useQuestion(sessionToken: string): Promise<boolean> {
|
||||||
this.lastAccessed.set(sessionToken, Date.now());
|
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
|
// New users can neither ask nor answer in this case
|
||||||
// Allow one question for this edge case
|
// Allow one question for this edge case
|
||||||
if (currentBalance === 0) {
|
if (currentBalance === 0) {
|
||||||
const poolSize = await getPoolSize();
|
const poolSize = await getPoolSize();
|
||||||
return poolSize === 0;
|
return poolSize === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.balances.set(sessionToken, currentBalance - 1);
|
this.balances.set(sessionToken, currentBalance - 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up sessions that haven't been accessed in 24 hours
|
// Clean up sessions that haven't been accessed in 24 hours
|
||||||
private cleanup(): void {
|
private cleanup(): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const expiryTime = 24 * 60 * 60 * 1000; // 24 hours
|
const expiryTime = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
|
||||||
for (const [token, lastAccess] of this.lastAccessed.entries()) {
|
for (const [token, lastAccess] of this.lastAccessed.entries()) {
|
||||||
if (now - lastAccess > expiryTime) {
|
if (now - lastAccess > expiryTime) {
|
||||||
this.balances.delete(token);
|
this.balances.delete(token);
|
||||||
this.lastAccessed.delete(token);
|
this.lastAccessed.delete(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For debugging in development
|
// For debugging in development
|
||||||
debugInfo(): unknown {
|
debugInfo(): unknown {
|
||||||
if (!dev) return null;
|
if (!dev) return null;
|
||||||
return {
|
return {
|
||||||
totalSessions: this.balances.size,
|
totalSessions: this.balances.size,
|
||||||
sessions: Object.fromEntries(this.balances),
|
sessions: Object.fromEntries(this.balances),
|
||||||
lastAccessed: Object.fromEntries(this.lastAccessed)
|
lastAccessed: Object.fromEntries(this.lastAccessed)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create singleton instance
|
// Create singleton instance
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { questions } from '$lib/server/db/schema';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const results = await db
|
const results = await db
|
||||||
.select({
|
.select({
|
||||||
poolSize: sql`COUNT(CASE WHEN answer_count < 5 THEN 1 END)`
|
poolSize: sql`COUNT(CASE WHEN answer_count < 5 THEN 1 END)`
|
||||||
})
|
})
|
||||||
.from(questions);
|
.from(questions);
|
||||||
|
|
||||||
return Number(results[0].poolSize);
|
return Number(results[0].poolSize);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,56 +3,56 @@ import { Ratelimit } from '@upstash/ratelimit';
|
||||||
import { UPSTASH_REDIS_TOKEN, UPSTASH_REDIS_URL } from '$env/static/private';
|
import { UPSTASH_REDIS_TOKEN, UPSTASH_REDIS_URL } from '$env/static/private';
|
||||||
|
|
||||||
const redis = new Redis({
|
const redis = new Redis({
|
||||||
url: UPSTASH_REDIS_URL,
|
url: UPSTASH_REDIS_URL,
|
||||||
token: UPSTASH_REDIS_TOKEN
|
token: UPSTASH_REDIS_TOKEN
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ratelimit = {
|
export const ratelimit = {
|
||||||
noSesh: new Ratelimit({
|
noSesh: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:noSesh',
|
prefix: 'ratelimit:noSesh',
|
||||||
limiter: Ratelimit.slidingWindow(1, '1m'),
|
limiter: Ratelimit.slidingWindow(1, '1m'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
pakubiiti: new Ratelimit({
|
pakubiiti: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:pakubiiti',
|
prefix: 'ratelimit:pakubiiti',
|
||||||
limiter: Ratelimit.slidingWindow(5, '15s'),
|
limiter: Ratelimit.slidingWindow(5, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
pakubiitiIP: new Ratelimit({
|
pakubiitiIP: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:pakubiitiIP',
|
prefix: 'ratelimit:pakubiitiIP',
|
||||||
limiter: Ratelimit.slidingWindow(5, '15s'),
|
limiter: Ratelimit.slidingWindow(5, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
rahvaAnswer: new Ratelimit({
|
rahvaAnswer: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:rahvaAnswer',
|
prefix: 'ratelimit:rahvaAnswer',
|
||||||
limiter: Ratelimit.slidingWindow(8, '15s'),
|
limiter: Ratelimit.slidingWindow(8, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
rahvaAnswerIP: new Ratelimit({
|
rahvaAnswerIP: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:rahvaAnswerIP',
|
prefix: 'ratelimit:rahvaAnswerIP',
|
||||||
limiter: Ratelimit.slidingWindow(8, '15s'),
|
limiter: Ratelimit.slidingWindow(8, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
rahvaQuestion: new Ratelimit({
|
rahvaQuestion: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:rahvaQuestion',
|
prefix: 'ratelimit:rahvaQuestion',
|
||||||
limiter: Ratelimit.slidingWindow(8, '15s'),
|
limiter: Ratelimit.slidingWindow(8, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
rahvaQuestionIP: new Ratelimit({
|
rahvaQuestionIP: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:rahvaQuestionIP',
|
prefix: 'ratelimit:rahvaQuestionIP',
|
||||||
limiter: Ratelimit.slidingWindow(8, '15s'),
|
limiter: Ratelimit.slidingWindow(8, '15s'),
|
||||||
enableProtection: true
|
enableProtection: true
|
||||||
}),
|
}),
|
||||||
rahvaQuestionAPI: new Ratelimit({
|
rahvaQuestionAPI: new Ratelimit({
|
||||||
redis,
|
redis,
|
||||||
prefix: 'ratelimit:rahvaQuestionAPI',
|
prefix: 'ratelimit:rahvaQuestionAPI',
|
||||||
limiter: Ratelimit.slidingWindow(15, '15s')
|
limiter: Ratelimit.slidingWindow(15, '15s')
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,19 +4,19 @@ import { json } from '@sveltejs/kit';
|
||||||
import { createChallenge, verifySolution } from 'altcha-lib';
|
import { createChallenge, verifySolution } from 'altcha-lib';
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const challenge = await createChallenge({
|
const challenge = await createChallenge({
|
||||||
hmacKey: ALTCHA_HMAC,
|
hmacKey: ALTCHA_HMAC,
|
||||||
maxNumber: 100000 // the maximum random number
|
maxNumber: 100000 // the maximum random number
|
||||||
});
|
});
|
||||||
console.log('challange get');
|
console.log('challange get');
|
||||||
|
|
||||||
return json(challenge);
|
return json(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
const { payload }: { payload: string } = await request.json();
|
const { payload }: { payload: string } = await request.json();
|
||||||
console.log('challange done');
|
console.log('challange done');
|
||||||
const ok = await verifySolution(payload, ALTCHA_HMAC);
|
const ok = await verifySolution(payload, ALTCHA_HMAC);
|
||||||
|
|
||||||
return json({ ok });
|
return json({ ok });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const formSchema = z.object({
|
export const formSchema = z.object({
|
||||||
questionId: z.string().length(21),
|
questionId: z.string().length(21),
|
||||||
answer: z
|
answer: z
|
||||||
.string()
|
.string()
|
||||||
.min(2, 'Vastus peab olema vähemalt 2 tähemärki.')
|
.min(2, 'Vastus peab olema vähemalt 2 tähemärki.')
|
||||||
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.'),
|
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.'),
|
||||||
altcha: z.string()
|
altcha: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormSchema = typeof formSchema;
|
export type FormSchema = typeof formSchema;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const formSchema = z.object({
|
export const formSchema = z.object({
|
||||||
question: z
|
question: z
|
||||||
.string()
|
.string()
|
||||||
.min(2, 'Küsimus peab olema vähemalt 2 tähemärki.')
|
.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.'),
|
.max(50, 'Küsimus ei või olla pikem kui 50 tähemärki.'),
|
||||||
altcha: z.string()
|
altcha: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormSchema = typeof formSchema;
|
export type FormSchema = typeof formSchema;
|
||||||
|
|
|
@ -14,165 +14,165 @@ import jetImg from '$lib/assets/vaukuivali/fighters.jpg?enhanced';
|
||||||
import hearingaidImg from '$lib/assets/vaukuivali/eardamage.jpg?enhanced';
|
import hearingaidImg from '$lib/assets/vaukuivali/eardamage.jpg?enhanced';
|
||||||
|
|
||||||
export const soundCheckpoints: Record<number, SoundCheckpoint> = {
|
export const soundCheckpoints: Record<number, SoundCheckpoint> = {
|
||||||
0: {
|
0: {
|
||||||
title: '',
|
title: '',
|
||||||
description: 'Kesket metsa mingis koopas, kedagi pole ümber',
|
description: 'Kesket metsa mingis koopas, kedagi pole ümber',
|
||||||
image: undefined
|
image: undefined
|
||||||
},
|
},
|
||||||
30: {
|
30: {
|
||||||
title: '"Vaikus"',
|
title: '"Vaikus"',
|
||||||
description: 'ehk elutoa pasiivne müra',
|
description: 'ehk elutoa pasiivne müra',
|
||||||
image: {
|
image: {
|
||||||
src: roomImg,
|
src: roomImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Kam Idris',
|
author: 'Kam Idris',
|
||||||
href: 'https://unsplash.com/@ka_idris'
|
href: 'https://unsplash.com/@ka_idris'
|
||||||
},
|
},
|
||||||
alt: 'Modernse ja minimalistliku disainiga siseruum.'
|
alt: 'Modernse ja minimalistliku disainiga siseruum.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
40: {
|
40: {
|
||||||
title: 'Tikk takk',
|
title: 'Tikk takk',
|
||||||
description: 'Mehaanilise kella tiksumine (va täistundidel)',
|
description: 'Mehaanilise kella tiksumine (va täistundidel)',
|
||||||
image: {
|
image: {
|
||||||
src: watchImg,
|
src: watchImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'János Venczák',
|
author: 'János Venczák',
|
||||||
href: 'https://unsplash.com/@venczakjanos'
|
href: 'https://unsplash.com/@venczakjanos'
|
||||||
},
|
},
|
||||||
alt: 'Lahti võetud vanamoodne käekell. Näha on kella sisemust, hammasrattaid.'
|
alt: 'Lahti võetud vanamoodne käekell. Näha on kella sisemust, hammasrattaid.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
50: {
|
50: {
|
||||||
title: 'Tava jutt',
|
title: 'Tava jutt',
|
||||||
description: 'Rahulik vestlus kodus',
|
description: 'Rahulik vestlus kodus',
|
||||||
image: {
|
image: {
|
||||||
src: convoImg,
|
src: convoImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Toa Heftiba',
|
author: 'Toa Heftiba',
|
||||||
href: 'https://unsplash.com/@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.'
|
alt: 'Noor paar köögis. Mees lõikab taldriku peal pannkooki, naine istub ta kõrval pliidi peal ja vaatab.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
60: {
|
60: {
|
||||||
title: 'Ma sain nurgad täis',
|
title: 'Ma sain nurgad täis',
|
||||||
description: 'Bingo õhtu Gennis (keset mängu)',
|
description: 'Bingo õhtu Gennis (keset mängu)',
|
||||||
image: {
|
image: {
|
||||||
src: gennImg,
|
src: gennImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Laila Kaasik',
|
author: 'Laila Kaasik',
|
||||||
href: 'https://tartu.postimees.ee/8154041/lallavad-pidutsejad-panid-tartu-otsima-tasakaalu-ooelu-ja-oorahu-vahel'
|
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.'
|
alt: 'Genialistide klubi tegutseb Tartus Magasini tänavas. Pilt on õhtusest ajast, rohkelt inimesti klubi välialal.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
70: {
|
70: {
|
||||||
title: 'Pult on kadunud',
|
title: 'Pult on kadunud',
|
||||||
description: 'Telekas, mis mängib natuke liiga valjult',
|
description: 'Telekas, mis mängib natuke liiga valjult',
|
||||||
image: {
|
image: {
|
||||||
src: tvImg,
|
src: tvImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Jonas Leupe',
|
author: 'Jonas Leupe',
|
||||||
href: 'https://unsplash.com/@jonasleupe'
|
href: 'https://unsplash.com/@jonasleupe'
|
||||||
},
|
},
|
||||||
alt: 'Keegi vaatab televiisorist filmi. Esiplaanil fookuses teleka pult, tagaplaanil udune tuba, mille seina vastas on telekas.'
|
alt: 'Keegi vaatab televiisorist filmi. Esiplaanil fookuses teleka pult, tagaplaanil udune tuba, mille seina vastas on telekas.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
80: {
|
80: {
|
||||||
title: 'USAs oleks hullem',
|
title: 'USAs oleks hullem',
|
||||||
description: 'Riia mäe liiklus (ootad bussi Kaubamaja ees)',
|
description: 'Riia mäe liiklus (ootad bussi Kaubamaja ees)',
|
||||||
image: {
|
image: {
|
||||||
src: trafficImg,
|
src: trafficImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Google Street View',
|
author: 'Google Street View',
|
||||||
href: 'https://maps.app.goo.gl/ZfADP4LnUid7d571A'
|
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.'
|
alt: 'Aastal 2012 tehtud Google Street View pilt. Näha on Tartu Kaubamaja ning selle Riia tänava küljel olevat bussipeatust.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
90: {
|
90: {
|
||||||
title: 'USAs oleks rohkem',
|
title: 'USAs oleks rohkem',
|
||||||
description: 'Harley sõidab sinust mööda',
|
description: 'Harley sõidab sinust mööda',
|
||||||
image: {
|
image: {
|
||||||
src: harleyImg,
|
src: harleyImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Harley-Davidson',
|
author: 'Harley-Davidson',
|
||||||
href: 'https://unsplash.com/@harleydavidson'
|
href: 'https://unsplash.com/@harleydavidson'
|
||||||
},
|
},
|
||||||
alt: 'Uue välimusega Harley-Davidson mootorrattas sõidab kiiresti mööda sirget maanteed.'
|
alt: 'Uue välimusega Harley-Davidson mootorrattas sõidab kiiresti mööda sirget maanteed.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
100: {
|
100: {
|
||||||
title: 'Põgenesid terminalist',
|
title: 'Põgenesid terminalist',
|
||||||
description: 'Boeing 707 1 meremiil enne maandumist',
|
description: 'Boeing 707 1 meremiil enne maandumist',
|
||||||
image: {
|
image: {
|
||||||
src: landingImg,
|
src: landingImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Scott Fillmer',
|
author: 'Scott Fillmer',
|
||||||
href: 'https://unsplash.com/@scottfillmer'
|
href: 'https://unsplash.com/@scottfillmer'
|
||||||
},
|
},
|
||||||
alt: 'Continental Airlines Boeing 777 maandub uduses Houston IAH lennujaamas.'
|
alt: 'Continental Airlines Boeing 777 maandub uduses Houston IAH lennujaamas.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
110: {
|
110: {
|
||||||
title: 'Maanteeraev',
|
title: 'Maanteeraev',
|
||||||
description: 'Autosignaal 1m kauguselt',
|
description: 'Autosignaal 1m kauguselt',
|
||||||
image: {
|
image: {
|
||||||
src: carCrashImg,
|
src: carCrashImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.instagram,
|
type: ImageCreditType.instagram,
|
||||||
author: 'Jordan Besson',
|
author: 'Jordan Besson',
|
||||||
href: 'https://www.instagram.com/mr.blue.photographie'
|
href: 'https://www.instagram.com/mr.blue.photographie'
|
||||||
},
|
},
|
||||||
alt: 'Dramaatiline auto trikk filmi jaoks. Kahe auto kokkupõrge.'
|
alt: 'Dramaatiline auto trikk filmi jaoks. Kahe auto kokkupõrge.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
120: {
|
120: {
|
||||||
title: 'Mootorsaag',
|
title: 'Mootorsaag',
|
||||||
description: 'Nüüd on juba valus. Soovitan kanda kõrvatroppe.',
|
description: 'Nüüd on juba valus. Soovitan kanda kõrvatroppe.',
|
||||||
image: {
|
image: {
|
||||||
src: chainsawImg,
|
src: chainsawImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Benjamin Jopen',
|
author: 'Benjamin Jopen',
|
||||||
href: 'https://unsplash.com/@benjopen'
|
href: 'https://unsplash.com/@benjopen'
|
||||||
},
|
},
|
||||||
alt: 'Oranži ja musta värvi mootorsega lõigatakse langenud puud väiksemateks tükkideks.'
|
alt: 'Oranži ja musta värvi mootorsega lõigatakse langenud puud väiksemateks tükkideks.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
130: {
|
130: {
|
||||||
title: 'Kuidas sa nii lähedale said?',
|
title: 'Kuidas sa nii lähedale said?',
|
||||||
description: 'Turboreaktiivmootoriga hävitaja lendutõus 15m kauguselt',
|
description: 'Turboreaktiivmootoriga hävitaja lendutõus 15m kauguselt',
|
||||||
image: {
|
image: {
|
||||||
src: jetImg,
|
src: jetImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Colin Lloyd',
|
author: 'Colin Lloyd',
|
||||||
href: 'https://unsplash.com/@onthesearchforpineapples'
|
href: 'https://unsplash.com/@onthesearchforpineapples'
|
||||||
},
|
},
|
||||||
alt: 'Kaheksa F-16 hävitajat lendavad koos formatsioonis taevas.'
|
alt: 'Kaheksa F-16 hävitajat lendavad koos formatsioonis taevas.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
150: {
|
150: {
|
||||||
title: 'Aia mu kõrvad',
|
title: 'Aia mu kõrvad',
|
||||||
description: 'Tubli töö! Su trummikile rebenes!',
|
description: 'Tubli töö! Su trummikile rebenes!',
|
||||||
image: {
|
image: {
|
||||||
src: hearingaidImg,
|
src: hearingaidImg,
|
||||||
credit: {
|
credit: {
|
||||||
type: ImageCreditType.web,
|
type: ImageCreditType.web,
|
||||||
author: 'Mark Paton',
|
author: 'Mark Paton',
|
||||||
href: 'https://unsplash.com/@heftiba'
|
href: 'https://unsplash.com/@heftiba'
|
||||||
},
|
},
|
||||||
alt: 'Lähivõte inimesest sisestamas oma kõrva kuuldeaparaati.'
|
alt: 'Lähivõte inimesest sisestamas oma kõrva kuuldeaparaati.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue