1019 lines
44 KiB
XML
1019 lines
44 KiB
XML
<?xml version="1.0"?>
|
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
<svg version="1.1"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
width="300"
|
|
height="600"
|
|
viewBox="0 0 300 600"
|
|
onload="run()"
|
|
overflow="auto"
|
|
style="font-family: sans; border-radius: 10px;"
|
|
id="movie"
|
|
>
|
|
<defs>
|
|
<symbol id="kaereste_foto" width="60" height="200">
|
|
<g style="fill: pink; stroke: pink;">
|
|
<circle cx="20" cy="20" r="20" />
|
|
<line x1="20" y1="40" x2="20" y2="60" />
|
|
<ellipse cx="20" cy="110" rx="10" ry="50" />
|
|
<!-- left arm -->
|
|
<polyline fill="none"
|
|
points="15,70 5,50 0,40" />
|
|
<!-- right arm -->
|
|
<polyline fill="none"
|
|
points="25,70 35,50 40,40" />
|
|
<!-- left leg -->
|
|
<polyline fill="none"
|
|
points="15,155 5,135 0,125" />
|
|
<!-- right leg -->
|
|
<polyline fill="none"
|
|
points="25,155 35,135 40,125" />
|
|
<!-- the thing -->
|
|
<text x="14" y="160" style="font: bold 20px sans-serif" fill="black">?</text>
|
|
</g>
|
|
</symbol>
|
|
<symbol id="strand" width="80" height="1200">
|
|
<rect width="80" height="130" fill="yellow"></rect>
|
|
<g style="fill: pink; stroke: pink;">
|
|
<circle cx="60" cy="20" r="20" />
|
|
<!-- spine -->
|
|
<line x1="55" y1="40" x2="22" y2="100" />
|
|
|
|
<!-- left leg -->
|
|
<polyline fill="none" points="20,100 60,90 35,125" />
|
|
|
|
<!-- right leg -->
|
|
<polyline fill="none" points="20,100 50,75 25,120" />
|
|
|
|
<!-- left arm -->
|
|
<polyline fill="none" points="37,70 20,45" />
|
|
|
|
<!-- right arm + bucket -->
|
|
<polyline fill="none" points="39,71 70,50" />
|
|
|
|
</g>
|
|
</symbol>
|
|
<symbol id="flag_denmark" width="20" height="15">
|
|
<path fill="#c8102e" d="M0,0H20V15H0Z"/>
|
|
<path fill="#fff" d="M0,6H6V0H8V6H20V8H8V15H6V8H0Z"/>
|
|
</symbol>
|
|
|
|
<symbol id="flag_uk" width="50" height="30">
|
|
<clipPath id="t">
|
|
<path d="M25,15h25v15zv15h-25zh-25v-15zv-15h25z"/>
|
|
</clipPath>
|
|
<path d="M0,0v30h50v-30z" fill="#012169"/>
|
|
<path d="M0,0 50,30M50,0 0,30" stroke="#fff" stroke-width="6"/>
|
|
<path d="M0,0 50,30M50,0 0,30" clip-path="url(#t)" stroke="#C8102E" stroke-width="4"/>
|
|
<path d="M-1 11h22v-12h8v12h22v8h-22v12h-8v-12h-22z" fill="#C8102E" stroke="#FFF" stroke-width="2"/>
|
|
</symbol>
|
|
</defs>
|
|
<script type="application/ecmascript">
|
|
<![CDATA[
|
|
'use strict';
|
|
|
|
const svgns = "http://www.w3.org/2000/svg";
|
|
|
|
// must be the same as the SVG dimensions
|
|
const width = 300;
|
|
const height = 550;
|
|
|
|
const line_height = 14;
|
|
const bubble_padding = 6;
|
|
const bubble_spacing = 5;
|
|
const visible_chat_area = {'top': 60,
|
|
'bottom': height - 100 // minus status & input areas
|
|
};
|
|
|
|
var typing_speed = 50;
|
|
function set_typing_speed(new_speed) {
|
|
typing_speed = new_speed;
|
|
}
|
|
function get_typing_speed() {
|
|
return typing_speed; // prevent closure capture
|
|
}
|
|
|
|
var conversation_count = 0;
|
|
var current_dialog = null;
|
|
|
|
var current_discussion_index = parseInt(new URLSearchParams(location.search).get('discussion') ?? 0);
|
|
var current_language = new URLSearchParams(location.search).get('lang') ?? 'da';
|
|
if (current_language==='tok') current_language = 'en';
|
|
|
|
/// SVG 1.1 doesn't do proper text splitting into several lines.
|
|
/// we need to do it ourselves.
|
|
function split_text_into_lines(text, upper_line_length) {
|
|
let result = [];
|
|
|
|
while(text.length) {
|
|
if(text.length < upper_line_length) {
|
|
result.push(text);
|
|
break; // we are done
|
|
}
|
|
|
|
let found_split_point = false;
|
|
for(let i = upper_line_length; i; i--) {
|
|
if(text[i] == ' ') {
|
|
result.push(text.slice(0, i));
|
|
text = text.slice(i+1);
|
|
found_split_point = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found_split_point) {
|
|
// no <space> found. Split at character boundary instead
|
|
result.push(text.slice(0, upper_line_length));
|
|
text = text.slice(upper_line_length);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function is_hyperlink(str) {
|
|
return str.startsWith('http');
|
|
}
|
|
|
|
function create_link_node(url) {
|
|
let link = create_svg_node('a', {target:'_blank', href: url});
|
|
link.appendChild(document.createTextNode(url));
|
|
return link;
|
|
}
|
|
|
|
/// A class holding a text chat message.
|
|
///
|
|
/// *ChatMessages are owned by a Dialog.
|
|
function TextChatMessage(message_text, is_myself) {
|
|
let lines = split_text_into_lines(message_text, 28);
|
|
let bubble_color = (is_myself)? '#0084FF': '#E4E6EB';
|
|
let text_color = (is_myself)? 'white': 'black';
|
|
let container = document.getElementById('messages');
|
|
|
|
// attributes
|
|
this.height = 0; // height on screen when fully visible
|
|
this.group = null; // a <g> with a transform=translate(0,y_shift) attribute
|
|
|
|
/// Render the chat message on the screen
|
|
this.draw = function(y_offset, y_shift) {
|
|
let group = create_svg_node('g', {'transform': `translate(0, ${y_shift})`});
|
|
|
|
let x = (is_myself ? 195 : 105);
|
|
let text = create_svg_node('text', {
|
|
'x': `${x}%`,
|
|
'font-size': `${line_height}px`,
|
|
'text-anchor': is_myself ? 'end' : 'start'
|
|
});
|
|
|
|
let height_so_far = y_offset;
|
|
|
|
lines.forEach(function(line) {
|
|
let y = height_so_far;
|
|
let tspan = create_svg_node('tspan', {
|
|
'x': `${x}%`,
|
|
'y': `${y + line_height}`, // important: y is lower text baseline
|
|
'fill': text_color,
|
|
'width': '5%'
|
|
});
|
|
enable_scrolling(tspan)
|
|
|
|
if(is_hyperlink(line)) {
|
|
tspan.appendChild(create_link_node(line));
|
|
} else {
|
|
tspan.appendChild(document.createTextNode(line));
|
|
}
|
|
text.appendChild(tspan);
|
|
|
|
height_so_far += line_height;
|
|
});
|
|
|
|
group.appendChild(text); // needs to be part of the DOM *now*
|
|
container.appendChild(group);
|
|
let bubble = create_bubble(text, bubble_color);
|
|
group.appendChild(bubble);
|
|
redraw_on_top(text);
|
|
|
|
this.height = bubble.getBBox().height + bubble_spacing;
|
|
this.group = group;
|
|
}
|
|
|
|
/// Move a chat message on the screen (to simulate scrolling)
|
|
this.shift_y_pos = function(by) {
|
|
redraw_on_top(document.getElementById('contact_name_box'));
|
|
this.group.setAttribute('transform', `translate(0, ${by})`);
|
|
}
|
|
}
|
|
|
|
/// A class holding a image-based chat message.
|
|
///
|
|
/// *ChatMessages are owned by a Dialog.
|
|
function ImageChatMessage(symbol_id, is_myself) {
|
|
let container = document.getElementById('messages');
|
|
let bubble_color = (is_myself)? 'white': '#DDFF66';
|
|
|
|
// attributes
|
|
this.height = 0; // height on screen when fully visible
|
|
this.group = null; // a <g> with a transform=translate(0,y_shift) attribute
|
|
|
|
this.draw = function(y_offset, y_shift) {
|
|
let group = create_svg_node('g', {'transform': `translate(0, ${y_shift})`});
|
|
|
|
let x = (is_myself ? 190 : 110);
|
|
let img = create_svg_node('use', {
|
|
'href': symbol_id,
|
|
x: `${x}%`,
|
|
y: y_offset,
|
|
});
|
|
|
|
group.appendChild(img); // needs to be part of the DOM *now*
|
|
container.appendChild(group);
|
|
let bubble = create_bubble(img, bubble_color);
|
|
group.appendChild(bubble);
|
|
redraw_on_top(img);
|
|
|
|
this.height = bubble.getBBox().height + bubble_spacing;
|
|
this.group = group;
|
|
}
|
|
|
|
this.shift_y_pos = function(by) {
|
|
redraw_on_top(document.getElementById('contact_name_box'));
|
|
this.group.setAttribute('transform', `translate(0, ${by})`);
|
|
}
|
|
}
|
|
|
|
/// A class for holding a final message that says that the chat partner is offline
|
|
///
|
|
/// *ChatMessages are owned by a Dialog.
|
|
function OfflineChatMessage(dollar_message, _is_myself) {
|
|
let offline_message = dollar_message.substring(1);
|
|
let container = document.getElementById('messages');
|
|
|
|
// attributes
|
|
this.height = 0; // height on screen when fully visible
|
|
this.group = null; // a <g> with a transform=translate(0,y_shift) attribute
|
|
|
|
this.draw = function(y_offset, y_shift) {
|
|
let group = create_svg_node('g', {'transform': `translate(0, ${y_shift})`});
|
|
let height_so_far = y_offset;
|
|
|
|
function post_process(element) {
|
|
enable_scrolling(element);
|
|
height_so_far += line_height;
|
|
group.appendChild(element);
|
|
redraw_on_top(element);
|
|
}
|
|
|
|
let x = 101;
|
|
let bar = create_svg_node('rect', {
|
|
'x': `${x}%`,
|
|
'y': `${height_so_far + line_height}`,
|
|
'fill': '#CCCCCC',
|
|
'width': '90%',
|
|
'height': '4px'
|
|
});
|
|
post_process(bar);
|
|
height_so_far += 5; // spacing
|
|
|
|
let text = create_svg_node('text', {
|
|
'x': `${x}%`,
|
|
'y': `${height_so_far + line_height}`,
|
|
'font-size': `${line_height}px`,
|
|
'text-anchor': 'start'
|
|
});
|
|
text.appendChild(document.createTextNode(offline_message));
|
|
post_process(text);
|
|
|
|
let back_link_text = create_svg_node('text', {
|
|
'x': `${x}%`,
|
|
'y': `${height_so_far + line_height}`,
|
|
'font-size': `${line_height}px`,
|
|
'text-anchor': 'start',
|
|
'style': 'text-decoration: underline',
|
|
'fill': 'blue'
|
|
});
|
|
let back_link = create_svg_node('a', {
|
|
'href': 'javascript: swipe_viewport()',
|
|
});
|
|
|
|
let link_text = 'tilbage til kontaktlisten';
|
|
if(current_language == 'en') {
|
|
link_text = 'back to the contact list';
|
|
}
|
|
back_link_text.appendChild(document.createTextNode(link_text));
|
|
back_link.appendChild(back_link_text);
|
|
post_process(back_link);
|
|
|
|
container.appendChild(group);
|
|
this.height = height_so_far - y_offset;
|
|
this.group = group;
|
|
|
|
}
|
|
|
|
this.shift_y_pos = function(by) {
|
|
redraw_on_top(document.getElementById('contact_name_box'));
|
|
this.group.setAttribute('transform', `translate(0, ${by})`);
|
|
}
|
|
}
|
|
|
|
function create_chat_message(content, is_myself) {
|
|
let constr = null;
|
|
if(content.startsWith('#')) {
|
|
constr = ImageChatMessage;
|
|
} else if(content.startsWith('$')) {
|
|
constr = OfflineChatMessage;
|
|
} else {
|
|
constr = TextChatMessage;
|
|
}
|
|
return new constr(content, is_myself);
|
|
}
|
|
|
|
/// Promise-based version of setTimeout
|
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
/// toggle viewport between message list and contact list
|
|
/// That means: show either the left or right side of the SVG
|
|
function swipe_viewport() {
|
|
let svg = document.getElementsByTagName('svg')[0];
|
|
let coords = svg.getAttribute('viewBox').split(' ');
|
|
let x = parseInt(coords.shift());
|
|
if(x == 0) {
|
|
// moving from messages to contacts
|
|
var step = 5;
|
|
var end = width;
|
|
} else {
|
|
// moving from contacts to messages
|
|
var step = -5;
|
|
var end = 0;
|
|
}
|
|
|
|
let viewBox_suffix = coords.join(' '); // only 3 elements
|
|
|
|
async function animate(resolve, reject) {
|
|
while(x!=end) {
|
|
await wait(1);
|
|
x += step;
|
|
svg.setAttribute('viewBox', `${x} ${viewBox_suffix}`);
|
|
}
|
|
resolve();
|
|
};
|
|
new Promise(animate);
|
|
}
|
|
|
|
|
|
// Create a chat bubble around an element.
|
|
//
|
|
// The element must already be inside the DOM for this to work.
|
|
function create_bubble(inner_element, color) {
|
|
const bbox = inner_element.getBBox();
|
|
let bubble = create_svg_node('rect', {
|
|
'x': bbox.x - bubble_padding,
|
|
'y': bbox.y - bubble_padding,
|
|
'width': bbox.width + 2 * bubble_padding,
|
|
'height': bbox.height + 2 * bubble_padding,
|
|
'rx': 8,
|
|
'fill': color,
|
|
});
|
|
enable_scrolling(bubble);
|
|
return bubble;
|
|
}
|
|
|
|
function create_svg_node(tag_name, attrs) {
|
|
let node = document.createElementNS(svgns, tag_name);
|
|
for(let attr in attrs) {
|
|
node.setAttribute(attr, attrs[attr]);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/// Ensure that an element is redrawn above all other elements
|
|
///
|
|
/// The element's children will also be redrawn on top
|
|
function redraw_on_top(element) {
|
|
let parent = element.parentNode;
|
|
parent.appendChild(parent.removeChild(element));
|
|
}
|
|
|
|
function removeAllChildren(parentNode) {
|
|
while(parentNode.firstChild) {
|
|
parentNode.removeChild(parentNode.lastChild);
|
|
}
|
|
}
|
|
|
|
function start_chat(index) {
|
|
current_discussion_index = index;
|
|
|
|
let contact = contact_list[current_language][index];
|
|
let who = contact.name;
|
|
let indicator = document.getElementById('contact_indicator');
|
|
indicator.childNodes[0].data = `Kontakt: ${who}`;
|
|
removeAllChildren(document.getElementById('messages'));
|
|
swipe_viewport();
|
|
|
|
contact.dialog();
|
|
}
|
|
|
|
function Dialog() {
|
|
let conversation_id = ++conversation_count;
|
|
this.messages = [];
|
|
this.y_shift = visible_chat_area.top; // for scrolling
|
|
this.all_elements_height = 0; // also for scrolling
|
|
|
|
this.is_active = function() {
|
|
return conversation_id == conversation_count;
|
|
}
|
|
|
|
async function post_message(dialog, message, is_myself) {
|
|
if(!is_myself && dialog.messages.length) {
|
|
await wait(message.length * get_typing_speed());
|
|
} // first message should be instant
|
|
|
|
if(!dialog.is_active()) {
|
|
return; // Do not add messages to old conversation
|
|
}
|
|
|
|
let chat_message = create_chat_message(message, is_myself);
|
|
chat_message.draw(dialog.all_elements_height, // offset
|
|
dialog.y_shift);
|
|
dialog.all_elements_height += chat_message.height;
|
|
dialog.messages.push(chat_message);
|
|
|
|
let bottom = dialog.all_elements_height + dialog.y_shift;
|
|
let scroll = Math.max(0, bottom - visible_chat_area.bottom);
|
|
while(scroll > line_height) {
|
|
dialog.scroll_by(-line_height);
|
|
scroll -= line_height;
|
|
await wait(get_typing_speed());
|
|
}
|
|
}
|
|
|
|
this.scroll_by = function(change) {
|
|
this.y_shift += change;
|
|
this.messages.forEach(msg => msg.shift_y_pos(this.y_shift));
|
|
}
|
|
|
|
this.me = async function(message, will_be_flagged) {
|
|
await wait(30 * get_typing_speed()); // give me some time to read
|
|
if(!this.is_active()) return;
|
|
await type_message(this, message);
|
|
if(!this.is_active()) return;
|
|
hit_send_button();
|
|
await local_message_processing(will_be_flagged);
|
|
clear_input_field();
|
|
release_send_button();
|
|
await post_message(this, message, true);
|
|
}
|
|
|
|
this.you = async function(message) {
|
|
await post_message(this, message, false);
|
|
}
|
|
|
|
this.end = async function(message) {
|
|
await post_message(this, `$${message}`, true);
|
|
}
|
|
}
|
|
|
|
async function type_message(dialog, message) {
|
|
clear_input_field(); // just to be sure
|
|
let text = document.getElementById('message_input_data').firstChild;
|
|
|
|
let lines = split_text_into_lines(message, 22);
|
|
for(let line of lines) {
|
|
text.data = '';
|
|
for(let c of line) {
|
|
await wait(get_typing_speed());
|
|
text.data += c;
|
|
}
|
|
if(!dialog.is_active()) break;
|
|
await wait(0.4 * get_typing_speed()); // otherwise it's too fast
|
|
}
|
|
}
|
|
|
|
function hit_send_button() {
|
|
document.getElementById('message_submit_button').setAttribute('fill', 'white');
|
|
document.getElementById('message_submit_box').setAttribute('fill', '#888888');
|
|
}
|
|
|
|
function release_send_button() {
|
|
document.getElementById('message_submit_button').setAttribute('fill', 'black');
|
|
document.getElementById('message_submit_box').setAttribute('fill', '#CCCCCC');
|
|
}
|
|
|
|
function clear_input_field() {
|
|
let element = document.getElementById('message_input_data')
|
|
removeAllChildren(element);
|
|
element.appendChild(document.createTextNode(''));
|
|
}
|
|
|
|
async function local_message_processing(will_be_flagged) {
|
|
let text_element = document.getElementById('message_status');
|
|
let indicator = text_element.childNodes[0];
|
|
indicator.data = 'scanning for illegal content';
|
|
await wait(16 * get_typing_speed());
|
|
if(will_be_flagged) {
|
|
let box = document.getElementById('message_status_box');
|
|
text_element.setAttribute('fill', 'red');
|
|
box.setAttribute('fill', 'yellow');
|
|
indicator.data = 'Reporting suspicious content';
|
|
await wait(20 * get_typing_speed());
|
|
text_element.setAttribute('fill', 'white');
|
|
box.setAttribute('fill', '#FF7B00');
|
|
}
|
|
indicator.data = 'encrypting message';
|
|
await wait(11 * get_typing_speed());
|
|
indicator.data = 'sending message';
|
|
await wait(10 * get_typing_speed());
|
|
indicator.data = 'message was sent';
|
|
}
|
|
|
|
async function scroll_up() {
|
|
current_dialog.scroll_by(line_height);
|
|
}
|
|
|
|
async function scroll_down() {
|
|
current_dialog.scroll_by(-line_height);
|
|
}
|
|
|
|
const on_wheel = (e) => { e.deltaY < 0 ? scroll_up() : scroll_down() }
|
|
|
|
const enable_scrolling = (elem) => elem.addEventListener('wheel', on_wheel);
|
|
|
|
// enable scrolling
|
|
document.addEventListener(
|
|
"DOMContentLoaded",
|
|
() => document.getElementById("message_box").addEventListener('wheel', on_wheel)
|
|
)
|
|
|
|
async function switch_language(language_code) {
|
|
let url = new URL(document.location);
|
|
url.searchParams.set('lang', language_code);
|
|
url.searchParams.set('discussion', current_discussion_index);
|
|
location.href = url;
|
|
}
|
|
|
|
async function dialog_ven_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me("hej");
|
|
await d.you("tak for sidst!");
|
|
await d.you('Har du hørt om den nye EU lov "ChatControl"?');
|
|
await d.me("nej, det har jeg ikke");
|
|
await d.me("hvad handler det om?");
|
|
await d.you('EU kommissionen planlægger at læse alle chatbeskeder i EU');
|
|
await d.me('Ja, men vi krypterer jo vores beskeder? Tough luck!');
|
|
await d.you('Det tager de højde for. Aflytningen sker før krypteringen på din telefon!');
|
|
await d.me('Det lyder overhovedet ikke rart. Hvorfor vil de gøre det?');
|
|
await d.you('De siger at det er for at beskytte børn på nettet.');
|
|
await d.you('Men det giver ikke meget mening');
|
|
await d.me('Alle vores beskeder skal scannes på grund af børn på nettet? Det lyder dumt!', true);
|
|
await d.you('Her kan du læse mere om det:');
|
|
await d.you('https://chatcontrol.dk');
|
|
await d.end('din ven er offline nu');
|
|
}
|
|
|
|
async function dialog_ven_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me("hi");
|
|
await d.you("hi, what's up?");
|
|
await d.you('Have you heard about the new EU law "ChatControl"?');
|
|
await d.me("No, I haven't");
|
|
await d.me("What is it about?");
|
|
await d.you('The EU commission plans to read all chat messages in the EU');
|
|
await d.me('Yeah? But we encrypt all of our chats? Tough luck!');
|
|
await d.you('That has been taken into account. The eavesdropping will happen before the encryption step on your phone!');
|
|
await d.me('That sounds stupid. Why do they want to do that?');
|
|
await d.you('They claim that this is for protecting children on the internet');
|
|
await d.you("But that doesn't make any sense.");
|
|
await d.me('All of our messages will be scanned because of children on the internet? That sounds really stupid!', true);
|
|
await d.you('Here you can read more about it:');
|
|
await d.you('https://chatcontrol.dk');
|
|
await d.end('Your friend is offline now');
|
|
}
|
|
|
|
async function dialog_mor_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you("Jeg har fundet nogle gamle familiebilleder fra vores ferie for 10 år siden");
|
|
await d.you("Her leger du på stranden");
|
|
await d.you("#strand");
|
|
await d.me('Tak, men jeg vil heller ikke have at mine nøgenbilleder kommer på nettet', true);
|
|
await d.you('Hvorfor er du bekymret over det? Det er jo bare dig som barn. Lange tid siden');
|
|
await d.me('Internettet er ikke et godt sted til at dele meget private billeder');
|
|
await d.me('Man taber let kontrollen over dem');
|
|
await d.you('Ja, men det er jo bare os to som deler billederne?');
|
|
await d.me('Vi krypterer faktisk vores beskeder, så det er ikke så slemt');
|
|
await d.me('Men EU komissionen vil indføre en funktion sådan at alle beskeder kan scannes, selv om de er krypteret');
|
|
await d.me('Det vil ikke være bare os to, som har adgang til vores private billeder');
|
|
await d.you('Det overrasker mig. Men så tænker jeg at jeg bare vil give dig en USB-nøgle næste gang du kommer på besøg');
|
|
await d.me('God idé. Tak :-)');
|
|
await d.end('Din mor er offline nu');
|
|
}
|
|
|
|
async function dialog_mor_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you("I have found some old family photos from our holiday from 10 years ago");
|
|
await d.you("This is you playing on the beach");
|
|
await d.you("#strand");
|
|
await d.me('Thanks, but I would rather not have my nude pictures on the internet', true);
|
|
await d.you('Why do you worry about that? This is just you as a child a long time ago');
|
|
await d.me('The Internet is not a good place to share very private pictures');
|
|
await d.me('It is very easy to lose control over them');
|
|
await d.you('Well, but it is just us who share the pictures?');
|
|
await d.me('We encrypt our messages, so it is kinda fine.');
|
|
await d.me('But the EU commission wants to introduce a function for scanning all messages even if they are encrypted');
|
|
await d.me('It won\'t be just us two who will have access to our private pictures');
|
|
await d.you('That surprises me. But then I think I will give you a USB stick next time you visit me');
|
|
await d.me('Good idea! Thanks :-)');
|
|
await d.end('Your mom is offline now');
|
|
}
|
|
|
|
async function dialog_kaereste_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('hej smukke');
|
|
await d.you('hej, jeg har lige tænkt på dig!');
|
|
await d.you('og derfor har jeg taget et sexet billede');
|
|
await d.you('#kaereste_foto');
|
|
await d.me('tak. Det har jeg savnet!', true);
|
|
await d.me('du ser stadig så ung ud.');
|
|
await d.me('Jeg tror at vi skal være forsigtige, fordi vores beskeder bliver overvåget');
|
|
await d.you('What the fuck??? Hvem overvåger os?????');
|
|
await d.me('Det kan være at udbyderen gør det, fordi de har lov til det');
|
|
await d.me("Men i fremtiden vil det faktisk blive obligatorisk at scanne beskeder i EU");
|
|
await d.me('Og hvis der er beskeder som "ligner" børneporno får politiet en kopi af dem');
|
|
await d.you('Jeg vil heller ikke have at politiet får mine nøgenbilleder. Det er min krop og jeg vil selv bestemme over den!');
|
|
await d.end('Din kæreste er offline nu');
|
|
}
|
|
|
|
async function dialog_kaereste_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('Hi beautiful');
|
|
await d.you('hi, I just thought about you');
|
|
await d.you("and that's why I have taken a sexy picture");
|
|
await d.you('#kaereste_foto');
|
|
await d.me('Thanks! I missed that!', true);
|
|
await d.me('you still look so young.');
|
|
await d.me('I think we have to be careful, because our messages are being read');
|
|
await d.you('What the fuck??? Who is watching us?????');
|
|
await d.me('It can be that the chat provider does this, because they are allowed to search for illegal material');
|
|
await d.me('But in the future it will also become mandatory to scan chat messages in the EU');
|
|
await d.me('And if a message looks like child pornography then the police will get a copy of it');
|
|
await d.you("I don't want to share my nudes with the police. This is my body and I decide who I share it with!");
|
|
await d.end('Your better half is offline now');
|
|
}
|
|
|
|
async function dialog_politi_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('Hej, jeg har et spørgsmål');
|
|
await d.me('Har du hørt om chatcontrollen og hvad tænker du om det?');
|
|
await d.you('Jeg er bekendt med det. Og det er faktisk en ret dårlig idé');
|
|
await d.you('Problemet er at der er så mange falske positiver som skal analyseres');
|
|
await d.you('Og jeg gider ikke kigge på folks private beskeder. Det er jo deres fortrolige samtaler');
|
|
await d.you('Jeg vil heller ikke have at mine kolleger ser hvad jeg skriver til min kæreste, bare fordi jeg hypotetisk kunne gøre noget illegalt');
|
|
await d.you('Tænk bare på hvor absurd idéen er: Hvorfor aflytter vi ikke bare alle telefoner? Der er altid forbrydere at fange!');
|
|
await d.you('Men så har vi masseovervågning og ikke længere et frit samfund.');
|
|
await d.end('Politibejenten er offline nu');
|
|
}
|
|
|
|
async function dialog_politi_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('Hi, I have a question');
|
|
await d.me('Have you heard about ChatControl and what do you think about it?');
|
|
await d.you('I know about it. It is actually a really bad idea');
|
|
await d.you('The problem is that there are many false positive hits that need to be analysed manually');
|
|
await d.you("And I do not want to read through people's private messages. These are confidential conversations");
|
|
await d.you("I don't like that my collegues can potentially see what I write to by partner, just because I could potentially do something illegal while chatting");
|
|
await d.you("Just think about how absurd the idea is: Why don't we wiretap all phones? There will be some crimes that we will detect!");
|
|
await d.you('But then we have total surveillance and no longer a free society.');
|
|
await d.end('The police officer is offline now');
|
|
}
|
|
|
|
async function dialog_support_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you('Velkommen til ChatWorld supporten. Hvad kan jeg hjælpe dig med?');
|
|
await d.me('Jeg mistænker at chatprogrammet scanner mine end-to-end krypterede beskeder. Er det rigtigt?');
|
|
await d.you('Ja, det er en feature som chefen gerne vil have.');
|
|
await d.you('Der er nogle som har misbrugt chattjenesten til børneporno og det giver meget dårlig PR. Derfor har chefen valgt at scanne end-to-end krypterede beskeder');
|
|
await d.you('børneporno er jo en meget dårlig ting. Men jeg synes ikke at det giver mening at aflytte alle private beskeder.');
|
|
await d.you('og teknologien kan misbruges til mange ting. Jeg som teknikker kan ikke se hvad søgefunktionen faktisk leder efter.');
|
|
await d.you('Det kan være at den kunne finde politiske beskeder og markere dem som illegale selv om de ikke er det.');
|
|
await d.you('Det er en fare for demokratiet hvis fortrolige beskeder bliver scannet for ting som den politiske elite ikke ønsker');
|
|
await d.you('Men det er desværre mit job at vedligeholde driften af overvågningen');
|
|
await d.me('Det løser ikke mit problem. Men tak for informationen. Jeg vil se hvad jeg kan gøre for at undgå overvågning i fremtiden', true);
|
|
await d.you('Beklager at jeg ikke kan gøre mere. Men jeg håber at du forstår situationen');
|
|
await d.end('Chatsupporten er offline nu');
|
|
}
|
|
|
|
async function dialog_support_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you('Welcome to the ChatWorld support. How can I help you?');
|
|
await d.me('I suspect that the chat program is scanning my end-to-end encrypted messages. Am I right?');
|
|
await d.you('Yes, that is a feature that our boss wanted to have');
|
|
await d.you('Some people have abused the chat service to share child pornography and that gives bad publicity. The boss has therefore chosen to scan all end-to-end encrypted messages');
|
|
await d.you("Child pornography is a really bad thing. But I don't think it is sensible to scan all messages.");
|
|
await d.you('and the scan technology can be abused for many things. I as a software engineer can not even see what the scanner is searching for');
|
|
await d.you("It can be that it will look for alternative political views and marks them as illegal even though they aren't");
|
|
await d.you('It is a danger to our democracy when confidential messages are scanned for things that the political elite does not like');
|
|
await d.you('But it is unfortunately my job to keep the surveillance working');
|
|
await d.me('That does not solve the problem. But thank you for the information. I will look for ways to avoid the surveillance in the future', true);
|
|
await d.you('I apologize that I can not offer something better. But I hope you understand the situation');
|
|
await d.end('The chat support is offline now');
|
|
}
|
|
|
|
async function dialog_nabopige_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you('Gæt hvem der snart har fødselsdag!');
|
|
await d.me('Er det dig?');
|
|
await d.you('Netop! Det var måske for nemt');
|
|
await d.me('Hvor gammel bliver du så?');
|
|
await d.you('ti');
|
|
await d.you('Jeg glæder mig så meget til festen. Jeg har inviteret en masse mennesker');
|
|
await d.you('Du er også inviteret!');
|
|
await d.you('Hey! Jeg vil gerne fortælle dig en hemmelighed');
|
|
await d.you('Men jeg kan desværre ikke gøre det over chatten');
|
|
await d.you('Fordi jeg ved at hvad vi skriver her bliver analyseret');
|
|
await d.you('Og det vil ikke være en hemmelighed hvis der er andre der læser med');
|
|
await d.me('Du kan fortælle mig hemligheden i morgen, når jeg kommer forbi', true);
|
|
await d.me('Jeg er overrasket over at du ved noget om chatovervågningen');
|
|
await d.you('Ja, det er trist. Jeg har læst noget om det og jeg kan overhovet ikke lide det');
|
|
await d.you('Det er bedre hvis man har gode voksne venner som man har tillid til, end hvis der er en computer som læser alt hvad man skriver');
|
|
await d.me('helt enig');
|
|
await d.me('børn har også brug for fortrolighed!');
|
|
await d.end('Pigen er afsted nu');
|
|
}
|
|
|
|
async function dialog_nabopige_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you("Guess who's birthday it is soon!");
|
|
await d.me("It's yours?");
|
|
await d.you('Right! That was maybe too easy');
|
|
await d.me('How old will you be?');
|
|
await d.you('ten');
|
|
await d.you('I am excited for the party. I have invited a lot of people');
|
|
await d.you('You are also invited!');
|
|
await d.you('Hi, I want to tell you a secret');
|
|
await d.you("But I just can't do that in the chat");
|
|
await d.you('Because what we write here is being analysed');
|
|
await d.you('And it is not going to be a secret when there are others who read the messages');
|
|
await d.me('You can tell me the secret tomorrow, when I come to your house', true);
|
|
await d.me('I am surprised that you know about the chat surveillance');
|
|
await d.you("Yes, it is stupid. I have read about it and I really don't like it");
|
|
await d.you('It is better to have adult friends whom I can trust that a computer who reads everything that I write.');
|
|
await d.me('Completely agree');
|
|
await d.me('Children also need their secrets and confidentiality');
|
|
await d.end('The girl is gone now');
|
|
}
|
|
|
|
async function dialog_erasmus_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you('Hej, jeg vil snart flytte igen!');
|
|
await d.me('Til hvilket land i denne gang?');
|
|
await d.you('Til tyskland. Jeg har fået en nyt job der.');
|
|
await d.me('Fedt! Det er godt at bo i EU, hvis man vil arbejde mange forskellige steder');
|
|
await d.you('Ja, det værdsætter jeg meget. Kan du huske der vi mødtes i Warszawa den første gang?');
|
|
await d.me('Ja, selvfølgelig!');
|
|
await d.you('Du kan sikkert huske Pedro fra denne tid. Han bliver gift i Rumænien næste måned!');
|
|
await d.me('Sejt! Jeg vil i hvert fald skrive til ham for at gratulere.');
|
|
await d.me('Det er så rart, at vi kender så mange mennesker fra hele EU.');
|
|
await d.me('Men, så vil EU kommissionen indføre at alle beskeder skal scannes for børnepornografi. Simplethen, fordi det er teoretisk muligt, at vores digitale samtaler er illegale.');
|
|
await d.you('Øv. Er det de samme politikere igen, som ikke er vokset op med internettet? ');
|
|
await d.you('Jeg bor i Europa. Men jeg bor også på nettet. Og der møder jeg min stor familie og mine gode venner som jeg har ikke set i årevis.');
|
|
await d.you('Det er internettet som gør det muligt at pleje mine personlige forhold over lange distancer.');
|
|
await d.you('Hvordan skal jeg forsætte med det, når mine samtaler bliver aflyttet som de gjorde det i den gamle Stasi-Østtyskland? ');
|
|
await d.you('Jeg kan ikke længere tale frit.');
|
|
await d.me('Ja, det passer overhoved ikke. Den moderne personlige samtale foregår faktisk over nettet i de fleste tilfælde i dag.');
|
|
await d.you('Denne ny overvågningslov er meget anti-europæisk, hvis du spørger mig');
|
|
await d.me('helt enig');
|
|
await d.end('Din gamle erasmusven er offline');
|
|
}
|
|
|
|
async function dialog_erasmus_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.you('Hi, I will soon move again!');
|
|
await d.me('To which country this time?');
|
|
await d.you('To Germany. I have gotten a job there.');
|
|
await d.me('Nice! The EU makes it possible to live and work at many different places.');
|
|
await d.you('Yes, I appreciate that very much. Do you remember how we meet in Warsaw for the first time?');
|
|
await d.me('Yes, of course!');
|
|
await d.you('You certainly remember Pedro from that time. He will get married in Romania next month!');
|
|
await d.me('Good to hear! I will definitely write to him to congratulate.');
|
|
await d.me('It is so nice that we know so many people from all over the EU.');
|
|
await d.me('However, the EU commission wants that all messages will be scanned for child porn. Simply because it is theoretically possible that our digital communication is illegal.');
|
|
await d.you('Ouch. Is this from the same politicians again, who have not grown up with the internet?');
|
|
await d.you('I live in Europe. But I also live on the internet. And that is where I meet my big family and my good friends who I haven\'t seen in years.');
|
|
await d.you('It is the internet that makes it possible for me to keep my personal connections alive over long distances.');
|
|
await d.you('How should I continue with this, when my conversations are being wiretapped like they did it in the old Stasi-East-Germany?');
|
|
await d.you('I can no longer speak freely.');
|
|
await d.me('Yes, this does not make any sense. Modern personal conversation happens online in most cases today.');
|
|
await d.you('That new surveillance law sounds very anti-european, if you ask me.');
|
|
await d.me('Totally agree');
|
|
await d.end('Your old Erasmus friend is offline');
|
|
}
|
|
|
|
async function dialog_nerd_da() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('Hej, har du hørt om chatcontrollen? Jeg undrer mig hvordan det vil fungere på et teknisk niveau');
|
|
await d.you('Ja, det har jeg.');
|
|
await d.you('Der er faktisk flere ting som skal identificeres:')
|
|
await d.you('1. Børneporno i billeder og film');
|
|
await d.you('2. Links til velkendte illegale hjemmesider');
|
|
await d.you('3. Illegale samtaler mellem voksne og børn');
|
|
await d.me('Nummer 2 er jo meget nemt! Det kræver bare at man tjekker adressen af linket!');
|
|
await d.you('Præcis! Det er trivialt at identificere. Nu tænk bare på abortdebatten i USA. Det vil være nemt at identificere folk som forsøger at finde informationer om abort.');
|
|
await d.me('Øv. Det er jo skræmmende. Jeg tænker lige på Polen. Der er nogle politikere som vil lide det meget!');
|
|
await d.you('Ja, og misbrugmulighederne er store.');
|
|
await d.me('Hvad er der så med børneporno i film og billeder?');
|
|
await d.you('Der findes systemer som fx. PhotoDNA fra Microsoft som bruger en proprietær algoritme for at beregne hashværdier til data.');
|
|
await d.you('Det vil sige, at, hvis du har en fil som indeholder data, så beregner softwaren en enkelte værdi af begrænset størrelse baseret på dataen. Værdien kalder man en "hash"');
|
|
await d.me('Hvis jeg har illegale filer, så vil softwaren genkende den på grundlag af hashværdien af filen?');
|
|
await d.you('Ja');
|
|
await d.you('Men der er et fundamentalt problem: Dataen kan være alle mulige størrelser mens hashværdien er altid begrænset i sin størrelse.');
|
|
await d.you('dvs. at en uendelig mængde af data bliver omregnet til en endelig mængde. Det fører til såkaldte kollisioner.', true);
|
|
await d.you('To helt forskellige filer kan have den samme hash.');
|
|
await d.you('Og dermed bliver man identificeret som pædofil selvom man ikke er det');
|
|
await d.me('Er det sket til nogen?');
|
|
await d.you('Ja, der er flere tilfælde, hvor adgange blev blokeret af Microsoft og Google.');
|
|
await d.you('Selvom Google og Microsoft er nogle af de bedste virksomheder når det kommer til kunstlig intelligens, er deres systemer langt fra perfekt.');
|
|
await d.you('Og de blokerer heller automatisk kontoer af uskyldige folk end at kigge på potentielt grimme børnepornografi');
|
|
await d.me('Vil det også være sådan med EU?');
|
|
await d.you('Nej. EU kommissionen har faktisk en anden plan: De vil oprette en center hvor der arbejder mennesker som kigger på mistænkelige chatindhold.');
|
|
await d.you('Og hvis mistanken er berettiget, så giver de informationen videre til Europol.');
|
|
await d.me('Så tror EU kommissionen heller ikke at systemet er så smart?');
|
|
await d.you('Åbenbart ikke. Og det gør det ikke bedre at der er folk som vil læse vores chatbeskeder, kun fordi en "smart" software siger at vi dyrker børnemisbrug', true);
|
|
await d.me('Hvad er den dumme "smarte" teknologi som skal bruges?');
|
|
await d.you('Ha! Det er en interessante historie');
|
|
await d.you('Der er en organisation som hedder "Thorn". Og de fremstiller en filtreringssoftware. En vigtig investor og lobbyist fra virksomheden hedder Ashton Kutcher. Han er tit på besøg på EU kommissionen for at sælge sin software');
|
|
await d.you('Og selvfølgelig påstår han at sin software kan på magiske måde løse børnemisbrugproblemer');
|
|
await d.you('Jeg hader det, når folk er så teknologitroende og køber alt den marketingnonsens');
|
|
await d.me('dvs at lovforeslaget er faktisk et resultat af kommercielt lobbyisme?');
|
|
await d.you('Det mener jeg');
|
|
await d.me('Og til sidst, hvad er med scanning af illegale samtaler?');
|
|
await d.you('Der er stadig ingen system som har bestået Turing-testen');
|
|
await d.you('Computer er stadig langt væk fra at forstå menneskelig sprog på det samme niveau som vi mennesker taler');
|
|
await d.you('Algorithme kan i nogle tilfælde identificere emnet der bliver talt om, men en virkelig forståelse findes ikke i den nuværende teknologi');
|
|
await d.end('Nørden genstarter sin computer');
|
|
}
|
|
|
|
async function dialog_nerd_en() {
|
|
let d = new Dialog();
|
|
current_dialog = d;
|
|
await d.me('Hi, have you heard about chatcontrol? I wonder how it will work on a technical level');
|
|
await d.you('Yes, I have heard about it');
|
|
await d.you('There are actually several things that have to be identified:')
|
|
await d.you('1. Childporn in pictures and movies');
|
|
await d.you('2. Links to know illegal websites');
|
|
await d.you('3. Illegal chats between adults and children');
|
|
await d.me('Number 2 sounds easy! You just check the address of the link!');
|
|
await d.you('Exactly! This is trivial. Now think about the abortion debate in the USA. It will be very easy to identify people who try to find information on abortions.');
|
|
await d.me('Ouch. That is horrible! I immediately think about Poland. There are some politicians who will like this a lot.');
|
|
await d.you('Yes, the potential for abuse is huge.');
|
|
await d.me('How does it work with childporn in movies and pictures?');
|
|
await d.you('There are systems like e.g. PhotoDNA from Microsoft which use algorithms to compute hash values of data.');
|
|
await d.you('That means if you have a file with data, the software will calculate a single value of limited size based on the data. This value is called a "hash"');
|
|
await d.me('If I have illegal files, then the software will identify it based on the hash value of the file?');
|
|
await d.you('Yes');
|
|
await d.you('But there is a fundamental problem: The data can be of any size while the hash value is always limited.');
|
|
await d.you('That means an infinite amount of data is projected into a finite amount of values. This leads to so-called collissions.', true);
|
|
await d.you('Two completely different files can have the same hash.');
|
|
await d.you('And thereby people get identified as pedophiles even though they are none');
|
|
await d.me('Has that happened to someone?');
|
|
await d.you('Yes, there are several cases, where the access has been blocked by Microsoft or Google.');
|
|
await d.you('Even though Google and Microsoft are some of the most advanced companies, when it comes to artificial intelligence, their systems are far from perfect.');
|
|
await d.you('And they rather block some accounts of innocent users instead of checking some potentially disgusting childporn.');
|
|
await d.me('Will the EU do the same?');
|
|
await d.you('No. The EU commission has actually a different plan: They will create a center where there are people who will look at suspicious chat contents.');
|
|
await d.you('And if the suspicion is justified, they will hand over the information to Europol.');
|
|
await d.me('That means the EU kommissionen doesn\'t actually think that the system is so smart?');
|
|
await d.you('Obviously not. But having people read our personal chat messages, just because some "smart" software says that we are child molesters, is not any better', true);
|
|
await d.me('What is the stupid "smart" technology that will be used?');
|
|
await d.you('Ha! That\'s an interesting story');
|
|
await d.you('There is an organisation called "Thorn". They produce software-based filters. An important investor and lobbyist from that organisation is Ashton Kutcher. He often visits the EU commission to promote his software');
|
|
await d.you('And of course he claims that his software can magically solve child abuse problems');
|
|
await d.you('I hate it when people are so blinding trusting technology and buy into all that marketing bullshit.');
|
|
await d.me('That means that the proposed law is actually the result of a comercial lobbying effort?');
|
|
await d.you('I think so');
|
|
await d.me('And lastly, what about scanning of illegal conversations?');
|
|
await d.you('No system has ever passed the Turing test');
|
|
await d.you('Computers are still far away from understanding human language on the same level as us humans');
|
|
await d.you('Algorithms can in some cases identify the topic of a conversation but there is no actual understanding behind the current technology');
|
|
await d.end('The nerd is rebooting');
|
|
}
|
|
|
|
|
|
const contact_list = {
|
|
"da": [
|
|
{name: "Ven",
|
|
dialog: dialog_ven_da},
|
|
{name: "Mor",
|
|
dialog: dialog_mor_da},
|
|
{name: "Kæreste ❤️",
|
|
dialog: dialog_kaereste_da},
|
|
{name: "Politiven",
|
|
dialog: dialog_politi_da},
|
|
{name: "Chat support",
|
|
dialog: dialog_support_da},
|
|
{name: "Nabopige",
|
|
dialog: dialog_nabopige_da},
|
|
{name: "Erasmus",
|
|
dialog: dialog_erasmus_da},
|
|
{name: "Nørd",
|
|
dialog: dialog_nerd_da}
|
|
],
|
|
"en": [
|
|
{name: "Friend",
|
|
dialog: dialog_ven_en},
|
|
{name: "Mom",
|
|
dialog: dialog_mor_en},
|
|
{name: "Sweetheart ❤️",
|
|
dialog: dialog_kaereste_en},
|
|
{name: "Police friend",
|
|
dialog: dialog_politi_en},
|
|
{name: "Chat support",
|
|
dialog: dialog_support_en},
|
|
{name: "Neighborhood girl",
|
|
dialog: dialog_nabopige_en},
|
|
{name: "Erasmus",
|
|
dialog: dialog_erasmus_en},
|
|
{name: "Nerd",
|
|
dialog: dialog_nerd_en}
|
|
]
|
|
};
|
|
|
|
function fill_contact_list(lang) {
|
|
let x = 15;
|
|
let y = 75;
|
|
let y_step = 25;
|
|
let group = document.getElementById('contacts');
|
|
removeAllChildren(group);
|
|
|
|
let contacts = contact_list[lang];
|
|
for(let i=0; i < contacts.length; i++) {
|
|
let text = create_svg_node('text', {
|
|
x: x,
|
|
y: y
|
|
});
|
|
y += y_step;
|
|
let contact = contacts[i];
|
|
text.onclick = function() { start_chat(i) };
|
|
text.appendChild(document.createTextNode(contact.name));
|
|
group.appendChild(text);
|
|
}
|
|
}
|
|
|
|
function run() {
|
|
fill_contact_list(current_language);
|
|
start_chat(current_discussion_index);
|
|
}
|
|
]]>
|
|
</script>
|
|
|
|
<!-- [right] contact name view -->
|
|
<g id="contact_name_box">
|
|
<rect x="300" y="0" width="300" height="50" fill="#F5F5F5"/>
|
|
<polyline points="300,20 320,0 320,40" fill="#0084FF" onclick="swipe_viewport()" id="to_contact_list_button" />
|
|
<polyline points="560,20 600,20 580,0" fill="#0084FF" onclick="scroll_up()" />
|
|
<polyline points="560,30 600,30 580,50" fill="#0084FF" onclick="scroll_down()" />
|
|
<text x="325" y="30" id="contact_indicator">loading...</text>
|
|
<use href="#flag_denmark" x="500" y="35" onclick="switch_language('da')" />
|
|
<g transform="scale(0.5,0.5)">
|
|
<use href="#flag_uk" x="1050" y="70" onclick="switch_language('en')" />
|
|
</g>
|
|
</g>
|
|
|
|
<!-- [right] messages view -->
|
|
<rect id="message_box" x="300" y="50" width="300" height="450" fill="white" />
|
|
<g id="messages"></g>
|
|
|
|
<!-- [right] message status -->
|
|
<rect id="message_status_box" x="300" y="520" fill="#FF7B00" width="300" height="30" />
|
|
<text id="message_status" x="310" y="540" fill="white">status</text>
|
|
|
|
<!-- [right] text input -->
|
|
<rect id="message_input_box" x="300" y="550" stroke="black" fill="white" width="250" height="50" onclick="return set_typing_speed" />
|
|
<rect id="message_submit_box" x="550" y="550" stroke="black" fill="#CCCCCC" width="50" height="50" />
|
|
<text id="message_input_data" x="310" y="575" fill="black"></text>
|
|
<text id="message_submit_button" x="555" y="580" fill="black">SEND</text>
|
|
|
|
<!-- [left] contact list -->
|
|
<rect x="0" y="0" width="300" height="50" fill="#E4E6EB" />
|
|
<text x="60" y="30">Dine kontakter</text>
|
|
<rect x="0" y="50" width="300" height="550" style="stroke: green; stroke-width: 0px" fill="white" />
|
|
|
|
<!-- contact list -->
|
|
<g id="contacts"></g>
|
|
|
|
</svg>
|