PageSourceSearch

https://www.grammatip.de/function

html grammatip.de collected 2026-10-01 23:52:18 UTC 19,927 bytes, 492 lines download raw bytes

1<!DOCTYPE html>
2<html lang="de">
3<head>
4
5    <meta charset="utf-8"/>
6    <meta name="viewport" content="width=device-width, initial-scale=1">
7    <meta name="description" content="Deutschunterricht. Einfach. Digital. - Durch einfache Zuweisung von Grammatikaufgaben und automatische Korrektur den Unterricht digital gestalten. Zeitsparend. Übersichtlich.">
8
9    <title>Grammatip</title>
10
11    
11<script src="/javascripts/cookie-helper.js?v=1655670100"></script>
11
12    <link rel="shortcut icon" href="/favicon.ico?v=1782478304" type="image/x-icon" />
13
14    <!-- PWA Start -->
15<!-- General -->
16<meta data-hid="mobile-web-app-capable" name="mobile-web-app-capable" content="yes">
17<meta data-hid="og:type" name="og:type" property="og:type" content="website">
18<meta data-hid="og:title" name="og:title" property="og:title" content="Grammatip">
19<meta data-hid="og:site_name" name="og:site_name" property="og:site_name" content="Grammatip">
20<link rel="icon" type="image/x-icon" href="/favicon.ico">
21<link data-hid="shortcut-icon" rel="shortcut icon" href="/android-chrome-192x192.png">
22
23<!-- Apple -->
24<meta data-hid="apple-mobile-web-app-title" name="apple-mobile-web-app-title" content="Grammatip">
25<meta name="theme-color" content="#2D57FC"/>
26<meta name="apple-mobile-web-app-status-bar" content="#2D57FC"/>
27<link data-hid="apple-touch-icon" rel="apple-touch-icon" href="/android-chrome-512x512.png" sizes="512x512">
28
29<!-- Windows -->
30<meta name="application-name" content="Grammatip"/>
31<meta name="msapplication-TileImage" content="/android-chrome-512x512.png">
32<meta name="msapplication-TileColor" content="#2D57FC">
33
34<!-- Manifest -->
35<link rel="manifest" href="/manifest.json" data-hid="manifest" crossorigin="use-credentials">
36
37<!-- Service worker -->
38<script>
39    
40    window.serviceWorkerSupported = true;
41
42    function isIOS16() {
43        return (
44                navigator.appVersion.includes('Version/16') ||
45                navigator.appVersion.includes('OS 16')
46            ) && (
47                /iPhone/.test(navigator.userAgent) ||
48                /iPad/.test(navigator.userAgent) ||
49                /iPod/.test(navigator.userAgent) ||
50                /Macintosh/.test(navigator.userAgent)
51            );
52    }
53
54
55    /**
56     * @param tryOnce
57     * @returns {Promise<void|ServiceWorker|*>}
58     *
59     * @see https://stackoverflow.com/a/67612740
60     */
61    async function registerServiceWorker(tryOnce = false) {
62        if (isIOS16()) {
63            window.serviceWorkerSupported = false;
64
65            try {
66                const reg = await navigator.serviceWorker.getRegistration('/');
67                await reg.unregister();
68            } catch (err) {
69                return;
70            }
71
72            return;
73        }
74
75        if (!('serviceWorker' in navigator)) {
76            window.serviceWorkerSupported = false;
77            throw new Error('serviceWorker not supported');
78        }
79
80        const url = (new URL(`/serviceWorker.js?v=g8534h`, location)).toString();
81        //console.info('Registering worker');
82        const registration = await navigator.serviceWorker.register(url, {
83            scope: '/',
84        });
85
86        const registeredWorker = registration.active || registration.waiting || registration.installing;
87        //console.info('Registered worker:', registeredWorker);
88        if (registeredWorker?.scriptURL != url) {
89            console.log('[ServiceWorker] Old URL:', registeredWorker?.scriptURL || 'none', 'updating to:', url);
90            await registration.update();
91            //console.info('Updated worker');
92        }
93
94        //console.info('Waiting for ready worker');
95        let serviceReg = await navigator.serviceWorker.ready;
96        //console.info('Ready registration:', serviceReg);
97
98        if (!navigator.serviceWorker.controller) {
99            //console.info('Worker isn’t controlling, re-register');
100            try {
101                const reg = await navigator.serviceWorker.getRegistration('/');
102                //console.info('Unregistering worker');
103                await reg.unregister();
104                //console.info('Successfully unregistered, trying registration again');
105                return registerServiceWorker();
106            } catch (err) {
107                window.serviceWorkerSupported = false;
108                console.error(`ServiceWorker failed to re-register after hard-refresh, reloading the page!`, err);
109                return location.reload();
110            }
111        }
112
113        let serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing;
114        if (!serviceWorker) {
115            //console.info('No worker on registration, getting registration again');
116            serviceReg = await navigator.serviceWorker.getRegistration('/');
117            serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing;
118        }
119
120        if (!serviceWorker) {
121            //console.info('No worker on registration, waiting 50ms');
122            await sleep(50); // adjustable or skippable, have a play around
123        }
124
125        serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing;
126        if (!serviceWorker) {
127            window.serviceWorkerSupported = false;
128            throw new Error('after waiting on .ready, still no worker');
129        }
130
131        if (serviceWorker.state == 'redundant') {
132            //console.info('Worker is redundant, trying again');
133            return registerServiceWorker();
134        }
135
136        if (serviceWorker.state != 'activated') {
137            //console.info('Worker IS controlling, but not active yet, waiting on event. state=', serviceWorker.state);
138            try {
139                // timeout is adjustable, but you do want one in case the statechange
140                // doesn't fire / with the wrong state because it gets queued,
141                // see ServiceWorker.onstatechange MDN docs.
142                await timeout(100, new Promise((resolve) => {
143                    serviceWorker.addEventListener('statechange', (e) => {
144                        if (e.target.state == 'activated') resolve();
145                    });
146                }));
147            } catch (err) {
148                if (err instanceof TimeoutError) {
149                    if (serviceWorker.state != 'activated') {
150                        if (tryOnce) {
151                            //console.info('Worker is still not active. state=', serviceWorker.state);
152                            window.serviceWorkerSupported = false;
153                            throw new Error('failed to activate service worker');
154                        } else {
155                            //console.info('Worker is still not active, retrying once');
156                            return registerServiceWorker(true);
157                        }
158                    }
159                } else {
160                    // should be unreachable
161                    throw err;
162                }
163            }
164        }
165
166        //console.info('Worker is controlling and active, we’re good folks!');
167        return serviceWorker;
168    }
169
170    class TimeoutError extends Error {
171    }
172
173    /**
174     * Run promise but reject after some timeout.
175     *
176     * @template T
177     * @param {number} ms Milliseconds until timing out
178     * @param {Promise<T>} promise Promise to run until timeout (note that it will keep running after timeout)
179     * @returns {Promise<T, Error>}
180     */
181    function timeout(ms, promise) {
182        return new Promise((resolve, reject) => {
183            const timer = setTimeout(() => {
184                reject(new TimeoutError);
185            }, ms);
186
187            promise.then((result) => {
188                clearTimeout(timer);
189                resolve(result);
190            }, (error) => {
191                clearTimeout(timer);
192                reject(error);
193            });
194        })
195    }
196
197    registerServiceWorker()
198    
199</script>
199
200<!-- PWA End -->
201
202    
203        <link rel="stylesheet" href="/assets/new/css/web-app.css?id=be3c8f791d151789a2b9627dc861ecbe" type="text/css">
204    
205    
206
207    
208<script>
209    var analyticsCookies = ['_gcl_au', '_ga', '_gid', '_gat_UA-630585-12'];
210    for (var i in analyticsCookies) {
211        if (!getCookieValue(analyticsCookies[i])) {
212            continue;
213        }
214
215        document.cookie = analyticsCookies[i] + '=; path=/; domain=; expires=Thu, 08 Jan 1970 00:00:01 GMT';
216    }
217</script>
217
218
219</head>
220<body class="landing-page-body ">
221
222<div id="vue-app">
223    <cookies-consent-popup></cookies-consent-popup>
224    
225    
226<div class="home-container">
227    <nav class="navbar sticky-top bg-white landing-page-header">
228    <div class="container-xxl">
229        <div class="navbar-header">
230            <a class="navbar-brand" href="/">
231                <span class="logo">
232                    <span class="d-none d-md-inline logo-text">grammatip.de</span>
233                </span>
234            </a>
235        </div>
236        <div class="nav">
237            <div class="nav-item d-none d-xl-block">
238                <a class="nav-link active" href="/function">So funktioniert Grammatip</a>
239            </div>
240            <div class="nav-item d-none d-xl-block">
241                <a class="nav-link " href="/blog">Blog</a>
242            </div>
243            <div class="nav-item">
244                <div class="nav-link">
245                    <a class="btn btn-outline-primary btn-lg btn-teardrop d-none d-sm-block" href="/register-school">Nutzerkonto anlegen</a>
246                    <a class="btn btn-outline-primary btn-lg btn-teardrop d-sm-none" href="/register-school">Konto anlegen</a>
247                </div>
248            </div>
249            <div class="nav-item">
250                <div class="nav-link">
251                    <a class="btn btn-primary btn-lg btn-teardrop d-none d-sm-block" href="/login">Einloggen</a>
252                    <a class="btn btn-primary btn-lg btn-teardrop d-sm-none" href="/login">Einloggen</a>
253                </div>
254            </div>
255            <div class="nav-item d-xl-none">
256                <div class="nav-link">
257                    <div class="dropdown lp-header-dropdown">
258                        <a class="btn btn-lg btn-icon dropdown-toggle flex-column menu-button" href="#" id="navbarHeaderDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
259                            <img src="/images/icons/dropdown-icon.svg" alt="">
260                            <span class="menu-text">MENÜ</span>
261                        </a>
262
263                        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarHeaderDropdown">
264                            <li><a class="dropdown-item" href="/function">So funktioniert Grammatip</a></li>
265                            <li><a class="dropdown-item" href="/blog">Blog</a></li>
266                        </ul>
267                    </div>
268                </div>
269            </div>
270        </div>
271    </div>
272</nav>
273
274    <main>
275        <div id="functions" class="container-xxl home-page-container">
276            <div class="grammatik-online-section">
277                <h2 class="text-32 text-sm-44 text-xl-60 lh-sm fw-bold"><span class="text-primary">So funktioniert</span><br> Grammatip</h2>
278            </div>
279            <landing-page-functions></landing-page-functions>
280
281            <div class="image-container">
282                <img class="image-one" src="/assets/images/landing-page/image_one.png" alt="" loading="lazy">
283                <img class="image-two" src="/assets/images/landing-page/image_two.png" alt="" loading="lazy">
284                <img class="image-three" src="/assets/images/landing-page/image_three.png" alt="" loading="lazy">
285            </div>
286            <div class="grammatiger">
287                <img class="grammatiger-jump" src="/assets/images/grammatiger/grammatiger_jump.png" alt="Grammatiger" loading="lazy">
288                <img class="grammatiger-left-hand" src="/assets/images/grammatiger/grammatiger_left_hand.png" alt="Grammatiger left hand" loading="lazy">
289                <img class="grammatiger-right-hand" src="/assets/images/grammatiger/grammatiger_right_hand.png" alt="Grammatiger right hand" loading="lazy">
290            </div>
291
292        </div>
293    </main>
294    <footer>
295    <div class="top-footer">
296        <div class="container-xxl">
297            <div class="row landing-page-content gap-3">
298                <div class="col-auto d-flex flex-column mb-3">
299                    <a href="/">
300                        <img class="grammatip-logo me-4" src="/assets/images/landing-page/grammatip.de.svg" alt="grammatip.com logo" loading="lazy">
301                    </a>
302                    <div class="d-md-flex d-none mt-1 flex-lg-row flex-wrap">
303                        <a class="btn btn-outline-primary btn-lg btn-teardrop text-nowrap me-3 mt-3 mb-lg-0" href="/function">So funktioniert Grammatip</a>
304                        <a class="btn btn-outline-primary btn-lg btn-teardrop text-nowrap me-3 mt-3 mb-lg-0" href="/blog">Blog</a>
305                    </div>
306                </div>
307                <div class="col flex-column mb-3 d-none d-xxl-flex">
308                    <a class="btn btn-outline-primary btn-lg btn-teardrop text-nowrap me-3 mt-3 mb-lg-0 ms-auto" href="/register-school">Konto anlegen</a>
309                    <a class="btn btn-outline-primary btn-lg btn-teardrop text-nowrap me-3 mt-3 mb-lg-0 ms-auto" href="/login">Einloggen</a>
310                </div>
311                <div class="col-auto ms-xl-auto">
312                    <div class="row contact-info">
313                        <div class="col me-3 mb-3 pe-3">
314                            <p class="fw-semibold">Kontakt:</p>
315                            <p>
316                                <a href="mailto:[email protected]">[email protected]</a>
317                            </p>
318                        </div>
319                        <div class="col">
320                            <ul class="policies">
321                                <li>
322                                    <a href="/policies/privacy">Datenschutz</a>
323                                </li>
324                                <li>
325                                    <a href="/grammatip_de_avv.pdf" target="_blank" download>Datenverarbeitungsvertrag (AVV)</a>
326                                </li>
327                                <li>
328                                    <a href="/policies/terms">AGB</a>
329                                </li>
330                                <li>
331                                    <a href="/policies/impressum">Impressum</a>
332                                </li>
333                                <li>
334                                    <span class="open-cookie-popup cursor-pointer">Cookie Einstellungen</span>
335                                </li>
336                            </ul>
337                        </div>
338                    </div>
339                </div>
340            </div>
341        </div>
342    </div>
343
344    <div class="bottom-footer">
345        <div class="container-xxl">
346            <div class="row flex-lg-row landing-page-content">
347                <div class="col-auto d-flex align-items-center mb-2 mb-sm-0">
348                    <p>Entwickelt von Lemma GmbH</p>
349                </div>
350                <div class="col-auto d-flex justify-content-lg-end align-items-center ms-lg-auto">
351                    <p>Lemma GmbH | Ericusspitze 4 | D-20457 Hamburg</p>
352                </div>
353            </div>
354        </div>
355    </div>
356</footer>
357    <call-me></call-me>
358</div>
359
360
361    </div>
362
363
364    
364<script>
365        window.COOKIE_DOMAIN = '.grammatip.de';
366        window.isPWA = ('' === '1');
367    </script>
367
368    
368<script src="/assets/new/js/manifest.js?id=d4bcf00853dad0a3d96b3597ed8ec186"></script>
368
369    
369<script src="/assets/new/js/vendor.js?id=a19c435f8194eff597d954c4ab255344"></script>
369
370    
370<script src="/assets/new/js/app.js?id=4377efb2389f3ca32b5da6ebbf1fa628"></script>
370
371    
371<script>
372    setInterval(function () {
373        var xmlHttp = new XMLHttpRequest();
374        xmlHttp.open('GET', '/api/v1/keep-alive?time=' + Date.now(), true);
375        xmlHttp.send(null);
376    }, 300 * 1000);
377</script>
377
378    
378<script>
379    // Sets the timeout variable to use later
380    let timeoutId;
381
382    // Listen for the window: online event
383    window.addEventListener('online', async (e) => {
384        timeoutId = setTimeout(loop, 2000);
385    });
386
387    // Loop function to be called every 2 seconds, via setTimeout
388    async function loop() {
389        // "Ping" the server and wait for response.
390        let status = await ping()
391        if (status === 200) {
392            // Make new notification
393            let notification = {
394                detail: {message: "Internetverbindung ist wieder hergestellt.", type: 'success'},
395            };
396
397            // Launch the notification
398            const event = new CustomEvent('add-notification', notification);
399            window.dispatchEvent(event);
400            // Stops the looping
401            clearTimeout(timeoutId)
402        } else {
403            // Run the loop if the "ping" failed
404            timeoutId = setTimeout(loop, 2000);
405        }
406    }
407
408    // "ping" the current website
409    function ping() {
410        let URL = window.location.origin;
411        // Defines the configurations for the request
412        let settings = {
413            url: URL,
414            method: "HEAD",
415            timeout: 5000,
416            // Minimizing the data send
417            maxContentLength: 1,
418            maxBodyLength: 1,
419            transformResponse: [function (data, headers) {
420                // Return empty string, because we are not interested in any data
421                return "";
422            }],
423            headers: {
424                accept: "application/json",
425            },
426        };
427
428        // Sends the request and the returned data is set to only be the status code
429        // Also catches and logs any error that might occur
430        return axios(settings)
431            .then((response) => this.status = response.status)
432            .catch((error) => console.log(error.message));
433    }
434
435
436    // Listens to offline event and sends a notification
437    window.addEventListener('offline', (e) => {
438        clearTimeout(timeoutId)
439        let notification = {
440            detail: {message: "Es besteht momentan keine Internetverbindung.", type: 'error'},
441        };
442
443        const event = new CustomEvent('add-notification', notification);
444
445        window.dispatchEvent(event);
446    });
447
448</script>
448
449
450    
451    <!-- Matomo -->
452    
452<script type="text/javascript">
453        var consentCookie = JSON.parse(getCookieValue('COOKIE_CONSENT'));
454                var siteId = '4'; // Prod site id
455        
456        var _paq = window._paq = window._paq || [];
457        if (!consentCookie || consentCookie.statistics != true) {
458            _paq.push(['requireCookieConsent']); // Require cookie consent before adding cookies
459        }
460
461        /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
462        
463                
464        _paq.push(['trackPageView']);
465        _paq.push(['enableLinkTracking']);
466        _paq.push(['enableHeartBeatTimer', 30]);
467        (function () {
468            var u = "//analytics.grammatip.com/";
469            _paq.push(['setTrackerUrl', u + 'matomo.php']);
470            _paq.push(['setSiteId', siteId]);
471            var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
472            g.type = 'text/javascript';
473            g.async = true;
474            g.src = u + 'matomo.js';
475            s.parentNode.insertBefore(g, s);
476        })();
477    </script>
477
478    <!-- End Matomo Code -->
479
480
481    
481<script>
482        // enable (bootstrap) tooltips
483        if (!window.isTouchDevice) {
484            var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
485            var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
486                return new bootstrap.Tooltip(tooltipTriggerEl);
487            })
488        }
489    </script>
489
490
491</body>
492</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.