text:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Oros
# Contributors :
#  purwowd
#  puyoulu
#  1kali2kali
#  petterreinholdtsen
#  nicoeg
#  dspinellis
#  fdl <Frederic.Lehobey@proxience.com>
#  lapolis
# 2020-08-15
# License : CC0 1.0 Universal

"""
This program shows you IMSI numbers of cellphones around you.

/! This program was made to understand how GSM network work. Not for bad hacking !
"""

import ctypes
import json
from optparse import OptionParser
import datetime
import io
import socket

imsitracker = None

class tracker:
    imsistate = {}
    # phones
    imsis = []  # [IMSI,...]
    tmsis = {}  # {TMSI:IMSI,...}
    nb_IMSI = 0  # count the number of IMSI

    mcc = ""
    mnc = ""
    lac = ""
    cell = ""
    country = ""
    brand = ""
    operator = ""

    # in minutes
    purgeTimer = 10  # default 10 min

    show_all_tmsi = False
    mcc_codes = None
    sqlite_con = None
    mysql_con = None
    mysql_cur = None
    textfilePath = None
    output_function = None

    def __init__(self):
        self.load_mcc_codes()
        self.track_this_imsi("")
        self.output_function = self.output

    def set_output_function(self, new_output_function):
        # New output function need this field :
        # cpt, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell, timestamp, packet=None
        self.output_function = new_output_function

    def track_this_imsi(self, imsi_to_track):
        self.imsi_to_track = imsi_to_track
        self.imsi_to_track_len = len(imsi_to_track)

    # return something like '0xd9605460'
    def str_tmsi(self, tmsi):
        if tmsi != "":
            new_tmsi = "0x"
            for a in tmsi:
                c = hex(a)
                if len(c) == 4:
                    new_tmsi += str(c[2]) + str(c[3])
                else:
                    new_tmsi += "0" + str(c[2])
            return new_tmsi
        else:
            return ""

    def decode_imsi(self, imsi):
        new_imsi = ''
        for a in imsi:
            c = hex(a)
            if len(c) == 4:
                new_imsi += str(c[3]) + str(c[2])
            else:
                new_imsi += str(c[2]) + "0"

        mcc = new_imsi[1:4]
        mnc = new_imsi[4:6]
        return new_imsi, mcc, mnc

    # return something like
    # '208 20 1752XXXXXX', 'France', 'Bouygues', 'Bouygues Telecom'
    def str_imsi(self, imsi, packet=""):
        new_imsi, mcc, mnc = self.decode_imsi(imsi)
        country = ""
        brand = ""
        operator = ""

        if mcc in self.mcc_codes:
            if mnc in self.mcc_codes[mcc]:
                brand, operator, country, _ = self.mcc_codes[mcc][mnc]
                new_imsi = f"{mcc} {mnc} {new_imsi[6:]}"
            elif mnc + new_imsi[6:7] in self.mcc_codes[mcc]:
                mnc += new_imsi[6:7]
                brand, operator, country, _ = self.mcc_codes[mcc][mnc]
                new_imsi = f"{mcc} {mnc} {new_imsi[7:]}"

        else:
            country = f"Unknown MCC {mcc}"
            brand = f"Unknown MNC {mnc}"
            operator = f"Unknown MNC {mnc}"
            new_imsi = f"{mcc} {mnc} {new_imsi[6:]}"

        try:
            return new_imsi, country, brand, operator
        except Exception:
            # m = ""
            print("Error", packet, new_imsi, country, brand, operator)
        return "", "", "", ""

    def load_mcc_codes(self):
        # mcc codes form https://en.wikipedia.org/wiki/Mobile_Network_Code
        with io.open('mcc-mnc/mcc_codes.json', 'r', encoding='utf8') as file:
            self.mcc_codes = json.load(file)

    def current_cell(self, mcc, mnc, lac, cell):
        brand = ""
        operator = ""
        country = ""
        if mcc in self.mcc_codes and mnc in self.mcc_codes[mcc]:
            brand, operator, country, _ = self.mcc_codes[mcc][mnc]
        else:
            country = f"Unknown MCC {mcc}"
            brand = f"Unknown MNC {mnc}"
            operator = f"Unknown MNC {mnc}"
        self.mcc = str(mcc)
        self.mnc = str(mnc)
        self.lac = str(lac)
        self.cell = str(cell)
        self.country = country
        self.brand = brand
        self.operator = operator

    def sqlite_file(self, filename):
        import sqlite3  # Avoid pulling in sqlite3 when not saving
        print("Saving to SQLite database in %s" % filename)
        self.sqlite_con = sqlite3.connect(filename)
        self.sqlite_con.text_factory = str
        # FIXME Figure out proper SQL type for each attribute
        self.sqlite_con.execute("CREATE TABLE IF NOT EXISTS observations(stamp datetime, tmsi1 text, tmsi2 text, imsi text, imsicountry text, imsibrand text, imsioperator text, mcc integer, mnc integer, lac integer, cell integer);")

    def text_file(self, filename):
        txt = open(filename, "w")
        txt.write("stamp, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell\n")
        txt.close()
        self.textfilePath = filename

    def mysql_file(self):
        import os.path
        if os.path.isfile('.env'):
            import MySQLdb as mdb
            from decouple import config
            self.mysql_con = mdb.connect(config("MYSQL_HOST"), config("MYSQL_USER"), config("MYSQL_PASSWORD"), config("MYSQL_DB"))
            self.mysql_cur = self.mysql_con.cursor()
            # Check MySQL connection
            if self.mysql_cur:
                print("mysql connection is success :)")
            else:
                print("mysql connection is failed!")
                exit()
        else:
            print("create file .env first")
            exit()

    def output(self, cpt, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell, now, packet=None):
        print(f"{str(cpt):7s} ; {tmsi1:10s} ; {tmsi2:10s} ; {imsi:17s} ; {imsicountry:16s} ; {imsibrand:14s} ; {imsioperator:21s} ; {str(mcc):4s} ; {str(mnc):5s} ; {str(lac):6s} ; {str(cell):6s} ; {now.isoformat():s}")

    def pfields(self, cpt, tmsi1, tmsi2, imsi, mcc, mnc, lac, cell, packet=None):
        imsicountry = ""
        imsibrand = ""
        imsioperator = ""
        if imsi:
            imsi, imsicountry, imsibrand, imsioperator = self.str_imsi(imsi, packet)
        else:
            imsi = ""
        now = datetime.datetime.now()
        self.output_function(cpt, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell, now, packet)

        if self.textfilePath:
            now = datetime.datetime.now()
            txt = open(self.textfilePath, "a")
            txt.write(f"{str(now)}, {tmsi1}, {tmsi2}, {imsi}, {imsicountry}, {imsibrand}, {imsioperator}, {mcc}, {mnc}, {lac}, {cell}\n")
            txt.close()

        if tmsi1 == "":
            tmsi1 = None
        if tmsi2 == "":
            tmsi2 = None

        if self.sqlite_con:
            self.sqlite_con.execute(
               u"INSERT INTO observations (stamp, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
               (now, tmsi1, tmsi2, imsi, imsicountry, imsibrand, imsioperator, mcc, mnc, lac, cell)
            )
            self.sqlite_con.commit()
            pass

        if self.mysql_cur:
            print("saving data to db...")
            # Example query
            query = ("INSERT INTO `imsi` (`tmsi1`, `tmsi2`, `imsi`,`mcc`, `mnc`, `lac`, `cell_id`, `stamp`, `deviceid`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)")
            arg = (tmsi1, tmsi2, imsi, mcc, mnc, lac, cell, now, "rtl")
            self.mysql_cur.execute(query, arg)
            self.mysql_con.commit()

    def header(self):
        print(f"{'Nb IMSI':7s} ; {'TMSI-1':10s} ; {'TMSI-2':10s} ; {'IMSI':17s} ; {'country':16s} ; {'brand':14s} ; {'operator':21s} ; {'MCC':4s} ; {'MNC':5s} ; {'LAC':6s} ; {'CellId':6s} ; {'Timestamp':s}")

    def register_imsi(self, arfcn, imsi1="", imsi2="", tmsi1="", tmsi2="", p=""):
        do_print = False
        n = ''
        tmsi1 = self.str_tmsi(tmsi1)
        tmsi2 = self.str_tmsi(tmsi2)
        if imsi1:
            self.imsi_seen(imsi1, arfcn)
        if imsi2:
            self.imsi_seen(imsi2, arfcn)
        if imsi1 and (not self.imsi_to_track or imsi1[:self.imsi_to_track_len] == self.imsi_to_track):
            if imsi1 not in self.imsis:
                # new IMSI
                do_print = True
                self.imsis.append(imsi1)
                self.nb_IMSI += 1
                n = self.nb_IMSI
            if self.tmsis and tmsi1 and (tmsi1 not in self.tmsis or self.tmsis[tmsi1] != imsi1):
                # new TMSI to an ISMI
                do_print = True
                self.tmsis[tmsi1] = imsi1
            if self.tmsis and tmsi2 and (tmsi2 not in self.tmsis or self.tmsis[tmsi2] != imsi1):
                # new TMSI to an ISMI
                do_print = True
                self.tmsis[tmsi2] = imsi1

        if imsi2 and (not self.imsi_to_track or imsi2[:self.imsi_to_track_len] == self.imsi_to_track):
            if imsi2 not in self.imsis:
                # new IMSI
                do_print = True
                self.imsis.append(imsi2)
                self.nb_IMSI += 1
                n = self.nb_IMSI
            if self.tmsis and tmsi1 and (tmsi1 not in self.tmsis or self.tmsis[tmsi1] != imsi2):
                # new TMSI to an ISMI
                do_print = True
                self.tmsis[tmsi1] = imsi2
            if self.tmsis and tmsi2 and (tmsi2 not in self.tmsis or self.tmsis[tmsi2] != imsi2):
                # new TMSI to an ISMI
                do_print = True
                self.tmsis[tmsi2] = imsi2

                # Unreachable or rarely reached branch? Add unit-test.
        if not imsi1 and not imsi2 and tmsi1 and tmsi2:
            if self.tmsis and tmsi2 in self.tmsis:
                # switch the TMSI
                do_print = True
                imsi1 = self.tmsis[tmsi2]
                self.tmsis[tmsi1] = imsi1
                del self.tmsis[tmsi2]

        if do_print:
            if imsi1:
                self.pfields(str(n), tmsi1, tmsi2, imsi1, str(self.mcc), str(self.mnc), str(self.lac), str(self.cell), p)
            if imsi2:
                self.pfields(str(n), tmsi1, tmsi2, imsi2, str(self.mcc), str(self.mnc), str(self.lac), str(self.cell), p)

        if not imsi1 and not imsi2:
            # Register IMSI as seen if a TMSI believed to
            # belong to the IMSI is seen.
            if self.tmsis and tmsi1 and tmsi1 in self.tmsis and "" != self.tmsis[tmsi1]:
                self.imsi_seen(self.tmsis[tmsi1], arfcn)
            if self.show_all_tmsi:
                do_print = False
                if tmsi1 and tmsi1 not in self.tmsis:
                    do_print = True
                    self.tmsis[tmsi1] = ""
                if tmsi1 and tmsi1 not in self.tmsis:
                    do_print = True
                    self.tmsis[tmsi2] = ""
                if do_print:
                    self.pfields(str(n), tmsi1, tmsi2, None, str(self.mcc), str(self.mnc), str(self.lac), str(self.cell), p)

    def imsi_seen(self, imsi, arfcn):
        now = datetime.datetime.utcnow().replace(microsecond=0)
        imsi, mcc, mnc = self.decode_imsi(imsi)
        if imsi in self.imsistate:
            self.imsistate[imsi]["lastseen"] = now
        else:
            self.imsistate[imsi] = {
                "firstseen": now,
                "lastseen": now,
                "imsi": imsi,
                "arfcn": arfcn,
            }
        self.imsi_purge_old()

    def imsi_purge_old(self):
        now = datetime.datetime.utcnow().replace(microsecond=0)
        maxage = datetime.timedelta(minutes=self.purgeTimer)
        limit = now - maxage
        remove = [imsi for imsi in self.imsistate if limit > self.imsistate[imsi]["lastseen"]]
        for k in remove:
            del self.imsistate[k]
        # keys = self.imsistate.keys()
        # for imsi in keys:
        #   if limit > self.imsistate[imsi]["lastseen"]:
        #       del self.imsistate[imsi]
        #       keys = self.imsistate.keys()

class gsmtap_hdr(ctypes.BigEndianStructure):
    _pack_ = 1
    # Based on gsmtap_hdr structure in <grgsm/gsmtap.h> from gr-gsm
    _fields_ = [
        ("version", ctypes.c_ubyte),
        ("hdr_len", ctypes.c_ubyte),
        ("type", ctypes.c_ubyte),
        ("timeslot", ctypes.c_ubyte),
        ("arfcn", ctypes.c_uint16),
        ("signal_dbm", ctypes.c_ubyte),
        ("snr_db", ctypes.c_ubyte),
        ("frame_number", ctypes.c_uint32),
        ("sub_type", ctypes.c_ubyte),
        ("antenna_nr", ctypes.c_ubyte),
        ("sub_slot", ctypes.c_ubyte),
        ("res", ctypes.c_ubyte),
    ]

    def __repr__(self):
        return "%s(version=%d, hdr_len=%d, type=%d, timeslot=%d, arfcn=%d, signal_dbm=%d, snr_db=%d, frame_number=%d, sub_type=%d, antenna_nr=%d, sub_slot=%d, res=%d)" % (
            self.__class__, self.version, self.hdr_len, self.type,
            self.timeslot, self.arfcn, self.signal_dbm, self.snr_db,
            self.frame_number, self.sub_type, self.antenna_nr, self.sub_slot,
            self.res,
        )

# return mcc mnc, lac, cell, country, brand, operator
def find_cell(gsm, udpdata, t=None):
    # find_cell() update all following variables
    global mcc
    global mnc
    global lac
    global cell
    global country
    global brand
    global operator

    """
    Dump of a packet from wireshark

    /! there are an offset of 0x2a
    0x12 (from the code) + 0x2a (offset) == 0x3c (in documentation's dump)

            0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
    0010   00 43 9a 6b 40 00 40 11 a2 3c 7f 00 00 01 7f 00
    0020   00 01 ed d1 12 79 00 2f fe 42 02 04 01 00 00 00
    0030   cc 00 00 07 9b 2c 01 00 00 00 49 06 1b 61 9d 02
    0040   f8 02 01 9c c8 03 1e 53 a5 07 79 00 00 80 01 40
    0050   db

    Channel Type: BCCH (1)
                            6
    0030                     01

    0x36 - 0x2a = position p[0x0c]

    Message Type: System Information Type 3
                                                c
    0030                                       1b

    0x3c - 0x2a = position p[0x12]

    Cell CI: 0x619d (24989)
                                                d  e
    0030                                          61 9d

    0x3d - 0x2a = position p[0x13]
    0x3e - 0x2a = position p[0x14]

    Location Area Identification (LAI) - 208/20/412
    Mobile Country Code (MCC): France (208) 0x02f8
    Mobile Network Code (MNC): Bouygues Telecom (20) 0xf802
    Location Area Code (LAC): 0x019c (412)
            0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    0030                                                02
    0040   f8 02 01 9c
    """
    if gsm.sub_type == 0x01:  # Channel Type == BCCH (0)
        p = bytearray(udpdata)
        if p[0x12] == 0x1b:  # (0x12 + 0x2a = 0x3c) Message Type: System Information Type 3
            # FIXME
            m = hex(p[0x15])
            if len(m) < 4:
                mcc = m[2] + '0'
            else:
                mcc = m[3] + m[2]
            mcc += str(p[0x16] & 0x0f)

            # FIXME not works with mnc like 005 or 490
            m = hex(p[0x17])
            if len(m) < 4:
                mnc = m[2] + '0'
            else:
                mnc = m[3] + m[2]

            lac = p[0x18] * 256 + p[0x19]
            cell = p[0x13] * 256 + p[0x14]
            t.current_cell(mcc, mnc, lac, cell)

def find_imsi(udpdata, t=None):
    if t is None:
        t = imsitracker

    # Create object representing gsmtap header in UDP payload
    gsm = gsmtap_hdr.from_buffer_copy(udpdata)

    if gsm.sub_type == 0x1:  # Channel Type == BCCH (0)
        # Update global cell info if found in package
        # FIXME : when you change the frequency, this informations is
        # not immediately updated.  So you could have wrong values when
        # printing IMSI :-/
        find_cell(gsm, udpdata, t=t)
    else:  # Channel Type != BCCH (0)
        p = bytearray(udpdata)
        tmsi1 = ""
        tmsi2 = ""
        imsi1 = ""
        imsi2 = ""
        if p[0x12] == 0x21:  # Message Type: Paging Request Type 1
            if p[0x14] == 0x08 and (p[0x15] & 0x1) == 0x1:  # Channel 1: TCH/F (Full rate) (2)
                # Mobile Identity 1 Type: IMSI (1)
                """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 1c d4 40 00 40 11 1f d4 7f 00 00 01 7f 00
                0020   00 01 c2 e4 12 79 00 2f fe 42 02 04 01 00 00 00
                0030   c9 00 00 16 21 26 02 00 07 00 31 06 21 00 08 XX
                0040   XX XX XX XX XX XX XX 2b 2b 2b 2b 2b 2b 2b 2b 2b
                0050   2b
                XX XX XX XX XX XX XX XX = IMSI
                """
                imsi1 = p[0x15:][:8]
                # p[0x10] == 0x59 = l2 pseudo length value: 22
                if p[0x10] == 0x59 and p[0x1E] == 0x08 and (p[0x1F] & 0x1) == 0x1:  # Channel 2: TCH/F (Full rate) (2)
                    # Mobile Identity 2 Type: IMSI (1)
                    """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 90 95 40 00 40 11 ac 12 7f 00 00 01 7f 00
                0020   00 01 b4 1c 12 79 00 2f fe 42 02 04 01 00 00 00
                0030   c8 00 00 16 51 c6 02 00 08 00 59 06 21 00 08 YY
                0040   YY YY YY YY YY YY YY 17 08 XX XX XX XX XX XX XX
                0050   XX
                YY YY YY YY YY YY YY YY = IMSI 1
                XX XX XX XX XX XX XX XX = IMSI 2
                    """
                    imsi2 = p[0x1F:][:8]
                elif p[0x10] == 0x4d and p[0x1E] == 0x05 and p[0x1F] == 0xf4:  # Channel 2: TCH/F (Full rate) (2)
                    # Mobile Identity - Mobile Identity 2 - IMSI
                    """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 f6 92 40 00 40 11 46 15 7f 00 00 01 7f 00
                0020   00 01 ab c1 12 79 00 2f fe 42 02 04 01 00 00 00
                0030   d8 00 00 23 3e be 02 00 05 00 4d 06 21 a0 08 YY
                0040   YY YY YY YY YY YY YY 17 05 f4 XX XX XX XX 2b 2b
                0050   2b
                YY YY YY YY YY YY YY YY = IMSI 1
                XX XX XX XX = TMSI
                    """
                    tmsi1 = p[0x20:][:4]

                t.register_imsi(gsm.arfcn, imsi1, imsi2, tmsi1, tmsi2, p)

            elif p[0x1B] == 0x08 and (p[0x1C] & 0x1) == 0x1:  # Channel 2: TCH/F (Full rate) (2)
                # Mobile Identity 2 Type: IMSI (1)
                """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 57 8e 40 00 40 11 e5 19 7f 00 00 01 7f 00
                0020   00 01 99 d4 12 79 00 2f fe 42 02 04 01 00 00 00
                0030   c7 00 00 11 05 99 02 00 03 00 4d 06 21 00 05 f4
                0040   yy yy yy yy 17 08 XX XX XX XX XX XX XX XX 2b 2b
                0050   2b
                yy yy yy yy = TMSI/P-TMSI - Mobile Identity 1
                XX XX XX XX XX XX XX XX = IMSI
                """
                tmsi1 = p[0x16:][:4]
                imsi2 = p[0x1C:][:8]
                t.register_imsi(gsm.arfcn, imsi1, imsi2, tmsi1, tmsi2, p)

            elif p[0x14] == 0x05 and (p[0x15] & 0x07) == 4:  # Mobile Identity - Mobile Identity 1 - TMSI/P-TMSI
                """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 b3 f7 40 00 40 11 88 b0 7f 00 00 01 7f 00
                0020   00 01 ce 50 12 79 00 2f fe 42 02 04 01 00 03 fd
                0030   d1 00 00 1b 03 5e 05 00 00 00 41 06 21 00 05 f4
                0040   XX XX XX XX 17 05 f4 YY YY YY YY 2b 2b 2b 2b 2b
                0050   2b
                XX XX XX XX = TMSI/P-TMSI - Mobile Identity 1
                YY YY YY YY = TMSI/P-TMSI - Mobile Identity 2
                """
                tmsi1 = p[0x16:][:4]
                if p[0x1B] == 0x05 and (p[0x1C] & 0x07) == 4:  # Mobile Identity - Mobile Identity 2 - TMSI/P-TMSI
                    tmsi2 = p[0x1D:][:4]
                else:
                    tmsi2 = ""

                t.register_imsi(gsm.arfcn, imsi1, imsi2, tmsi1, tmsi2, p)

        elif p[0x12] == 0x22:  # Message Type: Paging Request Type 2
            if p[0x1D] == 0x08 and (p[0x1E] & 0x1) == 0x1:  # Mobile Identity 3 Type: IMSI (1)
                """
                        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
                0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00
                0010   00 43 1c a6 40 00 40 11 20 02 7f 00 00 01 7f 00
                0020   00 01 c2 e4 12 79 00 2f fe 42 02 04 01 00 00 00
                0030   c9 00 00 16 20 e3 02 00 04 00 55 06 22 00 yy yy
                0040   yy yy zz zz zz 4e 17 08 XX XX XX XX XX XX XX XX
                0050   8b
                yy yy yy yy = TMSI/P-TMSI - Mobile Identity 1
                zz zz zz zz = TMSI/P-TMSI - Mobile Identity 2
                XX XX XX XX XX XX XX XX = IMSI
                """
                tmsi1 = p[0x14:][:4]
                tmsi2 = p[0x18:][:4]
                imsi2 = p[0x1E:][:8]
                t.register_imsi(gsm.arfcn, imsi1, imsi2, tmsi1, tmsi2, p)

def udpserver(port, prn):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server_address = ('localhost', port)
    sock.bind(server_address)
    while True:
        udpdata, address = sock.recvfrom(4096)
        if prn:
            prn(udpdata)

def find_imsi_from_pkt(p):
    udpdata = bytes(p[UDP].payload)
    find_imsi(udpdata)

if __name__ == "__main__":
    imsitracker = tracker()
    parser = OptionParser(usage="%prog: [options]")
    parser.add_option("-a", "--alltmsi", action="store_true", dest="show_all_tmsi", help="Show TMSI who haven't got IMSI (default  : false)")
    parser.add_option("-i", "--iface", dest="iface", default="lo", help="Interface (default : lo)")
    parser.add_option("-m", "--imsi", dest="imsi", default="", type="string", help='IMSI to track (default : None, Example: 123456789101112 or "123 45 6789101112")')
    parser.add_option("-p", "--port", dest="port", default="4729", type="int", help="Port (default : 4729)")
    parser.add_option("-s", "--sniff", action="store_true", dest="sniff", help="sniff on interface instead of listening on port (require root/suid access)")
    parser.add_option("-w", "--sqlite", dest="sqlite", default=None, type="string", help="Save observed IMSI values to specified SQLite file")
    parser.add_option("-t", "--txt", dest="txt", default=None, type="string", help="Save observed IMSI values to specified TXT file")
    parser.add_option("-z", "--mysql", action="store_true", dest="mysql", help="Save observed IMSI values to specified MYSQL DB (copy .env.dist to .env and edit it)")
    (options, args) = parser.parse_args()

    if options.sqlite:
        imsitracker.sqlite_file(options.sqlite)

    if options.txt:
        imsitracker.text_file(options.txt)

    if options.mysql:
        imsitracker.mysql_file()

    imsitracker.show_all_tmsi = options.show_all_tmsi
    imsi_to_track = ""
    if options.imsi:
        imsi = "9" + options.imsi.replace(" ", "")
        imsi_to_track_len = len(imsi)
        if imsi_to_track_len % 2 == 0 and imsi_to_track_len > 0 and imsi_to_track_len < 17:
            for i in range(0, imsi_to_track_len - 1, 2):
                imsi_to_track += chr(int(imsi[i + 1]) * 16 + int(imsi[i]))
            imsi_to_track_len = len(imsi_to_track)
        else:
            print("Wrong size for the IMSI to track!")
            print("Valid sizes :")
            print("123456789101112")
            print("1234567891011")
            print("12345678910")
            print("123456789")
            print("1234567")
            print("12345")
            print("123")
            exit(1)
    imsitracker.track_this_imsi(imsi_to_track)
    if options.sniff:
        from scapy.all import sniff, UDP
        imsitracker.header()
        sniff(iface=options.iface, filter=f"port {options.port} and not icmp and udp", prn=find_imsi_from_pkt, store=0)
    else:
        imsitracker.header()
        udpserver(port=options.port, prn=find_imsi)


text:
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Author: Petter Reinholdtsen
# Contributor : Oros
# Lisence: GPL v2 or later at your own choice

"""
Script to scan for base stations and start submitt GSM packages on one
or more the frequencies found using grgsm_livemon_headless to the
loopback network device

The idea is to gather GSM packages from as many of the GSM base
stations and phones in the area as possible, by spreading across as
many operators as possible.
"""

import imp
from optparse import OptionParser
import subprocess
import sys
import distutils.spawn

def find_gsm_bases():
	grgsm_scanner_path = distutils.spawn.find_executable("grgsm_scanner")
	if grgsm_scanner_path is None:
		print("Error : Please install gr-gsm")
		exit(1)

	scanner = imp.load_source('scanner', grgsm_scanner_path)
	sys.modules['scanner'] = scanner
	(options, args) = scanner.argument_parser().parse_args() #FIXME conflic with argument_parser line 93
	list = scanner.do_scan(options.samp_rate, options.band, options.speed,
		options.ppm, options.gain, options.args)
	return list

def select_freqs(serverlist, count = 1):
	"""
	Select the requested number of frequencies, spread across as many base
	station operators (mcc+mnc) as possible, pick the ones with the
	strongest signals.

	Could consider arfcn, freq, cid, lac, mcc, mnc, ccch_conf, power,
	neighbours, cell_arfcns to get as wide spread as possible, but is
	only using mcc, mnc and power at the moment.
	"""

	# Make shallow copy to avoid modifying the list of the caller when
	# removing elements below.
	listcopy = []
	for s in serverlist:
		listcopy.append(s)

	freqs = []
	sorted_list = sorted(listcopy, key=lambda s:(-s.power, s.mcc, s.mnc))

	for s in sorted_list:
		if count > 0:
			freqs.append(s.freq)
		else:
			break
		count = count - 1

	return freqs

def argument_parser():
	parser = OptionParser(usage="%prog: [options]")
	parser.add_option("-n", "--numrecv", dest="numrecv", type="int",
		default=1,
		help="Set number of livemon processes to start (use one per receiver) [default=%default]")
	return parser

def main(options = None):
	if options is None:
		(options, args) = argument_parser().parse_args()

	print("Locating potential GSM base station frequencies (this can take a few minutes).")
	list = find_gsm_bases()
	print("Found %d frequences" % len(list))
	if len(list) > 0:
		numreceivers = options.numrecv
		freqs = select_freqs(list, numreceivers)

		print("Listening on the frequencies for %d potential GSM base stations." % numreceivers)

		# Make sure a user process can listen on port 4729 by asking
		# livemon processes to listen on other ports.
		serverport = 4730
		procs = []
		for freq in freqs:
			print("Starting livemon for freqency %.0f, server on port %d" % (freq, serverport))
			proc = subprocess.Popen(["grgsm_livemon_headless",
				"--serverport=%d" % serverport,
				"-f", str(freq)])
			procs.append(proc)
			serverport = serverport + 1
		# Keep the processes in our process group, to make sure they all die when scan-and-livemon is killed
		for proc in procs:
			proc.wait()

if __name__ == '__main__':
	main()

repo_language: Python


text:
FROM ubuntu:bionic

MAINTAINER Dmitry Abakumov, a.k.a 0MazaHacka0 <killerinshadow2@gmail.com>

# Update container
RUN apt-get update

# GR-GSM
RUN export TZ=America/New_York && export DEBIAN_FRONTEND=noninteractive && apt-get install -y  gnuradio gnuradio-dev git cmake autoconf libtool pkg-config g++ gcc make libc6 \
libc6-dev libcppunit-1.14-0 libcppunit-dev swig doxygen liblog4cpp5v5 liblog4cpp5-dev python-scipy \
gr-osmosdr libosmocore libosmocore-dev

RUN git clone https://git.osmocom.org/gr-gsm

RUN cd gr-gsm && mkdir build && cd build && cmake .. && make && make install && ldconfig

# IMSI-catcher script
RUN apt-get install -y python-numpy python-scipy python-scapy

ADD . /imsi-catcher/

# Wireshark
RUN export DEBIAN_FRONTEND=noninteractive && apt-get install -y wireshark tshark


text:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `demo`
--

-- --------------------------------------------------------

--
-- Table structure for table `imsi`
--

CREATE TABLE `imsi` (
  `id` int(11) NOT NULL,
  `tmsi1` varchar(100) DEFAULT NULL,
  `tmsi2` varchar(100) DEFAULT NULL,
  `imsi` varchar(100) DEFAULT NULL,
  `stamp` datetime DEFAULT NULL,
  `deviceid` varchar(100) DEFAULT NULL,
  `cell_id` varchar(225) DEFAULT NULL,
  `lac` varchar(225) DEFAULT NULL,
  `mcc` varchar(10) DEFAULT NULL,
  `mnc` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `imsi`
--

--
-- Ex query: saving data to table `imsi`
--
-- INSERT INTO `imsi` (`id`, `tmsi1`, `tmsi2`, `imsi`, `stamp`, `deviceid`, `cell_id`, `lac`, `mcc`, `mnc`) VALUES
-- (4, NULL, NULL, '510 10 1552980000', '2020-05-11 15:00:18', 'rtl', '0000', '000', '510', '10');

--
-- Indexes for dumped tables
--

--
-- Indexes for table `imsi`
--
ALTER TABLE `imsi`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `imsi`
--
ALTER TABLE `imsi`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;


text:
# IMSI-catcher

This program shows you IMSI numbers, country, brand and operator of cellphones around you.

/!\ This program was made to understand how GSM network work. Not for bad hacking !

![screenshot0](capture_simple_IMSI-catcher.png)

## What you need

1 PC with Gnu/Linux. Tested with :
- debian 10
- Ubuntu 20.04/LinuxMint 20+
- Kali 2020+

1 SDR receiver. Tested with :
- [USB DVB-T key (RTL2832U)](https://osmocom.org/projects/sdr/wiki/rtl-sdr) with antenna (less than 15$)
- [OsmocomBB phone](https://osmocom.org/projects/baseband/wiki/Phones)
- [HackRF](https://greatscottgadgets.com/hackrf/)
- [BladeRF](https://www.nuand.com/bladerf-2-0-micro/)

## Setup

```bash
git clone https://github.com/Oros42/IMSI-catcher.git
cd IMSI-catcher
```
or
```bash
wget https://github.com/Oros42/IMSI-catcher/archive/master.zip && unzip -q master.zip
cd IMSI-catcher-master
```

```bash
sudo apt install python3-numpy python3-scipy python3-scapy
```
Warning : don't use python 3.9 (ctypes bug)!

You have the choice with 2 types of gr-gsm's install : in your OS or with docker.

### Install gr-gsm in your OS (recommended)

```bash
sudo apt-get install -y \
    cmake \
    autoconf \
    libtool \
    pkg-config \
    build-essential \
    python-docutils \
    libcppunit-dev \
    swig \
    doxygen \
    liblog4cpp5-dev \
    gnuradio-dev \
    gr-osmosdr \
    libosmocore-dev \
    liborc-0.4-dev \
    swig
```
```bash
gnuradio-config-info -v
```
if >= 3.8
```bash
git clone -b maint-3.8 https://github.com/velichkov/gr-gsm.git
```
else (3.7)
```bash
git clone https://git.osmocom.org/gr-gsm
```

```bash
cd gr-gsm
mkdir build
cd build
cmake ..
make -j 4
sudo make install
sudo ldconfig
echo 'export PYTHONPATH=/usr/local/lib/python3/dist-packages/:$PYTHONPATH' >> ~/.bashrc
```

### Install gr-gsm with Docker

```bash
sudo xhost +local:docker
docker pull atomicpowerman/imsi-catcher
docker run -ti --net=host -e DISPLAY=$DISPLAY --privileged -v /dev/bus/usb:/dev/bus/usb  atomicpowerman/imsi-catcher bash
```
Run all `grgsm_*` in this docker.

## Usage

We use `grgsm_livemon` to decode GSM signals and `simple_IMSI-catcher.py` to find IMSIs.

```bash
python3 simple_IMSI-catcher.py -h
```
```
Usage: simple_IMSI-catcher.py: [options]

Options:
  -h, --help            show this help message and exit
  -a, --alltmsi         Show TMSI who haven't got IMSI (default  : false)
  -i IFACE, --iface=IFACE
                        Interface (default : lo)
  -m IMSI, --imsi=IMSI  IMSI to track (default : None, Example:
                        123456789101112 or "123 45 6789101112")
  -p PORT, --port=PORT  Port (default : 4729)
  -s, --sniff           sniff on interface instead of listening on port
                        (require root/suid access)
  -w SQLITE, --sqlite=SQLITE
                        Save observed IMSI values to specified SQLite file
  -t TXT, --txt=TXT     Save observed IMSI values to specified TXT file
  -z, --mysql           Save observed IMSI values to specified MYSQL DB (copy
                        .env.dist to .env and edit it)
```

Open 2 terminals.

In terminal 1
```bash
sudo python3 simple_IMSI-catcher.py -s
```

In terminal 2
```bash
grgsm_livemon
```
Now, change the frequency until it display, in terminal, something like that :
```
15 06 21 00 01 f0 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
25 06 21 00 05 f4 f8 68 03 26 23 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
49 06 1b 95 cc 02 f8 02 01 9c c8 03 1e 57 a5 01 79 00 00 1c 13 2b 2b
```

### Wireshark

You can watch GSM packets with wireshark.
```bash
sudo apt install wireshark
sudo wireshark -k -Y '!icmp && gsmtap' -i lo
```

### Find frequencies

```bash
grgsm_scanner
```
```
ARFCN:  974, Freq:  925.0M, CID:     2, LAC:   1337, MCC: 208, MNC:  20, Pwr: -41
ARFCN:  976, Freq:  925.4M, CID:  4242, LAC:   1007, MCC: 208, MNC:  20, Pwr: -45
```
Now, you can set the frequency for `grgsm_livemon` :
```bash
grgsm_livemon -f 925.4M
```

Or, for hackrf, fetch the kalibrate-hackrf tool like this:
```bash
sudo apt-get install automake autoconf libhackrf-dev
git clone https://github.com/scateu/kalibrate-hackrf
cd kalibrate-hackrf/
./bootstrap
./configure
make
sudo make install
```
Run
```bash
kal -s GSM900
```
```
kal: Scanning for GSM-900 base stations.
GSM-900:
	chan:   14 (937.8MHz + 10.449kHz)	power: 3327428.82
	chan:   15 (938.0MHz + 4.662kHz)	power: 3190712.41
...
```

### Log data in mysql

Use `db-example.sql` to create your DB.

```bash
cp .env.dist .env
nano .env
# set your config
sudo apt install python-decouple python3-mysqldb
```

```bash
sudo python3 simple_IMSI-catcher.py -s --mysql
```

### scan-and-livemon (no longer used)

Scan frequencies and listen the 1st found :
In terminal 1
```bash
python3 scan-and-livemon
```

In terminal 2
```bash
python3 simple_IMSI-catcher.py
```

# Links

Setup of Gr-Gsm : https://osmocom.org/projects/gr-gsm/wiki/Installation and https://github.com/velichkov/gr-gsm
Frequency : http://www.worldtimezone.com/gsm.html and https://fr.wikipedia.org/wiki/Global_System_for_Mobile_Communications
Mobile Network Code : https://en.wikipedia.org/wiki/Mobile_Network_Code
Scapy : http://secdev.org/projects/scapy/doc/usage.html
IMSI : https://fr.wikipedia.org/wiki/IMSI
Realtek RTL2832U : https://osmocom.org/projects/sdr/wiki/rtl-sdr and http://doc.ubuntu-fr.org/rtl2832u and http://doc.ubuntu-fr.org/rtl-sdr

# Donate

To support my work, a tipee would be nice ;-)
https://liberapay.com/Oros/


text:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Oros
# 2020-01-23
# License : CC0 1.0 Universal

# This scipt update mcc_codes.json from this page https://en.wikipedia.org/wiki/Mobile_Network_Code

# setup :
# sudo apt-get install python-bs4
# 	https://www.crummy.com/software/BeautifulSoup/bs4/doc/
# run :
# python update_codes.py

try:
	from bs4 import BeautifulSoup
except ImportError:
	print("Need BeautifulSoup.\nsudo apt-get install python-bs4")
	import sys
	sys.exit()

try:
	# For Python 3.0 and later
	from urllib.request import urlopen
	pythonVersion="3"
except ImportError:
	# Fall back to Python 2's urllib2
	from urllib2 import urlopen
	pythonVersion="2"
import json
import io

wikipediaURLs = [
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_2xx_(Europe)',
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_3xx_(North_America)',
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_4xx_(Asia)',
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_5xx_(Oceania)',
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_6xx_(Africa)',
	'https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_7xx_(South_America)'
]
mcc_codes={}
for url in wikipediaURLs:
	webpage = urlopen(url)
	soup = BeautifulSoup(webpage,'html.parser')
	"""
	mcc_codes={
		...
		'208':{ # MCC
			'c':['France', 'FR'],
			'MNC':{
				# 'MNC':[Brand, Operator],
				'01':['Orange', 'Orange S.A.'],
				'02':['Orange', 'Orange S.A.'],
				'03':['MobiquiThings', 'MobiquiThings'],
				...
			}
		},
		...
	}
	"""
	for t in soup.find_all("table", class_="wikitable"):
		try:
			if not 'MCC' in t.text:
				continue
			h4=t.find_previous_sibling("h4")
			if not h4 or ' - ' not in h4.text or '[edit]' not in h4.text:
				continue
			h4=h4.text.split(' - ')
			country_name=h4[0]
			country_code=h4[1][:-6] # rm '[edit]'

			for tr in t.find_all('tr'):
				td=tr.find_all('td')
				if not td:
					continue
				MCC=td[0].text
				if not MCC:
					continue
				MNC=td[1].text
				Brand=td[2].text
				Operator=td[3].text
				if MCC not in mcc_codes:
					mcc_codes[MCC]={}

				mcc_codes[MCC][MNC] = [Brand, Operator, country_name, country_code]
		except Exception:
			pass

if mcc_codes:
	with io.open('mcc_codes.json', 'w', encoding='utf8') as outfile:
		if pythonVersion == "2":
			outfile.write(json.dumps(mcc_codes, ensure_ascii=False, encoding="utf-8"))
		else:
			outfile.write(json.dumps(mcc_codes, ensure_ascii=False))


text:
# Community Participation Guidelines

This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).

## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.

<!--
## Project Specific Etiquette

In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
Please update for your project.
-->

repo_name: mdn/learning-area

repo_language: HTML

mime_type: text/plain


text:
# learning-area
GitHub repo for the [MDN Learning Area](https://developer.mozilla.org/en-US/Learn).

Most of the code examples shown and discussed in the Learning Area can be found here; the directories in the repo match the module structure of the articles.

The one exception is the [Server-side website programming](https://developer.mozilla.org/en-US/docs/Learn/Server-side) examples — we broke these out into separate repos to make them easier to deal with:

* https://github.com/mdn/express-locallibrary-tutorial
* https://github.com/mdn/django-locallibrary-tutorial


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Form validation example</title>

Form validation example

    <form>

        Enter your name:
        <input type="text" name="name" id="name">

        Enter your age:
        <input type="number" name="age" id="age">

        <input type="submit">

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Form CSS example</title>

Form CSS example

    <form>

        Enter your name:
        <input type="text" name="name" id="name">

        Enter your age:
        <input type="text" name="age" id="age">

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Mouse and keyboard events example</title>

Mouse and keyboard events example


text:
const inputs = document.querySelectorAll('input');
const labels = document.querySelectorAll('label');
const form = document.querySelector('form');

let formItems = [];

const errorField = document.querySelector('.errors');
const errorList = document.querySelector('.errors ul');

for(let i = 0; i < inputs.length-1; i++) {
  let obj = {};
  obj.label = labels[i];
  obj.input = inputs[i];
  formItems.push(obj);
}

errorField.style.left = '-100%';

form.onsubmit = validate;

function validate(e) {
  errorList.innerHTML = '';
  for(let i = 0; i < formItems.length; i++) {
    let testItem = formItems[i];
    if(testItem.input.value === '') {
      errorField.style.left = '360px';
      createLink(testItem);
    }
  }

  if(errorList.innerHTML !== '') {
    e.preventDefault();
  }
}

function createLink(testItem) {
  const listItem = document.createElement('li');
  const anchor = document.createElement('a');
  anchor.textContent = testItem.input.name + ' field is empty: fill in your ' + testItem.input.name + '.';
  anchor.href = '#' + testItem.input.name;
  anchor.onclick = function() {
    testItem.input.focus();
  };
  listItem.appendChild(anchor);
  errorList.appendChild(listItem);
}


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Table CSS example</title>

Table CSS example

        Name
        Age
        Gender

        Gabriel
        13
        Male

        Elva
        8
        Female

        Freida
        5
        Female


text:
/* Some default styling for cookbook examples */
/*
rgb(53,43,34)
rgb(75,70,74)
rgb(95,97,110)
rgb(137,151,188)
rgb(160,178,226)
*/
body {
    background-color: #fff;
    color: #333;
    font: 1em / 1.4 Helvetica Neue, Helvetica, Arial, sans-serif;
    padding: 0;
    margin: 0;
}

h1 {
  font-size: 2rem;
  margin: 0;
}

h2 {
  font-size: 1.6rem;
  margin: 0;
}

p {
  margin: 0.5em 0;
}

/* styles for the editor */

.playable {
    font-family: monospace;
    display: block;
    margin-bottom: 10px;
    background-color: #F4F7F8;
    border: none;
    border-left: 6px solid #558ABB;
    color: #4D4E53;
    width: 90%;
    max-width: 700px;
    padding: 10px 10px 0px;
    font-size: 90%;
  }

  .playable-css {
    height: 80px;
  }

  .playable-js {
    height: 160px;
  }

  .playable-buttons {
    text-align: right;
    width: 90%;
    max-width: 700px;
    padding: 5px 10px 5px 26px;
    font-size: 100%;
  }

  .preview {
    width: 90%;
    max-width: 700px;
    border: 1px solid #4D4E53;
    border-radius: 2px;
    padding: 10px 14px 10px 10px;
    margin-bottom: 10px;
  }

  .preview input {
    display: block;
    margin: 5px;
  }


text:
var section = document.querySelector('section');
var editable = document.querySelector('.editable');
var textareaJS = document.querySelector('.playable-js');
var reset = document.getElementById('reset');
var jsCode = textareaJS.value;

function fillCode() {
    editable.textContent = textareaJS.value;
    try {
      eval(editable.textContent);
    } catch(e) {
      let para = document.createElement('p');
      para.textContent = e;
      section.appendChild(para);
    }
}

reset.addEventListener('click', function () {
    textareaJS.value = jsCode;
    fillCode();
});

textareaJS.addEventListener('input', fillCode);
window.addEventListener('load', fillCode);


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>JavaScript accessibility: Task 1</title>

    <link rel="stylesheet" href="../styles.css" />

Animal summaries

The following list of animals can be clicked to display a description of that animal.

Ibex

Dugong

Quokka


text:
/* Some default styling for cookbook examples */
/*
rgb(53,43,34)
rgb(75,70,74)
rgb(95,97,110)
rgb(137,151,188)
rgb(160,178,226)
*/
body {
    background-color: #fff;
    color: #333;
    font: 1em / 1.4 Helvetica Neue, Helvetica, Arial, sans-serif;
    padding: 0;
    margin: 0;
}

h1 {
  font-size: 2rem;
  margin: 0;
}

h2 {
  font-size: 1.6rem;
  margin: 0;
}

p {
  margin: 0.5em 0;
}

/* styles for the editor */

.playable {
    font-family: monospace;
    display: block;
    margin-bottom: 10px;
    background-color: #F4F7F8;
    border: none;
    border-left: 6px solid #558ABB;
    color: #4D4E53;
    width: 90%;
    max-width: 700px;
    padding: 10px 10px 0px;
    font-size: 90%;
  }

  .playable-css {
    height: 80px;
  }

  .playable-html {
    height: 160px;
  }

  .playable-buttons {
    text-align: right;
    width: 90%;
    max-width: 700px;
    padding: 5px 10px 5px 26px;
    font-size: 100%;
  }

  .preview {
    width: 90%;
    max-width: 700px;
    border: 1px solid #4D4E53;
    border-radius: 2px;
    padding: 10px 14px 10px 10px;
    margin-bottom: 10px;
  }

  .preview input {
    display: block;
    margin: 5px;
  }


text:
var section = document.querySelector('section');
var editable = document.querySelector('.editable');
var textareaHTML = document.querySelector('.playable-html');
var textareaCSS = document.querySelector('.playable-css');
var reset = document.getElementById('reset');
var htmlCode = textareaHTML.value;
var cssCode = textareaCSS.value;

function fillCode() {
    editable.innerHTML = textareaCSS.value;
    section.innerHTML = textareaHTML.value;
}

reset.addEventListener('click', function () {
    textareaHTML.value = htmlCode;
    textareaCSS.value = cssCode;
    fillCode();
});

textareaHTML.addEventListener('input', fillCode);
textareaCSS.addEventListener('input', fillCode);
window.addEventListener('load', fillCode);


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>CSS accessibility: Task 1</title>
    <link rel="stylesheet" href="../styles.css" />

Animals

Computers

Diversity and inclusion

Food

Medicine

Music

    <textarea class="playable playable-css" style="height: 130px;">
a {
  text-decoration: none;
  color: #666;
  outline: none;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">

Animals

Computers

Diversity and inclusion

Food

Medicine

Music

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
# CSS and JS accessibility marking guide

The aim of these tasks is to demonstrate an understanding of the techniques covered in the [CSS and JavaScript accessibility best practices](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript) lesson in Learn Web Development on MDN.

## Task 1

In the first task you are presented with a list of links. However, their accessibility is pretty bad — there is no way to really tell that they are links, or to tell which one the user is focussed on.

We'd like you to assume that the existing ruleset with the `a` selector is supplied by some CMS, and that you can't change it — instead, you need to create new rules to make the links look and behave like links, and for the user to be able to tell where they are in the list.

The CSS could look something like this:

```
li a {
  text-decoration: underline;
  color: rgb(150,0,0);
}

li a:hover, li a:focus {
  text-decoration: none;
  color: rgb(255,0,0);
}
```

## Task 2

In this next task you are presented with a simple bit of content — just headings and paragraphs. There are  accessibility issues with the colors and sizing of the text; we'd like you to:

1. Explain what the problems are, and what the guidelines are that state the acceptable values for color and sizing.
2. Select new values for the color and font-size that fix the problem.
3. Update the CSS with these new values to fix the problem.
4. Test the code to make sure the problem is now fixed. Explain what tools or methods you used to select the new values and test the code.

The answers:

1. (Q1) The problems are that first of all, the color contrast is not acceptable, as per WCAG criteria [1.4.3 (AA)](https://www.w3.org/TR/WCAG21/#contrast-minimum) and [1.4.6 (AAA)](https://www.w3.org/TR/WCAG21/#contrast-enhanced), and secondly, the text is sized using vw units, which means that it is not zoomable in most browsers. [WCAG 1.4.4 (AA)](https://www.w3.org/TR/WCAG21/#resize-text) states that text should be resizable.
2. (Qs 2 and 3) to fix the code, you need to
  * Choose a much better contrasting set of background and foreground colors.
  * Use some different units to size the text (such as rem or even px), or even implement something that uses a combination of vw and other units, if you want it resizable but still relative in part to the viewport size
3. For testing:
  * You can test color contrast using a tool such as [aXe](https://www.deque.com/axe/), The [Firefox Accessibility Inspector](https://developer.mozilla.org/en-US/docs/Tools/Accessibility_inspector), or even a simple standalone web page tool like the [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/).
  * For text resizing, you'd need to load the example in a browser and try to resize it. Resizing vw unit-sized text works in Safari, but not Firefox or Chromium-based browsers.

For the updated code, something like this would work for the updated color scheme:

```
main {
  padding: 20px;
  background-color: red;
}

h1, h2, p {
  color: black;
}
```

And something like this would work for the font sizing:

```
h1 {
  font-size: calc(2.5rem);
}

h2 {
  font-size: calc(2rem);
}

p {
  font-size: calc(1.2rem);
}
```

Or this, if you want to do something a bit cleverer that gives you resizable viewport-relative units:

```
h1 {
  font-size: calc(1.5vw + 1rem);
}

h2 {
  font-size: calc(1.2vw + 0.7rem);
}

p {
  font-size: calc(1vw + 0.4rem);
}
```

## Task 3

In our final task here, you have some JavaScripting to do. We have a simple app that presents a list of animal names. Clicking one of the animal names causes a further description of that animal to appear in a box below the list.

But it is not very accessible — in its current state you can only operate it with the mouse. We'd like you to add to the HTML and JavaScript to make it keyboard acessible too.

Answers:

1. To begin with, you'll need to add `tabindex="0"` to the list items to make them focusable via the keyboard.
2. Then you'll need to add in another event listener inside the `forEach()` loop, to make the code respond to keys being pressed while the list items are selected. It is probably a good idea to make it respond to a specific key, such as "Enter", in which case something like the following is probably acceptable:

```
item.addEventListener('keyup', function(e) {
  if(e.key === 'Enter') {
    handleSelection(e);
  }
});
```


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>CSS accessibility: Task 1</title>

Animals

Computers

Diversity and inclusion

Food

Medicine

Music


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>CSS accessibility: Task 2</title>

I am the eggman

Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.

They are the eggman

Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.

I am the walrus

Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors.


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>CSS accessibility: Task 2</title>
    <link rel="stylesheet" href="../styles.css" />

I am the eggman

Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.

They are the eggman

Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.

I am the walrus

Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors.

    <textarea class="playable playable-css" style="height: 130px;">
main {
  padding: 20px;
  background-color: red;
}

h1, h2, p {
  color: #999;
}

h1 {
  font-size: 2vw;
}

h2 {
  font-size: 1.5vw;
}

p {
  font-size: 1.2vw;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">

I am the eggman

Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.

They are the eggman

Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.

I am the walrus

Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors.

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
# HTML accessibility marking guide

The aim of these tasks is to demonstrate an understanding of the techniques covered in the [HTML: A good basis for accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML) lesson in Learn Web Development on MDN.

## Task 1

In this task we will test your understanding of text semantics, and why they are good for accessibility. You don't need to worry too much about recreating the _exact_ same look and text sizing, as long as the semantics are good.

The given text is a simple information panel with action buttons:

```
<font size="7">Need help?</font>
<br><br>
If you have any problems with our products, our  support center can offer you all the help you need, whether you want:
<br><br>
1. Advice choosing a new product
<br>
2. Tech support on an existing product
<br>
3. Refund and cancellation assistance
<br><br>
<font size="5">Contact us now</font>
<br><br>
Our help center contains live chat, e-mail addresses, and phone numbers.
<br><br>
<div class="button">Find Contact Details</div>
<br><br>
<font size="5">Find out answers</font>
<br><br>
Our Forums section contains a large knowledge base of searchable previously asked questions, and you can always ask a new question if you can't find the answer you're looking for.
<br><br>
<div class="button">Access forums</div>
```

But of course, this is terrible for semantics and accessibility. A much better set of markup would look like so:

```
<h2>Need help?</h2>

<p>If you have any problems with our products, our  support center can offer you all the help you need, whether you want:</p>
<ul>
  <li>Advice choosing a new product</li>
  <li>Tech support on an existing product</li>
  <li>Refund and cancellation assistance</li>
</ul>
<h3>Contact us now</h3>

<p>Our help center contains live chat, e-mail addresses, and phone numbers.</p>

<button>Find Contact Details</button>

<h3>Find out answers</h3>

<p>Our Forums section contains a large knowledge base of searchable previously asked questions, and you can always ask a new question if you can't find the answer you're looking for.</p>

<button>Access forums</button>
```

You can get a couple of extra marks for:

1. Just using `<button>`, not `<button class="button">` (repeating semantics is unnecessary), and updating the CSS selector to make sure the button still picks up the styles
2. Using an unordered list, not an ordered list — the list of items doesn't really need to be in any order.

## Task 2
In the second task, you have a form containing three input fields. You need to:

1. Semantically associate the input with their labels.
2. Assume that these inputs will be part of a larger form, and wrap them in an element that associates them all together as a single related group.
3. Give the group a description/title that summarises all of the information as personal data.

This is fairly simple, especially if you have previously worked through our [Web forms](https://developer.mozilla.org/en-US/docs/Learn/Forms) module. You need to:

1. Use `<label>` elements and `id`s to associate labels with inputs.
2. Use a `<fieldset>` to associate the elements together as one group.
3. Use a `<legend>` to give the `<fieldset>` a description/title.

Your answer should end up looking something like this:

```
<form>
  <fieldset>
  <legend>Personal data</legend>
    <ul>
      <li>
        <label for="name">Name</label>
        <input type="text" name="name" id="name">
      </li>
      <li>
        <label for="age">Age</label>
        <input type="number" name="age" id="age">
      </li>
      <li>
        <label for="email">Email address</label>
        <input type="email" name="email" id="email">
      </li>
    </ul>
  </fieldset>
</form>
```

# Task 3

In this task you are required to turn all the information links in the paragraph into good, accessible links.

1. The first two links just go to regular web pages.
2. The third link goes to a PDF, and it's large — 8MB.
3. The fourth link goes to a Word document, so the user will need some kind of application installed that can handle that.

Initially the paragraph looks like this:

```
<p>For more information about our activities, check out our fundraising page (<a href="/fundraising">click here</a>), education page (<a href="/education">click here</a>), sponsorship pack (<a href="/resources/sponsorship.pdf">click here</a>), and assessment sheets (<a href="/resources/assessments.docx">click here</a>).</p>
```

But you should update it to something like this:

```
<p>For more information about our activities, check out our <a href="/fundraising">fundraising page</a>, <a href="/education">education page</a>, <a href="/resources/sponsorship.pdf">sponsorship pack (PDF, 8MB)</a>, and <a href="/resources/assessments.docx">assessment sheets (Word document)</a>.</p>
```

## Task 4

In our final HTML accessibility task, you are given a simple image gallery, which has some accessibility problems. Can you fix them?

1. The header image has an accessiiblity issue, and so do the gallery images.
2. You could take the header image further and implement it using CSS for better accessibility. Why is this better, and what would a solution look like?

So, on to the answers:

1. The header image is decorative, so doesn't really need alt text. The best solution if you are going to use decorative HTML images is to put `alt=""`, so a screenreader will just read out nothing — rather than a descrition, or the image file name. It is not part of the content.
2. The gallery images need alt text, and they are part of the content. The updated HTML could look something like this:

```
<header>
  <img src="images/star.png" alt="">
  <h1>Groovy images</h1>
</header>
<main>
  <img src="images/teapot.jpg" alt="a black teapot placed on a shelf just inside a window">
  <img src="images/football.jpg" alt="A black and white round panelled ball">
</main>
```

3. It would be arguable better to implement the background header image using CSS background images. To do this, you would remove the following line:

```
<img src="images/star.png" alt="A star that I use to decorate my page">
```

And add in a CSS rule along these lines:

```
h1 {
  background: url(images/star.png) no-repeat left;
  padding-left: 50px;
}
```


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>HTML accessibility: Task 3</title>
    <link rel="stylesheet" href="../styles.css" />

For more information about our activities, check out our fundraising page (click here), education page (click here), sponsorship pack (click here), and assessment sheets (click here).

    <textarea class="playable playable-css" style="height: 130px;">

    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">

For more information about our activities, check out our fundraising page (click here), education page (click here), sponsorship pack (click here), and assessment sheets (click here).

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>HTML accessibility: Task 4</title>
    <link rel="stylesheet" href="../styles.css" />

Groovy images

    <textarea class="playable playable-css" style="height: 130px;">
.preview {
  width: 400px;
  margin: 0 auto;
}

main img {
  display: block;
  width: 250px;
  margin: 20px auto;
  box-shadow: 5px 5px 0 black;
}

header {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 20px;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">

Groovy images

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>HTML accessibility: Task 2</title>
    <link rel="stylesheet" href="../styles.css" />

      <form>

            Name
            <input type="text" name="name">

            Age
            <input type="number" name="age">

            Email address
            <input type="email" name="email">

      </form>

    <textarea class="playable playable-css" style="height: 130px;">
form {
  width: 400px;
}

li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">
<form>

      Name
      <input type="text" name="name">

      Age
      <input type="number" name="age">

      Email address
      <input type="email" name="email">

</form>
    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>HTML accessibility: Task 1</title>

    Need help?

    If you have any problems with our products, our  support center can offer you all the help you need, whether you want:

    1. Advice choosing a new product

    2. Tech support on an existing product

    3. Refund and cancellation assistance

    Contact us now

    Our help center contains live chat, e-mail addresses, and phone numbers.

Find Contact Details

    Find out answers

    Our Forums section contains a large knowledge base of searchable previously asked questions, and you can always ask a new question if you can't find the answer you're looking for.

Access Forums


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>HTML accessibility: Task 1</title>
    <link rel="stylesheet" href="../styles.css" />

      Need help?

      If you have any problems with our products, our  support center can offer you all the help you need, whether you want:

      1. Advice choosing a new product

      2. Tech support on an existing product

      3. Refund and cancellation assistance

      Contact us now

      Our help center contains live chat, e-mail addresses, and phone numbers.

Find Contact Details

      Find out answers

      Our Forums section contains a large knowledge base of searchable previously asked questions, and you can always ask a new question if you can't find the answer you're looking for.

Access Forums

    <textarea class="playable playable-css" style="height: 130px;">
.button {
  color: white;
  background-color: blue;
  border-radius: 10px;
  width: 170px;
  padding: 10px;
  text-align: center;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">
Need help?

If you have any problems with our products, our  support center can offer you all the help you need, whether you want:

1. Advice choosing a new product

2. Tech support on an existing product

3. Refund and cancellation assistance

Contact us now

Our help center contains live chat, e-mail addresses, and phone numbers.

Find Contact Details

Find out answers

Our Forums section contains a large knowledge base of searchable previously asked questions, and you can always ask a new question if you can't find the answer you're looking for.

Access Forums

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>HTML accessibility: Task 2</title>

    <form>

          Name
          <input type="text" name="name">

          Age
          <input type="number" name="age">

          Email address
          <input type="email" name="email">

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>HTML accessibility: Task 3</title>

For more information about our activities, check out our fundraising page (click here), education page (click here), sponsorship pack (click here), and assessment sheets (click here).


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>HTML accessibility: Task 4</title>

Groovy images


text:
# WAI-ARIA marking guide

The aim of these tasks is to demonstrate an understanding of the techniques covered in the [WAI-ARIA Basics](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics) lesson in Learn Web Development on MDN.

## Task 1

In our first ARIA task, we present you with a section of non-semantic markup, which is obviously meant to be a list. Assuming you are not able to change the elements used, how can you allow screenreader users to recognize this as a list?

The `list` and `listitem` roles are what you need here. The updated markup would look like so:

```
<div role="list">
  <div role="listitem">Pig</div>
  <div role="listitem">Gazelle</div>
  <div role="listitem">Llama</div>
  <div role="listitem">Majestic moose</div>
  <div role="listitem">Hedgehog</div>
</div>
```

## Task 2

In our second WAI-ARIA task, we present a simple search form, and we want you to add in a couple of WAI-ARIA features to improve its accessibility:

1. How can you allow the search form to be called out as a separate landmark on the page by screenreaders, to make it easily findable?
2. How can you give the search input a suitable label, without explicitly adding a visible text label to the DOM?

Answers:

1. Give the `<form>` element a `role="search"`. Most screenreaders will explicitly call this out as a search form, and/or include it as a separate landmark in the page's landmarks list.
2. Include label text inside an `aria-label=""` attribute on the `<input>` element. This way it won't be visible on the page, but it will be read out by screenreaders.

The finished HTML should look something like this:

```
<form role="search">
  <input type="search" name="search"
    aria-label="Search for your favorite content on our site">
</form>
```

## Task 3
For this final WAI-ARIA task, we return to an example we previously saw in the [CSS and JavaScript skilltest](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript/Test_your_skills:_CSS_and_JavaScript_accessibility). As before, we have a simple app that presents a list of animal names. Clicking one of the animal names causes a further description of that animal to appear in a box below the list. Here, we are starting with a mouse- and keyboard-accessible version.

The problem we have now is that when the DOM changes to show a new decription, screenreaders cannot see what has changed. Can you update it so that description changes are annouced by the screenreader?

There are two ways to solve this:

* Add an `aria-live=""` attribute to the animal-description `<div>` to turn it into a live region, so when its content changes, the updated content will be read out by a screenreader. The best value is probably `assertive`, which makes the screenreader read out the updated content as soon as it changed. `polite` means that the screenreader will wait until other descriptions have finished before it starts reading out the changed content.
* Add a `role="alert"` attribute to the animal-description `<div>`, to make it have alert box semantics. This has the same effect on the screenreader as setting `aria-live="assertive"` on it.


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>WAI-ARIA: Task 1</title>

My favorite animals:

Pig

Gazelle

Llama

Majestic moose

Hedgehog


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>WAI-ARIA: Task 2</title>
    <link rel="stylesheet" href="../styles.css" />

      <form>
        <input type="search" name="search">
      </form>

    <textarea class="playable playable-css" style="height: 130px;">

    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">
<form>
  <input type="search" name="search">
</form>
    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>WAI-ARIA: Task 2</title>
    <style>
      body {
        background-color: #fff;
        color: #333;
        font: 1em / 1.4 'Helvetica Neue', Helvetica, Arial, sans-serif;
        padding: 1em;
        margin: 0;
      }

      * {
        box-sizing: border-box;
      }
    </style>
  </head>

  <body>
    <form>
      <input type="search" name="search">
    </form>
  </body>

</html>


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>WAI-ARIA: Task 1</title>
    <link rel="stylesheet" href="../styles.css" />

My favorite animals:

Pig

Gazelle

Llama

Majestic moose

Hedgehog

    <textarea class="playable playable-css" style="height: 130px;">
div > div {
  padding-left: 20px;
  position: relative;
}

div > div::before {
  content: " ";
  width: 8px;
  height: 8px;
  background-color: black;
  border-radius: 50%;
  position: absolute;
  left: 0;
  top: 8px;
}
    </textarea>

    <textarea class="playable playable-html" style="height: 220px;">

My favorite animals:

Pig

Gazelle

Llama

Majestic moose

Hedgehog

    </textarea>

      <input id="reset" type="button" value="Reset" />


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Dinosaur longdesc</title>

A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Bad links example</title>

Bad links

Further information

For more information on whales, click here.

For more information on squirrels, click here.

For more information on bees, click here.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Bad form example</title>

Bad form

    <form>

Enter your name:

        <input type="text" name="name">

Enter your age:

        <input type="text" name="age">

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Native keyboard accessibility</title>

Links

This is a link to Mozilla.

Another link, to the Mozilla Developer Network.

Buttons

      <button data-message="This is from the first button">Click me!</button>
      <button data-message="This is from the second button">Click me too!</button>
      <button data-message="This is from the third button">And me!</button>

Form

    <form>

        Fill in your name:
        <input type="text" id="name" name="name">

        Enter your age:
        <input type="text" id="age" name="age">

        Choose your mood:
        <select id="mood" name="mood">
          <option>Happy</option>
          <option>Sad</option>
          <option>Angry</option>
          <option>Worried</option>
        </select>

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Good links example</title>

Good links

Further information

Further information on Whales.

Further information on Squirrels.

Further information on Bees.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Good semantics example</title>

My heading

Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab barque interloper chantey doubloon starboard grog black jack gangway rutters. Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.

Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits. Crack Jennys tea cup Sail ho killick transom trysail Chain Shot. Measured fer yer chains lateen sail Davy Jones' Locker prow wench weigh anchor. Port me hang the jib Davy Jones' Locker skysail pirate. Sail ho run a shot across the bow fluke belaying pin boom carouser. Chase guns broadside Corsair mizzen provost Admiral of the Black. Red ensign list brigantine line booty hands. Booty topgallant grapple coffer pinnace fire ship.

My subheading

Yard Pirate Round hearties Sail ho log salmagundi. Ye scuttle smartly Jack Tar man-of-war measured fer yer chains. Shrouds sloop transom quarter wench case shot. Hempen halter Cat o'nine tails red ensign strike colors lugsail hogshead. Barbary Coast parrel bilge ho aft gibbet. Tack red ensign crimp yo-ho-ho Buccaneer gaff. Keel careen scuppers port hands reef sails. Hands square-rigged draft lugsail heave to bounty. Clap of thunder scuppers trysail cable yo-ho-ho coxswain. Jury mast cable mizzenmast hail-shot keelhaul execution dock. Black jack ho shrouds bilge water avast hardtack. Clipper deadlights brigantine measured fer yer chains poop deck Blimey. Topsail Arr yo-ho-ho pillage bucko handsomely. Draft pinnace holystone Cat o'nine tails Letter of Marque loaded to the gunwalls.

My 2nd subheading

Topgallant American Main coxswain poop deck sloop come about. Piracy hail-shot handsomely Spanish Main flogging interloper. Run a rig lee yo-ho-ho gangplank Pieces of Eight gabion. Sloop Spanish Main reef capstan deadlights rope's end. Six pounders red ensign mizzenmast mizzen chase guns topmast. Broadside fore splice the main brace heave down Chain Shot bounty. Poop deck ballast Jack Tar pressgang jack walk the plank.


text:
/* || General setup */

html, body {
  margin: 0;
  padding: 0;
}

html {
  font-size: 10px;
}

body {
  width: 1200px;
  margin: 0 auto;
}

table {
  border-collapse: collapse;
}

/* || typography */

h1, h2, h3 {
  font-family: 'Sonsie One', cursive;
  color: #2a2a2a;
}

p, input, li, a {
  font-family: 'Open Sans Condensed', sans-serif;
  color: #2a2a2a;
}

h1 {
  font-size: 4rem;
  color: white;
  text-shadow: 2px 2px 10px black;
}

h2 {
  font-size: 3rem;
}

h3 {
  font-size: 2.2rem;
}

p, li, a {
  font-size: 1.6rem;
  line-height: 1.5;
}

/* || header layout */

#main td, #aside td, #footer td {
  padding: 10px;
}

#content {
  margin-right: 10px;
}

#nav {
  height: 50px;
}

#nav td {
  padding: 20px;
}

#nav a {
  display: block;
  font-size: 2rem;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
}

input {
  font-size: 1.6rem;
  height: 32px;
  width: 100%;
}

button {
  width: 100%;
  height: 32px;
  background: #333;
  border: 0;
  color: white;
}


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Bad semantics example</title>

    My heading

    Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab barque interloper chantey doubloon starboard grog black jack gangway rutters. Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.

    Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits. Crack Jennys tea cup Sail ho killick transom trysail Chain Shot. Measured fer yer chains lateen sail Davy Jones' Locker prow wench weigh anchor. Port me hang the jib Davy Jones' Locker skysail pirate. Sail ho run a shot across the bow fluke belaying pin boom carouser. Chase guns broadside Corsair mizzen provost Admiral of the Black. Red ensign list brigantine line booty hands. Booty topgallant grapple coffer pinnace fire ship.

    My subheading

    Yard Pirate Round hearties Sail ho log salmagundi. Ye scuttle smartly Jack Tar man-of-war measured fer yer chains. Shrouds sloop transom quarter wench case shot. Hempen halter Cat o'nine tails red ensign strike colors lugsail hogshead. Barbary Coast parrel bilge ho aft gibbet. Tack red ensign crimp yo-ho-ho Buccaneer gaff. Keel careen scuppers port hands reef sails. Hands square-rigged draft lugsail heave to bounty. Clap of thunder scuppers trysail cable yo-ho-ho coxswain. Jury mast cable mizzenmast hail-shot keelhaul execution dock. Black jack ho shrouds bilge water avast hardtack. Clipper deadlights brigantine measured fer yer chains poop deck Blimey. Topsail Arr yo-ho-ho pillage bucko handsomely. Draft pinnace holystone Cat o'nine tails Letter of Marque loaded to the gunwalls.

    My 2nd subheading

    Topgallant American Main coxswain poop deck sloop come about. Piracy hail-shot handsomely Spanish Main flogging interloper. Run a rig lee yo-ho-ho gangplank Pieces of Eight gabion. Sloop Spanish Main reef capstan deadlights rope's end. Six pounders red ensign mizzenmast mizzen chase guns topmast. Broadside fore splice the main brace heave down Chain Shot bounty. Poop deck ballast Jack Tar pressgang jack walk the plank.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Accessible image example</title>

Images

The Mozilla red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">

    <title>My page title</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Sonsie+One" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="style.css">

Header

          Home

          Our team

          Projects

          Contact

          <form width="300">
            <input type="search" name="q" placeholder="Search query" width="300">
          </form>

          <button width="100">Go!</button>

Article heading

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Donec a diam lectus. Set sit amet ipsum mauris. Maecenas congue ligula as quam viverra nec consectetur ant hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur.

subsection

Donec ut librero sed accu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor.

Pelientesque auctor nisi id magna consequat sagittis. Curabitur dapibus, enim sit amet elit pharetra tincidunt feugiat nist imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros.

Another subsection

Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum soclis natoque penatibus et manis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.

Vivamus fermentum semper porta. Nunc diam velit, adipscing ut tristique vitae sagittis vel odio. Maecenas convallis ullamcorper ultricied. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, is fringille sem nunc vet mi.

Related

Oh I do like to be beside the seaside

Oh I do like to be beside the sea

Although in the North of England

It never stops raining

Oh well...

©Copyright 2050 by nobody. All rights reversed.


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Good form example</title>

Good form

    <form>

        Enter your name:
        <input type="text" name="name" id="name">

        Enter your age:
        <input type="text" name="age" id="age">

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Bad table example</title>

Bad table

        Name
        Age
        Gender

        Gabriel
        13
        Male

        Elva
        8
        Female

        Freida
        5
        Female


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Basic custom controls</title>
    <link href="custom-controls.css" rel="stylesheet">

Basic custom controls

    <video controls>
      <source src="rabbit320.mp4" type="video/mp4">
      <source src="rabbit320.webm" type="video/webm">

Your browser doesn't support HTML5 video. Here is a link to the video instead.

    </video>

      <button class="playpause">Play</button>
      <button class="stop">Stop</button>
      <button class="rwd">Rwd</button>
      <button class="fwd">Fwd</button>

00:00


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Multiple media players with native controls</title>

Multiple media players with native controls

    <audio controls>
      <source src="viper.mp3" type="audio/mp3">
      <source src="viper.ogg" type="audio/ogg">

Your browser doesn't support HTML5 audio. Here is a link to the audio instead.

    </audio>

    <video controls>
      <source src="rabbit320.mp4" type="video/mp4">
      <source src="rabbit320.webm" type="video/webm">

Your browser doesn't support HTML5 video. Here is a link to the video instead.

    </video>


text:
* {
  box-sizing: border-box;
}

html {
  font-family: sans-serif;
}

.player {
  width: 480px;
  margin: 10px auto;
  border: 1px solid #ccc;
}

video, audio, .controls {
  width: 100%;
}

.controls {
  height: 36px;
}

button {
  display: block;
  float: left;
  border: 0;
  margin-right: 2px;
  height: 100%;
  width: 50px;
  cursor: pointer;
}

button:hover, button:focus {
  background-color: #ddd;
}

button:active {
  background-color: #ccc;
}

.controls div {
  font-size: 12px;
  display: inline-block;
  line-height: 36px;
  width: 45px;
  float: right;
}

video {
  margin-bottom: -5px;
}


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Basic custom controls</title>
    <link href="../custom-controls.css" rel="stylesheet">

Basic custom controls

    <video controls>
      <source src="../rabbit320.mp4" type="video/mp4">
      <source src="../rabbit320.webm" type="video/webm">

Your browser doesn't support HTML5 video. Here is a link to the video instead.

    </video>

      <button class="playpause">Play</button>
      <button class="stop">Stop</button>
      <button class="rwd">Rwd</button>
      <button class="fwd">Fwd</button>

00:00


text:
// grab references to buttons and video

const playPauseBtn = document.querySelector('.playpause');
const stopBtn = document.querySelector('.stop');
const rwdBtn = document.querySelector('.rwd');
const fwdBtn = document.querySelector('.fwd');
const timeLabel = document.querySelector('.time');

const player = document.querySelector('video');

// Remove the native controls from all players

player.removeAttribute('controls');

// Define constructor for player controls object

playPauseBtn.onclick = function() {
  if(player.paused) {
    player.play();
    playPauseBtn.textContent = 'Pause';
  } else {
    player.pause();
    playPauseBtn.textContent = 'Play';
  }
};

stopBtn.onclick = function() {
  player.pause();
  player.currentTime = 0;
  playPauseBtn.textContent = 'Play';
};

rwdBtn.onclick = function() {
  player.currentTime -= 3;
};

fwdBtn.onclick = function() {
  player.currentTime += 3;
  if(player.currentTime >= player.duration || player.paused) {
    player.pause();
    player.currentTime = 0;
    playPauseBtn.textContent = 'Play';
  }
};

player.ontimeupdate = function() {
  let minutes = Math.floor(player.currentTime / 60);
  let seconds = Math.floor(player.currentTime - minutes * 60);
  let minuteValue;
  let secondValue;

  if (minutes<10) {
    minuteValue = "0" + minutes;
  } else {
    minuteValue = minutes;
  }

  if (seconds<10) {
    secondValue = "0" + seconds;
  } else {
    secondValue = seconds;
  }

  mediaTime = minuteValue + ":" + secondValue;
  timeLabel.textContent = mediaTime;
};


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Multiple media players with custom controls</title>
    <link href="../custom-controls.css" rel="stylesheet">

Multiple media players with custom controls

Audio player

    <audio controls>
      <source src="../viper.mp3" type="audio/mp3">
      <source src="../viper.ogg" type="audio/ogg">

Your browser doesn't support HTML5 audio. Here is a link to the audio instead.

    </audio>

Video player

    <video controls>
      <source src="../rabbit320.mp4" type="video/mp4">
      <source src="../rabbit320.webm" type="video/webm">

Your browser doesn't support HTML5 video. Here is a link to the video instead.

    </video>


text:
// Get references to all audio and video players
// Store them all in a single array

const videos = document.querySelectorAll('video');

const audios = document.querySelectorAll('audio');

let players = [];

for(let a = 0; a < audios.length; a++) {
  players.push(audios[a]);
}

for(let v = 0; v < videos.length; v++) {
  players.push(videos[v]);
}

// Remove the native controls from all players

for(let p = 0; p < players.length; p++) {
  players[p].removeAttribute('controls');
}

// Define constructor for player controls object

function PlayerController(player, playPauseBtn, stopBtn, rwdBtn, fwdBtn, timeLabel) {
  this.player = player;
  this.playPauseBtn = playPauseBtn;
  this.stopBtn = stopBtn;
  this.rwdBtn = rwdBtn;
  this.fwdBtn = fwdBtn;
  this.timeLabel = timeLabel;

  this.interval;

  this.playPauseBtn.onclick = function() {
    if(player.paused) {
      player.play();
      playPauseBtn.textContent = 'Pause';
    } else {
      player.pause();
      playPauseBtn.textContent = 'Play';
    }
  }

  this.stopBtn.onclick = function() {
    player.pause();
    player.currentTime = 0;
    playPauseBtn.textContent = 'Play';
  }

  this.rwdBtn.onclick = function() {
    player.currentTime -= 3;
  }

  this.fwdBtn.onclick = function() {
    player.currentTime += 3;
    if(player.currentTime >= player.duration || player.paused) {
      player.pause();
      player.currentTime = 0;
      playPauseBtn.textContent = 'Play';
    }
  }

  this.player.ontimeupdate = function() {
    let minutes = Math.floor(player.currentTime / 60);
    let seconds = Math.floor(player.currentTime - minutes * 60);
    let minuteValue;
    let secondValue;

    if (minutes<10) {
      minuteValue = "0" + minutes;
    } else {
      minuteValue = minutes;
    }

    if (seconds<10) {
      secondValue = "0" + seconds;
    } else {
      secondValue = seconds;
    }

    mediaTime = minuteValue + ":" + secondValue;
    timeLabel.textContent = mediaTime;
  }
}

// Add the controls bar to all players

for(let i = 0; i < players.length; i++) {
  const controls = document.createElement('div');
  controls.setAttribute('class', 'controls');
  players[i].parentNode.appendChild(controls);

  const playpause = document.createElement('button');
  const stop = document.createElement('button');
  const rwd = document.createElement('button');
  const fwd = document.createElement('button');
  const time = document.createElement('div');

  playpause.setAttribute('class', 'playpause');
  stop.setAttribute('class', 'stop');
  rwd.setAttribute('class', 'rwd');
  fwd.setAttribute('class', 'fwd');
  time.setAttribute('class', 'time');

  playpause.textContent = 'Play';
  stop.textContent = 'Stop';
  rwd.textContent = 'Rwd';
  fwd.textContent = 'Fwd';
  time.textContent = '00:00';

  controls.appendChild(playpause);
  controls.appendChild(stop);
  controls.appendChild(rwd);
  controls.appendChild(fwd);
  controls.appendChild(time);

  let playerInstance = new PlayerController(players[i], playpause, stop, rwd, fwd, time);
}


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Audio transcript example</title>
    <link href="../custom-controls.css" rel="stylesheet">

Audio transcript example

    <audio controls>
      <source src="../viper.mp3" type="audio/mp3">
      <source src="../viper.ogg" type="audio/ogg">

Your browser doesn't support HTML5 audio. Here is a link to the audio instead.

    </audio>

      <button class="playpause">Play</button>
      <button class="stop">Stop</button>
      <button class="rwd">Rwd</button>
      <button class="fwd">Fwd</button>

00:00

      <button>Show transcript</button>

Person1: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque at convallis libero, ut ultricies massa. Integer vel tempor nisi. Nunc scelerisque enim est. Duis lorem ipsum, semper vitae sem at, ultricies interdum ex.

Person 2: In consectetur maximus blandit. Curabitur convallis iaculis quam, eu porta erat suscipit sit amet. Morbi viverra placerat ipsum, non bibendum libero egestas eu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed quis condimentum ante. Nullam ullamcorper faucibus eros. Quisque egestas non risus a faucibus. Curabitur luctus tincidunt turpis in scelerisque.

Person 1: Suspendisse sapien ipsum, lacinia eu bibendum in, congue eu purus. Sed fringilla sollicitudin faucibus. Cras sodales sapien neque, eget gravida augue lobortis ac. Mauris blandit faucibus porta.

Person 2: Fusce non libero eget nisl porta egestas ut sed elit. Nullam molestie lectus ut elementum sagittis. Donec dictum nunc leo, quis gravida diam varius non. Nam ac nisl vel nibh iaculis convallis vel eget arcu. Mauris pharetra purus ac ornare pellentesque. Fusce in leo nunc. Nunc eget vehicula sem, quis aliquam lorem.

Person 3: Ut auctor ornare pulvinar. Morbi nisl ipsum, pellentesque et bibendum eget, vestibulum in massa. Cras egestas cursus est sed ultrices. Etiam vel dui felis.


text:
// grab references to buttons and video

const playPauseBtn = document.querySelector('.playpause');
const stopBtn = document.querySelector('.stop');
const rwdBtn = document.querySelector('.rwd');
const fwdBtn = document.querySelector('.fwd');
const timeLabel = document.querySelector('.time');

const player = document.querySelector('audio');

// Remove the native controls from all players

player.removeAttribute('controls');

// Define constructor for player controls object

playPauseBtn.onclick = function() {
  if(player.paused) {
    player.play();
    playPauseBtn.textContent = 'Pause';
  } else {
    player.pause();
    playPauseBtn.textContent = 'Play';
  }
};

stopBtn.onclick = function() {
  player.pause();
  player.currentTime = 0;
  playPauseBtn.textContent = 'Play';
};

rwdBtn.onclick = function() {
  player.currentTime -= 3;
};

fwdBtn.onclick = function() {
  player.currentTime += 3;
  if(player.currentTime >= player.duration || player.paused) {
    player.pause();
    player.currentTime = 0;
    playPauseBtn.textContent = 'Play';
  }
};

player.ontimeupdate = function() {
  let minutes = Math.floor(player.currentTime / 60);
  let seconds = Math.floor(player.currentTime - minutes * 60);
  let minuteValue;
  let secondValue;

  if (minutes<10) {
    minuteValue = "0" + minutes;
  } else {
    minuteValue = minutes;
  }

  if (seconds<10) {
    secondValue = "0" + seconds;
  } else {
    secondValue = seconds;
  }

  mediaTime = minuteValue + ":" + secondValue;
  timeLabel.textContent = mediaTime;
};

// Control transcript display

const transcript = document.querySelector('.transcript');
const transcriptBtn = document.querySelector('.transcript-container button');

transcriptBtn.onclick = function() {
  if(transcriptBtn.textContent === 'Show transcript') {
    transcript.style.height = '150px';
    transcriptBtn.textContent = 'Hide transcript';
  } else {
    transcript.style.height = '0';
    transcriptBtn.textContent = 'Show transcript';
  }
};


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">

    <title>Accessibility assessment</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Sonsie+One" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="style.css">

      Welcome to our wildlife website

Home

Our team

Projects

Blog

      <form class="search">
        <input type="search" name="q" placeholder="Search query">
        <input type="submit" value="Go!">
      </form>

        The trouble with Bears

        By Evan Wild

        Tall, lumbering, angry, dangerous. The real live bears of this world are proud, independent creatures, self-serving and always on the hunt for food. Nothing like the bears you see on TV, like Baloo from renowned documentary, The Jungle Book.

        So what are bears really like, and why does the world's media portray them with such a skewed vision? In this article we try to answer those questions, and give you a real insight into the life of the bear.

        Types of bear

        Bears come in two varieties — large and medium. You don't get small bears. If you have seen a small bear, then it was in fact probably a baby bear (cub) from another species.

        Bears can also be classified in terms of their habitat — both large and medium bears are just as at home in urban areas as they are in the countryside. Different habitats encourange different behaviour however, as you'll find out below. The below table also gives you some useful facts about bears.

              Bear Type
              Coat
              Adult size
              Habitat
              Lifespan
              Diet

              Wild
              Brown or black
              1.4 to 2.8 meters
              Woods and forests
              25 to 28 years
              Fish, meat, plants

              Urban
              North Face
              18 to 22
              Condos and coffee shops
              20 to 32 years
              Starbucks, sushi

        Habitats and Eating habits

        Wild bears eat a variety of meat, fish, fruit, nuts, and other natually growing ingredients. In general they will hunt for food themselves in woodland or rivers, but at a push they will track down their sustenance from farms or country houses. They tend to live in relative isolation, in caves, tents, or cottages.

        Urban (gentrified) bears on the other hand have largely abandoned the old ways. They will hunt other urban creatures if necessary (including other predators like rats and foxes), but prefer to scavenge from readily available urban food outlets like dumpsters, bins, and fast food joints. When food has proven scarce, urban bears have even been known to break into people's kitchens and steal essentials like baked beans, ready meals, and microwave ovens.

        Urban bears will sleep anywhere they can, from bus shelters and parks, to the toilets in McDonalds, to their own apartment.

        Mating rituals

        Bears are romantic creatures by nature, and will naturally look for a mate that they can spend the rest of their lives with. They will woo a potential suitor by making their dwelling look attractive — for example with cave paintings or a bed of reeds in the case of a wild bear, and mood lighting and a Michael Bublé CD in the case of an urban bear.

        The following audio clip contains a fact file providing more details about bear mating rituals, along with samples and quotes from experts.

        <audio controls>
          <source src="media/bear.mp3" type="audio/mp3">
          <source src="media/bear.ogg" type="audio/ogg">

It looks like your browser doesn't support HTML5 audio players.

        </audio>

          About the author

          Evan Wild is an unemployed plumber from Doncaster, who has been really "in to" wildlife since childhood. He once went to Chester Zoo on holiday, but got ill after eating a doner kebab.

          He has never seen a bear, but once read a Daily Mail article about them, and thinks they sound cool.

Show comments

            Add comment

            <form class="comment-form">

                Your name:
                <input type="text" name="name" id="name" placeholder="Enter your name">

                Your comment:
                <input type="text" name="comment" id="comment" placeholder="Enter your comment">

                <input type="submit" value="Submit comment">

            </form>

            Comments

Bob Fossil

Oh I am so glad you taught me all about the big brown angry guys in the woods. With their sniffing little noses and their bad attitudes, they can sure be a menace — I was thinking of putting them all in a truck and driving them outta here. I run a zoo, you know.

        Related

The trouble with Bees

The trouble with Otters

The trouble with Penguins

The trouble with Octopi

The trouble with Lemurs

©Copyright 2050 by nobody. All rights reversed.